| // 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 |