Skip to content

Commit

Permalink
[Payments] Add a substring search method in WebNode
Browse files Browse the repository at this point in the history
This patch adds a WebNode method that searches text in descendant
elements to determine whether there is a matching substring in one of
them. If there is a match, then the method returns the text content of
the element that contains the substring. Otherwise, the method returns
an empty string.

Bug: 1475426
Change-Id: I7c2883c1f474006455fbf0c88c7606658d4b2061
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4951976
Reviewed-by: David Baron <dbaron@chromium.org>
Commit-Queue: Rouslan Solomakhin <rouslan@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1232844}
  • Loading branch information
rsolomakhin authored and Chromium LUCI CQ committed Dec 4, 2023
1 parent d190de3 commit 77b04b9
Show file tree
Hide file tree
Showing 7 changed files with 209 additions and 2 deletions.
4 changes: 4 additions & 0 deletions third_party/blink/public/web/web_node.h
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,10 @@ class BLINK_EXPORT WebNode {

WebVector<WebElement> QuerySelectorAll(const WebString& selector) const;

// Returns the contents of the first descendant element, if any, that contains
// only text, a part of which is the given substring.
WebString FindTextInElementWith(const WebString& substring) const;

bool Focused() const;

WebPluginContainer* PluginContainer() const;
Expand Down
1 change: 1 addition & 0 deletions third_party/blink/renderer/core/dom/build.gni
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,7 @@ blink_core_sources_dom = [
blink_core_tests_dom = [
"abort_signal_test.cc",
"attr_test.cc",
"container_node_test.cc",
"document_statistics_collector_test.cc",
"document_test.cc",
"dom_node_ids_test.cc",
Expand Down
32 changes: 32 additions & 0 deletions third_party/blink/renderer/core/dom/container_node.cc
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@
#include "third_party/blink/renderer/platform/instrumentation/use_counter.h"
#include "third_party/blink/renderer/platform/runtime_enabled_features.h"
#include "third_party/blink/renderer/platform/wtf/casting.h"
#include "third_party/blink/renderer/platform/wtf/wtf_size_t.h"

namespace blink {

Expand Down Expand Up @@ -1253,6 +1254,24 @@ unsigned ContainerNode::CountChildren() const {
return count;
}

bool ContainerNode::HasOnlyText() const {
bool has_text = false;
for (Node* child = firstChild(); child; child = child->nextSibling()) {
switch (child->getNodeType()) {
case kTextNode:
case kCdataSectionNode:
has_text = has_text || !To<Text>(child)->data().empty();
break;
case kCommentNode:
// Ignore comments.
break;
default:
return false;
}
}
return has_text;
}

Element* ContainerNode::QuerySelector(const AtomicString& selectors,
ExceptionState& exception_state) {
SelectorQuery* selector_query = GetDocument().GetSelectorQueryCache().Add(
Expand Down Expand Up @@ -1571,6 +1590,19 @@ RadioNodeList* ContainerNode::GetRadioNodeList(const AtomicString& name,
return EnsureCachedCollection<RadioNodeList>(type, name);
}

String ContainerNode::FindTextInElementWith(
const AtomicString& substring) const {
for (Element& element : ElementTraversal::DescendantsOf(*this)) {
if (element.HasOnlyText()) {
const String& text = element.TextFromChildren();
if (text.Find(substring) != WTF::kNotFound) {
return text;
}
}
}
return String();
}

Element* ContainerNode::getElementById(const AtomicString& id) const {
// According to https://dom.spec.whatwg.org/#concept-id, empty IDs are
// treated as equivalent to the lack of an id attribute.
Expand Down
13 changes: 11 additions & 2 deletions third_party/blink/renderer/core/dom/container_node.h
Original file line number Diff line number Diff line change
Expand Up @@ -111,12 +111,17 @@ class CORE_EXPORT ContainerNode : public Node {
bool HasOneChild() const {
return first_child_ && !first_child_->HasNextSibling();
}

bool HasChildCount(unsigned) const;
unsigned CountChildren() const;

bool HasOneTextChild() const {
return HasOneChild() && first_child_->IsTextNode();
}
bool HasChildCount(unsigned) const;

unsigned CountChildren() const;
// Returns true if all children are text nodes and at least one of them is not
// empty. Ignores comments.
bool HasOnlyText() const;

Element* QuerySelector(const AtomicString& selectors, ExceptionState&);
Element* QuerySelector(const AtomicString& selectors);
Expand Down Expand Up @@ -146,6 +151,10 @@ class CORE_EXPORT ContainerNode : public Node {
RadioNodeList* GetRadioNodeList(const AtomicString&,
bool only_match_img_elements = false);

// Returns the contents of the first descendant element, if any, that contains
// only text, a part of which is the given substring.
String FindTextInElementWith(const AtomicString& substring) const;

// These methods are only used during parsing.
// They don't send DOM mutation events or accept DocumentFragments.
void ParserAppendChild(Node*);
Expand Down
131 changes: 131 additions & 0 deletions third_party/blink/renderer/core/dom/container_node_test.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "third_party/blink/renderer/core/dom/container_node.h"

#include "third_party/blink/renderer/core/editing/testing/editing_test_base.h"
#include "third_party/blink/renderer/platform/wtf/text/atomic_string.h"
#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"

namespace blink {

using ContainerNodeTest = EditingTestBase;

TEST_F(ContainerNodeTest, HasOnlyTextReturnsFalseForEmptySpan) {
SetBodyContent(R"HTML(<body><span id="id"><span><body>)HTML");

EXPECT_FALSE(GetDocument().getElementById(AtomicString("id"))->HasOnlyText());
}

TEST_F(ContainerNodeTest, HasOnlyTextReturnsFalseForNonTextChild) {
SetBodyContent(R"HTML(<body><div id="id"><div>Nested</div><div><body>)HTML");

EXPECT_FALSE(GetDocument().getElementById(AtomicString("id"))->HasOnlyText());
}

TEST_F(ContainerNodeTest, HasOnlyTextReturnsTrueForSomeText) {
SetBodyContent(R"HTML(<body><p id="id"> Here is some text <p><body>)HTML");

EXPECT_TRUE(GetDocument().getElementById(AtomicString("id"))->HasOnlyText());
}

TEST_F(ContainerNodeTest, HasOnlyTextIgnoresComments) {
SetBodyContent(R"HTML(
<body>
<p id="id"> Here is some text
<!-- This is a comment that should be ignored. -->
<p>
<body>
)HTML");

EXPECT_TRUE(GetDocument().getElementById(AtomicString("id"))->HasOnlyText());
}

TEST_F(ContainerNodeTest, CannotFindTextInElementWithoutDescendants) {
SetBodyContent(R"HTML(<body><span id="id"></span></body>)HTML");

String text = GetDocument().FindTextInElementWith(AtomicString("anything"));

EXPECT_TRUE(text.empty());
}

TEST_F(ContainerNodeTest, CannotFindTextInElementWithNonTextDescendants) {
SetBodyContent(R"HTML(<body><span id="id"> Hello
<span></span> world! </span></body>)HTML");

String text = GetDocument().FindTextInElementWith(AtomicString("anything"));

EXPECT_TRUE(text.empty());
}

TEST_F(ContainerNodeTest, CannotFindTextInElementWithoutMatchingSubtring) {
SetBodyContent(R"HTML(<body><span id="id"> Hello </span></body>)HTML");

String text = GetDocument().FindTextInElementWith(AtomicString("Goodbye"));

EXPECT_TRUE(text.empty());
}

TEST_F(ContainerNodeTest, CanFindTextInElementWithOnlyTextDescendants) {
SetBodyContent(
R"HTML(<body><span id="id"> Find me please </span></body>)HTML");

String text = GetDocument().FindTextInElementWith(AtomicString("me"));

EXPECT_EQ(String(" Find me please "), text);
}

TEST_F(ContainerNodeTest, CanFindTextInElementWithManyDescendants) {
SetBodyContent(R"HTML(
<body>
<div id="id">
<div>
No need to find this
</div>
<div>
Something something here
<div> Find me please </div>
also over here
</div>
<div>
And more information here
</div>
</div>
<div>
Hi
</div>
</body>
)HTML");

String text = GetDocument().FindTextInElementWith(AtomicString(" me "));

EXPECT_EQ(String(" Find me please "), text);
}

TEST_F(ContainerNodeTest, FindTextInElementWithFirstMatch) {
SetBodyContent(R"HTML(
<body><div id="id">
<div> Text match #1 </div>
<div> Text match #2 </div>
</div></body>
)HTML");

String text = GetDocument().FindTextInElementWith(AtomicString(" match "));

EXPECT_EQ(String(" Text match #1 "), text);
}

TEST_F(ContainerNodeTest, FindTextInElementWithSubstringIgnoresComments) {
SetBodyContent(R"HTML(
<body>
<p id="id"> Before comment, <!-- The comment. --> after comment. <p>
<body>
)HTML");

String text = GetDocument().FindTextInElementWith(AtomicString("comment"));

EXPECT_EQ(String(" Before comment, after comment. "), text);
}

} // namespace blink
9 changes: 9 additions & 0 deletions third_party/blink/renderer/core/exported/web_node.cc
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,15 @@ WebVector<WebElement> WebNode::QuerySelectorAll(
return WebVector<WebElement>();
}

WebString WebNode::FindTextInElementWith(const WebString& substring) const {
ContainerNode* container_node =
blink::DynamicTo<ContainerNode>(private_.Get());
if (!container_node) {
return WebString();
}
return WebString(container_node->FindTextInElementWith(substring));
}

bool WebNode::Focused() const {
return private_->IsFocused();
}
Expand Down
21 changes: 21 additions & 0 deletions third_party/blink/renderer/core/exported/web_node_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -87,4 +87,25 @@ TEST_F(WebNodeSimTest, IsFocused) {
EXPECT_TRUE(input_node.IsFocusable());
}

TEST_F(WebNodeTest, CannotFindTextInElementThatIsNotAContainer) {
SetInnerHTML(R"HTML(
<div><br class="not-a-container"/> Hello world! </div>
)HTML");
WebElement element = Root().QuerySelector(AtomicString(".not-a-container"));

EXPECT_FALSE(element.IsNull());
EXPECT_TRUE(element.FindTextInElementWith("Hello world").IsEmpty());
}

TEST_F(WebNodeTest, CanFindTextInElementThatIsAContainer) {
SetInnerHTML(R"HTML(
<body class="container"><div> Hello world! </div></body>
)HTML");
WebElement element = Root().QuerySelector(AtomicString(".container"));

EXPECT_FALSE(element.IsNull());
EXPECT_EQ(WebString(" Hello world! "),
element.FindTextInElementWith("Hello world"));
}

} // namespace blink

0 comments on commit 77b04b9

Please sign in to comment.