blob: af39e3c2f4ca9b49901eae6f46ecc37ce781135c [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/element_area.h"
#include <algorithm>
#include "base/bind.h"
#include "base/logging.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/script_executor_delegate.h"
#include "components/autofill_assistant/browser/web_controller.h"
namespace autofill_assistant {
namespace {
// Waiting period between two checks.
static constexpr base::TimeDelta kCheckDelay =
base::TimeDelta::FromMilliseconds(100);
} // namespace
ElementArea::ElementArea(ScriptExecutorDelegate* delegate)
: delegate_(delegate), scheduled_update_(false), weak_ptr_factory_(this) {
DCHECK(delegate_);
}
ElementArea::~ElementArea() = default;
void ElementArea::Clear() {
rectangles_.clear();
ReportUpdate();
}
void ElementArea::SetFromProto(const ElementAreaProto& proto) {
rectangles_.clear();
for (const auto& rectangle_proto : proto.rectangles()) {
rectangles_.emplace_back();
Rectangle& rectangle = rectangles_.back();
rectangle.full_width = rectangle_proto.full_width();
DVLOG(3) << "Touchable Rectangle"
<< (rectangle.full_width ? " (full_width)" : "") << ":";
for (const auto& element_proto : rectangle_proto.elements()) {
rectangle.positions.emplace_back();
ElementPosition& position = rectangle.positions.back();
position.selector = Selector(element_proto).MustBeVisible();
DVLOG(3) << " " << position.selector;
}
}
ReportUpdate();
if (rectangles_.empty())
return;
if (!scheduled_update_) {
// Check once and schedule regular updates.
scheduled_update_ = true;
KeepUpdatingElementPositions();
} else {
// If regular updates are already scheduled, just force a check of position
// right away and keep running the scheduled updates.
UpdatePositions();
}
}
void ElementArea::UpdatePositions() {
if (rectangles_.empty())
return;
for (auto& rectangle : rectangles_) {
for (auto& position : rectangle.positions) {
// To avoid reporting partial rectangles, all element positions become
// pending at the same time.
position.pending_update = true;
}
for (auto& position : rectangle.positions) {
delegate_->GetWebController()->GetElementPosition(
position.selector,
base::BindOnce(&ElementArea::OnGetElementPosition,
weak_ptr_factory_.GetWeakPtr(), position.selector));
}
}
}
void ElementArea::GetRectangles(std::vector<RectF>* area) {
for (auto& rectangle : rectangles_) {
area->emplace_back();
rectangle.FillRect(&area->back());
}
}
ElementArea::ElementPosition::ElementPosition() = default;
ElementArea::ElementPosition::ElementPosition(const ElementPosition& orig) =
default;
ElementArea::ElementPosition::~ElementPosition() = default;
ElementArea::Rectangle::Rectangle() = default;
ElementArea::Rectangle::Rectangle(const Rectangle& orig) = default;
ElementArea::Rectangle::~Rectangle() = default;
bool ElementArea::Rectangle::IsPending() const {
for (const auto& position : positions) {
if (position.pending_update)
return true;
}
return false;
}
void ElementArea::Rectangle::FillRect(RectF* rect) const {
bool has_first_rect = false;
for (const auto& position : positions) {
if (position.rect.empty())
continue;
if (!has_first_rect) {
*rect = position.rect;
has_first_rect = true;
continue;
}
rect->top = std::min(rect->top, position.rect.top);
rect->bottom = std::max(rect->bottom, position.rect.bottom);
rect->left = std::min(rect->left, position.rect.left);
rect->right = std::max(rect->right, position.rect.right);
}
if (has_first_rect && full_width) {
rect->left = 0.0;
rect->right = 1.0;
}
return;
}
void ElementArea::KeepUpdatingElementPositions() {
if (rectangles_.empty()) {
scheduled_update_ = false;
return;
}
UpdatePositions();
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&ElementArea::KeepUpdatingElementPositions,
weak_ptr_factory_.GetWeakPtr()),
kCheckDelay);
}
void ElementArea::OnGetElementPosition(const Selector& selector,
bool found,
const RectF& rect) {
// found == false, has all coordinates set to 0.0, which clears the area.
bool updated = false;
for (auto& rectangle : rectangles_) {
for (auto& position : rectangle.positions) {
if (selector == position.selector) {
position.pending_update = false;
position.rect = rect;
updated = true;
}
}
}
if (updated) {
ReportUpdate();
}
// If the set of elements has changed, the given selector will not be found in
// rectangles_. This is fine.
}
void ElementArea::ReportUpdate() {
if (!on_update_)
return;
for (const auto& rectangle : rectangles_) {
if (rectangle.IsPending()) {
// We don't have everything we need yet
return;
}
}
std::vector<RectF> area;
GetRectangles(&area);
on_update_.Run(area);
}
} // namespace autofill_assistant