
This is a reland of commit 143885f9e5
https://crrev.com/1045406 reverted this CL as a speculative fix for the
flakiness in https://crbug.com/1362116. The test remains flaky after
the revert, so reland this CL.
Original change's description:
> Fix PDF viewer jumping immediately after the user presses 'ctrl+f'
>
> This behavior is disruptive and inconsistent with the search behavior
> of non-PDF sites. After this commit, pressing 'ctrl+f' will not start
> a search until the user presses additional keys (add characters /
> press enter).
>
> This bug is a side effect of fixing another bug "Chrome PDF Search is
> not executed in background" (crbug.com/764635). The fix reverts the
> changes done by the previous bug fix (pdfium_engine.cc) and provides an
> alternative solution (find_request_manager.cc) which requires to update
> some tests (pdf_find_request_manager_browsertest.cc,
> find_request_manager_browsertest.cc, findtext_unittest.cc).
>
> Lastly this is my first bug so I added my name to AUTHORS.
>
> Bug: 1290824
> Change-Id: I8e152087fb2ac019124e477b4d04d73d7b8f2f21
> Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3455153
> Commit-Queue: Lei Zhang <thestig@chromium.org>
> Reviewed-by: Lei Zhang <thestig@chromium.org>
> Reviewed-by: Joey Arhar <jarhar@chromium.org>
> Reviewed-by: Pavel Feldman <pfeldman@chromium.org>
> Cr-Commit-Position: refs/heads/main@{#1043764}
Bug: 1290824
Change-Id: I1413c4db077a4cfd5c8e8dafe0a4ff31cd238ec3
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3892090
Commit-Queue: Lei Zhang <thestig@chromium.org>
Reviewed-by: K. Moon <kmoon@chromium.org>
Reviewed-by: Alex Moshchuk <alexmos@chromium.org>
Reviewed-by: Joey Arhar <jarhar@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1060267}
1873 lines
69 KiB
C++
1873 lines
69 KiB
C++
// Copyright 2016 The Chromium Authors
|
|
// 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/memory/raw_ptr.h"
|
|
#include "base/strings/utf_string_conversions.h"
|
|
#include "base/test/scoped_feature_list.h"
|
|
#include "build/build_config.h"
|
|
#include "content/browser/find_in_page_client.h"
|
|
#include "content/browser/find_request_manager.h"
|
|
#include "content/browser/web_contents/web_contents_impl.h"
|
|
#include "content/public/browser/browser_message_filter.h"
|
|
#include "content/public/browser/content_browser_client.h"
|
|
#include "content/public/browser/notification_types.h"
|
|
#include "content/public/common/content_client.h"
|
|
#include "content/public/common/content_switches.h"
|
|
#include "content/public/test/browser_test.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/fenced_frame_test_util.h"
|
|
#include "content/public/test/find_test_utils.h"
|
|
#include "content/public/test/prerender_test_util.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/blink/public/common/features.h"
|
|
#include "third_party/blink/public/common/web_preferences/web_preferences.h"
|
|
#include "third_party/blink/public/mojom/page/widget.mojom-test-utils.h"
|
|
#include "url/origin.h"
|
|
|
|
#if BUILDFLAG(IS_ANDROID)
|
|
#include "ui/android/view_android.h"
|
|
#endif
|
|
|
|
namespace content {
|
|
|
|
namespace {
|
|
|
|
const int kInvalidId = -1;
|
|
|
|
const url::Origin& GetOriginForFrameTreeNode(FrameTreeNode* node) {
|
|
return node->current_frame_host()->GetLastCommittedOrigin();
|
|
}
|
|
|
|
#if BUILDFLAG(IS_ANDROID)
|
|
double GetFrameDeviceScaleFactor(const ToRenderFrameHost& adapter) {
|
|
return EvalJs(adapter, "window.devicePixelRatio;").ExtractDouble();
|
|
}
|
|
#endif // BUILDFLAG(IS_ANDROID)
|
|
|
|
} // namespace
|
|
|
|
class FindRequestManagerTestBase : public ContentBrowserTest {
|
|
public:
|
|
FindRequestManagerTestBase()
|
|
: normal_delegate_(nullptr), last_request_id_(0) {}
|
|
|
|
FindRequestManagerTestBase(const FindRequestManagerTestBase&) = delete;
|
|
FindRequestManagerTestBase& operator=(const FindRequestManagerTestBase&) =
|
|
delete;
|
|
|
|
~FindRequestManagerTestBase() override = default;
|
|
|
|
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_);
|
|
}
|
|
|
|
void SetUpCommandLine(base::CommandLine* command_line) override {
|
|
IsolateAllSitesForTesting(command_line);
|
|
}
|
|
|
|
protected:
|
|
// Navigates to |url| and waits for it to finish loading.
|
|
void LoadAndWait(const std::string& url) {
|
|
TestNavigationObserver navigation_observer(contents());
|
|
EXPECT_TRUE(
|
|
NavigateToURL(shell(), embedded_test_server()->GetURL("a.com", url)));
|
|
ASSERT_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");
|
|
LoadMultiFramePageChildFrames(height, cross_process, root());
|
|
}
|
|
|
|
// Reloads the child frame cross-process.
|
|
void MakeChildFrameCrossProcess() {
|
|
FrameTreeNode* child = first_child();
|
|
GURL url =
|
|
embedded_test_server()->GetURL("b.com", child->current_url().path());
|
|
EXPECT_TRUE(NavigateToURLFromRenderer(child, url));
|
|
}
|
|
|
|
void Find(const std::string& search_text,
|
|
blink::mojom::FindOptionsPtr options) {
|
|
delegate()->UpdateLastRequest(++last_request_id_);
|
|
contents()->Find(last_request_id_, base::UTF8ToUTF16(search_text),
|
|
std::move(options));
|
|
}
|
|
|
|
WebContentsImpl* contents() const {
|
|
return static_cast<WebContentsImpl*>(shell()->web_contents());
|
|
}
|
|
|
|
FindTestWebContentsDelegate* delegate() const {
|
|
return static_cast<FindTestWebContentsDelegate*>(contents()->GetDelegate());
|
|
}
|
|
|
|
int last_request_id() const {
|
|
return last_request_id_;
|
|
}
|
|
|
|
FrameTreeNode* root() { return contents()->GetPrimaryFrameTree().root(); }
|
|
|
|
FrameTreeNode* first_child() { return root()->child_at(0); }
|
|
|
|
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);
|
|
}
|
|
|
|
FindTestWebContentsDelegate test_delegate_;
|
|
raw_ptr<WebContentsDelegate, DanglingUntriaged> normal_delegate_;
|
|
|
|
// The ID of the last find request requested.
|
|
int last_request_id_;
|
|
};
|
|
|
|
class FindRequestManagerTest : public FindRequestManagerTestBase,
|
|
public testing::WithParamInterface<bool> {
|
|
protected:
|
|
bool test_with_oopif() const { return GetParam(); }
|
|
};
|
|
|
|
INSTANTIATE_TEST_SUITE_P(FindRequestManagerTests,
|
|
FindRequestManagerTest,
|
|
testing::Bool());
|
|
|
|
// TODO(crbug.com/615291): These tests frequently fail on Android.
|
|
#if BUILDFLAG(IS_ANDROID)
|
|
#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 (test_with_oopif())
|
|
MakeChildFrameCrossProcess();
|
|
|
|
auto options = blink::mojom::FindOptions::New();
|
|
options->run_synchronously_for_testing = true;
|
|
Find("result", options->Clone());
|
|
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->new_session = false;
|
|
for (int i = 2; i <= 10; ++i) {
|
|
Find("result", options->Clone());
|
|
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->Clone());
|
|
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);
|
|
}
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_P(FindRequestManagerTest, FindInPage_Issue615291) {
|
|
LoadAndWait("/find_in_simple_page.html");
|
|
|
|
auto options = blink::mojom::FindOptions::New();
|
|
options->run_synchronously_for_testing = true;
|
|
options->find_match = false;
|
|
Find("result", options->Clone());
|
|
delegate()->WaitForFinalReply();
|
|
|
|
FindResults results = delegate()->GetFindResults();
|
|
EXPECT_EQ(5, results.number_of_matches);
|
|
EXPECT_EQ(0, results.active_match_ordinal);
|
|
|
|
options->new_session = false;
|
|
Find("result", options->Clone());
|
|
// With the issue being tested, this would loop forever and cause the
|
|
// test to timeout.
|
|
delegate()->WaitForFinalReply();
|
|
results = delegate()->GetFindResults();
|
|
EXPECT_EQ(5, results.number_of_matches);
|
|
EXPECT_EQ(0, results.active_match_ordinal);
|
|
}
|
|
|
|
bool ExecuteScriptAndExtractRect(FrameTreeNode* frame,
|
|
const std::string& script,
|
|
gfx::Rect* out) {
|
|
std::string script_and_extract =
|
|
script + "rect.x + ',' + rect.y + ',' + rect.width + ',' + rect.height;";
|
|
std::string result = EvalJs(frame, script_and_extract).ExtractString();
|
|
|
|
std::vector<std::string> tokens = base::SplitString(
|
|
result, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
|
|
if (tokens.size() != 4U)
|
|
return false;
|
|
|
|
double x, y, width, height;
|
|
if (!base::StringToDouble(tokens[0], &x) ||
|
|
!base::StringToDouble(tokens[1], &y) ||
|
|
!base::StringToDouble(tokens[2], &width) ||
|
|
!base::StringToDouble(tokens[3], &height))
|
|
return false;
|
|
|
|
*out = gfx::Rect(static_cast<int>(x), static_cast<int>(y),
|
|
static_cast<int>(width), static_cast<int>(height));
|
|
return true;
|
|
}
|
|
|
|
// Basic test that a search result is actually brought into view.
|
|
IN_PROC_BROWSER_TEST_P(FindRequestManagerTest, ScrollAndZoomIntoView) {
|
|
WebContentsImpl* web_contents =
|
|
static_cast<WebContentsImpl*>(shell()->web_contents());
|
|
blink::web_pref::WebPreferences prefs =
|
|
web_contents->GetOrCreateWebPreferences();
|
|
prefs.smooth_scroll_for_find_enabled = false;
|
|
web_contents->SetWebPreferences(prefs);
|
|
|
|
LoadAndWait("/find_in_page_desktop.html");
|
|
// Note: for now, don't run this test on Android in OOPIF mode.
|
|
if (test_with_oopif())
|
|
#if BUILDFLAG(IS_ANDROID)
|
|
return;
|
|
#else
|
|
MakeChildFrameCrossProcess();
|
|
#endif // BUILDFLAG(IS_ANDROID)
|
|
|
|
FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
|
|
->GetPrimaryFrameTree()
|
|
.root();
|
|
FrameTreeNode* child = root->child_at(0);
|
|
|
|
// Start off at a non-origin scroll offset to ensure coordinate conversisons
|
|
// work correctly.
|
|
ASSERT_TRUE(ExecJs(root, "window.scrollTo(3500, 1500);"));
|
|
|
|
// Search for a result further down in the iframe.
|
|
auto options = blink::mojom::FindOptions::New();
|
|
options->run_synchronously_for_testing = true;
|
|
Find("result 17", options->Clone());
|
|
delegate()->WaitForFinalReply();
|
|
|
|
// gBCR of result box in iframe.
|
|
gfx::Rect target_in_iframe;
|
|
|
|
// gBCR of iframe in main document.
|
|
gfx::Rect iframe_rect;
|
|
|
|
// Window size with location at origin (for comparison with gBCR).
|
|
gfx::Rect root_rect;
|
|
|
|
// Visual viewport rect relative to root_rect.
|
|
gfx::Rect visual_rect;
|
|
|
|
ASSERT_TRUE(ExecuteScriptAndExtractRect(
|
|
child,
|
|
"var result = document.querySelector('.margin-overflow');"
|
|
"var rect = result.getBoundingClientRect();",
|
|
&target_in_iframe));
|
|
ASSERT_TRUE(ExecuteScriptAndExtractRect(
|
|
root,
|
|
"var rect = document.querySelector('#frame').getBoundingClientRect();",
|
|
&iframe_rect));
|
|
ASSERT_TRUE(ExecuteScriptAndExtractRect(
|
|
root,
|
|
"var rect = new DOMRect(0, 0, window.innerWidth, window.innerHeight);",
|
|
&root_rect));
|
|
ASSERT_TRUE(ExecuteScriptAndExtractRect(
|
|
root,
|
|
"var rect = new DOMRect(visualViewport.offsetLeft, "
|
|
" visualViewport.offsetTop,"
|
|
" visualViewport.width,"
|
|
" visualViewport.height);",
|
|
&visual_rect));
|
|
|
|
gfx::Rect result_in_root = target_in_iframe + iframe_rect.OffsetFromOrigin();
|
|
|
|
EXPECT_TRUE(gfx::Rect(iframe_rect.size()).Contains(target_in_iframe))
|
|
<< "Result rect[ " << target_in_iframe.ToString()
|
|
<< " ] not visible in iframe [ 0,0 " << iframe_rect.size().ToString()
|
|
<< " ].";
|
|
|
|
EXPECT_TRUE(root_rect.Contains(result_in_root))
|
|
<< "Result rect[ " << result_in_root.ToString()
|
|
<< " ] not visible in root frame [ " << root_rect.ToString() << " ].";
|
|
|
|
EXPECT_TRUE(visual_rect.Contains(result_in_root))
|
|
<< "Result rect[ " << result_in_root.ToString()
|
|
<< " ] not visible in visual viewport [ " << visual_rect.ToString()
|
|
<< " ].";
|
|
}
|
|
|
|
// 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 (test_with_oopif())
|
|
MakeChildFrameCrossProcess();
|
|
|
|
auto default_options = blink::mojom::FindOptions::New();
|
|
default_options->run_synchronously_for_testing = true;
|
|
Find("r", default_options->Clone());
|
|
Find("re", default_options->Clone());
|
|
Find("res", default_options->Clone());
|
|
Find("resu", default_options->Clone());
|
|
Find("resul", default_options->Clone());
|
|
Find("result", default_options->Clone());
|
|
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);
|
|
}
|
|
|
|
// TODO(crbug.com/615291): This test frequently fails on Android.
|
|
// TODO(crbug.com/674742): This test is flaky on Win
|
|
// TODO(crbug.com/850286): Flaky on CrOS MSan
|
|
// Tests sending a large number of find requests subsequently.
|
|
IN_PROC_BROWSER_TEST_P(FindRequestManagerTest, DISABLED_RapidFire) {
|
|
LoadAndWait("/find_in_page.html");
|
|
if (test_with_oopif())
|
|
MakeChildFrameCrossProcess();
|
|
|
|
auto options = blink::mojom::FindOptions::New();
|
|
options->run_synchronously_for_testing = true;
|
|
Find("result", options.Clone());
|
|
|
|
options->new_session = false;
|
|
for (int i = 2; i <= 1000; ++i)
|
|
Find("result", options.Clone());
|
|
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.
|
|
// TODO(crbug.com/657331): Test is flaky on all platforms.
|
|
IN_PROC_BROWSER_TEST_P(FindRequestManagerTest, DISABLED_RemoveFrame) {
|
|
LoadMultiFramePage(2 /* height */, test_with_oopif() /* cross_process */);
|
|
|
|
auto options = blink::mojom::FindOptions::New();
|
|
options->run_synchronously_for_testing = true;
|
|
Find("result", options->Clone());
|
|
delegate()->WaitForFinalReply();
|
|
options->new_session = false;
|
|
options->forward = false;
|
|
Find("result", options->Clone());
|
|
Find("result", options->Clone());
|
|
Find("result", options->Clone());
|
|
Find("result", options->Clone());
|
|
Find("result", options->Clone());
|
|
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.
|
|
root()->current_frame_host()->RemoveChild(first_child());
|
|
|
|
// 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);
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_P(FindRequestManagerTest, RemoveMainFrame) {
|
|
LoadAndWait("/find_in_page.html");
|
|
|
|
auto options = blink::mojom::FindOptions::New();
|
|
options->run_synchronously_for_testing = true;
|
|
Find("result", options->Clone());
|
|
delegate()->WaitForFinalReply();
|
|
options->new_session = false;
|
|
options->forward = false;
|
|
Find("result", options->Clone());
|
|
Find("result", options->Clone());
|
|
Find("result", options->Clone());
|
|
Find("result", options->Clone());
|
|
Find("result", options->Clone());
|
|
|
|
// Don't wait for the reply, and end the test. This will remove the main
|
|
// frame, which should not crash.
|
|
}
|
|
|
|
// Tests adding a frame during a find session.
|
|
// TODO(crbug.com/657331): Test is flaky on all platforms.
|
|
IN_PROC_BROWSER_TEST_P(FindRequestManagerTest, DISABLED_AddFrame) {
|
|
LoadMultiFramePage(2 /* height */, test_with_oopif() /* cross_process */);
|
|
|
|
auto options = blink::mojom::FindOptions::New();
|
|
options->run_synchronously_for_testing = true;
|
|
Find("result", options.Clone());
|
|
options->new_session = false;
|
|
Find("result", options.Clone());
|
|
Find("result", options.Clone());
|
|
Find("result", options.Clone());
|
|
Find("result", options.Clone());
|
|
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(test_with_oopif() ? "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(ExecJs(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());
|
|
EXPECT_TRUE(NavigateToURL(shell(), GURL("about:blank")));
|
|
EXPECT_TRUE(navigation_observer.last_navigation_succeeded());
|
|
|
|
auto default_options = blink::mojom::FindOptions::New();
|
|
default_options->run_synchronously_for_testing = true;
|
|
Find("result", default_options.Clone());
|
|
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(ExecJs(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 */, test_with_oopif() /* cross_process */);
|
|
|
|
auto options = blink::mojom::FindOptions::New();
|
|
options->run_synchronously_for_testing = true;
|
|
Find("result", options.Clone());
|
|
options->new_session = false;
|
|
options->forward = false;
|
|
Find("result", options.Clone());
|
|
Find("result", options.Clone());
|
|
Find("result", options.Clone());
|
|
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())
|
|
->GetPrimaryFrameTree()
|
|
.root();
|
|
GURL url(embedded_test_server()->GetURL(test_with_oopif() ? "b.com" : "a.com",
|
|
"/find_in_simple_page.html"));
|
|
delegate()->MarkNextReply();
|
|
TestNavigationObserver navigation_observer(contents());
|
|
EXPECT_TRUE(NavigateToURLFromRenderer(
|
|
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");
|
|
|
|
auto default_options = blink::mojom::FindOptions::New();
|
|
default_options->run_synchronously_for_testing = true;
|
|
Find("hello", default_options.Clone());
|
|
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");
|
|
|
|
auto options = blink::mojom::FindOptions::New();
|
|
options->run_synchronously_for_testing = true;
|
|
Find("result", options.Clone());
|
|
options->new_session = false;
|
|
Find("result", options.Clone());
|
|
Find("result", options.Clone());
|
|
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(ExecJs(contents()->GetPrimaryMainFrame(), "addNewText()"));
|
|
|
|
Find("result", options.Clone());
|
|
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);
|
|
}
|
|
|
|
// TODO(crbug.com/615291): These tests frequently fail on Android.
|
|
// TODO(crbug.com/779912): Flaky timeout on Win7 (dbg).
|
|
// TODO(crbug.com/875306): Flaky on Win10.
|
|
#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_WIN)
|
|
#define MAYBE_FindInPage_Issue627799 DISABLED_FindInPage_Issue627799
|
|
#else
|
|
#define MAYBE_FindInPage_Issue627799 FindInPage_Issue627799
|
|
#endif
|
|
|
|
IN_PROC_BROWSER_TEST_F(FindRequestManagerTest, MAYBE_FindInPage_Issue627799) {
|
|
LoadAndWait("/find_in_long_page.html");
|
|
|
|
auto options = blink::mojom::FindOptions::New();
|
|
options->run_synchronously_for_testing = true;
|
|
Find("42", options.Clone());
|
|
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->new_session = false;
|
|
options->forward = false;
|
|
Find("42", options.Clone());
|
|
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, DetachFrameWithMatch) {
|
|
// Detaching an iframe with matches when the main document doesn't
|
|
// have matches should work and just remove the matches from the
|
|
// removed frame.
|
|
LoadAndWait("/find_in_page_two_frames.html");
|
|
auto options = blink::mojom::FindOptions::New();
|
|
options->run_synchronously_for_testing = true;
|
|
|
|
Find("result", options.Clone());
|
|
delegate()->WaitForFinalReply();
|
|
FindResults results = delegate()->GetFindResults();
|
|
EXPECT_EQ(last_request_id(), results.request_id);
|
|
EXPECT_EQ(6, results.number_of_matches);
|
|
EXPECT_EQ(1, results.active_match_ordinal);
|
|
EXPECT_TRUE(ExecJs(shell(),
|
|
"document.body.removeChild("
|
|
"document.querySelectorAll('iframe')[0])"));
|
|
|
|
Find("result", options.Clone());
|
|
delegate()->WaitForFinalReply();
|
|
results = delegate()->GetFindResults();
|
|
EXPECT_EQ(last_request_id(), results.request_id);
|
|
EXPECT_EQ(3, results.number_of_matches);
|
|
EXPECT_EQ(1, results.active_match_ordinal);
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(FindRequestManagerTest, MAYBE(FindInPage_Issue644448)) {
|
|
TestNavigationObserver navigation_observer(contents());
|
|
EXPECT_TRUE(NavigateToURL(shell(), GURL("about:blank")));
|
|
EXPECT_TRUE(navigation_observer.last_navigation_succeeded());
|
|
|
|
auto default_options = blink::mojom::FindOptions::New();
|
|
default_options->run_synchronously_for_testing = true;
|
|
Find("result", default_options.Clone());
|
|
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.Clone());
|
|
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 BUILDFLAG(IS_ANDROID)
|
|
// Tests empty active match rect when kWrapAround is false.
|
|
IN_PROC_BROWSER_TEST_F(FindRequestManagerTest, EmptyActiveMatchRect) {
|
|
LoadAndWait("/find_in_page.html");
|
|
|
|
// kWrapAround is false by default.
|
|
auto default_options = blink::mojom::FindOptions::New();
|
|
default_options->run_synchronously_for_testing = true;
|
|
Find("result 01", default_options.Clone());
|
|
delegate()->WaitForFinalReply();
|
|
EXPECT_EQ(1, 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());
|
|
|
|
Find("result 00", default_options.Clone());
|
|
delegate()->WaitForFinalReply();
|
|
EXPECT_EQ(1, delegate()->GetFindResults().number_of_matches);
|
|
|
|
// Request the find match rects.
|
|
contents()->RequestFindMatchRects(-1);
|
|
delegate()->WaitForMatchRects();
|
|
|
|
// The active match rect should be empty.
|
|
EXPECT_EQ(gfx::RectF(), delegate()->active_match_rect());
|
|
}
|
|
|
|
class MainFrameSizeChangedWaiter : public WebContentsObserver {
|
|
public:
|
|
MainFrameSizeChangedWaiter(WebContents* web_contents)
|
|
: WebContentsObserver(web_contents) {}
|
|
void Wait() { run_loop_.Run(); }
|
|
|
|
private:
|
|
void FrameSizeChanged(RenderFrameHost* render_frame_host,
|
|
const gfx::Size& frame_size) override {
|
|
if (render_frame_host == web_contents()->GetPrimaryMainFrame())
|
|
run_loop_.Quit();
|
|
}
|
|
|
|
base::RunLoop run_loop_;
|
|
};
|
|
|
|
// Tests match rects in the iframe are updated with the size of the main frame,
|
|
// and the active match rect should be in it.
|
|
IN_PROC_BROWSER_TEST_F(FindRequestManagerTest,
|
|
RectsUpdateWhenMainFrameSizeChanged) {
|
|
LoadAndWait("/find_in_page.html");
|
|
|
|
// Make a initial size for native view.
|
|
const int kWidth = 1080;
|
|
const int kHeight = 1286;
|
|
gfx::Size size(kWidth, kHeight);
|
|
contents()->GetNativeView()->OnSizeChanged(kWidth, kHeight);
|
|
contents()->GetNativeView()->OnPhysicalBackingSizeChanged(size);
|
|
|
|
// Make a FindRequest for "result".
|
|
auto options = blink::mojom::FindOptions::New();
|
|
options->run_synchronously_for_testing = true;
|
|
Find("result", options->Clone());
|
|
delegate()->WaitForFinalReply();
|
|
EXPECT_EQ(19, delegate()->GetFindResults().number_of_matches);
|
|
|
|
contents()->RequestFindMatchRects(-1);
|
|
delegate()->WaitForMatchRects();
|
|
|
|
// Change the size of native view.
|
|
const int kNewHeight = 2121;
|
|
size = gfx::Size(kWidth, kNewHeight);
|
|
contents()->GetNativeView()->OnSizeChanged(kWidth, kNewHeight);
|
|
contents()->GetNativeView()->OnPhysicalBackingSizeChanged(size);
|
|
|
|
// Wait for the size of the mainframe to change, and then the position
|
|
// of match rects should change as expected.
|
|
MainFrameSizeChangedWaiter(contents()).Wait();
|
|
|
|
contents()->RequestFindMatchRects(-1);
|
|
delegate()->WaitForMatchRects();
|
|
std::vector<gfx::RectF> new_rects = delegate()->find_match_rects();
|
|
|
|
// The first match should be active.
|
|
EXPECT_EQ(new_rects[0], delegate()->active_match_rect());
|
|
|
|
// Check that all active rects (including iframe) matches with corresponding
|
|
// match rect.
|
|
for (int i = 1; i < 19; i++) {
|
|
options->new_session = false;
|
|
options->forward = true;
|
|
Find("result", options->Clone());
|
|
delegate()->WaitForFinalReply();
|
|
|
|
EXPECT_EQ(19, delegate()->GetFindResults().number_of_matches);
|
|
|
|
// Request the find match rects.
|
|
contents()->RequestFindMatchRects(-1);
|
|
delegate()->WaitForMatchRects();
|
|
new_rects = delegate()->find_match_rects();
|
|
|
|
// The active rect should be equal to the corresponding match rect.
|
|
EXPECT_EQ(new_rects[i], delegate()->active_match_rect());
|
|
}
|
|
}
|
|
|
|
// TODO(wjmaclean): This test, if re-enabled, may require work to make it
|
|
// OOPIF-compatible.
|
|
// Tests requesting find match rects.
|
|
IN_PROC_BROWSER_TEST_F(FindRequestManagerTest, MAYBE(FindMatchRects)) {
|
|
LoadAndWait("/find_in_page.html");
|
|
if (test_with_oopif())
|
|
MakeChildFrameCrossProcess();
|
|
|
|
auto default_options = blink::mojom::FindOptions::New();
|
|
default_options->run_synchronously_for_testing = true;
|
|
Find("result", default_options.Clone());
|
|
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());
|
|
}
|
|
|
|
namespace {
|
|
|
|
class ZoomToFindInPageRectMessageFilter
|
|
: public blink::mojom::FrameWidgetHostInterceptorForTesting {
|
|
public:
|
|
ZoomToFindInPageRectMessageFilter(RenderWidgetHostImpl* rwhi)
|
|
: impl_(rwhi->frame_widget_host_receiver_for_testing().SwapImplForTesting(
|
|
this)),
|
|
widget_message_seen_(false) {}
|
|
|
|
ZoomToFindInPageRectMessageFilter(const ZoomToFindInPageRectMessageFilter&) =
|
|
delete;
|
|
ZoomToFindInPageRectMessageFilter& operator=(
|
|
const ZoomToFindInPageRectMessageFilter&) = delete;
|
|
|
|
~ZoomToFindInPageRectMessageFilter() override {}
|
|
|
|
blink::mojom::FrameWidgetHost* GetForwardingInterface() override {
|
|
return impl_;
|
|
}
|
|
|
|
void Reset() {
|
|
widget_rect_seen_ = gfx::Rect();
|
|
widget_message_seen_ = false;
|
|
}
|
|
|
|
void WaitForWidgetHostMessage() {
|
|
if (widget_message_seen_)
|
|
return;
|
|
|
|
base::RunLoop run_loop;
|
|
quit_closure_ = run_loop.QuitClosure();
|
|
run_loop.Run();
|
|
}
|
|
|
|
gfx::Rect& widget_message_rect() { return widget_rect_seen_; }
|
|
|
|
private:
|
|
void ZoomToFindInPageRectInMainFrame(const gfx::Rect& rect_to_zoom) override {
|
|
widget_rect_seen_ = rect_to_zoom;
|
|
widget_message_seen_ = true;
|
|
if (!quit_closure_.is_null())
|
|
std::move(quit_closure_).Run();
|
|
}
|
|
|
|
raw_ptr<blink::mojom::FrameWidgetHost> impl_;
|
|
gfx::Rect widget_rect_seen_;
|
|
bool widget_message_seen_;
|
|
base::OnceClosure quit_closure_;
|
|
};
|
|
|
|
} // namespace
|
|
|
|
// Tests activating the find match nearest to a given point.
|
|
// TODO(crbug.com/1362116): Fix flaky failures.
|
|
IN_PROC_BROWSER_TEST_P(FindRequestManagerTest,
|
|
DISABLED_ActivateNearestFindMatch) {
|
|
LoadAndWait("/find_in_page.html");
|
|
if (test_with_oopif())
|
|
MakeChildFrameCrossProcess();
|
|
|
|
std::unique_ptr<ZoomToFindInPageRectMessageFilter> message_interceptor_child;
|
|
|
|
if (test_with_oopif()) {
|
|
message_interceptor_child =
|
|
std::make_unique<ZoomToFindInPageRectMessageFilter>(
|
|
first_child()->current_frame_host()->GetRenderWidgetHost());
|
|
}
|
|
|
|
auto default_options = blink::mojom::FindOptions::New();
|
|
default_options->run_synchronously_for_testing = true;
|
|
Find("result", default_options.Clone());
|
|
delegate()->WaitForFinalReply();
|
|
EXPECT_EQ(19, delegate()->GetFindResults().number_of_matches);
|
|
|
|
auto* find_request_manager = contents()->GetFindRequestManagerForTesting();
|
|
|
|
// Get the find match rects.
|
|
contents()->RequestFindMatchRects(-1);
|
|
delegate()->WaitForMatchRects();
|
|
const std::vector<gfx::RectF>& rects = delegate()->find_match_rects();
|
|
|
|
double device_scale_factor = GetFrameDeviceScaleFactor(contents());
|
|
|
|
// 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();
|
|
|
|
bool is_match_in_oopif = order[i] > 1 && test_with_oopif();
|
|
// Check widget message rect to make sure it matches.
|
|
if (is_match_in_oopif) {
|
|
message_interceptor_child->WaitForWidgetHostMessage();
|
|
auto expected_rect = gfx::ScaleToEnclosingRect(
|
|
message_interceptor_child->widget_message_rect(),
|
|
1.f / device_scale_factor);
|
|
EXPECT_EQ(find_request_manager->GetSelectionRectForTesting(),
|
|
expected_rect);
|
|
message_interceptor_child->Reset();
|
|
}
|
|
|
|
EXPECT_EQ(order[i] + 1, delegate()->GetFindResults().active_match_ordinal);
|
|
}
|
|
}
|
|
#endif // BUILDFLAG(IS_ANDROID)
|
|
|
|
// Test basic find-in-page functionality after going back and forth to the same
|
|
// page. In particular, find-in-page should continue to work after going back to
|
|
// a page using the back-forward cache.
|
|
// Flaky everywhere: https://crbug.com/1115102
|
|
IN_PROC_BROWSER_TEST_P(FindRequestManagerTest, DISABLED_HistoryBackAndForth) {
|
|
GURL url_a = embedded_test_server()->GetURL("a.com", "/find_in_page.html");
|
|
GURL url_b = embedded_test_server()->GetURL("b.com", "/find_in_page.html");
|
|
|
|
auto test_page = [&] {
|
|
if (test_with_oopif())
|
|
MakeChildFrameCrossProcess();
|
|
|
|
auto options = blink::mojom::FindOptions::New();
|
|
|
|
// The initial find-in-page request.
|
|
Find("result", options->Clone());
|
|
delegate()->WaitForFinalReply();
|
|
|
|
FindResults results = delegate()->GetFindResults();
|
|
EXPECT_EQ(last_request_id(), results.request_id);
|
|
EXPECT_EQ(19, results.number_of_matches);
|
|
|
|
// Iterate forward/backward over a few elements.
|
|
int match_index = results.active_match_ordinal;
|
|
for (int delta : {-1, -1, +1, +1, +1, +1, -1, +1, +1}) {
|
|
options->new_session = false;
|
|
options->forward = delta > 0;
|
|
// |active_match_ordinal| uses 1-based index. It belongs to [1, 19].
|
|
match_index += delta;
|
|
match_index = (match_index + 18) % 19 + 1;
|
|
|
|
Find("result", options->Clone());
|
|
delegate()->WaitForFinalReply();
|
|
results = delegate()->GetFindResults();
|
|
|
|
EXPECT_EQ(last_request_id(), results.request_id);
|
|
EXPECT_EQ(19, results.number_of_matches);
|
|
EXPECT_EQ(match_index, results.active_match_ordinal);
|
|
}
|
|
};
|
|
|
|
// 1) Navigate to A.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a));
|
|
test_page();
|
|
|
|
// 2) Navigate to B.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_b));
|
|
test_page();
|
|
|
|
// 3) Go back to A.
|
|
contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
test_page();
|
|
|
|
// 4) Go forward to B.
|
|
contents()->GetController().GoForward();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
test_page();
|
|
}
|
|
|
|
class FindInPageDisabledForOriginBrowserClient : public ContentBrowserClient {
|
|
public:
|
|
FindInPageDisabledForOriginBrowserClient() = default;
|
|
~FindInPageDisabledForOriginBrowserClient() override = default;
|
|
|
|
// ContentBrowserClient:
|
|
bool IsFindInPageDisabledForOrigin(const url::Origin& origin) override {
|
|
return origin.host() == "b.com";
|
|
}
|
|
};
|
|
|
|
// Tests that find-in-page won't show results for origins that disabled
|
|
// find-in-page.
|
|
IN_PROC_BROWSER_TEST_P(FindRequestManagerTest, FindInPageDisabledForOrigin) {
|
|
FindInPageDisabledForOriginBrowserClient browser_client;
|
|
auto* old_client = content::SetBrowserClientForTesting(&browser_client);
|
|
|
|
// Start with a basic case to set a baseline.
|
|
LoadAndWait("/find_in_page.html");
|
|
url::Origin root_origin = GetOriginForFrameTreeNode(root());
|
|
url::Origin child_origin = GetOriginForFrameTreeNode(first_child());
|
|
EXPECT_EQ("a.com", root_origin.host());
|
|
EXPECT_EQ("a.com", child_origin.host());
|
|
EXPECT_FALSE(browser_client.IsFindInPageDisabledForOrigin(root_origin));
|
|
EXPECT_FALSE(browser_client.IsFindInPageDisabledForOrigin(child_origin));
|
|
|
|
auto options = blink::mojom::FindOptions::New();
|
|
options->run_synchronously_for_testing = true;
|
|
Find("result", options->Clone());
|
|
delegate()->WaitForFinalReply();
|
|
|
|
FindResults results = delegate()->GetFindResults();
|
|
EXPECT_EQ(last_request_id(), results.request_id);
|
|
EXPECT_EQ(19, results.number_of_matches);
|
|
|
|
// Navigate child frame to b.com.
|
|
EXPECT_TRUE(NavigateToURLFromRenderer(
|
|
first_child(), embedded_test_server()->GetURL(
|
|
"b.com", first_child()->current_url().path())));
|
|
root_origin = GetOriginForFrameTreeNode(root());
|
|
child_origin = GetOriginForFrameTreeNode(first_child());
|
|
EXPECT_EQ("a.com", root_origin.host());
|
|
EXPECT_EQ("b.com", child_origin.host());
|
|
EXPECT_FALSE(browser_client.IsFindInPageDisabledForOrigin(root_origin));
|
|
EXPECT_TRUE(browser_client.IsFindInPageDisabledForOrigin(child_origin));
|
|
|
|
Find("result", options->Clone());
|
|
delegate()->WaitForFinalReply();
|
|
|
|
// Given the custom `browser_client` disabled find-in-page for b.com, only the
|
|
// results from the root node should show up now.
|
|
results = delegate()->GetFindResults();
|
|
EXPECT_EQ(last_request_id(), results.request_id);
|
|
EXPECT_EQ(2, results.number_of_matches);
|
|
|
|
// Navigate child frame, but remain on b.com.
|
|
EXPECT_TRUE(NavigateToURLFromRenderer(
|
|
first_child(),
|
|
embedded_test_server()->GetURL("b.com", "/find_in_simple_page.html")));
|
|
root_origin = GetOriginForFrameTreeNode(root());
|
|
child_origin = GetOriginForFrameTreeNode(first_child());
|
|
EXPECT_EQ("a.com", root_origin.host());
|
|
EXPECT_EQ("b.com", child_origin.host());
|
|
EXPECT_FALSE(browser_client.IsFindInPageDisabledForOrigin(root_origin));
|
|
EXPECT_TRUE(browser_client.IsFindInPageDisabledForOrigin(child_origin));
|
|
|
|
// Results from the child frame on b.com still do not show up.
|
|
results = delegate()->GetFindResults();
|
|
EXPECT_EQ(last_request_id(), results.request_id);
|
|
EXPECT_EQ(2, results.number_of_matches);
|
|
|
|
// Navigate child frame to a.com again.
|
|
EXPECT_TRUE(NavigateToURLFromRenderer(
|
|
first_child(),
|
|
embedded_test_server()->GetURL("a.com", "/find_in_simple_page.html")));
|
|
root_origin = GetOriginForFrameTreeNode(root());
|
|
child_origin = GetOriginForFrameTreeNode(first_child());
|
|
EXPECT_EQ("a.com", root_origin.host());
|
|
EXPECT_EQ("a.com", child_origin.host());
|
|
EXPECT_FALSE(browser_client.IsFindInPageDisabledForOrigin(root_origin));
|
|
EXPECT_FALSE(browser_client.IsFindInPageDisabledForOrigin(child_origin));
|
|
|
|
Find("result", options->Clone());
|
|
delegate()->WaitForFinalReply();
|
|
|
|
// Since the child frame is now on a.com, find-in-page is enabled, so its
|
|
// results show up again.
|
|
results = delegate()->GetFindResults();
|
|
EXPECT_EQ(last_request_id(), results.request_id);
|
|
EXPECT_EQ(7, results.number_of_matches);
|
|
|
|
content::SetBrowserClientForTesting(old_client);
|
|
}
|
|
|
|
class FindRequestManagerPortalTest : public FindRequestManagerTest {
|
|
public:
|
|
FindRequestManagerPortalTest() {
|
|
scoped_feature_list_.InitAndEnableFeature(blink::features::kPortals);
|
|
}
|
|
~FindRequestManagerPortalTest() override = default;
|
|
|
|
private:
|
|
base::test::ScopedFeatureList scoped_feature_list_;
|
|
};
|
|
|
|
// Tests that find-in-page won't show results inside a portal.
|
|
IN_PROC_BROWSER_TEST_F(FindRequestManagerPortalTest, Portal) {
|
|
TestNavigationObserver navigation_observer(contents());
|
|
EXPECT_TRUE(
|
|
NavigateToURL(shell(), embedded_test_server()->GetURL(
|
|
"a.com", "/find_in_page_with_portal.html")));
|
|
ASSERT_TRUE(navigation_observer.last_navigation_succeeded());
|
|
|
|
auto options = blink::mojom::FindOptions::New();
|
|
options->run_synchronously_for_testing = true;
|
|
Find("result", options->Clone());
|
|
delegate()->WaitForFinalReply();
|
|
|
|
FindResults results = delegate()->GetFindResults();
|
|
EXPECT_EQ(last_request_id(), results.request_id);
|
|
EXPECT_EQ(2, results.number_of_matches);
|
|
EXPECT_EQ(1, results.active_match_ordinal);
|
|
}
|
|
|
|
class FindTestWebContentsPrerenderingDelegate
|
|
: public FindTestWebContentsDelegate {
|
|
public:
|
|
bool IsPrerender2Supported(WebContents& web_contents) override {
|
|
return true;
|
|
}
|
|
};
|
|
|
|
class FindRequestManagerPrerenderingTest : public FindRequestManagerTest {
|
|
public:
|
|
FindRequestManagerPrerenderingTest()
|
|
: prerender_helper_(base::BindRepeating(
|
|
&FindRequestManagerPrerenderingTest::web_contents,
|
|
base::Unretained(this))) {}
|
|
~FindRequestManagerPrerenderingTest() override = default;
|
|
|
|
void SetUpOnMainThread() override {
|
|
FindRequestManagerTest::SetUpOnMainThread();
|
|
contents()->SetDelegate(&delegate_);
|
|
}
|
|
|
|
content::test::PrerenderTestHelper* prerender_helper() {
|
|
return &prerender_helper_;
|
|
}
|
|
|
|
content::WebContents* web_contents() { return shell()->web_contents(); }
|
|
|
|
private:
|
|
content::test::PrerenderTestHelper prerender_helper_;
|
|
FindTestWebContentsPrerenderingDelegate delegate_;
|
|
};
|
|
|
|
// Tests that find-in-page won't show results inside a prerendering page.
|
|
IN_PROC_BROWSER_TEST_F(FindRequestManagerPrerenderingTest, Basic) {
|
|
EXPECT_TRUE(
|
|
NavigateToURL(shell(), embedded_test_server()->GetURL("/empty.html")));
|
|
auto options = blink::mojom::FindOptions::New();
|
|
options->run_synchronously_for_testing = true;
|
|
Find("result", options->Clone());
|
|
delegate()->WaitForFinalReply();
|
|
|
|
// Do a find-in-page on an empty page.
|
|
FindResults results = delegate()->GetFindResults();
|
|
EXPECT_EQ(last_request_id(), results.request_id);
|
|
EXPECT_EQ(0, results.number_of_matches);
|
|
|
|
// Load a page that has 5 matches for "result" in the prerender.
|
|
auto prerender_url =
|
|
embedded_test_server()->GetURL("/find_in_simple_page.html?prerendering");
|
|
prerender_helper()->AddPrerender(prerender_url);
|
|
|
|
Find("result", options->Clone());
|
|
delegate()->WaitForFinalReply();
|
|
|
|
results = delegate()->GetFindResults();
|
|
EXPECT_EQ(last_request_id(), results.request_id);
|
|
// The prerendering page shouldn't affect the results of a find-in-page .
|
|
EXPECT_EQ(0, results.number_of_matches);
|
|
|
|
// Activate the page from the prerendering.
|
|
prerender_helper()->NavigatePrimaryPage(prerender_url);
|
|
Find("result", options->Clone());
|
|
delegate()->WaitForFinalReply();
|
|
|
|
results = delegate()->GetFindResults();
|
|
// The results from the prerendered page getting activated should be 5 as the
|
|
// mainframe(5 results) and no subframe.
|
|
EXPECT_EQ(5, results.number_of_matches);
|
|
}
|
|
|
|
class FindRequestManagerTestWithBFCache : public FindRequestManagerTest {
|
|
public:
|
|
FindRequestManagerTestWithBFCache() {
|
|
scoped_feature_list_.InitWithFeaturesAndParameters(
|
|
{{features::kBackForwardCache,
|
|
{{"TimeToLiveInBackForwardCacheInSeconds", "3600"}}}},
|
|
// Allow BackForwardCache for all devices regardless of their memory.
|
|
{features::kBackForwardCacheMemoryControls});
|
|
}
|
|
~FindRequestManagerTestWithBFCache() override = default;
|
|
|
|
content::RenderFrameHost* render_frame_host() {
|
|
return contents()->GetPrimaryMainFrame();
|
|
}
|
|
|
|
private:
|
|
base::test::ScopedFeatureList scoped_feature_list_;
|
|
};
|
|
|
|
// Test basic find-in-page functionality when a page gets into and out of
|
|
// BFCache.
|
|
IN_PROC_BROWSER_TEST_F(FindRequestManagerTestWithBFCache, Basic) {
|
|
GURL url_a = embedded_test_server()->GetURL("a.com", "/find_in_page.html");
|
|
GURL url_b =
|
|
embedded_test_server()->GetURL("b.com", "/find_in_simple_page.html");
|
|
|
|
auto options = blink::mojom::FindOptions::New();
|
|
auto expect_match_results = [&](int expected_number_of_matches) {
|
|
// The initial find-in-page request.
|
|
Find("result", options->Clone());
|
|
delegate()->WaitForFinalReply();
|
|
|
|
FindResults results = delegate()->GetFindResults();
|
|
EXPECT_EQ(last_request_id(), results.request_id);
|
|
EXPECT_EQ(expected_number_of_matches, results.number_of_matches);
|
|
};
|
|
|
|
// 1) Navigate to A.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a));
|
|
content::RenderFrameHostWrapper rfh_a(render_frame_host());
|
|
// The results from the page A should be 19 as the mainframe(2 results) and
|
|
// the new subframe (17 results).
|
|
expect_match_results(19);
|
|
|
|
// 2) Navigate to B.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_b));
|
|
content::RenderFrameHostWrapper rfh_b(render_frame_host());
|
|
// The results from the page B should be 5 as the mainframe(5 results) and no
|
|
// subframe.
|
|
expect_match_results(5);
|
|
|
|
// Ensure A is cached.
|
|
EXPECT_EQ(rfh_a->GetLifecycleState(),
|
|
content::RenderFrameHost::LifecycleState::kInBackForwardCache);
|
|
|
|
// 3) Go back to A.
|
|
contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
// |rfh_a| should become the active frame.
|
|
EXPECT_EQ(rfh_a.get(), render_frame_host());
|
|
// The results from the page A should be 19 as the mainframe(2 results) and
|
|
// the new subframe (17 results).
|
|
expect_match_results(19);
|
|
|
|
// Ensure B is cached.
|
|
EXPECT_EQ(rfh_b->GetLifecycleState(),
|
|
content::RenderFrameHost::LifecycleState::kInBackForwardCache);
|
|
|
|
// 4) Go forward to B.
|
|
contents()->GetController().GoForward();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
// |rfh_b| should become the active frame.
|
|
EXPECT_EQ(rfh_b.get(), render_frame_host());
|
|
// The results from the page B should be 5 as the mainframe(5 results) and no
|
|
// subframe.
|
|
expect_match_results(5);
|
|
}
|
|
|
|
class WaitForFindTestWebContentsDelegate : public FindTestWebContentsDelegate {
|
|
public:
|
|
void WaitForFramesReply(int wait_count) {
|
|
wait_count_ = wait_count;
|
|
EXPECT_GT(wait_count_, 0);
|
|
run_loop_ = std::make_unique<base::RunLoop>();
|
|
run_loop_->Run();
|
|
run_loop_.reset();
|
|
}
|
|
|
|
void TryToStopWaiting() {
|
|
if (run_loop_ && !--wait_count_)
|
|
run_loop_->Quit();
|
|
}
|
|
|
|
bool ShouldWait() { return wait_count_ > 0; }
|
|
|
|
private:
|
|
int wait_count_ = 0;
|
|
std::unique_ptr<base::RunLoop> run_loop_;
|
|
};
|
|
|
|
class FindRequestManagerFencedFrameTest : public FindRequestManagerTest {
|
|
public:
|
|
FindRequestManagerFencedFrameTest() = default;
|
|
~FindRequestManagerFencedFrameTest() override = default;
|
|
FindRequestManagerFencedFrameTest(const FindRequestManagerFencedFrameTest&) =
|
|
delete;
|
|
|
|
FindRequestManagerFencedFrameTest& operator=(
|
|
const FindRequestManagerFencedFrameTest&) = delete;
|
|
|
|
content::test::FencedFrameTestHelper& fenced_frame_test_helper() {
|
|
return fenced_frame_helper_;
|
|
}
|
|
|
|
content::WebContents* GetWebContents() { return shell()->web_contents(); }
|
|
|
|
int find_request_queue_size() {
|
|
return contents()
|
|
->GetFindRequestManagerForTesting()
|
|
->find_request_queue_.size();
|
|
}
|
|
|
|
bool CheckFrame(RenderFrameHost* render_frame_host) const {
|
|
return contents()->GetFindRequestManagerForTesting()->CheckFrame(
|
|
render_frame_host);
|
|
}
|
|
|
|
private:
|
|
content::test::FencedFrameTestHelper fenced_frame_helper_;
|
|
};
|
|
|
|
// This find-in-page client will make the find-request-queue not empty so that
|
|
// we can test a fenced frame doesn't clear the find-request-queue when it's
|
|
// deleted. To keep the find-request-queue not empty, this class
|
|
// intercepts the Mojo methods calls, and changes the FindMatchUpdateType to
|
|
// kMoreUpdatesComing (including those that were marked as kFinalUpdate), so
|
|
// that the find-request-queue won't get popped and will stay non-empty.
|
|
class NeverFinishFencedFrameFindInPageClient : public FindInPageClient {
|
|
public:
|
|
NeverFinishFencedFrameFindInPageClient(
|
|
FindRequestManager* find_request_manager,
|
|
RenderFrameHostImpl* rfh)
|
|
: FindInPageClient(find_request_manager, rfh) {
|
|
content::WebContents* web_contents =
|
|
content::WebContents::FromRenderFrameHost(rfh);
|
|
delegate_ = static_cast<WaitForFindTestWebContentsDelegate*>(
|
|
web_contents->GetDelegate());
|
|
}
|
|
~NeverFinishFencedFrameFindInPageClient() override = default;
|
|
|
|
// blink::mojom::FindInPageClient overrides
|
|
void SetNumberOfMatches(
|
|
int request_id,
|
|
unsigned int current_number_of_matches,
|
|
blink::mojom::FindMatchUpdateType update_type) override {
|
|
update_type = blink::mojom::FindMatchUpdateType::kMoreUpdatesComing;
|
|
FindInPageClient::SetNumberOfMatches(request_id, current_number_of_matches,
|
|
update_type);
|
|
}
|
|
|
|
// Do nothing on SetActiveMatch() calls, since this can potentially trigger
|
|
// FindRequestManager::AdvanceQueue() and pop an item from the
|
|
// find-request-queue.
|
|
void SetActiveMatch(int request_id,
|
|
const gfx::Rect& active_match_rect,
|
|
int active_match_ordinal,
|
|
blink::mojom::FindMatchUpdateType update_type) override {}
|
|
|
|
private:
|
|
raw_ptr<WaitForFindTestWebContentsDelegate> delegate_;
|
|
};
|
|
|
|
static std::unique_ptr<FindInPageClient> CreateFencedFrameFindInPageClient(
|
|
FindRequestManager* find_request_manager,
|
|
RenderFrameHostImpl* rfh) {
|
|
return std::make_unique<NeverFinishFencedFrameFindInPageClient>(
|
|
find_request_manager, rfh);
|
|
}
|
|
|
|
// Tests that a main frame, a sub frame, and a fenced frame clear the
|
|
// find-request-queue when the fenced frame is deleted.
|
|
IN_PROC_BROWSER_TEST_F(FindRequestManagerFencedFrameTest,
|
|
OnlyPrimaryMainFrameClearsFindRequestQueue) {
|
|
WaitForFindTestWebContentsDelegate delegate;
|
|
contents()->SetDelegate(&delegate);
|
|
|
|
// Override the FindInPageClient class so that we can intercept the Mojo
|
|
// methods calls to keep its find request queue non-empty.
|
|
contents()
|
|
->GetFindRequestManagerForTesting()
|
|
->SetCreateFindInPageClientFunctionForTesting(
|
|
&CreateFencedFrameFindInPageClient);
|
|
|
|
LoadAndWait("/find_in_page.html");
|
|
auto options = blink::mojom::FindOptions::New();
|
|
options->run_synchronously_for_testing = true;
|
|
Find("result", options.Clone());
|
|
// Initial find request is pop from the queue immediately so we make a second
|
|
// find request.
|
|
options->new_session = false;
|
|
Find("result", options.Clone());
|
|
|
|
// Create a fenced frame.
|
|
GURL find_test_url =
|
|
embedded_test_server()->GetURL("/fenced_frames/find_in_page.html");
|
|
content::RenderFrameHost* fenced_frame_host =
|
|
fenced_frame_test_helper().CreateFencedFrame(
|
|
GetWebContents()->GetPrimaryMainFrame(), find_test_url);
|
|
EXPECT_NE(nullptr, fenced_frame_host);
|
|
EXPECT_TRUE(CheckFrame(fenced_frame_host));
|
|
EXPECT_EQ(find_request_queue_size(), 1);
|
|
EXPECT_EQ(last_request_id(), delegate.GetFindResults().request_id);
|
|
|
|
// Navigate the fenced frame, this won't cause the find request queue to be
|
|
// cleared, since it's not a primary main frame.
|
|
fenced_frame_test_helper().NavigateFrameInFencedFrameTree(fenced_frame_host,
|
|
find_test_url);
|
|
EXPECT_TRUE(CheckFrame(fenced_frame_host));
|
|
EXPECT_EQ(find_request_queue_size(), 1);
|
|
EXPECT_EQ(last_request_id(), delegate.GetFindResults().request_id);
|
|
|
|
// Navigate the non-fenced frame subframe, this also won't cause the find
|
|
// request queue to be cleared, since it's not a primary main frame.
|
|
FrameTreeNode* root = contents()->GetPrimaryFrameTree().root();
|
|
EXPECT_TRUE(NavigateToURLFromRenderer(root->child_at(0), find_test_url));
|
|
EXPECT_TRUE(CheckFrame(root->child_at(0)->current_frame_host()));
|
|
EXPECT_EQ(find_request_queue_size(), 1);
|
|
EXPECT_EQ(last_request_id(), delegate.GetFindResults().request_id);
|
|
|
|
// Navigate the main frame, this causes the find request queue to be cleared,
|
|
// since it's the primary main frame.
|
|
EXPECT_TRUE(NavigateToURL(shell(), find_test_url));
|
|
EXPECT_TRUE(CheckFrame(GetWebContents()->GetPrimaryMainFrame()));
|
|
EXPECT_EQ(find_request_queue_size(), 0);
|
|
}
|
|
|
|
// This find-in-page client will make it so that we never stop listening for
|
|
// find-in-page updates only for subframes, through modifying final updates to
|
|
// be marked as non-final updates. It helps us to simulate various things that
|
|
// can happen before a find-in-page session finishes (e.g. navigation,
|
|
// lifecycle state change) without finishing the find session.
|
|
class NeverFinishSubframeFindInPageClient : public FindInPageClient {
|
|
public:
|
|
NeverFinishSubframeFindInPageClient(FindRequestManager* find_request_manager,
|
|
RenderFrameHostImpl* rfh)
|
|
: FindInPageClient(find_request_manager, rfh), rfh_(rfh) {
|
|
content::WebContents* web_contents =
|
|
content::WebContents::FromRenderFrameHost(rfh);
|
|
delegate_ = static_cast<WaitForFindTestWebContentsDelegate*>(
|
|
web_contents->GetDelegate());
|
|
}
|
|
~NeverFinishSubframeFindInPageClient() override = default;
|
|
|
|
// blink::mojom::FindInPageClient overrides
|
|
void SetNumberOfMatches(
|
|
int request_id,
|
|
unsigned int current_number_of_matches,
|
|
blink::mojom::FindMatchUpdateType update_type) override {
|
|
bool should_wait = delegate_->ShouldWait();
|
|
if (update_type == blink::mojom::FindMatchUpdateType::kFinalUpdate)
|
|
delegate_->TryToStopWaiting();
|
|
|
|
// Make sure subframe's reply is not marked as the final update.
|
|
if (!rfh_->is_main_frame() && should_wait)
|
|
update_type = blink::mojom::FindMatchUpdateType::kMoreUpdatesComing;
|
|
|
|
FindInPageClient::SetNumberOfMatches(request_id, current_number_of_matches,
|
|
update_type);
|
|
}
|
|
|
|
void SetActiveMatch(int request_id,
|
|
const gfx::Rect& active_match_rect,
|
|
int active_match_ordinal,
|
|
blink::mojom::FindMatchUpdateType update_type) override {
|
|
if (update_type == blink::mojom::FindMatchUpdateType::kFinalUpdate)
|
|
delegate_->TryToStopWaiting();
|
|
|
|
// Make sure subframe's reply is not marked as the final update.
|
|
if (!rfh_->is_main_frame())
|
|
update_type = blink::mojom::FindMatchUpdateType::kMoreUpdatesComing;
|
|
|
|
FindInPageClient::SetActiveMatch(request_id, active_match_rect,
|
|
active_match_ordinal, update_type);
|
|
}
|
|
|
|
private:
|
|
raw_ptr<RenderFrameHostImpl> rfh_;
|
|
raw_ptr<WaitForFindTestWebContentsDelegate> delegate_;
|
|
};
|
|
|
|
class FindRequestManagerTestObserver : public WebContentsObserver {
|
|
public:
|
|
explicit FindRequestManagerTestObserver(WebContents* web_contents)
|
|
: WebContentsObserver(web_contents) {}
|
|
|
|
void DidFinishLoad(RenderFrameHost* render_frame_host,
|
|
const GURL& url) override {
|
|
auto* delegate = static_cast<FindTestWebContentsDelegate*>(
|
|
web_contents()->GetDelegate());
|
|
delegate->MarkNextReply();
|
|
}
|
|
};
|
|
|
|
static std::unique_ptr<FindInPageClient> CreateFindInPageClient(
|
|
FindRequestManager* find_request_manager,
|
|
RenderFrameHostImpl* rfh) {
|
|
return std::make_unique<NeverFinishSubframeFindInPageClient>(
|
|
find_request_manager, rfh);
|
|
}
|
|
|
|
enum class FrameSiteType {
|
|
kSameOrigin,
|
|
kCrossOrigin,
|
|
};
|
|
|
|
enum class FrameTestType {
|
|
kIFrame,
|
|
kFencedFrame,
|
|
};
|
|
|
|
class FindRequestManagerTestWithTestConfig
|
|
: public FindRequestManagerTestBase,
|
|
public testing::WithParamInterface<
|
|
::testing::tuple<FrameSiteType, FrameTestType>> {
|
|
public:
|
|
FrameSiteType GetFrameSiteType() const { return std::get<0>(GetParam()); }
|
|
|
|
FrameTestType GetFrameTestType() const { return std::get<1>(GetParam()); }
|
|
|
|
test::FencedFrameTestHelper& fenced_frame_test_helper() {
|
|
return fenced_frame_test_helper_;
|
|
}
|
|
|
|
private:
|
|
test::FencedFrameTestHelper fenced_frame_test_helper_;
|
|
};
|
|
|
|
INSTANTIATE_TEST_SUITE_P(
|
|
FindRequestManagers,
|
|
FindRequestManagerTestWithTestConfig,
|
|
::testing::Combine(::testing::Values(FrameSiteType::kSameOrigin,
|
|
FrameSiteType::kCrossOrigin),
|
|
::testing::Values(FrameTestType::kIFrame,
|
|
FrameTestType::kFencedFrame)));
|
|
|
|
// Tests that the previous results from old document are removed and we get the
|
|
// new results from the new document when we navigate the subframe that
|
|
// hasn't finished the find-in-page session to the new document.
|
|
// TODO(crbug.com/1311444): Fix flakiness and reenable the test.
|
|
#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN)
|
|
#define MAYBE_NavigateFrameDuringFind DISABLED_NavigateFrameDuringFind
|
|
#else
|
|
#define MAYBE_NavigateFrameDuringFind NavigateFrameDuringFind
|
|
#endif
|
|
IN_PROC_BROWSER_TEST_P(FindRequestManagerTestWithTestConfig,
|
|
MAYBE_NavigateFrameDuringFind) {
|
|
WaitForFindTestWebContentsDelegate delegate;
|
|
contents()->SetDelegate(&delegate);
|
|
|
|
// 1) Load a main frame with 5 matches.
|
|
LoadAndWait("/find_in_simple_page.html");
|
|
|
|
GURL frame_url =
|
|
embedded_test_server()->GetURL("a.com", "/find_in_page_frame.html");
|
|
content::RenderFrameHost* fenced_frame_host = nullptr;
|
|
|
|
// 2) Load a subframe with 17 matches.
|
|
if (GetFrameTestType() == FrameTestType::kIFrame) {
|
|
EXPECT_TRUE(ExecJs(shell(), JsReplace(R"(
|
|
var frame = document.createElement('iframe');
|
|
frame.src = $1;
|
|
document.body.appendChild(frame);
|
|
)",
|
|
frame_url)));
|
|
ASSERT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
} else {
|
|
fenced_frame_host = fenced_frame_test_helper().CreateFencedFrame(
|
|
shell()->web_contents()->GetPrimaryMainFrame(), frame_url);
|
|
EXPECT_NE(nullptr, fenced_frame_host);
|
|
}
|
|
|
|
auto options = blink::mojom::FindOptions::New();
|
|
options->run_synchronously_for_testing = true;
|
|
|
|
// 2) First try a normal find-in-page session that finishes completely.
|
|
Find("result", options.Clone());
|
|
delegate.WaitForFinalReply();
|
|
|
|
FindResults results = delegate.GetFindResults();
|
|
EXPECT_EQ(last_request_id(), results.request_id);
|
|
EXPECT_EQ(22, results.number_of_matches);
|
|
EXPECT_EQ(1, results.active_match_ordinal);
|
|
|
|
// 3) Override the FindInPageClient class so that we can simulate a subframe
|
|
// change that happens in the middle of a find-in-page session.
|
|
contents()
|
|
->GetFindRequestManagerForTesting()
|
|
->SetCreateFindInPageClientFunctionForTesting(&CreateFindInPageClient);
|
|
|
|
// 4) Try to find-in-page again, but this time the subframe won't be marked as
|
|
// finished before it got navigated.
|
|
Find("result", options.Clone());
|
|
|
|
// 5) Wait for the find request of the main frame's reply.
|
|
delegate.WaitForFramesReply(2);
|
|
results = delegate.GetFindResults();
|
|
EXPECT_EQ(last_request_id(), results.request_id);
|
|
EXPECT_EQ(22, results.number_of_matches);
|
|
EXPECT_EQ(2, results.active_match_ordinal);
|
|
|
|
// 6) Navigate the subframe that hasn't finished the find-in-page session to a
|
|
// document with 5 matches. This will trigger a find-in-page request on the
|
|
// new document on the unfinished subframe, and removes the result from the
|
|
// old document.
|
|
FindRequestManagerTestObserver observer(contents());
|
|
GURL url(embedded_test_server()->GetURL(
|
|
GetFrameSiteType() == FrameSiteType::kSameOrigin ? "a.com" : "b.com",
|
|
"/find_in_simple_page.html"));
|
|
if (GetFrameTestType() == FrameTestType::kIFrame) {
|
|
FrameTreeNode* root = contents()->GetPrimaryFrameTree().root();
|
|
TestNavigationObserver navigation_observer(contents());
|
|
EXPECT_TRUE(NavigateToURLFromRenderer(root->child_at(0), url));
|
|
EXPECT_TRUE(navigation_observer.last_navigation_succeeded());
|
|
} else {
|
|
fenced_frame_test_helper().NavigateFrameInFencedFrameTree(fenced_frame_host,
|
|
url);
|
|
}
|
|
|
|
delegate.WaitForNextReply();
|
|
|
|
results = delegate.GetFindResults();
|
|
EXPECT_EQ(last_request_id(), results.request_id);
|
|
// The results from the old subframe (17 results) is removed entirely even
|
|
// when it hasn't finished, and we added the next reply from the new subframe
|
|
// (5 results). So, the final results should be 10 as the mainframe(5 results)
|
|
// and the new subframe (5 results).
|
|
EXPECT_EQ(10, results.number_of_matches);
|
|
EXPECT_EQ(2, results.active_match_ordinal);
|
|
}
|
|
|
|
// Tests that the previous results from the old documents are removed and we
|
|
// get the new results from the new document when we go back to the page in
|
|
// BFCache from the page that hasn't finished the find-in-page session.
|
|
// This TC does not intentionally check the |active_match_ordinal| value,
|
|
// because the main frame is not focused on Android, so it has a different
|
|
// result on Android.
|
|
IN_PROC_BROWSER_TEST_F(FindRequestManagerTestWithBFCache,
|
|
NavigateFrameDuringFind) {
|
|
WaitForFindTestWebContentsDelegate delegate;
|
|
contents()->SetDelegate(&delegate);
|
|
|
|
GURL url_a = embedded_test_server()->GetURL("a.com", "/find_in_page.html");
|
|
GURL url_b =
|
|
embedded_test_server()->GetURL("b.com", "/find_in_page_two_frames.html");
|
|
|
|
// 1) Load A that is a main frame with 2 matches and a subframe with 17
|
|
// matches.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_a));
|
|
content::RenderFrameHostWrapper rfh_a(render_frame_host());
|
|
|
|
// 2) Load B that is a main frame with no match and two subframes with each 3
|
|
// matches.
|
|
EXPECT_TRUE(NavigateToURL(shell(), url_b));
|
|
// Ensure A is cached.
|
|
EXPECT_EQ(rfh_a->GetLifecycleState(),
|
|
content::RenderFrameHost::LifecycleState::kInBackForwardCache);
|
|
content::RenderFrameHostWrapper rfh_b(render_frame_host());
|
|
|
|
// 3) Override the FindInPageClient class so that we can simulate a subframe
|
|
// change that happens in the middle of a find-in-page session.
|
|
contents()
|
|
->GetFindRequestManagerForTesting()
|
|
->SetCreateFindInPageClientFunctionForTesting(&CreateFindInPageClient);
|
|
|
|
auto options = blink::mojom::FindOptions::New();
|
|
options->run_synchronously_for_testing = true;
|
|
|
|
// 4) Try to find-in-page again, but this time the subframe won't be marked as
|
|
// finished before it goes back in the BF cache.
|
|
Find("result", options.Clone());
|
|
|
|
// 5) Wait for replies from the main frame and the subframes.
|
|
delegate.WaitForFramesReply(3);
|
|
FindResults results = delegate.GetFindResults();
|
|
EXPECT_EQ(last_request_id(), results.request_id);
|
|
EXPECT_EQ(6, results.number_of_matches);
|
|
|
|
// 6) Go back to A which has a main frame with 2 matches and the subframe with
|
|
// 17 matches.
|
|
FindRequestManagerTestObserver observer1(contents());
|
|
contents()->GetController().GoBack();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
// |rfh_a| should become the active frame.
|
|
EXPECT_EQ(rfh_a.get(), render_frame_host());
|
|
// Ensure B is cached.
|
|
EXPECT_EQ(rfh_b->GetLifecycleState(),
|
|
content::RenderFrameHost::LifecycleState::kInBackForwardCache);
|
|
|
|
// 7) Wait for replies from the main frame and the subframes.
|
|
delegate.WaitForFramesReply(2);
|
|
results = delegate.GetFindResults();
|
|
EXPECT_EQ(last_request_id(), results.request_id);
|
|
// The results from the old page (6 results) is removed entirely even when
|
|
// it hasn't finished, and we added the next reply from the new page (19
|
|
// results). So, the final results should be 19.
|
|
EXPECT_EQ(19, results.number_of_matches);
|
|
|
|
// 8) Go forward to B which has a main frame with no match and two subframes
|
|
// with each 3 matches.
|
|
contents()->GetController().GoForward();
|
|
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
|
|
// |rfh_b| should become the active frame.
|
|
EXPECT_EQ(rfh_b.get(), render_frame_host());
|
|
|
|
// 9) Wait for replies from the main frame and the subframes.
|
|
delegate.WaitForFinalReply();
|
|
results = delegate.GetFindResults();
|
|
EXPECT_EQ(last_request_id(), results.request_id);
|
|
// The results from the old page (19 results) is removed entirely even when
|
|
// it hasn't finished, and we added the next reply from the new page (6
|
|
// results). So, the final results should be 6.
|
|
EXPECT_EQ(6, results.number_of_matches);
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(FindRequestManagerTest, CrashDuringFind) {
|
|
WaitForFindTestWebContentsDelegate delegate;
|
|
contents()->SetDelegate(&delegate);
|
|
|
|
// 1) Load a main frame with 2 matches and a subframe with 17 matches.
|
|
LoadAndWait("/find_in_page.html");
|
|
MakeChildFrameCrossProcess();
|
|
|
|
// 2) Override the FindInPageClient class so that we can simulate a subframe
|
|
// change that happens in the middle of a find-in-page session.
|
|
contents()
|
|
->GetFindRequestManagerForTesting()
|
|
->SetCreateFindInPageClientFunctionForTesting(&CreateFindInPageClient);
|
|
|
|
auto options = blink::mojom::FindOptions::New();
|
|
options->run_synchronously_for_testing = true;
|
|
|
|
// 3) Try to find-in-page again, but this time the subframe won't be marked as
|
|
// finished before it crashed.
|
|
Find("result", options.Clone());
|
|
|
|
// 4) Wait for the find request of the main frame's reply.
|
|
delegate.WaitForFramesReply(2);
|
|
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);
|
|
|
|
// 5) Crash the subframe that hasn't finished the find-in-page
|
|
// session. This will remove the result from the crashed document.
|
|
{
|
|
FrameTreeNode* root = contents()->GetPrimaryFrameTree().root();
|
|
content::ScopedAllowRendererCrashes scoped_allow_renderer_crashes;
|
|
content::RenderFrameDeletedObserver crash_observer(
|
|
root->child_at(0)->current_frame_host());
|
|
root->child_at(0)->current_frame_host()->GetProcess()->Shutdown(1);
|
|
crash_observer.WaitUntilDeleted();
|
|
}
|
|
|
|
// 6) Wait for the crashed frame to be deleted.
|
|
delegate.WaitForFinalReply();
|
|
results = delegate.GetFindResults();
|
|
EXPECT_EQ(last_request_id(), results.request_id);
|
|
// The results from the crashed subframe (17 results) is removed entirely and
|
|
// only have 2 results from the main frame.
|
|
EXPECT_EQ(2, results.number_of_matches);
|
|
EXPECT_EQ(1, results.active_match_ordinal);
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_P(FindRequestManagerTest, DelayThenStop) {
|
|
LoadAndWait("/find_in_page.html");
|
|
if (test_with_oopif())
|
|
MakeChildFrameCrossProcess();
|
|
|
|
auto default_options = blink::mojom::FindOptions::New();
|
|
Find("r", default_options->Clone());
|
|
contents()->StopFinding(STOP_FIND_ACTION_CLEAR_SELECTION);
|
|
|
|
FindResults results = delegate()->GetFindResults();
|
|
EXPECT_EQ(0, results.number_of_matches);
|
|
|
|
EXPECT_FALSE(contents()
|
|
->GetFindRequestManagerForTesting()
|
|
->RunDelayedFindTaskForTesting());
|
|
}
|
|
|
|
} // namespace content
|