blob: 2bc25775735b750c98ee39043b2a765e929a0986 [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/selector.h"
#include "base/logging.h"
#include "base/notreached.h"
#include "base/strings/string_util.h"
#include "components/autofill_assistant/browser/action_value.pb.h"
namespace autofill_assistant {
// Comparison operations are in the autofill_assistant scope, even though
// they're not shared outside of this module, for them to be visible to
// std::make_tuple and std::lexicographical_compare.
bool operator<(const TextFilter& a, const TextFilter& b) {
return std::make_tuple(a.re2(), a.case_sensitive()) <
std::make_tuple(b.re2(), b.case_sensitive());
}
// Used by operator<(RepeatedPtrField<Filter>, RepeatedPtrField<Filter>)
bool operator<(const SelectorProto::Filter& a, const SelectorProto::Filter& b);
bool operator<(
const google::protobuf::RepeatedPtrField<SelectorProto::Filter>& a,
const google::protobuf::RepeatedPtrField<SelectorProto::Filter>& b) {
return std::lexicographical_compare(a.begin(), a.end(), b.begin(), b.end());
}
bool operator<(const SelectorProto::Filter& a, const SelectorProto::Filter& b) {
if (a.filter_case() < b.filter_case()) {
return true;
}
if (a.filter_case() != b.filter_case()) {
return false;
}
switch (a.filter_case()) {
case SelectorProto::Filter::kCssSelector:
return a.css_selector() < b.css_selector();
case SelectorProto::Filter::kInnerText:
return a.inner_text() < b.inner_text();
case SelectorProto::Filter::kValue:
return a.value() < b.value();
case SelectorProto::Filter::kPseudoType:
return a.pseudo_type() < b.pseudo_type();
case SelectorProto::Filter::kPseudoElementContent:
return std::make_tuple(a.pseudo_element_content().pseudo_type(),
a.pseudo_element_content().content()) <
std::make_tuple(b.pseudo_element_content().pseudo_type(),
b.pseudo_element_content().content());
case SelectorProto::Filter::kCssStyle:
return std::make_tuple(a.css_style().property(),
a.css_style().pseudo_element(),
a.css_style().value()) <
std::make_tuple(b.css_style().property(),
b.css_style().pseudo_element(),
b.css_style().value());
case SelectorProto::Filter::kOnTop:
return std::make_tuple(a.on_top().scroll_into_view_if_needed(),
a.on_top().accept_element_if_not_in_view()) <
std::make_tuple(b.on_top().scroll_into_view_if_needed(),
b.on_top().accept_element_if_not_in_view());
case SelectorProto::Filter::kBoundingBox:
return a.bounding_box().require_nonempty() <
b.bounding_box().require_nonempty();
case SelectorProto::Filter::kEnterFrame:
case SelectorProto::Filter::kLabelled:
return false;
case SelectorProto::Filter::kMatchCssSelector:
return a.match_css_selector() < b.match_css_selector();
case SelectorProto::Filter::kNthMatch:
return a.nth_match().index() < b.nth_match().index();
case SelectorProto::Filter::FILTER_NOT_SET:
return false;
}
return false;
}
SelectorProto ToSelectorProto(const std::string& s) {
return ToSelectorProto(std::vector<std::string>{s});
}
SelectorProto ToSelectorProto(const std::vector<std::string>& s) {
SelectorProto proto;
if (!s.empty()) {
for (size_t i = 0; i < s.size(); i++) {
if (i > 0) {
proto.add_filters()->mutable_nth_match()->set_index(0);
proto.add_filters()->mutable_enter_frame();
}
proto.add_filters()->set_css_selector(s[i]);
}
}
return proto;
}
std::string PseudoTypeName(PseudoType pseudo_type) {
switch (pseudo_type) {
case UNDEFINED:
return "undefined";
case FIRST_LINE:
return "first-line";
case FIRST_LETTER:
return "first-letter";
case BEFORE:
return "before";
case AFTER:
return "after";
case BACKDROP:
return "backdrop";
case SELECTION:
return "selection";
case FIRST_LINE_INHERITED:
return "first-line-inherited";
case SCROLLBAR:
return "scrollbar";
case SCROLLBAR_THUMB:
return "scrollbar-thumb";
case SCROLLBAR_BUTTON:
return "scrollbar-button";
case SCROLLBAR_TRACK:
return "scrollbar-track";
case SCROLLBAR_TRACK_PIECE:
return "scrollbar-track-piece";
case SCROLLBAR_CORNER:
return "scrollbar-corner";
case RESIZER:
return "resizer";
case INPUT_LIST_BUTTON:
return "input-list-button";
// Intentionally no default case to make compilation fail if a new value
// was added to the enum but not to this list.
}
}
Selector::Selector() {}
Selector::Selector(const SelectorProto& selector_proto)
: proto(selector_proto) {}
Selector::~Selector() = default;
Selector::Selector(Selector&& other) = default;
Selector::Selector(const Selector& other) = default;
Selector& Selector::operator=(const Selector& other) = default;
Selector& Selector::operator=(Selector&& other) = default;
bool Selector::operator<(const Selector& other) const {
return proto.filters() < other.proto.filters();
}
bool Selector::operator==(const Selector& other) const {
return !(*this < other) && !(other < *this);
}
Selector& Selector::MustBeVisible() {
int filter_count = proto.filters().size();
if (filter_count == 0 || proto.filters(filter_count - 1).has_bounding_box()) {
// Avoids adding duplicate visibility requirements in the common case.
return *this;
}
proto.add_filters()->mutable_bounding_box();
return *this;
}
bool Selector::empty() const {
bool has_css_selector = false;
for (const SelectorProto::Filter& filter : proto.filters()) {
switch (filter.filter_case()) {
case SelectorProto::Filter::FILTER_NOT_SET:
// There must not be any unknown or invalid filters.
return true;
case SelectorProto::Filter::kCssSelector:
// There must be at least one CSS selector, since it's the only
// way we have of expanding the set of matches.
has_css_selector = true;
break;
default:
break;
}
}
return !has_css_selector;
}
std::ostream& operator<<(std::ostream& out, const Selector& selector) {
return out << selector.proto;
}
#ifndef NDEBUG
namespace {
// Debug output for pseudo types.
std::ostream& operator<<(std::ostream& out, PseudoType pseudo_type) {
return out << PseudoTypeName(pseudo_type);
}
std::ostream& operator<<(std::ostream& out, const TextFilter& c) {
out << "/" << c.re2() << "/";
if (c.case_sensitive()) {
out << "i";
}
return out;
}
std::ostream& operator<<(
std::ostream& out,
const google::protobuf::RepeatedPtrField<SelectorProto::Filter>& filters) {
out << "[";
std::string separator = "";
for (const SelectorProto::Filter& filter : filters) {
out << separator << filter;
separator = " ";
}
out << "]";
return out;
}
} // namespace
#endif // NDEBUG
std::ostream& operator<<(std::ostream& out, const SelectorProto& selector) {
#ifdef NDEBUG
out << selector.filters().size() << " filter(s)";
#else
out << selector.filters();
#endif // NDEBUG
return out;
}
std::ostream& operator<<(std::ostream& out, const SelectorProto::Filter& f) {
#ifdef NDEBUG
// DEBUG output not available.
return out << "filter case=" << f.filter_case();
#else
switch (f.filter_case()) {
case SelectorProto::Filter::kEnterFrame:
out << "/";
return out;
case SelectorProto::Filter::kCssSelector:
out << f.css_selector();
return out;
case SelectorProto::Filter::kInnerText:
out << "innerText~=" << f.inner_text();
return out;
case SelectorProto::Filter::kValue:
out << "value~=" << f.value();
return out;
case SelectorProto::Filter::kPseudoType:
out << "::" << f.pseudo_type();
return out;
case SelectorProto::Filter::kPseudoElementContent:
out << "::" << f.pseudo_element_content().pseudo_type()
<< "~=" << f.pseudo_element_content().content();
return out;
case SelectorProto::Filter::kCssStyle:
if (!f.css_style().pseudo_element().empty()) {
out << f.css_style().pseudo_element() << " ";
}
out << "style." << f.css_style().property()
<< "~=" << f.css_style().value();
return out;
case SelectorProto::Filter::kBoundingBox:
if (f.bounding_box().require_nonempty()) {
out << "bounding_box (nonempty)";
} else {
out << "bounding_box (any)";
}
return out;
case SelectorProto::Filter::kNthMatch:
out << "nth_match[" << f.nth_match().index() << "]";
return out;
case SelectorProto::Filter::kLabelled:
out << "labelled";
return out;
case SelectorProto::Filter::kMatchCssSelector:
out << "matches: " << f.css_selector();
return out;
case SelectorProto::Filter::kOnTop:
out << "on_top";
if (!f.on_top().scroll_into_view_if_needed()) {
out << "(no scroll)";
}
if (f.on_top().accept_element_if_not_in_view()) {
out << "(accept not in view)";
}
return out;
case SelectorProto::Filter::FILTER_NOT_SET:
// Either unset or set to an unsupported value. Let's assume the worse.
out << "INVALID";
return out;
}
#endif // NDEBUG
}
} // namespace autofill_assistant