blob: 499d4e4e717671239f47d3551fa138696ef43e7a [file] [log] [blame]
// Copyright 2016 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 "base/command_line.h"
#include "base/strings/utf_string_conversions.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/public/browser/notification_types.h"
#include "content/public/common/content_switches.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/content_browser_test.h"
#include "content/public/test/content_browser_test_utils.h"
#include "content/public/test/test_navigation_observer.h"
#include "content/public/test/test_utils.h"
#include "content/shell/browser/shell.h"
#include "content/test/content_browser_test_utils_internal.h"
#include "net/dns/mock_host_resolver.h"
#include "third_party/WebKit/public/web/WebFindOptions.h"
namespace content {
namespace {
const int kInvalidId = -1;
// The results of a find request.
struct FindResults {
FindResults(int request_id, int number_of_matches, int active_match_ordinal)
: request_id(request_id),
number_of_matches(number_of_matches),
active_match_ordinal(active_match_ordinal) {}
FindResults() : FindResults(kInvalidId, 0, 0) {}
int request_id;
int number_of_matches;
int active_match_ordinal;
};
} // namespace
class TestWebContentsDelegate : public WebContentsDelegate {
public:
TestWebContentsDelegate()
: last_request_id_(kInvalidId),
last_finished_request_id_(kInvalidId),
next_reply_received_(false),
record_replies_(false),
waiting_for_(NOTHING) {}
~TestWebContentsDelegate() override {}
// Returns the current find results.
const FindResults& GetFindResults() const {
return current_results_;
}
// Waits for all pending replies to be received.
void WaitForFinalReply() {
if (last_finished_request_id_ >= last_request_id_)
return;
WaitFor(FINAL_REPLY);
}
// Waits for the next find reply. This is useful for waiting for a single
// match to be activated, or for a new frame to be searched.
void WaitForNextReply() {
if (next_reply_received_)
return;
WaitFor(NEXT_REPLY);
}
// Indicates that the next find reply from this point will be the one to wait
// for when WaitForNextReply() is called. It may be the case that the reply
// comes before the call to WaitForNextReply(), in which case it will return
// immediately.
void MarkNextReply() {
next_reply_received_ = false;
}
// Called when a new find request is issued, so the delegate knows the last
// request ID.
void UpdateLastRequest(int request_id) {
last_request_id_ = request_id;
}
// From when this function is called, all replies coming in via FindReply()
// will be recorded. These replies can be retrieved via GetReplyRecord().
void StartReplyRecord() {
reply_record_.clear();
record_replies_ = true;
}
// Retreives the results from the find replies recorded since the last call to
// StartReplyRecord(). Calling this function also stops the recording new find
// replies.
const std::vector<FindResults>& GetReplyRecord() {
record_replies_ = false;
return reply_record_;
}
#if defined(OS_ANDROID)
// Waits for all of the find match rects to be received.
void WaitForMatchRects() {
WaitFor(MATCH_RECTS);
}
const std::vector<gfx::RectF>& find_match_rects() const {
return find_match_rects_;
}
const gfx::RectF& active_match_rect() const {
return active_match_rect_;
}
#endif
private:
enum WaitingFor {
NOTHING,
FINAL_REPLY,
NEXT_REPLY,
#if defined(OS_ANDROID)
MATCH_RECTS
#endif
};
// WebContentsDelegate override.
void FindReply(WebContents* web_contents,
int request_id,
int number_of_matches,
const gfx::Rect& selection_rect,
int active_match_ordinal,
bool final_update) override {
if (record_replies_) {
reply_record_.emplace_back(
request_id, number_of_matches, active_match_ordinal);
}
// Update the current results.
if (request_id > current_results_.request_id)
current_results_.request_id = request_id;
if (number_of_matches != -1)
current_results_.number_of_matches = number_of_matches;
if (active_match_ordinal != -1)
current_results_.active_match_ordinal = active_match_ordinal;
if (!final_update)
return;
if (request_id > last_finished_request_id_)
last_finished_request_id_ = request_id;
next_reply_received_ = true;
// If we are waiting for this find reply, stop waiting.
if (waiting_for_ == NEXT_REPLY ||
(waiting_for_ == FINAL_REPLY &&
last_finished_request_id_ >= last_request_id_)) {
StopWaiting();
}
}
// Uses |message_loop_runner_| to wait for various things.
void WaitFor(WaitingFor wait_for) {
ASSERT_EQ(NOTHING, waiting_for_);
ASSERT_NE(NOTHING, wait_for);
// Wait for |wait_for|.
waiting_for_ = wait_for;
message_loop_runner_ = new content::MessageLoopRunner;
message_loop_runner_->Run();
// Done waiting.
waiting_for_ = NOTHING;
message_loop_runner_ = nullptr;
}
// Stop waiting for |waiting_for_|.
void StopWaiting() {
if (!message_loop_runner_.get())
return;
ASSERT_NE(NOTHING, waiting_for_);
message_loop_runner_->Quit();
}
#if defined(OS_ANDROID)
// WebContentsDelegate override.
void FindMatchRectsReply(WebContents* web_contents,
int version,
const std::vector<gfx::RectF>& rects,
const gfx::RectF& active_rect) override {
// Update the current rects.
find_match_rects_ = rects;
active_match_rect_ = active_rect;
// If we are waiting for match rects, stop waiting.
if (waiting_for_ == MATCH_RECTS)
StopWaiting();
}
std::vector<gfx::RectF> find_match_rects_;
gfx::RectF active_match_rect_;
#endif
// The latest known results from the current find request.
FindResults current_results_;
// The ID of the last find request issued.
int last_request_id_;
// The ID of the last find request to finish (all replies received).
int last_finished_request_id_;
// Indicates whether the next reply after MarkNextReply() has been received.
bool next_reply_received_;
// Indicates whether the find results from incoming find replies are currently
// being recorded.
bool record_replies_;
// A record of all find replies that have come in via FindReply() since
// StartReplyRecor() was last called.
std::vector<FindResults> reply_record_;
// Indicates what |message_loop_runner_| is waiting for, if anything.
WaitingFor waiting_for_;
scoped_refptr<content::MessageLoopRunner> message_loop_runner_;
DISALLOW_COPY_AND_ASSIGN(TestWebContentsDelegate);
};
class FindRequestManagerTest : public ContentBrowserTest,
public testing::WithParamInterface<bool> {
public:
FindRequestManagerTest()
: normal_delegate_(nullptr),
last_request_id_(0) {}
~FindRequestManagerTest() override {}
void SetUpOnMainThread() override {
host_resolver()->AddRule("*", "127.0.0.1");
ASSERT_TRUE(embedded_test_server()->Start());
// Swap the WebContents's delegate for our test delegate.
normal_delegate_ = contents()->GetDelegate();
contents()->SetDelegate(&test_delegate_);
}
void TearDownOnMainThread() override {
// Swap the WebContents's delegate back to its usual delegate.
contents()->SetDelegate(normal_delegate_);
}
#if !defined(OS_ANDROID)
void SetUpCommandLine(base::CommandLine* command_line) override {
IsolateAllSitesForTesting(command_line);
}
#endif
protected:
// Navigates to |url| and waits for it to finish loading.
void LoadAndWait(const std::string& url) {
TestNavigationObserver navigation_observer(contents());
NavigateToURL(shell(), embedded_test_server()->GetURL("a.com", url));
EXPECT_TRUE(navigation_observer.last_navigation_succeeded());
}
// Loads a multi-frame page. The page will have a full binary frame tree of
// height |height|. If |cross_process| is true, child frames will be loaded
// cross-process.
void LoadMultiFramePage(int height, bool cross_process) {
LoadAndWait("/find_in_page_multi_frame.html");
FrameTreeNode* root = contents()->GetFrameTree()->root();
LoadMultiFramePageChildFrames(height, cross_process, root);
}
// Reloads the child frame cross-process.
void MakeChildFrameCrossProcess() {
FrameTreeNode* root = contents()->GetFrameTree()->root();
FrameTreeNode* child = root->child_at(0);
GURL url(embedded_test_server()->GetURL(
"b.com", child->current_url().path()));
TestNavigationObserver observer(shell()->web_contents());
NavigateFrameToURL(child, url);
EXPECT_EQ(url, observer.last_navigation_url());
EXPECT_TRUE(observer.last_navigation_succeeded());
}
void Find(const std::string& search_text,
const blink::WebFindOptions& options) {
delegate()->UpdateLastRequest(++last_request_id_);
contents()->Find(last_request_id_,
base::UTF8ToUTF16(search_text),
options);
}
WebContentsImpl* contents() const {
return static_cast<WebContentsImpl*>(shell()->web_contents());
}
TestWebContentsDelegate* delegate() const {
return static_cast<TestWebContentsDelegate*>(contents()->GetDelegate());
}
int last_request_id() const {
return last_request_id_;
}
private:
// Helper function for LoadMultiFramePage. Loads child frames until the frame
// tree rooted at |root| is a full binary tree of height |height|.
void LoadMultiFramePageChildFrames(int height,
bool cross_process,
FrameTreeNode* root) {
if (height == 0)
return;
std::string hostname = root->current_origin().host();
if (cross_process)
hostname.insert(0, 1, 'a');
GURL url(embedded_test_server()->GetURL(hostname,
"/find_in_page_multi_frame.html"));
TestNavigationObserver observer(shell()->web_contents());
FrameTreeNode* child = root->child_at(0);
NavigateFrameToURL(child, url);
EXPECT_TRUE(observer.last_navigation_succeeded());
LoadMultiFramePageChildFrames(height - 1, cross_process, child);
child = root->child_at(1);
NavigateFrameToURL(child, url);
EXPECT_TRUE(observer.last_navigation_succeeded());
LoadMultiFramePageChildFrames(height - 1, cross_process, child);
}
TestWebContentsDelegate test_delegate_;
WebContentsDelegate* normal_delegate_;
// The ID of the last find request requested.
int last_request_id_;
DISALLOW_COPY_AND_ASSIGN(FindRequestManagerTest);
};
// Frames are made cross-process when the test param is set to
// true. Cross-process frames are not used on android.
#if defined(OS_ANDROID)
INSTANTIATE_TEST_CASE_P(
FindRequestManagerTests, FindRequestManagerTest, testing::Values(false));
#else
INSTANTIATE_TEST_CASE_P(
FindRequestManagerTests, FindRequestManagerTest, testing::Bool());
#endif
// TODO(crbug.com/615291): These tests sometimes fail on the
// linux_android_rel_ng trybot.
#if defined(OS_ANDROID) && defined(NDEBUG)
#define MAYBE(x) DISABLED_##x
#else
#define MAYBE(x) x
#endif
// Tests basic find-in-page functionality (such as searching forward and
// backward) and check for correct results at each step.
IN_PROC_BROWSER_TEST_P(FindRequestManagerTest, MAYBE(Basic)) {
LoadAndWait("/find_in_page.html");
if (GetParam())
MakeChildFrameCrossProcess();
blink::WebFindOptions options;
Find("result", options);
delegate()->WaitForFinalReply();
FindResults results = delegate()->GetFindResults();
EXPECT_EQ(last_request_id(), results.request_id);
EXPECT_EQ(19, results.number_of_matches);
EXPECT_EQ(1, results.active_match_ordinal);
options.findNext = true;
for (int i = 2; i <= 10; ++i) {
Find("result", options);
delegate()->WaitForFinalReply();
results = delegate()->GetFindResults();
EXPECT_EQ(last_request_id(), results.request_id);
EXPECT_EQ(19, results.number_of_matches);
EXPECT_EQ(i, results.active_match_ordinal);
}
options.forward = false;
for (int i = 9; i >= 5; --i) {
Find("result", options);
delegate()->WaitForFinalReply();
results = delegate()->GetFindResults();
EXPECT_EQ(last_request_id(), results.request_id);
EXPECT_EQ(19, results.number_of_matches);
EXPECT_EQ(i, results.active_match_ordinal);
}
}
// Tests searching for a word character-by-character, as would typically be done
// by a user typing into the find bar.
IN_PROC_BROWSER_TEST_P(FindRequestManagerTest, MAYBE(CharacterByCharacter)) {
LoadAndWait("/find_in_page.html");
if (GetParam())
MakeChildFrameCrossProcess();
blink::WebFindOptions default_options;
Find("r", default_options);
Find("re", default_options);
Find("res", default_options);
Find("resu", default_options);
Find("resul", default_options);
Find("result", default_options);
delegate()->WaitForFinalReply();
FindResults results = delegate()->GetFindResults();
EXPECT_EQ(last_request_id(), results.request_id);
EXPECT_EQ(19, results.number_of_matches);
EXPECT_EQ(1, results.active_match_ordinal);
}
// Tests sending a large number of find requests subsequently.
IN_PROC_BROWSER_TEST_P(FindRequestManagerTest, MAYBE(RapidFire)) {
LoadAndWait("/find_in_page.html");
if (GetParam())
MakeChildFrameCrossProcess();
blink::WebFindOptions options;
Find("result", options);
options.findNext = true;
for (int i = 2; i <= 1000; ++i)
Find("result", options);
delegate()->WaitForFinalReply();
FindResults results = delegate()->GetFindResults();
EXPECT_EQ(last_request_id(), results.request_id);
EXPECT_EQ(19, results.number_of_matches);
EXPECT_EQ(last_request_id() % results.number_of_matches,
results.active_match_ordinal);
}
// Tests removing a frame during a find session.
IN_PROC_BROWSER_TEST_P(FindRequestManagerTest, MAYBE(RemoveFrame)) {
LoadMultiFramePage(2 /* height */, GetParam() /* cross_process */);
blink::WebFindOptions options;
Find("result", options);
delegate()->WaitForFinalReply();
options.findNext = true;
options.forward = false;
Find("result", options);
Find("result", options);
Find("result", options);
Find("result", options);
Find("result", options);
delegate()->WaitForFinalReply();
FindResults results = delegate()->GetFindResults();
EXPECT_EQ(last_request_id(), results.request_id);
EXPECT_EQ(21, results.number_of_matches);
EXPECT_EQ(17, results.active_match_ordinal);
// Remove a frame.
FrameTreeNode* root = contents()->GetFrameTree()->root();
root->RemoveChild(root->child_at(0));
// The number of matches and active match ordinal should update automatically
// to exclude the matches from the removed frame.
results = delegate()->GetFindResults();
EXPECT_EQ(12, results.number_of_matches);
EXPECT_EQ(8, results.active_match_ordinal);
}
// Tests adding a frame during a find session.
IN_PROC_BROWSER_TEST_P(FindRequestManagerTest, MAYBE(AddFrame)) {
LoadMultiFramePage(2 /* height */, GetParam() /* cross_process */);
blink::WebFindOptions options;
Find("result", options);
options.findNext = true;
Find("result", options);
Find("result", options);
Find("result", options);
Find("result", options);
delegate()->WaitForFinalReply();
FindResults results = delegate()->GetFindResults();
EXPECT_EQ(last_request_id(), results.request_id);
EXPECT_EQ(21, results.number_of_matches);
EXPECT_EQ(5, results.active_match_ordinal);
// Add a frame. It contains 5 new matches.
std::string url = embedded_test_server()->GetURL(
GetParam() ? "b.com" : "a.com", "/find_in_simple_page.html").spec();
std::string script = std::string() +
"var frame = document.createElement('iframe');" +
"frame.src = '" + url + "';" +
"document.body.appendChild(frame);";
delegate()->MarkNextReply();
ASSERT_TRUE(ExecuteScript(shell(), script));
delegate()->WaitForNextReply();
// The number of matches should update automatically to include the matches
// from the newly added frame.
results = delegate()->GetFindResults();
EXPECT_EQ(26, results.number_of_matches);
EXPECT_EQ(5, results.active_match_ordinal);
}
// Tests adding a frame during a find session where there were previously no
// matches.
IN_PROC_BROWSER_TEST_F(FindRequestManagerTest, MAYBE(AddFrameAfterNoMatches)) {
TestNavigationObserver navigation_observer(contents());
NavigateToURL(shell(), GURL("about:blank"));
EXPECT_TRUE(navigation_observer.last_navigation_succeeded());
blink::WebFindOptions default_options;
Find("result", default_options);
delegate()->WaitForFinalReply();
// Initially, there are no matches on the page.
FindResults results = delegate()->GetFindResults();
EXPECT_EQ(last_request_id(), results.request_id);
EXPECT_EQ(0, results.number_of_matches);
EXPECT_EQ(0, results.active_match_ordinal);
// Add a frame. It contains 5 new matches.
std::string url =
embedded_test_server()->GetURL("/find_in_simple_page.html").spec();
std::string script = std::string() +
"var frame = document.createElement('iframe');" +
"frame.src = '" + url + "';" +
"document.body.appendChild(frame);";
delegate()->MarkNextReply();
ASSERT_TRUE(ExecuteScript(shell(), script));
delegate()->WaitForNextReply();
// The matches from the new frame should be found automatically, and the first
// match in the frame should be activated.
results = delegate()->GetFindResults();
EXPECT_EQ(5, results.number_of_matches);
EXPECT_EQ(1, results.active_match_ordinal);
}
// Tests a frame navigating to a different page during a find session.
IN_PROC_BROWSER_TEST_P(FindRequestManagerTest, MAYBE(NavigateFrame)) {
LoadMultiFramePage(2 /* height */, GetParam() /* cross_process */);
blink::WebFindOptions options;
Find("result", options);
options.findNext = true;
options.forward = false;
Find("result", options);
Find("result", options);
Find("result", options);
delegate()->WaitForFinalReply();
FindResults results = delegate()->GetFindResults();
EXPECT_EQ(last_request_id(), results.request_id);
EXPECT_EQ(21, results.number_of_matches);
EXPECT_EQ(19, results.active_match_ordinal);
// Navigate one of the empty frames to a page with 5 matches.
FrameTreeNode* root =
static_cast<WebContentsImpl*>(shell()->web_contents())->
GetFrameTree()->root();
GURL url(embedded_test_server()->GetURL(
GetParam() ? "b.com" : "a.com", "/find_in_simple_page.html"));
delegate()->MarkNextReply();
TestNavigationObserver navigation_observer(contents());
NavigateFrameToURL(root->child_at(0)->child_at(1)->child_at(0), url);
EXPECT_TRUE(navigation_observer.last_navigation_succeeded());
delegate()->WaitForNextReply();
// The navigation results in an extra reply before the one we care about. This
// extra reply happens because the RenderFrameHost changes before it navigates
// (because the navigation is cross-origin). The first reply will not change
// the number of matches because the frame that is navigating was empty
// before.
if (delegate()->GetFindResults().number_of_matches == 21) {
delegate()->MarkNextReply();
delegate()->WaitForNextReply();
}
// The number of matches and the active match ordinal should update
// automatically to include the new matches.
results = delegate()->GetFindResults();
EXPECT_EQ(26, results.number_of_matches);
EXPECT_EQ(24, results.active_match_ordinal);
}
// Tests Searching in a hidden frame. Matches in the hidden frame should be
// ignored.
IN_PROC_BROWSER_TEST_F(FindRequestManagerTest, MAYBE(HiddenFrame)) {
LoadAndWait("/find_in_hidden_frame.html");
blink::WebFindOptions default_options;
Find("hello", default_options);
delegate()->WaitForFinalReply();
FindResults results = delegate()->GetFindResults();
EXPECT_EQ(last_request_id(), results.request_id);
EXPECT_EQ(1, results.number_of_matches);
EXPECT_EQ(1, results.active_match_ordinal);
}
// Tests that new matches can be found in dynamically added text.
IN_PROC_BROWSER_TEST_P(FindRequestManagerTest, MAYBE(FindNewMatches)) {
LoadAndWait("/find_in_dynamic_page.html");
blink::WebFindOptions options;
Find("result", options);
options.findNext = true;
Find("result", options);
Find("result", options);
delegate()->WaitForFinalReply();
FindResults results = delegate()->GetFindResults();
EXPECT_EQ(last_request_id(), results.request_id);
EXPECT_EQ(3, results.number_of_matches);
EXPECT_EQ(3, results.active_match_ordinal);
// Dynamically add new text to the page. This text contains 5 new matches for
// "result".
ASSERT_TRUE(ExecuteScript(contents()->GetMainFrame(), "addNewText()"));
Find("result", options);
delegate()->WaitForFinalReply();
results = delegate()->GetFindResults();
EXPECT_EQ(last_request_id(), results.request_id);
EXPECT_EQ(8, results.number_of_matches);
EXPECT_EQ(4, results.active_match_ordinal);
}
IN_PROC_BROWSER_TEST_F(FindRequestManagerTest, MAYBE(FindInPage_Issue627799)) {
LoadAndWait("/find_in_long_page.html");
blink::WebFindOptions options;
Find("42", options);
delegate()->WaitForFinalReply();
FindResults results = delegate()->GetFindResults();
EXPECT_EQ(last_request_id(), results.request_id);
EXPECT_EQ(970, results.number_of_matches);
EXPECT_EQ(1, results.active_match_ordinal);
delegate()->StartReplyRecord();
options.findNext = true;
options.forward = false;
Find("42", options);
delegate()->WaitForFinalReply();
// This is the crux of the issue that this test guards against. Searching
// across the frame boundary should not cause the frame to be re-scoped. If
// the re-scope occurs, then we will see the number of matches change in one
// of the recorded find replies.
for (auto& reply : delegate()->GetReplyRecord()) {
EXPECT_EQ(last_request_id(), reply.request_id);
EXPECT_TRUE(reply.number_of_matches == kInvalidId ||
reply.number_of_matches == results.number_of_matches);
}
}
IN_PROC_BROWSER_TEST_F(FindRequestManagerTest, MAYBE(FindInPage_Issue644448)) {
TestNavigationObserver navigation_observer(contents());
NavigateToURL(shell(), GURL("about:blank"));
EXPECT_TRUE(navigation_observer.last_navigation_succeeded());
blink::WebFindOptions default_options;
Find("result", default_options);
delegate()->WaitForFinalReply();
// Initially, there are no matches on the page.
FindResults results = delegate()->GetFindResults();
EXPECT_EQ(last_request_id(), results.request_id);
EXPECT_EQ(0, results.number_of_matches);
EXPECT_EQ(0, results.active_match_ordinal);
// Load a page with matches.
LoadAndWait("/find_in_simple_page.html");
Find("result", default_options);
delegate()->WaitForFinalReply();
// There should now be matches found. When the bug was present, there were
// still no matches found.
results = delegate()->GetFindResults();
EXPECT_EQ(last_request_id(), results.request_id);
EXPECT_EQ(5, results.number_of_matches);
}
#if defined(OS_ANDROID)
// Tests requesting find match rects.
IN_PROC_BROWSER_TEST_F(FindRequestManagerTest, MAYBE(FindMatchRects)) {
LoadAndWait("/find_in_page.html");
blink::WebFindOptions default_options;
Find("result", default_options);
delegate()->WaitForFinalReply();
EXPECT_EQ(19, delegate()->GetFindResults().number_of_matches);
// Request the find match rects.
contents()->RequestFindMatchRects(-1);
delegate()->WaitForMatchRects();
const std::vector<gfx::RectF>& rects = delegate()->find_match_rects();
// The first match should be active.
EXPECT_EQ(rects[0], delegate()->active_match_rect());
// All results after the first two should be between them in find-in-page
// coordinates. This is because results 2 to 19 are inside an iframe located
// between results 0 and 1. This applies to the fixed div too.
EXPECT_LT(rects[0].y(), rects[1].y());
for (int i = 2; i < 19; ++i) {
EXPECT_LT(rects[0].y(), rects[i].y());
EXPECT_GT(rects[1].y(), rects[i].y());
}
// Result 3 should be below results 2 and 4. This is caused by the CSS
// transform in the containing div. If the transform doesn't work then result
// 3 will be between results 2 and 4.
EXPECT_GT(rects[3].y(), rects[2].y());
EXPECT_GT(rects[3].y(), rects[4].y());
// Results 6, 7, 8 and 9 should be one below the other in that same order. If
// overflow:scroll is not properly handled then result 8 would be below result
// 9 or result 7 above result 6 depending on the scroll.
EXPECT_LT(rects[6].y(), rects[7].y());
EXPECT_LT(rects[7].y(), rects[8].y());
EXPECT_LT(rects[8].y(), rects[9].y());
// Results 11, 12, 13 and 14 should be between results 10 and 15, as they are
// inside the table.
EXPECT_GT(rects[11].y(), rects[10].y());
EXPECT_GT(rects[12].y(), rects[10].y());
EXPECT_GT(rects[13].y(), rects[10].y());
EXPECT_GT(rects[14].y(), rects[10].y());
EXPECT_LT(rects[11].y(), rects[15].y());
EXPECT_LT(rects[12].y(), rects[15].y());
EXPECT_LT(rects[13].y(), rects[15].y());
EXPECT_LT(rects[14].y(), rects[15].y());
// Result 11 should be above results 12, 13 and 14 as it's in the table
// header.
EXPECT_LT(rects[11].y(), rects[12].y());
EXPECT_LT(rects[11].y(), rects[13].y());
EXPECT_LT(rects[11].y(), rects[14].y());
// Result 11 should also be right of results 12, 13 and 14 because of the
// colspan.
EXPECT_GT(rects[11].x(), rects[12].x());
EXPECT_GT(rects[11].x(), rects[13].x());
EXPECT_GT(rects[11].x(), rects[14].x());
// Result 12 should be left of results 11, 13 and 14 in the table layout.
EXPECT_LT(rects[12].x(), rects[11].x());
EXPECT_LT(rects[12].x(), rects[13].x());
EXPECT_LT(rects[12].x(), rects[14].x());
// Results 13, 12 and 14 should be one above the other in that order because
// of the rowspan and vertical-align: middle by default.
EXPECT_LT(rects[13].y(), rects[12].y());
EXPECT_LT(rects[12].y(), rects[14].y());
// Result 16 should be below result 15.
EXPECT_GT(rects[15].y(), rects[14].y());
// Result 18 should be normalized with respect to the position:relative div,
// and not it's immediate containing div. Consequently, result 18 should be
// above result 17.
EXPECT_GT(rects[17].y(), rects[18].y());
}
// Tests activating the find match nearest to a given point.
IN_PROC_BROWSER_TEST_F(FindRequestManagerTest,
MAYBE(ActivateNearestFindMatch)) {
LoadAndWait("/find_in_page.html");
blink::WebFindOptions default_options;
Find("result", default_options);
delegate()->WaitForFinalReply();
EXPECT_EQ(19, delegate()->GetFindResults().number_of_matches);
// Get the find match rects.
contents()->RequestFindMatchRects(-1);
delegate()->WaitForMatchRects();
const std::vector<gfx::RectF>& rects = delegate()->find_match_rects();
// Activate matches via points inside each of the find match rects, in an
// arbitrary order. Check that the correct match becomes active after each
// activation.
int order[19] =
{11, 13, 2, 0, 16, 5, 7, 10, 6, 1, 15, 14, 9, 17, 18, 3, 8, 12, 4};
for (int i = 0; i < 19; ++i) {
delegate()->MarkNextReply();
contents()->ActivateNearestFindResult(
rects[order[i]].CenterPoint().x(), rects[order[i]].CenterPoint().y());
delegate()->WaitForNextReply();
EXPECT_EQ(order[i] + 1, delegate()->GetFindResults().active_match_ordinal);
}
}
#endif // defined(OS_ANDROID)
} // namespace content