Commit 6876864a authored by Stephane Zermatten's avatar Stephane Zermatten Committed by Commit Bot

[Autofill Assistant] Separate check for element existence or visibility.

Before this change, WebController::ElementExists always checked for both
existence and visibility.

With this change, WebController::ElementExists checks for existence, and
WebController::ElementVisible for visibility. Element existence is used
for preconditions, visibility is used for all other cases, such as when
waiting for dom or waiting for an element to act on.

This change also simplifies BatchElementChecker a bit by keeping element
existence, visibility and field checks completely separate.

Bug: 806868
Change-Id: Ibd232c317aff4ba4640e5c47a0a93a2b4637ec76
Reviewed-on: https://chromium-review.googlesource.com/c/1301783
Commit-Queue: Stephane Zermatten <szermatt@chromium.org>
Reviewed-by: default avatarGanggui Tang <gogerald@chromium.org>
Cr-Commit-Position: refs/heads/master@{#603556}
parent 0c2d1e1f
...@@ -29,8 +29,9 @@ void WaitForDomAction::InternalProcessAction(ActionDelegate* delegate, ...@@ -29,8 +29,9 @@ void WaitForDomAction::InternalProcessAction(ActionDelegate* delegate,
ProcessActionCallback callback) { ProcessActionCallback callback) {
DCHECK_GT(proto_.wait_for_dom().selectors_size(), 0); DCHECK_GT(proto_.wait_for_dom().selectors_size(), 0);
batch_element_checker_ = delegate->CreateBatchElementChecker(); batch_element_checker_ = delegate->CreateBatchElementChecker();
batch_element_checker_->AddElementExistenceCheck( batch_element_checker_->AddElementCheck(
ExtractSelectors(proto_.wait_for_dom().selectors()), base::DoNothing()); kVisibilityCheck, ExtractSelectors(proto_.wait_for_dom().selectors()),
base::DoNothing());
base::TimeDelta duration = kDefaultCheckDuration; base::TimeDelta duration = kDefaultCheckDuration;
int timeout_ms = proto_.wait_for_dom().timeout_ms(); int timeout_ms = proto_.wait_for_dom().timeout_ms();
......
...@@ -21,7 +21,7 @@ static constexpr base::TimeDelta kCheckPeriod = ...@@ -21,7 +21,7 @@ static constexpr base::TimeDelta kCheckPeriod =
BatchElementChecker::BatchElementChecker(WebController* web_controller) BatchElementChecker::BatchElementChecker(WebController* web_controller)
: web_controller_(web_controller), : web_controller_(web_controller),
pending_preconditions_to_check_count_(0), pending_checks_count_(0),
all_found_(false), all_found_(false),
stopped_(false), stopped_(false),
weak_ptr_factory_(this) { weak_ptr_factory_(this) {
...@@ -30,22 +30,22 @@ BatchElementChecker::BatchElementChecker(WebController* web_controller) ...@@ -30,22 +30,22 @@ BatchElementChecker::BatchElementChecker(WebController* web_controller)
BatchElementChecker::~BatchElementChecker() {} BatchElementChecker::~BatchElementChecker() {}
void BatchElementChecker::AddElementExistenceCheck( void BatchElementChecker::AddElementCheck(
ElementCheckType check_type,
const std::vector<std::string>& selectors, const std::vector<std::string>& selectors,
base::OnceCallback<void(bool)> callback) { ElementCheckCallback callback) {
DCHECK(!try_done_callback_); DCHECK(!try_done_callback_);
element_callback_map_[selectors].element_exists_callbacks.emplace_back( element_check_callbacks_[std::make_pair(check_type, selectors)].emplace_back(
std::move(callback)); std::move(callback));
} }
void BatchElementChecker::AddFieldValueCheck( void BatchElementChecker::AddFieldValueCheck(
const std::vector<std::string>& selectors, const std::vector<std::string>& selectors,
base::OnceCallback<void(bool, const std::string&)> callback) { GetFieldValueCallback callback) {
DCHECK(!try_done_callback_); DCHECK(!try_done_callback_);
element_callback_map_[selectors].get_field_value_callbacks.emplace_back( get_field_value_callbacks_[selectors].emplace_back(std::move(callback));
std::move(callback));
} }
void BatchElementChecker::Run(const base::TimeDelta& duration, void BatchElementChecker::Run(const base::TimeDelta& duration,
...@@ -62,9 +62,6 @@ void BatchElementChecker::Run(const base::TimeDelta& duration, ...@@ -62,9 +62,6 @@ void BatchElementChecker::Run(const base::TimeDelta& duration,
base::Unretained(this), try_count, try_done, std::move(all_done))); base::Unretained(this), try_count, try_done, std::move(all_done)));
} }
BatchElementChecker::ElementCallbacks::ElementCallbacks() {}
BatchElementChecker::ElementCallbacks::~ElementCallbacks() {}
void BatchElementChecker::Try(base::OnceCallback<void()> try_done_callback) { void BatchElementChecker::Try(base::OnceCallback<void()> try_done_callback) {
DCHECK(!try_done_callback_); DCHECK(!try_done_callback_);
...@@ -74,43 +71,53 @@ void BatchElementChecker::Try(base::OnceCallback<void()> try_done_callback) { ...@@ -74,43 +71,53 @@ void BatchElementChecker::Try(base::OnceCallback<void()> try_done_callback) {
} }
try_done_callback_ = std::move(try_done_callback); try_done_callback_ = std::move(try_done_callback);
DCHECK_EQ(pending_preconditions_to_check_count_, 0); DCHECK_EQ(pending_checks_count_, 0);
pending_preconditions_to_check_count_ = element_callback_map_.size(); pending_checks_count_ =
// Done the check if there is nothing to check. element_check_callbacks_.size() + get_field_value_callbacks_.size() + 1;
if (!pending_preconditions_to_check_count_) {
CheckTryDone(); for (auto& entry : element_check_callbacks_) {
return; 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)));
} }
size_t total_number_of_loops = pending_preconditions_to_check_count_; for (auto& entry : get_field_value_callbacks_) {
for (auto iter = element_callback_map_.begin(); if (entry.second.empty()) {
iter != element_callback_map_.end(); iter++) { pending_checks_count_--;
if (!iter->second.get_field_value_callbacks.empty()) { continue;
web_controller_->GetFieldValue(
iter->first, base::BindOnce(&BatchElementChecker::OnGetFieldValue,
weak_ptr_factory_.GetWeakPtr(), iter));
} else if (!iter->second.element_exists_callbacks.empty()) {
web_controller_->ElementExists(
iter->first, base::BindOnce(&BatchElementChecker::OnElementExists,
weak_ptr_factory_.GetWeakPtr(), iter));
} else {
// Uninteresting entries
pending_preconditions_to_check_count_--;
CheckTryDone();
} }
// This is an ugly workaround for unit tests. 'all_done' callback will be web_controller_->GetFieldValue(
// called synchronously since mocked web_controller is synchronous. And we entry.first,
// can not simply make it asynchronous since a lot of unit tests rely on base::BindOnce(
// synchronous callback. During the callback consumer may destruct this &BatchElementChecker::OnGetFieldValue,
// class which causes crash since 'element_callback_map_' is deleted. weak_ptr_factory_.GetWeakPtr(),
// // Guaranteed to exist for the lifetime of this instance, because
// TODO(crbug.com/806868): make sure 'all_done' callback is called // the map isn't modified after Run has been called.
// asynchronously and fix unit tests accordingly. base::Unretained(&entry.second)));
total_number_of_loops--;
if (!total_number_of_loops)
return;
} }
// 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, void BatchElementChecker::OnTryDone(int64_t remaining_attempts,
...@@ -144,68 +151,71 @@ void BatchElementChecker::OnTryDone(int64_t remaining_attempts, ...@@ -144,68 +151,71 @@ void BatchElementChecker::OnTryDone(int64_t remaining_attempts,
} }
void BatchElementChecker::GiveUp() { void BatchElementChecker::GiveUp() {
for (auto& entry : element_callback_map_) { for (auto& entry : element_check_callbacks_) {
ElementCallbacks* callbacks = &entry.second; RunCallbacks(&entry.second, false);
RunElementExistsCallbacks(callbacks, false); }
RunGetFieldValueCallbacks(callbacks, false, ""); for (auto& entry : get_field_value_callbacks_) {
RunCallbacks(&entry.second, false, "");
} }
} }
void BatchElementChecker::OnElementExists(ElementCallbackMap::iterator iter, void BatchElementChecker::OnElementChecked(
bool exists) { std::vector<ElementCheckCallback>* callbacks,
pending_preconditions_to_check_count_--; bool exists) {
pending_checks_count_--;
if (exists) if (exists)
RunElementExistsCallbacks(&iter->second, true); RunCallbacks(callbacks, true);
CheckTryDone(); CheckTryDone();
} }
void BatchElementChecker::OnGetFieldValue(ElementCallbackMap::iterator iter, void BatchElementChecker::OnGetFieldValue(
bool exists, std::vector<GetFieldValueCallback>* callbacks,
const std::string& value) { bool exists,
pending_preconditions_to_check_count_--; const std::string& value) {
if (exists) { pending_checks_count_--;
RunElementExistsCallbacks(&iter->second, exists); if (exists)
RunGetFieldValueCallbacks(&iter->second, exists, value); RunCallbacks(callbacks, exists, value);
}
CheckTryDone(); CheckTryDone();
} }
void BatchElementChecker::CheckTryDone() { void BatchElementChecker::CheckTryDone() {
DCHECK_GE(pending_preconditions_to_check_count_, 0); DCHECK_GE(pending_checks_count_, 0);
if (pending_preconditions_to_check_count_ <= 0 && try_done_callback_) { if (pending_checks_count_ <= 0 && try_done_callback_) {
all_found_ = !HasMoreChecksToRun(); all_found_ = !HasMoreChecksToRun();
std::move(try_done_callback_).Run(); std::move(try_done_callback_).Run();
} }
} }
bool BatchElementChecker::HasMoreChecksToRun() { void BatchElementChecker::RunCallbacks(
for (const auto& entry : element_callback_map_) { std::vector<ElementCheckCallback>* callbacks,
if (!entry.second.element_exists_callbacks.empty()) bool result) {
return true; for (auto& callback : *callbacks) {
if (!entry.second.get_field_value_callbacks.empty())
return true;
}
return false;
}
void BatchElementChecker::RunElementExistsCallbacks(ElementCallbacks* callbacks,
bool result) {
for (auto& callback : callbacks->element_exists_callbacks) {
std::move(callback).Run(result); std::move(callback).Run(result);
} }
callbacks->element_exists_callbacks.clear(); callbacks->clear();
} }
void BatchElementChecker::RunGetFieldValueCallbacks(ElementCallbacks* callbacks, void BatchElementChecker::RunCallbacks(
bool exists, std::vector<GetFieldValueCallback>* callbacks,
const std::string& value) { bool exists,
for (auto& callback : callbacks->get_field_value_callbacks) { const std::string& value) {
for (auto& callback : *callbacks) {
std::move(callback).Run(exists, value); std::move(callback).Run(exists, value);
} }
callbacks->get_field_value_callbacks.clear(); 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 } // namespace autofill_assistant
...@@ -19,6 +19,9 @@ ...@@ -19,6 +19,9 @@
namespace autofill_assistant { namespace autofill_assistant {
class WebController; class WebController;
// Types of element checks.
enum ElementCheckType { kExistenceCheck, kVisibilityCheck };
// Helper for checking a set of elements at the same time. It avoids duplicate // Helper for checking a set of elements at the same time. It avoids duplicate
// checks and supports retries. // checks and supports retries.
// //
...@@ -27,12 +30,12 @@ class WebController; ...@@ -27,12 +30,12 @@ class WebController;
// The simplest way of using a BatchElementChecker is to: // The simplest way of using a BatchElementChecker is to:
// - create an instance, using WebController::CreateBatchElementChecker or // - create an instance, using WebController::CreateBatchElementChecker or
// ActionDelegate::CreateBatchElementChecker // ActionDelegate::CreateBatchElementChecker
// - call AddElementExistenceCheck() and AddFieldValueCheck() // - call AddElementCheck() and AddFieldValueCheck()
// - call Run() with duration set to 0. // - call Run() with duration set to 0.
// //
// The result of the checks is reported to the callbacks passed to // The result of the checks is reported to the callbacks passed to
// AddElementExistenceCheck() and AddFieldValueCheck(), then the callback passed // AddElementCheck(kExistenceCheck, ) and AddFieldValueCheck(), then the
// to Run() is called, to report the end of the a run. // callback passed to Run() is called, to report the end of the a run.
// //
// Check with retries: // Check with retries:
// //
...@@ -46,19 +49,34 @@ class BatchElementChecker { ...@@ -46,19 +49,34 @@ class BatchElementChecker {
explicit BatchElementChecker(WebController* web_controller); explicit BatchElementChecker(WebController* web_controller);
virtual ~BatchElementChecker(); virtual ~BatchElementChecker();
// Checks whether the element given by |selectors| exists on the web page. // Callback for AddElementCheck. Argument is true if the check passed.
using ElementCheckCallback = base::OnceCallback<void(bool)>;
// Callback for AddFieldValueCheck. Argument is true is the element exists.
// The string contains the field value, or an empty string if accessing the
// value failed.
using GetFieldValueCallback =
base::OnceCallback<void(bool, const std::string&)>;
// Checks an an element.
//
// kElementCheck checks whether at least one element given by |selectors|
// exists on the web page.
//
// kVisibilityCheck checks whether at least one element given by |selectors|
// is visible on the page.
// //
// New element existence checks cannot be added once Run has been called. // New element checks cannot be added once Run has been called.
void AddElementExistenceCheck(const std::vector<std::string>& selectors, void AddElementCheck(ElementCheckType check_type,
base::OnceCallback<void(bool)> callback); const std::vector<std::string>& selectors,
ElementCheckCallback callback);
// Gets the value of |selectors| and return the result through |callback|. The // Gets the value of |selectors| and return the result through |callback|. The
// returned value will be the empty string in case of error or empty value. // returned value will be the empty string in case of error or empty value.
// //
// New field checks cannot be added once Run has been called. // New field checks cannot be added once Run has been called.
void AddFieldValueCheck( void AddFieldValueCheck(const std::vector<std::string>& selectors,
const std::vector<std::string>& selectors, GetFieldValueCallback callback);
base::OnceCallback<void(bool, const std::string&)> callback);
// Runs the checks until all elements exist or for |duration|, whichever one // Runs the checks until all elements exist or for |duration|, whichever one
// comes first. Elements found are reported as soon as they're founds. // comes first. Elements found are reported as soon as they're founds.
...@@ -78,58 +96,52 @@ class BatchElementChecker { ...@@ -78,58 +96,52 @@ class BatchElementChecker {
bool all_found() { return all_found_; } bool all_found() { return all_found_; }
private: private:
// Checks to run on a specific element, and the callbacks to report them to.
struct ElementCallbacks {
ElementCallbacks();
~ElementCallbacks();
// All the callbacks registered for that element by separate calls to
// AddElementExistenceCheck with the same selector.
std::vector<base::OnceCallback<void(bool)>> element_exists_callbacks;
// All the callbacks registered for that field by separate calls to
// AddFieldValueCheck with the same selector.
std::vector<base::OnceCallback<void(bool, const std::string&)>>
get_field_value_callbacks;
};
using ElementCallbackMap =
std::map<std::vector<std::string>, ElementCallbacks>;
// Tries running the checks, reporting only successes. // Tries running the checks, reporting only successes.
// //
// Calls |try_done_callback| at the end of the run. // Calls |try_done_callback| at the end of the run.
void Try(base::OnceCallback<void()> try_done_callback); void Try(base::OnceCallback<void()> try_done_callback);
void OnTryDone(int64_t remaining_attempts,
base::RepeatingCallback<void()> try_done,
base::OnceCallback<void()> all_done);
// If there are still callbacks not called by a previous call to Try, call // If there are still callbacks not called by a previous call to Try, call
// them now. When this method returns, all callbacks are guaranteed to have // them now. When this method returns, all callbacks are guaranteed to have
// been run. // been run.
void GiveUp(); void GiveUp();
void OnTryDone(int64_t remaining_attempts, void OnElementChecked(std::vector<ElementCheckCallback>* callbacks,
base::RepeatingCallback<void()> try_done, bool exists);
base::OnceCallback<void()> all_done); void OnGetFieldValue(std::vector<GetFieldValueCallback>* callbacks,
bool HasMoreChecksToRun();
void OnElementExists(ElementCallbackMap::iterator iter, bool exists);
void OnGetFieldValue(ElementCallbackMap::iterator iter,
bool exists, bool exists,
const std::string& value); const std::string& value);
void CheckTryDone(); void CheckTryDone();
void RunElementExistsCallbacks(ElementCallbacks* element, bool result); void RunCallbacks(std::vector<ElementCheckCallback>* callbacks, bool result);
void RunGetFieldValueCallbacks(ElementCallbacks* element, void RunCallbacks(std::vector<GetFieldValueCallback>* callbacks,
bool exists, bool result,
const std::string& value); const std::string& value);
bool HasMoreChecksToRun();
WebController* const web_controller_; WebController* const web_controller_;
ElementCallbackMap element_callback_map_;
int pending_preconditions_to_check_count_; // A map of ElementCheck arguments (check_type, selectors) to callbacks that
// take the result of the check.
std::map<std::pair<ElementCheckType, std::vector<std::string>>,
std::vector<ElementCheckCallback>>
element_check_callbacks_;
// A map of GetFieldValue arguments (selectors) to callbacks that take the
// field value.
std::map<std::vector<std::string>, std::vector<GetFieldValueCallback>>
get_field_value_callbacks_;
int pending_checks_count_;
bool all_found_; bool all_found_;
bool stopped_; bool stopped_;
// The callback built for Try(). It is kept around while going through // The callback built for Try(). It is kept around while going through the
// |element_callback_map_| and called once all selectors in that map have been // maps and called once all selectors in that map have been looked up once,
// looked up once, whether that lookup was successful or not. Also used to // whether that lookup was successful or not. Also used to guarantee that only
// guarantee that only one Try runs at a time. // one Try runs at a time.
base::OnceCallback<void()> try_done_callback_; base::OnceCallback<void()> try_done_callback_;
base::WeakPtrFactory<BatchElementChecker> weak_ptr_factory_; base::WeakPtrFactory<BatchElementChecker> weak_ptr_factory_;
......
...@@ -41,12 +41,14 @@ class MockWebController : public WebController { ...@@ -41,12 +41,14 @@ class MockWebController : public WebController {
void(const std::vector<std::string>& selectors, void(const std::vector<std::string>& selectors,
base::OnceCallback<void(bool)>& callback)); base::OnceCallback<void(bool)>& callback));
void ElementExists(const std::vector<std::string>& selectors, void ElementCheck(ElementCheckType check_type,
base::OnceCallback<void(bool)> callback) override { const std::vector<std::string>& selectors,
OnElementExists(selectors, callback); base::OnceCallback<void(bool)> callback) override {
OnElementCheck(check_type, selectors, callback);
} }
MOCK_METHOD2(OnElementExists, MOCK_METHOD3(OnElementCheck,
void(const std::vector<std::string>& selectors, void(ElementCheckType check_type,
const std::vector<std::string>& selectors,
base::OnceCallback<void(bool)>& callback)); base::OnceCallback<void(bool)>& callback));
void GetFieldValue( void GetFieldValue(
......
...@@ -64,7 +64,7 @@ ScriptExecutor::CreateBatchElementChecker() { ...@@ -64,7 +64,7 @@ ScriptExecutor::CreateBatchElementChecker() {
void ScriptExecutor::WaitForElement(const std::vector<std::string>& selectors, void ScriptExecutor::WaitForElement(const std::vector<std::string>& selectors,
base::OnceCallback<void(bool)> callback) { base::OnceCallback<void(bool)> callback) {
std::unique_ptr<BatchElementChecker> checker = CreateBatchElementChecker(); std::unique_ptr<BatchElementChecker> checker = CreateBatchElementChecker();
checker->AddElementExistenceCheck(selectors, base::DoNothing()); checker->AddElementCheck(kVisibilityCheck, selectors, base::DoNothing());
checker->Run(kWaitForSelectorDeadline, checker->Run(kWaitForSelectorDeadline,
/* try_done= */ base::DoNothing(), /* try_done= */ base::DoNothing(),
/* all_done= */ /* all_done= */
......
...@@ -43,8 +43,8 @@ class ScriptExecutorTest : public testing::Test, ...@@ -43,8 +43,8 @@ class ScriptExecutorTest : public testing::Test,
// In this test, "tell" actions always succeed and "click" actions always // In this test, "tell" actions always succeed and "click" actions always
// fail. The following makes a click action fail immediately // fail. The following makes a click action fail immediately
ON_CALL(mock_web_controller_, OnElementExists(_, _)) ON_CALL(mock_web_controller_, OnElementCheck(_, _, _))
.WillByDefault(RunOnceCallback<1>(true)); .WillByDefault(RunOnceCallback<2>(true));
ON_CALL(mock_web_controller_, OnClickElement(_, _)) ON_CALL(mock_web_controller_, OnClickElement(_, _))
.WillByDefault(RunOnceCallback<1>(false)); .WillByDefault(RunOnceCallback<1>(false));
ON_CALL(mock_web_controller_, OnFocusElement(_, _)) ON_CALL(mock_web_controller_, OnFocusElement(_, _))
......
...@@ -98,7 +98,8 @@ void ScriptPrecondition::Check( ...@@ -98,7 +98,8 @@ void ScriptPrecondition::Check(
base::OnceCallback<void(bool)> callback = base::OnceCallback<void(bool)> callback =
base::BindOnce(&ScriptPrecondition::OnCheckElementExists, base::BindOnce(&ScriptPrecondition::OnCheckElementExists,
weak_ptr_factory_.GetWeakPtr()); weak_ptr_factory_.GetWeakPtr());
batch_checks->AddElementExistenceCheck(selector, std::move(callback)); batch_checks->AddElementCheck(kExistenceCheck, selector,
std::move(callback));
} }
for (const auto& value_match : form_value_match_) { for (const auto& value_match : form_value_match_) {
DCHECK(!value_match.element().selectors().empty()); DCHECK(!value_match.element().selectors().empty());
......
...@@ -55,11 +55,12 @@ class DirectCallback { ...@@ -55,11 +55,12 @@ class DirectCallback {
class ScriptPreconditionTest : public testing::Test { class ScriptPreconditionTest : public testing::Test {
public: public:
void SetUp() override { void SetUp() override {
ON_CALL(mock_web_controller_, OnElementExists(ElementsAre("exists"), _))
.WillByDefault(RunOnceCallback<1>(true));
ON_CALL(mock_web_controller_, ON_CALL(mock_web_controller_,
OnElementExists(ElementsAre("does_not_exist"), _)) OnElementCheck(kExistenceCheck, ElementsAre("exists"), _))
.WillByDefault(RunOnceCallback<1>(false)); .WillByDefault(RunOnceCallback<2>(true));
ON_CALL(mock_web_controller_,
OnElementCheck(kExistenceCheck, ElementsAre("does_not_exist"), _))
.WillByDefault(RunOnceCallback<2>(false));
SetUrl("http://www.example.com/path"); SetUrl("http://www.example.com/path");
ON_CALL(mock_web_controller_, OnGetFieldValue(ElementsAre("exists"), _)) ON_CALL(mock_web_controller_, OnGetFieldValue(ElementsAre("exists"), _))
...@@ -177,8 +178,9 @@ TEST_F(ScriptPreconditionTest, BadPathPattern) { ...@@ -177,8 +178,9 @@ TEST_F(ScriptPreconditionTest, BadPathPattern) {
} }
TEST_F(ScriptPreconditionTest, IgnoreEmptyElementsExist) { TEST_F(ScriptPreconditionTest, IgnoreEmptyElementsExist) {
EXPECT_CALL(mock_web_controller_, OnElementExists(ElementsAre("exists"), _)) EXPECT_CALL(mock_web_controller_,
.WillOnce(RunOnceCallback<1>(true)); OnElementCheck(kExistenceCheck, ElementsAre("exists"), _))
.WillOnce(RunOnceCallback<2>(true));
ScriptPreconditionProto proto; ScriptPreconditionProto proto;
proto.add_elements_exist()->add_selectors("exists"); proto.add_elements_exist()->add_selectors("exists");
......
...@@ -32,11 +32,12 @@ class ScriptTrackerTest : public testing::Test, ...@@ -32,11 +32,12 @@ class ScriptTrackerTest : public testing::Test,
public ScriptExecutorDelegate { public ScriptExecutorDelegate {
public: public:
void SetUp() override { void SetUp() override {
ON_CALL(mock_web_controller_, OnElementExists(ElementsAre("exists"), _))
.WillByDefault(RunOnceCallback<1>(true));
ON_CALL(mock_web_controller_, ON_CALL(mock_web_controller_,
OnElementExists(ElementsAre("does_not_exist"), _)) OnElementCheck(kExistenceCheck, ElementsAre("exists"), _))
.WillByDefault(RunOnceCallback<1>(false)); .WillByDefault(RunOnceCallback<2>(true));
ON_CALL(mock_web_controller_,
OnElementCheck(kExistenceCheck, ElementsAre("does_not_exist"), _))
.WillByDefault(RunOnceCallback<2>(false));
ON_CALL(mock_web_controller_, GetUrl()).WillByDefault(ReturnRef(url_)); ON_CALL(mock_web_controller_, GetUrl()).WillByDefault(ReturnRef(url_));
// Scripts run, but have no actions. // Scripts run, but have no actions.
...@@ -250,8 +251,8 @@ TEST_F(ScriptTrackerTest, CheckScriptsAgainAfterScriptEnd) { ...@@ -250,8 +251,8 @@ TEST_F(ScriptTrackerTest, CheckScriptsAgainAfterScriptEnd) {
TEST_F(ScriptTrackerTest, CheckScriptsAfterDOMChange) { TEST_F(ScriptTrackerTest, CheckScriptsAfterDOMChange) {
EXPECT_CALL(mock_web_controller_, EXPECT_CALL(mock_web_controller_,
OnElementExists(ElementsAre("maybe_exists"), _)) OnElementCheck(kExistenceCheck, ElementsAre("maybe_exists"), _))
.WillOnce(RunOnceCallback<1>(false)); .WillOnce(RunOnceCallback<2>(false));
SupportsScriptResponseProto scripts; SupportsScriptResponseProto scripts;
AddScript(&scripts, "script name", "script path", "maybe_exists"); AddScript(&scripts, "script name", "script path", "maybe_exists");
...@@ -262,8 +263,8 @@ TEST_F(ScriptTrackerTest, CheckScriptsAfterDOMChange) { ...@@ -262,8 +263,8 @@ TEST_F(ScriptTrackerTest, CheckScriptsAfterDOMChange) {
// DOM has changed; OnElementExists now returns true. // DOM has changed; OnElementExists now returns true.
EXPECT_CALL(mock_web_controller_, EXPECT_CALL(mock_web_controller_,
OnElementExists(ElementsAre("maybe_exists"), _)) OnElementCheck(kExistenceCheck, ElementsAre("maybe_exists"), _))
.WillOnce(RunOnceCallback<1>(true)); .WillOnce(RunOnceCallback<2>(true));
tracker_.CheckScripts(base::TimeDelta::FromSeconds(0)); tracker_.CheckScripts(base::TimeDelta::FromSeconds(0));
// The script can now run // The script can now run
...@@ -275,9 +276,10 @@ TEST_F(ScriptTrackerTest, DuplicateCheckCalls) { ...@@ -275,9 +276,10 @@ TEST_F(ScriptTrackerTest, DuplicateCheckCalls) {
AddScript(&scripts, "runnable name", "runnable path", "exists"); AddScript(&scripts, "runnable name", "runnable path", "exists");
base::OnceCallback<void(bool)> captured_callback; base::OnceCallback<void(bool)> captured_callback;
EXPECT_CALL(mock_web_controller_, OnElementExists(ElementsAre("exists"), _)) EXPECT_CALL(mock_web_controller_,
.WillOnce(CaptureOnceCallback<1>(&captured_callback)) OnElementCheck(kExistenceCheck, ElementsAre("exists"), _))
.WillOnce(RunOnceCallback<1>(false)); .WillOnce(CaptureOnceCallback<2>(&captured_callback))
.WillOnce(RunOnceCallback<2>(false));
SetAndCheckScripts(scripts); SetAndCheckScripts(scripts);
// At this point, since the callback hasn't been run, there's still a check in // At this point, since the callback hasn't been run, there's still a check in
......
...@@ -238,32 +238,39 @@ void WebController::OnDispatchReleaseMouseEvent( ...@@ -238,32 +238,39 @@ void WebController::OnDispatchReleaseMouseEvent(
OnResult(true, std::move(callback)); OnResult(true, std::move(callback));
} }
void WebController::ElementExists(const std::vector<std::string>& selectors, void WebController::ElementCheck(ElementCheckType check_type,
base::OnceCallback<void(bool)> callback) { const std::vector<std::string>& selectors,
base::OnceCallback<void(bool)> callback) {
DCHECK(!selectors.empty()); DCHECK(!selectors.empty());
// We don't use strict_mode because we only check for the existence of an // We don't use strict_mode because we only check for the existence of at
// element and we don't act on it. // least one such element and we don't act on it.
FindElement( FindElement(selectors, /* strict_mode= */ false,
selectors, /* strict_mode= */ false, base::BindOnce(&WebController::OnFindElementForCheck,
base::BindOnce(&WebController::OnFindElementForExist, weak_ptr_factory_.GetWeakPtr(), check_type,
weak_ptr_factory_.GetWeakPtr(), std::move(callback))); std::move(callback)));
} }
void WebController::OnFindElementForExist( void WebController::OnFindElementForCheck(
ElementCheckType check_type,
base::OnceCallback<void(bool)> callback, base::OnceCallback<void(bool)> callback,
std::unique_ptr<FindElementResult> result) { std::unique_ptr<FindElementResult> result) {
if (result->object_id.empty()) { if (result->object_id.empty()) {
OnResult(false, std::move(callback)); OnResult(false, std::move(callback));
return; return;
} }
if (check_type == kExistenceCheck) {
OnResult(true, std::move(callback));
return;
}
DCHECK_EQ(check_type, kVisibilityCheck);
devtools_client_->GetDOM()->GetBoxModel( devtools_client_->GetDOM()->GetBoxModel(
dom::GetBoxModelParams::Builder().SetObjectId(result->object_id).Build(), dom::GetBoxModelParams::Builder().SetObjectId(result->object_id).Build(),
base::BindOnce(&WebController::OnGetBoxModelForExist, base::BindOnce(&WebController::OnGetBoxModelForVisible,
weak_ptr_factory_.GetWeakPtr(), std::move(callback))); weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
} }
void WebController::OnGetBoxModelForExist( void WebController::OnGetBoxModelForVisible(
base::OnceCallback<void(bool)> callback, base::OnceCallback<void(bool)> callback,
std::unique_ptr<dom::GetBoxModelResult> result) { std::unique_ptr<dom::GetBoxModelResult> result) {
OnResult(result && result->GetModel() && result->GetModel()->GetContent(), OnResult(result && result->GetModel() && result->GetModel()->GetContent(),
......
...@@ -115,13 +115,18 @@ class WebController { ...@@ -115,13 +115,18 @@ class WebController {
protected: protected:
friend class BatchElementChecker; friend class BatchElementChecker;
// Check whether at least one element given by |selectors| exists on the web // Checks an element for:
// page. The element must have a valid BoxModel (i.e. it must be visible and //
// have a size greater than 0). // kExistenceCheck: Checks whether at least one element given by |selectors|
// exists on the web page.
//
// kVisibilityCheck: Checks whether at least on element given by |selectors|
// is visible on the web page.
// //
// Normally done through BatchElementChecker. // Normally done through BatchElementChecker.
virtual void ElementExists(const std::vector<std::string>& selectors, virtual void ElementCheck(ElementCheckType type,
base::OnceCallback<void(bool)> callback); const std::vector<std::string>& selectors,
base::OnceCallback<void(bool)> callback);
// Get the value of |selectors| and return the result through |callback|. The // Get the value of |selectors| and return the result through |callback|. The
// returned value might be false, if the element cannot be found, true and the // returned value might be false, if the element cannot be found, true and the
...@@ -183,10 +188,11 @@ class WebController { ...@@ -183,10 +188,11 @@ class WebController {
void OnDispatchReleaseMouseEvent( void OnDispatchReleaseMouseEvent(
base::OnceCallback<void(bool)> callback, base::OnceCallback<void(bool)> callback,
std::unique_ptr<input::DispatchMouseEventResult> result); std::unique_ptr<input::DispatchMouseEventResult> result);
void OnFindElementForExist(base::OnceCallback<void(bool)> callback, void OnFindElementForCheck(ElementCheckType check_type,
base::OnceCallback<void(bool)> callback,
std::unique_ptr<FindElementResult> result); std::unique_ptr<FindElementResult> result);
void OnGetBoxModelForExist(base::OnceCallback<void(bool)> callback, void OnGetBoxModelForVisible(base::OnceCallback<void(bool)> callback,
std::unique_ptr<dom::GetBoxModelResult> result); std::unique_ptr<dom::GetBoxModelResult> result);
// Find the element given by |selectors|. If multiple elements match // Find the element given by |selectors|. If multiple elements match
// |selectors| and if |strict_mode| is false, return the first one that is // |selectors| and if |strict_mode| is false, return the first one that is
......
...@@ -34,25 +34,26 @@ class WebControllerBrowserTest : public content::ContentBrowserTest { ...@@ -34,25 +34,26 @@ class WebControllerBrowserTest : public content::ContentBrowserTest {
WebController::CreateForWebContents(shell()->web_contents()); WebController::CreateForWebContents(shell()->web_contents());
} }
void AreElementsExist(const std::vector<std::vector<std::string>>& selectors, void RunElementChecks(ElementCheckType check_type,
const std::vector<std::vector<std::string>>& selectors,
const std::vector<bool> results) { const std::vector<bool> results) {
base::RunLoop run_loop; base::RunLoop run_loop;
ASSERT_EQ(selectors.size(), results.size()); ASSERT_EQ(selectors.size(), results.size());
size_t pending_number_of_checks = selectors.size(); size_t pending_number_of_checks = selectors.size();
for (size_t i = 0; i < selectors.size(); i++) { for (size_t i = 0; i < selectors.size(); i++) {
web_controller_->ElementExists( web_controller_->ElementCheck(
selectors[i], check_type, selectors[i],
base::BindOnce(&WebControllerBrowserTest::CheckElementExistCallback, base::BindOnce(&WebControllerBrowserTest::CheckElementVisibleCallback,
base::Unretained(this), run_loop.QuitClosure(), base::Unretained(this), run_loop.QuitClosure(),
&pending_number_of_checks, results[i])); &pending_number_of_checks, results[i]));
} }
run_loop.Run(); run_loop.Run();
} }
void CheckElementExistCallback(const base::Closure& done_callback, void CheckElementVisibleCallback(const base::Closure& done_callback,
size_t* pending_number_of_checks_output, size_t* pending_number_of_checks_output,
bool expected_result, bool expected_result,
bool result) { bool result) {
ASSERT_EQ(expected_result, result); ASSERT_EQ(expected_result, result);
*pending_number_of_checks_output -= 1; *pending_number_of_checks_output -= 1;
if (*pending_number_of_checks_output == 0) { if (*pending_number_of_checks_output == 0) {
...@@ -77,8 +78,8 @@ class WebControllerBrowserTest : public content::ContentBrowserTest { ...@@ -77,8 +78,8 @@ class WebControllerBrowserTest : public content::ContentBrowserTest {
void WaitForElementRemove(const std::vector<std::string>& selectors) { void WaitForElementRemove(const std::vector<std::string>& selectors) {
base::RunLoop run_loop; base::RunLoop run_loop;
web_controller_->ElementExists( web_controller_->ElementCheck(
selectors, kExistenceCheck, selectors,
base::BindOnce(&WebControllerBrowserTest::OnWaitForElementRemove, base::BindOnce(&WebControllerBrowserTest::OnWaitForElementRemove,
base::Unretained(this), run_loop.QuitClosure(), base::Unretained(this), run_loop.QuitClosure(),
selectors)); selectors));
...@@ -255,7 +256,7 @@ class WebControllerBrowserTest : public content::ContentBrowserTest { ...@@ -255,7 +256,7 @@ class WebControllerBrowserTest : public content::ContentBrowserTest {
DISALLOW_COPY_AND_ASSIGN(WebControllerBrowserTest); DISALLOW_COPY_AND_ASSIGN(WebControllerBrowserTest);
}; };
IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, ConcurrentElementsExist) { IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, ConcurrentElementsVisible) {
std::vector<std::vector<std::string>> selectors; std::vector<std::vector<std::string>> selectors;
std::vector<bool> results; std::vector<bool> results;
...@@ -315,7 +316,33 @@ IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, ConcurrentElementsExist) { ...@@ -315,7 +316,33 @@ IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, ConcurrentElementsExist) {
selectors.emplace_back(a_selector); selectors.emplace_back(a_selector);
results.emplace_back(false); results.emplace_back(false);
AreElementsExist(selectors, results); RunElementChecks(kVisibilityCheck, selectors, results);
}
IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, ElementExists) {
std::vector<std::vector<std::string>> selectors;
std::vector<bool> results;
std::vector<std::string> a_selector;
// A visible element
a_selector.emplace_back("#button");
selectors.emplace_back(a_selector);
results.emplace_back(true);
// A hidden element.
a_selector.clear();
a_selector.emplace_back("#hidden");
selectors.emplace_back(a_selector);
results.emplace_back(true);
// A nonexistent element.
a_selector.clear();
a_selector.emplace_back("#doesnotexist");
selectors.emplace_back(a_selector);
results.emplace_back(false);
RunElementChecks(kExistenceCheck, selectors, results);
} }
IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, ClickElement) { IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, ClickElement) {
......
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