Commit ed0be1ff authored by Stephane Zermatten's avatar Stephane Zermatten Committed by Commit Bot

[Autofill Assistant] Filter elements by CSS style in Selectors.

With this change, it becomes possible to filter element by computed CSS
property, for example, to find button that has not been hidden using
CSS, do:
  filters { css_selector: "button" }
  filters {
    css_style {
      property: "visibility"
      value { re2: "visible" }
    }
  }

The implementation is based on window.getComputedStyle.

Bug: b/171464032
Change-Id: Ic89219f969c614258edabec91705e9c37348138c
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2498484
Commit-Queue: Stephane Zermatten <szermatt@chromium.org>
Reviewed-by: default avatarClemens Arbesser <arbesser@google.com>
Cr-Commit-Position: refs/heads/master@{#821238}
parent ed6c24ca
......@@ -55,6 +55,14 @@ bool operator<(const SelectorProto::Filter& a, const SelectorProto::Filter& b) {
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::kBoundingBox:
case SelectorProto::Filter::kEnterFrame:
case SelectorProto::Filter::kPickOne:
......@@ -220,6 +228,7 @@ base::Optional<std::string> Selector::ExtractSingleCssSelectorForAutofill()
case SelectorProto::Filter::kValue:
case SelectorProto::Filter::kPseudoType:
case SelectorProto::Filter::kPseudoElementContent:
case SelectorProto::Filter::kCssStyle:
case SelectorProto::Filter::kLabelled:
case SelectorProto::Filter::kClosest:
case SelectorProto::Filter::kMatchCssSelector:
......@@ -323,6 +332,14 @@ std::ostream& operator<<(std::ostream& out, const SelectorProto::Filter& f) {
<< "~=" << 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:
out << "bounding_box";
return out;
......
......@@ -902,6 +902,8 @@ message SelectorProto {
//
// Note that this just filters out elements. It doesn't select the
// pseudo-element; use pseudo_type for that.
//
// Deprecated: prefer css_style. This should be removed in Chrome M89.
PseudoElementContent pseudo_element_content = 8;
// Go from label to the labelled control. Only works starting with current
......@@ -931,6 +933,10 @@ message SelectorProto {
// Only keep results that match the given CSS selector.
string match_css_selector = 11;
// Only keep elements whose computed style match the given filter. This is
// based on Window.computedStyle()
CssStyleFilter css_style = 12;
}
}
......@@ -952,6 +958,35 @@ message SelectorProto {
optional TextFilter content = 2;
}
// Only keep elements whose computed style match the given filter. This is
// based on Window.computedStyle()
//
// See
// https://developer.mozilla.org/en-US/docs/Web/API/Window/getComputedStyle
message CssStyleFilter {
// CSS property name.
optional string property = 3;
// Name of the pseudo-element whose style should be checked. Leave it unset
// or set to the empty string to check the style of the real element.
//
// This is the pseudoElt argument of JavaScript's
// window.getComputedStyle(element, [, pseudoElt]).
optional string pseudo_element = 4;
// By default the text filter in |value| must match. Set this to false to
// require the text condition not to match.
optional bool should_match = 5 [default = true];
// CSS property value match.
//
// Valid computed CSS properties always have a value, even if it's a default
// value. The default value depends on the property.
optional TextFilter value = 6;
reserved 1, 2;
}
// Filter elements by their position on the page, relative to a given target
// element.
message ProximityFilter {
......
......@@ -158,6 +158,23 @@ bool ElementFinder::JsFilterBuilder::AddFilter(
return true;
}
case SelectorProto::Filter::kCssStyle: {
std::string re_var = AddRegexpInstance(filter.css_style().value());
std::string property = AddArgument(filter.css_style().property());
std::string element = AddArgument(filter.css_style().pseudo_element());
AddLine("elements = elements.filter((e) => {");
AddLine(" const s = window.getComputedStyle(e, ");
AddLine({" ", element, " === '' ? null : ", element, ");"});
AddLine({" const match = ", re_var, ".test(s[", property, "]);"});
if (filter.css_style().should_match()) {
AddLine(" return match;");
} else {
AddLine(" return !match;");
}
AddLine("});");
return true;
}
case SelectorProto::Filter::kLabelled:
AddLine(R"(elements = elements.flatMap((e) => {
if (e.tagName != 'LABEL') return [];
......@@ -363,8 +380,9 @@ void ElementFinder::ExecuteNextTask() {
case SelectorProto::Filter::kValue:
case SelectorProto::Filter::kBoundingBox:
case SelectorProto::Filter::kPseudoElementContent:
case SelectorProto::Filter::kLabelled:
case SelectorProto::Filter::kMatchCssSelector: {
case SelectorProto::Filter::kMatchCssSelector:
case SelectorProto::Filter::kCssStyle:
case SelectorProto::Filter::kLabelled: {
std::vector<std::string> matches;
if (!ConsumeAllMatchesOrFail(matches))
return;
......
......@@ -1139,6 +1139,41 @@ IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, PseudoTypeContent) {
RunLaxElementCheck(selector, false);
}
IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest,
PseudoElementContentWithCssStyle) {
Selector selector({"#with_inner_text span"});
auto* style = selector.proto.add_filters()->mutable_css_style();
style->set_property("content");
style->set_pseudo_element("before");
style->mutable_value()->set_re2("\"before\"");
RunLaxElementCheck(selector, true);
style->mutable_value()->set_re2("\"nomatch\"");
RunLaxElementCheck(selector, false);
}
IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, CssVisibility) {
Selector selector({"#button"});
auto* style = selector.proto.add_filters()->mutable_css_style();
style->set_property("visibility");
style->mutable_value()->set_re2("visible");
EXPECT_TRUE(content::ExecJs(shell(), R"(
document.getElementById("button").style.visibility = 'hidden';
)"));
RunLaxElementCheck(selector, false);
style->set_should_match(false);
RunLaxElementCheck(selector, true);
EXPECT_TRUE(content::ExecJs(shell(), R"(
document.getElementById("button").style.visibility = 'visible';
)"));
style->set_should_match(true);
RunLaxElementCheck(selector, true);
style->set_should_match(false);
RunLaxElementCheck(selector, false);
}
IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, InnerTextThenCss) {
// There are two divs containing "Section with text", but only one has a
// button, which removes #button.
......
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