blob: e610f1e14188e4d1cd6132422ffa4ff23215f258 [file] [log] [blame]
// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/optimization_guide/content/browser/page_text_observer.h"
#include <optional>
#include <string>
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/strings/strcat.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/types/optional_util.h"
#include "components/optimization_guide/content/mojom/page_text_service.mojom.h"
#include "content/public/browser/navigation_entry.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/test/navigation_simulator.h"
#include "content/public/test/prerender_test_util.h"
#include "content/public/test/test_renderer_host.h"
#include "content/public/test/web_contents_tester.h"
#include "mojo/public/cpp/bindings/associated_receiver.h"
#include "mojo/public/cpp/bindings/associated_remote.h"
#include "mojo/public/cpp/bindings/receiver.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "net/http/http_response_headers.h"
#include "services/service_manager/public/cpp/interface_provider.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
#include "third_party/blink/public/common/features.h"
namespace optimization_guide {
namespace {
FrameTextDumpResult MakeFrameDump(mojom::TextDumpEvent event,
content::GlobalRenderFrameHostId rfh_id,
bool amp_frame,
int unique_navigation_id,
const std::u16string& contents) {
return FrameTextDumpResult::Initialize(event, rfh_id, amp_frame,
unique_navigation_id)
.CompleteWithContents(contents);
}
class TestConsumer : public PageTextObserver::Consumer {
public:
TestConsumer() = default;
~TestConsumer() = default;
void Reset() { was_called_ = false; }
void PopulateRequest(uint32_t max_size,
const std::set<mojom::TextDumpEvent>& events,
bool request_amp = false) {
request_ = std::make_unique<PageTextObserver::ConsumerTextDumpRequest>();
request_->max_size = max_size;
request_->events = events;
request_->callback =
base::BindOnce(&TestConsumer::OnGotTextDump, base::Unretained(this));
request_->dump_amp_subframes = request_amp;
}
void WaitForPageText() {
if (result_) {
return;
}
base::RunLoop run_loop;
on_page_text_closure_ = run_loop.QuitClosure();
run_loop.Run();
}
bool was_called() const { return was_called_; }
std::optional<PageTextDumpResult> result() {
return base::OptionalFromPtr(result_.get());
}
// PageTextObserver::Consumer:
std::unique_ptr<PageTextObserver::ConsumerTextDumpRequest>
MaybeRequestFrameTextDump(content::NavigationHandle* handle) override {
was_called_ = true;
return std::move(request_);
}
private:
void OnGotTextDump(const PageTextDumpResult& result) {
result_ = std::make_unique<PageTextDumpResult>(result);
if (on_page_text_closure_) {
std::move(on_page_text_closure_).Run();
}
}
bool was_called_ = false;
std::unique_ptr<PageTextObserver::ConsumerTextDumpRequest> request_;
base::OnceClosure on_page_text_closure_;
std::unique_ptr<PageTextDumpResult> result_;
};
class FakePageTextService : public mojom::PageTextService {
public:
FakePageTextService() = default;
~FakePageTextService() override = default;
void BindPendingReceiver(mojo::ScopedInterfaceEndpointHandle handle) {
receiver_.Bind(mojo::PendingAssociatedReceiver<mojom::PageTextService>(
std::move(handle)));
if (disconnect_all_) {
receiver_.reset();
}
}
void set_disconnect_all(bool disconnect_all) {
disconnect_all_ = disconnect_all;
}
const std::set<mojom::PageTextDumpRequest>& requests() { return requests_; }
// Sets a sequence of responses to send on the next page dump request for the
// given |event|. If an element has a value, |OnTextDumpChunk| is called with
// the text chunk. If an element does not have a value, |OnChunksEnd| is
// called.
void SetRemoteResponsesForEvent(
mojom::TextDumpEvent event,
const std::vector<std::optional<std::u16string>> responses) {
responses_.emplace(event, responses);
}
// A single request that matches the given |event| will hang until the class
// is destroyed, or another request for the same event is sent.
void SetEventToHangForver(mojom::TextDumpEvent event) { hang_event_ = event; }
// mojom::PageTextService:
void RequestPageTextDump(
mojom::PageTextDumpRequestPtr request,
mojo::PendingRemote<mojom::PageTextConsumer> consumer) override {
auto responses_iter = responses_.find(request->event);
requests_.emplace(*request);
if (hang_event_ && hang_event_.value() == request->event) {
hung_consumer_remote_.Bind(std::move(consumer));
return;
}
if (responses_iter == responses_.end()) {
return;
}
mojo::Remote<mojom::PageTextConsumer> consumer_remote;
consumer_remote.Bind(std::move(consumer));
for (const std::optional<std::u16string>& resp : responses_iter->second) {
if (resp) {
consumer_remote->OnTextDumpChunk(*resp);
} else {
consumer_remote->OnChunksEnd();
}
}
}
private:
mojo::AssociatedReceiver<mojom::PageTextService> receiver_{this};
std::set<mojom::PageTextDumpRequest> requests_;
bool disconnect_all_ = false;
// Used to timeout a request.
std::optional<mojom::TextDumpEvent> hang_event_;
mojo::Remote<mojom::PageTextConsumer> hung_consumer_remote_;
// For each event, a sequence of responses to send on the next page dump
// request. If an element has a value, |OnTextDumpChunk| is called with the
// text chunk. If an element does not have a value, |OnChunksEnd| is called.
std::map<mojom::TextDumpEvent, std::vector<std::optional<std::u16string>>>
responses_;
};
class TestPageTextObserver : public PageTextObserver {
public:
explicit TestPageTextObserver(content::WebContents* web_contents)
: PageTextObserver(web_contents) {}
~TestPageTextObserver() override = default;
void SetIsOOPIF(content::RenderFrameHost* rfh, bool is_oopif) {
oopif_overrides_.erase(rfh);
oopif_overrides_.emplace(rfh, is_oopif);
}
bool IsOOPIF(content::RenderFrameHost* rfh) const override {
auto iter = oopif_overrides_.find(rfh);
if (iter != oopif_overrides_.end()) {
return iter->second;
}
return false;
}
void DidFinishLoad(content::RenderFrameHost* render_frame_host,
const GURL& validated_url) override {
// Intentionally do nothing so that subframes can be added in tests.
}
void CallDidFinishLoad() {
PageTextObserver::DidFinishLoad(web_contents()->GetPrimaryMainFrame(),
GURL());
}
private:
std::map<content::RenderFrameHost*, bool> oopif_overrides_;
};
} // namespace
class PageTextObserverTest : public content::RenderViewHostTestHarness {
public:
PageTextObserverTest() = default;
~PageTextObserverTest() override = default;
// content::RenderViewHostTestHarness:
void SetUp() override {
content::RenderViewHostTestHarness::SetUp();
observer_ = std::make_unique<TestPageTextObserver>(web_contents());
ASSERT_TRUE(observer());
}
TestPageTextObserver* observer() const { return observer_.get(); }
private:
std::unique_ptr<TestPageTextObserver> observer_;
};
TEST_F(PageTextObserverTest, ConsumerCalled) {
TestConsumer consumer;
observer()->AddConsumer(&consumer);
EXPECT_FALSE(consumer.was_called());
content::NavigationSimulator::NavigateAndCommitFromBrowser(
web_contents(), GURL("http://test.com"));
EXPECT_TRUE(consumer.was_called());
}
TEST_F(PageTextObserverTest, ConsumerCalledSameDocument) {
TestConsumer consumer;
observer()->AddConsumer(&consumer);
content::NavigationSimulator::CreateRendererInitiated(
GURL("http://test.com/"), main_rfh())
->Commit();
EXPECT_TRUE(consumer.was_called());
consumer.Reset();
content::NavigationSimulator::CreateRendererInitiated(
GURL("http://test.com/#fragment"), main_rfh())
->CommitSameDocument();
observer()->CallDidFinishLoad();
EXPECT_TRUE(consumer.was_called());
}
TEST_F(PageTextObserverTest, ConsumerNotCalledSubframe) {
TestConsumer consumer;
observer()->AddConsumer(&consumer);
content::NavigationSimulator::CreateRendererInitiated(
GURL("http://test.com/"), main_rfh())
->Commit();
consumer.Reset();
content::NavigationSimulator::NavigateAndCommitFromDocument(
GURL("http://subframe.com"),
content::RenderFrameHostTester::For(web_contents()->GetPrimaryMainFrame())
->AppendChild("subframe"));
EXPECT_FALSE(consumer.was_called());
}
TEST_F(PageTextObserverTest, ConsumerNotCalledNoCommit) {
TestConsumer consumer;
observer()->AddConsumer(&consumer);
auto simulator = content::NavigationSimulator::CreateRendererInitiated(
GURL("http://test.com/document.pdf"), main_rfh());
simulator->Start();
simulator->SetResponseHeaders(base::MakeRefCounted<net::HttpResponseHeaders>(
"HTTP/1.1 204 No Content\r\n"));
simulator->Commit();
EXPECT_FALSE(consumer.was_called());
}
TEST_F(PageTextObserverTest, MojoPlumbingSuccessCase) {
TestConsumer consumer;
observer()->AddConsumer(&consumer);
consumer.PopulateRequest(/*max_size=*/1024,
/*events=*/{mojom::TextDumpEvent::kFirstLayout});
FakePageTextService fake_renderer_service;
fake_renderer_service.SetRemoteResponsesForEvent(
mojom::TextDumpEvent::kFirstLayout, {
u"a",
u"b",
u"c",
std::nullopt,
});
blink::AssociatedInterfaceProvider* remote_interfaces =
main_rfh()->GetRemoteAssociatedInterfaces();
remote_interfaces->OverrideBinderForTesting(
mojom::PageTextService::Name_,
base::BindRepeating(&FakePageTextService::BindPendingReceiver,
base::Unretained(&fake_renderer_service)));
content::NavigationSimulator::NavigateAndCommitFromBrowser(
web_contents(), GURL("http://test.com"));
EXPECT_TRUE(consumer.was_called());
observer()->CallDidFinishLoad();
consumer.WaitForPageText();
ASSERT_TRUE(consumer.result());
EXPECT_THAT(
consumer.result()->frame_results(),
::testing::UnorderedElementsAreArray({
MakeFrameDump(
mojom::TextDumpEvent::kFirstLayout, main_rfh()->GetGlobalId(),
/*amp_frame=*/false,
web_contents()->GetController().GetVisibleEntry()->GetUniqueID(),
u"abc"),
}));
EXPECT_THAT(
fake_renderer_service.requests(),
::testing::UnorderedElementsAreArray({
mojom::PageTextDumpRequest(1024U, mojom::TextDumpEvent::kFirstLayout),
}));
}
TEST_F(PageTextObserverTest, CompletedFrameDumpMetrics_Empty) {
base::HistogramTester histogram_tester;
TestConsumer consumer;
observer()->AddConsumer(&consumer);
consumer.PopulateRequest(/*max_size=*/1024,
/*events=*/{mojom::TextDumpEvent::kFirstLayout});
FakePageTextService fake_renderer_service;
fake_renderer_service.SetRemoteResponsesForEvent(
mojom::TextDumpEvent::kFirstLayout, {
std::nullopt,
});
blink::AssociatedInterfaceProvider* remote_interfaces =
main_rfh()->GetRemoteAssociatedInterfaces();
remote_interfaces->OverrideBinderForTesting(
mojom::PageTextService::Name_,
base::BindRepeating(&FakePageTextService::BindPendingReceiver,
base::Unretained(&fake_renderer_service)));
content::NavigationSimulator::NavigateAndCommitFromBrowser(
web_contents(), GURL("http://test.com"));
ASSERT_TRUE(consumer.was_called());
observer()->CallDidFinishLoad();
consumer.WaitForPageText();
histogram_tester.ExpectUniqueSample(
"OptimizationGuide.PageTextDump.FrameDumpLength.FirstLayout", 0, 1);
histogram_tester.ExpectTotalCount(
"OptimizationGuide.PageTextDump.OutstandingRequests.DidFinishLoad", 1);
histogram_tester.ExpectTotalCount(
"OptimizationGuide.PageTextDump.TimeUntilFrameDumpCompleted.FirstLayout",
1);
histogram_tester.ExpectTotalCount(
"OptimizationGuide.PageTextDump.TimeUntilFrameDisconnected.FirstLayout",
0);
}
TEST_F(PageTextObserverTest, CompletedFrameDumpMetrics_NotEmpty) {
base::HistogramTester histogram_tester;
TestConsumer consumer;
observer()->AddConsumer(&consumer);
consumer.PopulateRequest(/*max_size=*/1024,
/*events=*/{mojom::TextDumpEvent::kFirstLayout});
FakePageTextService fake_renderer_service;
fake_renderer_service.SetRemoteResponsesForEvent(
mojom::TextDumpEvent::kFirstLayout, {
u"a",
u"b",
u"c",
std::nullopt,
});
blink::AssociatedInterfaceProvider* remote_interfaces =
main_rfh()->GetRemoteAssociatedInterfaces();
remote_interfaces->OverrideBinderForTesting(
mojom::PageTextService::Name_,
base::BindRepeating(&FakePageTextService::BindPendingReceiver,
base::Unretained(&fake_renderer_service)));
content::NavigationSimulator::NavigateAndCommitFromBrowser(
web_contents(), GURL("http://test.com"));
ASSERT_TRUE(consumer.was_called());
observer()->CallDidFinishLoad();
consumer.WaitForPageText();
histogram_tester.ExpectUniqueSample(
"OptimizationGuide.PageTextDump.FrameDumpLength.FirstLayout", 3, 1);
histogram_tester.ExpectTotalCount(
"OptimizationGuide.PageTextDump.OutstandingRequests.DidFinishLoad", 1);
histogram_tester.ExpectTotalCount(
"OptimizationGuide.PageTextDump.TimeUntilFrameDumpCompleted.FirstLayout",
1);
histogram_tester.ExpectTotalCount(
"OptimizationGuide.PageTextDump.TimeUntilFrameDisconnected.FirstLayout",
0);
}
TEST_F(PageTextObserverTest, DisconnectedFrameDumpMetrics) {
base::HistogramTester histogram_tester;
TestConsumer consumer;
observer()->AddConsumer(&consumer);
consumer.PopulateRequest(/*max_size=*/1024,
/*events=*/{mojom::TextDumpEvent::kFirstLayout});
FakePageTextService fake_renderer_service;
fake_renderer_service.set_disconnect_all(true);
blink::AssociatedInterfaceProvider* remote_interfaces =
main_rfh()->GetRemoteAssociatedInterfaces();
remote_interfaces->OverrideBinderForTesting(
mojom::PageTextService::Name_,
base::BindRepeating(&FakePageTextService::BindPendingReceiver,
base::Unretained(&fake_renderer_service)));
content::NavigationSimulator::NavigateAndCommitFromBrowser(
web_contents(), GURL("http://test.com"));
ASSERT_TRUE(consumer.was_called());
base::RunLoop().RunUntilIdle();
observer()->CallDidFinishLoad();
histogram_tester.ExpectTotalCount(
"OptimizationGuide.PageTextDump.FrameDumpLength.FirstLayout", 0);
histogram_tester.ExpectTotalCount(
"OptimizationGuide.PageTextDump.OutstandingRequests.DidFinishLoad", 1);
histogram_tester.ExpectTotalCount(
"OptimizationGuide.PageTextDump.TimeUntilFrameDumpCompleted.FirstLayout",
0);
histogram_tester.ExpectTotalCount(
"OptimizationGuide.PageTextDump.TimeUntilFrameDisconnected.FirstLayout",
1);
}
TEST_F(PageTextObserverTest, MaxLengthOnChunkBorder) {
TestConsumer consumer;
observer()->AddConsumer(&consumer);
consumer.PopulateRequest(/*max_size=*/3,
/*events=*/{mojom::TextDumpEvent::kFirstLayout});
FakePageTextService fake_renderer_service;
fake_renderer_service.SetRemoteResponsesForEvent(
mojom::TextDumpEvent::kFirstLayout, {
u"abc",
u"def",
std::nullopt,
});
blink::AssociatedInterfaceProvider* remote_interfaces =
main_rfh()->GetRemoteAssociatedInterfaces();
remote_interfaces->OverrideBinderForTesting(
mojom::PageTextService::Name_,
base::BindRepeating(&FakePageTextService::BindPendingReceiver,
base::Unretained(&fake_renderer_service)));
content::NavigationSimulator::NavigateAndCommitFromBrowser(
web_contents(), GURL("http://test.com"));
EXPECT_TRUE(consumer.was_called());
observer()->CallDidFinishLoad();
consumer.WaitForPageText();
ASSERT_TRUE(consumer.result());
EXPECT_THAT(
consumer.result()->frame_results(),
::testing::UnorderedElementsAreArray({
MakeFrameDump(
mojom::TextDumpEvent::kFirstLayout, main_rfh()->GetGlobalId(),
/*amp_frame=*/false,
web_contents()->GetController().GetVisibleEntry()->GetUniqueID(),
u"abc"),
}));
EXPECT_THAT(
fake_renderer_service.requests(),
::testing::UnorderedElementsAreArray({
mojom::PageTextDumpRequest(3U, mojom::TextDumpEvent::kFirstLayout),
}));
}
TEST_F(PageTextObserverTest, MaxLengthWithinChunk) {
TestConsumer consumer;
observer()->AddConsumer(&consumer);
consumer.PopulateRequest(/*max_size=*/4,
/*events=*/{mojom::TextDumpEvent::kFirstLayout});
FakePageTextService fake_renderer_service;
fake_renderer_service.SetRemoteResponsesForEvent(
mojom::TextDumpEvent::kFirstLayout, {
u"abc",
u"def",
std::nullopt,
});
blink::AssociatedInterfaceProvider* remote_interfaces =
main_rfh()->GetRemoteAssociatedInterfaces();
remote_interfaces->OverrideBinderForTesting(
mojom::PageTextService::Name_,
base::BindRepeating(&FakePageTextService::BindPendingReceiver,
base::Unretained(&fake_renderer_service)));
content::NavigationSimulator::NavigateAndCommitFromBrowser(
web_contents(), GURL("http://test.com"));
EXPECT_TRUE(consumer.was_called());
observer()->CallDidFinishLoad();
consumer.WaitForPageText();
ASSERT_TRUE(consumer.result());
EXPECT_THAT(
consumer.result()->frame_results(),
::testing::UnorderedElementsAreArray({
MakeFrameDump(
mojom::TextDumpEvent::kFirstLayout, main_rfh()->GetGlobalId(),
/*amp_frame=*/false,
web_contents()->GetController().GetVisibleEntry()->GetUniqueID(),
u"abcd"),
}));
EXPECT_THAT(
fake_renderer_service.requests(),
::testing::UnorderedElementsAreArray({
mojom::PageTextDumpRequest(4U, mojom::TextDumpEvent::kFirstLayout),
}));
}
TEST_F(PageTextObserverTest, MaxLengthWithoutOnEnd) {
TestConsumer consumer;
observer()->AddConsumer(&consumer);
consumer.PopulateRequest(/*max_size=*/4,
/*events=*/{mojom::TextDumpEvent::kFirstLayout});
FakePageTextService fake_renderer_service;
fake_renderer_service.SetRemoteResponsesForEvent(
mojom::TextDumpEvent::kFirstLayout, {
u"abc",
u"def",
});
blink::AssociatedInterfaceProvider* remote_interfaces =
main_rfh()->GetRemoteAssociatedInterfaces();
remote_interfaces->OverrideBinderForTesting(
mojom::PageTextService::Name_,
base::BindRepeating(&FakePageTextService::BindPendingReceiver,
base::Unretained(&fake_renderer_service)));
content::NavigationSimulator::NavigateAndCommitFromBrowser(
web_contents(), GURL("http://test.com"));
EXPECT_TRUE(consumer.was_called());
observer()->CallDidFinishLoad();
consumer.WaitForPageText();
ASSERT_TRUE(consumer.result());
EXPECT_THAT(
consumer.result()->frame_results(),
::testing::UnorderedElementsAreArray({
MakeFrameDump(
mojom::TextDumpEvent::kFirstLayout, main_rfh()->GetGlobalId(),
/*amp_frame=*/false,
web_contents()->GetController().GetVisibleEntry()->GetUniqueID(),
u"abcd"),
}));
EXPECT_THAT(
fake_renderer_service.requests(),
::testing::UnorderedElementsAreArray({
mojom::PageTextDumpRequest(4U, mojom::TextDumpEvent::kFirstLayout),
}));
}
TEST_F(PageTextObserverTest, TwoConsumers) {
TestConsumer consumer1;
TestConsumer consumer2;
observer()->AddConsumer(&consumer1);
observer()->AddConsumer(&consumer2);
consumer1.PopulateRequest(/*max_size=*/2,
/*events=*/{mojom::TextDumpEvent::kFirstLayout});
consumer2.PopulateRequest(/*max_size=*/3,
/*events=*/{mojom::TextDumpEvent::kFirstLayout});
FakePageTextService fake_renderer_service;
fake_renderer_service.SetRemoteResponsesForEvent(
mojom::TextDumpEvent::kFirstLayout, {
u"a",
u"b",
u"c",
std::nullopt,
});
blink::AssociatedInterfaceProvider* remote_interfaces =
main_rfh()->GetRemoteAssociatedInterfaces();
remote_interfaces->OverrideBinderForTesting(
mojom::PageTextService::Name_,
base::BindRepeating(&FakePageTextService::BindPendingReceiver,
base::Unretained(&fake_renderer_service)));
content::NavigationSimulator::NavigateAndCommitFromBrowser(
web_contents(), GURL("http://test.com"));
EXPECT_TRUE(consumer1.was_called());
EXPECT_TRUE(consumer2.was_called());
observer()->CallDidFinishLoad();
consumer1.WaitForPageText();
consumer2.WaitForPageText();
ASSERT_TRUE(consumer1.result());
EXPECT_THAT(
consumer1.result()->frame_results(),
::testing::UnorderedElementsAreArray({
MakeFrameDump(
mojom::TextDumpEvent::kFirstLayout, main_rfh()->GetGlobalId(),
/*amp_frame=*/false,
web_contents()->GetController().GetVisibleEntry()->GetUniqueID(),
u"abc"),
}));
ASSERT_TRUE(consumer2.result());
EXPECT_THAT(
consumer2.result()->frame_results(),
::testing::UnorderedElementsAreArray({
MakeFrameDump(
mojom::TextDumpEvent::kFirstLayout, main_rfh()->GetGlobalId(),
/*amp_frame=*/false,
web_contents()->GetController().GetVisibleEntry()->GetUniqueID(),
u"abc"),
}));
EXPECT_THAT(
fake_renderer_service.requests(),
::testing::UnorderedElementsAreArray({
mojom::PageTextDumpRequest(3U, mojom::TextDumpEvent::kFirstLayout),
}));
}
TEST_F(PageTextObserverTest, RemoveConsumer) {
TestConsumer consumer1;
TestConsumer consumer2;
observer()->AddConsumer(&consumer1);
observer()->AddConsumer(&consumer2);
observer()->RemoveConsumer(&consumer2);
consumer1.PopulateRequest(/*max_size=*/3,
/*events=*/{mojom::TextDumpEvent::kFirstLayout});
consumer2.PopulateRequest(/*max_size=*/4,
/*events=*/{mojom::TextDumpEvent::kFirstLayout});
FakePageTextService fake_renderer_service;
fake_renderer_service.SetRemoteResponsesForEvent(
mojom::TextDumpEvent::kFirstLayout, {
u"a",
u"b",
u"c",
std::nullopt,
});
blink::AssociatedInterfaceProvider* remote_interfaces =
main_rfh()->GetRemoteAssociatedInterfaces();
remote_interfaces->OverrideBinderForTesting(
mojom::PageTextService::Name_,
base::BindRepeating(&FakePageTextService::BindPendingReceiver,
base::Unretained(&fake_renderer_service)));
content::NavigationSimulator::NavigateAndCommitFromBrowser(
web_contents(), GURL("http://test.com"));
EXPECT_TRUE(consumer1.was_called());
EXPECT_FALSE(consumer2.was_called());
observer()->CallDidFinishLoad();
consumer1.WaitForPageText();
ASSERT_TRUE(consumer1.result());
EXPECT_THAT(
consumer1.result()->frame_results(),
::testing::UnorderedElementsAreArray({
MakeFrameDump(
mojom::TextDumpEvent::kFirstLayout, main_rfh()->GetGlobalId(),
/*amp_frame=*/false,
web_contents()->GetController().GetVisibleEntry()->GetUniqueID(),
u"abc"),
}));
EXPECT_FALSE(consumer2.result());
EXPECT_THAT(
fake_renderer_service.requests(),
::testing::UnorderedElementsAreArray({
mojom::PageTextDumpRequest(3U, mojom::TextDumpEvent::kFirstLayout),
}));
}
TEST_F(PageTextObserverTest, TwoEventsRequested) {
TestConsumer consumer1;
TestConsumer consumer2;
observer()->AddConsumer(&consumer1);
observer()->AddConsumer(&consumer2);
consumer1.PopulateRequest(/*max_size=*/4,
/*events=*/{mojom::TextDumpEvent::kFirstLayout});
consumer2.PopulateRequest(/*max_size=*/4,
/*events=*/{mojom::TextDumpEvent::kFinishedLoad});
FakePageTextService fake_renderer_service;
fake_renderer_service.SetRemoteResponsesForEvent(
mojom::TextDumpEvent::kFirstLayout, {
u"abc",
std::nullopt,
});
fake_renderer_service.SetRemoteResponsesForEvent(
mojom::TextDumpEvent::kFinishedLoad, {
u"xyz",
std::nullopt,
});
blink::AssociatedInterfaceProvider* remote_interfaces =
main_rfh()->GetRemoteAssociatedInterfaces();
remote_interfaces->OverrideBinderForTesting(
mojom::PageTextService::Name_,
base::BindRepeating(&FakePageTextService::BindPendingReceiver,
base::Unretained(&fake_renderer_service)));
content::NavigationSimulator::NavigateAndCommitFromBrowser(
web_contents(), GURL("http://test.com"));
EXPECT_TRUE(consumer1.was_called());
EXPECT_TRUE(consumer2.was_called());
observer()->CallDidFinishLoad();
consumer1.WaitForPageText();
consumer2.WaitForPageText();
ASSERT_TRUE(consumer1.result());
EXPECT_THAT(
consumer1.result()->frame_results(),
::testing::UnorderedElementsAreArray({
MakeFrameDump(
mojom::TextDumpEvent::kFirstLayout, main_rfh()->GetGlobalId(),
/*amp_frame=*/false,
web_contents()->GetController().GetVisibleEntry()->GetUniqueID(),
u"abc"),
MakeFrameDump(
mojom::TextDumpEvent::kFinishedLoad, main_rfh()->GetGlobalId(),
/*amp_frame=*/false,
web_contents()->GetController().GetVisibleEntry()->GetUniqueID(),
u"xyz"),
}));
EXPECT_EQ(consumer1.result(), consumer2.result());
EXPECT_THAT(
fake_renderer_service.requests(),
::testing::UnorderedElementsAreArray({
mojom::PageTextDumpRequest(4U, mojom::TextDumpEvent::kFirstLayout),
mojom::PageTextDumpRequest(4U, mojom::TextDumpEvent::kFinishedLoad),
}));
}
TEST_F(PageTextObserverTest, AbandonedRequest) {
TestConsumer consumer1;
TestConsumer consumer2;
observer()->AddConsumer(&consumer1);
observer()->AddConsumer(&consumer2);
consumer1.PopulateRequest(/*max_size=*/4,
/*events=*/{mojom::TextDumpEvent::kFirstLayout});
consumer2.PopulateRequest(/*max_size=*/4,
/*events=*/{mojom::TextDumpEvent::kFinishedLoad});
FakePageTextService fake_renderer_service;
fake_renderer_service.SetRemoteResponsesForEvent(
mojom::TextDumpEvent::kFirstLayout, {
u"abc",
std::nullopt,
});
fake_renderer_service.SetEventToHangForver(
mojom::TextDumpEvent::kFinishedLoad);
blink::AssociatedInterfaceProvider* remote_interfaces =
main_rfh()->GetRemoteAssociatedInterfaces();
remote_interfaces->OverrideBinderForTesting(
mojom::PageTextService::Name_,
base::BindRepeating(&FakePageTextService::BindPendingReceiver,
base::Unretained(&fake_renderer_service)));
base::HistogramTester histogram_tester;
content::NavigationSimulator::NavigateAndCommitFromBrowser(
web_contents(), GURL("http://test.com"));
EXPECT_TRUE(consumer1.was_called());
EXPECT_TRUE(consumer2.was_called());
observer()->CallDidFinishLoad();
consumer1.WaitForPageText();
consumer2.WaitForPageText();
ASSERT_TRUE(consumer1.result());
EXPECT_THAT(
consumer1.result()->frame_results(),
::testing::UnorderedElementsAreArray({
MakeFrameDump(
mojom::TextDumpEvent::kFirstLayout, main_rfh()->GetGlobalId(),
/*amp_frame=*/false,
web_contents()->GetController().GetVisibleEntry()->GetUniqueID(),
u"abc"),
}));
EXPECT_EQ(consumer1.result(), consumer2.result());
EXPECT_THAT(
fake_renderer_service.requests(),
::testing::UnorderedElementsAreArray({
mojom::PageTextDumpRequest(4U, mojom::TextDumpEvent::kFirstLayout),
mojom::PageTextDumpRequest(4U, mojom::TextDumpEvent::kFinishedLoad),
}));
histogram_tester.ExpectUniqueSample(
"OptimizationGuide.PageTextDump.AbandonedRequests", 1, 1);
}
TEST_F(PageTextObserverTest, AMPRequestedOnOOPIF) {
TestConsumer consumer;
observer()->AddConsumer(&consumer);
consumer.PopulateRequest(
/*max_size=*/1024,
/*events=*/{mojom::TextDumpEvent::kFirstLayout},
/*request_amp=*/true);
FakePageTextService fake_renderer_service;
fake_renderer_service.SetRemoteResponsesForEvent(
mojom::TextDumpEvent::kFirstLayout, {
u"abc",
u"def",
std::nullopt,
});
blink::AssociatedInterfaceProvider* remote_interfaces =
main_rfh()->GetRemoteAssociatedInterfaces();
remote_interfaces->OverrideBinderForTesting(
mojom::PageTextService::Name_,
base::BindRepeating(&FakePageTextService::BindPendingReceiver,
base::Unretained(&fake_renderer_service)));
content::NavigationSimulator::NavigateAndCommitFromBrowser(
web_contents(), GURL("http://test.com"));
EXPECT_TRUE(consumer.was_called());
// Add an OOPIF subframe.
content::RenderFrameHost* oopif_subframe =
content::RenderFrameHostTester::For(main_rfh())->AppendChild("subframe");
observer()->SetIsOOPIF(oopif_subframe, true);
FakePageTextService subframe_fake_renderer_service;
blink::AssociatedInterfaceProvider* subframe_remote_interfaces =
oopif_subframe->GetRemoteAssociatedInterfaces();
subframe_remote_interfaces->OverrideBinderForTesting(
mojom::PageTextService::Name_,
base::BindRepeating(&FakePageTextService::BindPendingReceiver,
base::Unretained(&subframe_fake_renderer_service)));
subframe_fake_renderer_service.SetRemoteResponsesForEvent(
mojom::TextDumpEvent::kFinishedLoad, {
u"amp",
std::nullopt,
});
observer()->RenderFrameCreated(oopif_subframe);
observer()->CallDidFinishLoad();
consumer.WaitForPageText();
EXPECT_THAT(
fake_renderer_service.requests(),
::testing::UnorderedElementsAreArray({
mojom::PageTextDumpRequest(1024U, mojom::TextDumpEvent::kFirstLayout),
}));
EXPECT_THAT(subframe_fake_renderer_service.requests(),
::testing::UnorderedElementsAreArray({
mojom::PageTextDumpRequest(
1024U, mojom::TextDumpEvent::kFinishedLoad),
}));
ASSERT_TRUE(consumer.result());
EXPECT_THAT(
consumer.result()->frame_results(),
::testing::UnorderedElementsAreArray({
MakeFrameDump(
mojom::TextDumpEvent::kFinishedLoad,
oopif_subframe->GetGlobalId(),
/*amp_frame=*/true,
web_contents()->GetController().GetVisibleEntry()->GetUniqueID(),
u"amp"),
MakeFrameDump(
mojom::TextDumpEvent::kFirstLayout, main_rfh()->GetGlobalId(),
/*amp_frame=*/false,
web_contents()->GetController().GetVisibleEntry()->GetUniqueID(),
u"abcdef"),
}));
}
TEST_F(PageTextObserverTest, AMPNotRequestedOnOOPIF) {
TestConsumer consumer;
observer()->AddConsumer(&consumer);
consumer.PopulateRequest(
/*max_size=*/1024,
/*events=*/{mojom::TextDumpEvent::kFirstLayout},
/*request_amp=*/false);
FakePageTextService fake_renderer_service;
fake_renderer_service.SetRemoteResponsesForEvent(
mojom::TextDumpEvent::kFirstLayout, {
u"abc",
u"def",
std::nullopt,
});
blink::AssociatedInterfaceProvider* remote_interfaces =
main_rfh()->GetRemoteAssociatedInterfaces();
remote_interfaces->OverrideBinderForTesting(
mojom::PageTextService::Name_,
base::BindRepeating(&FakePageTextService::BindPendingReceiver,
base::Unretained(&fake_renderer_service)));
content::NavigationSimulator::NavigateAndCommitFromBrowser(
web_contents(), GURL("http://test.com"));
EXPECT_TRUE(consumer.was_called());
// Add an OOPIF subframe.
content::RenderFrameHost* oopif_subframe =
content::RenderFrameHostTester::For(main_rfh())->AppendChild("subframe");
observer()->SetIsOOPIF(oopif_subframe, true);
FakePageTextService subframe_fake_renderer_service;
blink::AssociatedInterfaceProvider* subframe_remote_interfaces =
oopif_subframe->GetRemoteAssociatedInterfaces();
subframe_remote_interfaces->OverrideBinderForTesting(
mojom::PageTextService::Name_,
base::BindRepeating(&FakePageTextService::BindPendingReceiver,
base::Unretained(&subframe_fake_renderer_service)));
subframe_fake_renderer_service.SetRemoteResponsesForEvent(
mojom::TextDumpEvent::kFinishedLoad, {
u"\n",
u"amp",
std::nullopt,
});
observer()->RenderFrameCreated(oopif_subframe);
observer()->CallDidFinishLoad();
base::RunLoop().RunUntilIdle();
EXPECT_THAT(
fake_renderer_service.requests(),
::testing::UnorderedElementsAreArray({
mojom::PageTextDumpRequest(1024U, mojom::TextDumpEvent::kFirstLayout),
}));
EXPECT_TRUE(subframe_fake_renderer_service.requests().empty());
ASSERT_TRUE(consumer.result());
EXPECT_THAT(
consumer.result()->frame_results(),
::testing::UnorderedElementsAreArray({
MakeFrameDump(
mojom::TextDumpEvent::kFirstLayout, main_rfh()->GetGlobalId(),
/*amp_frame=*/false,
web_contents()->GetController().GetVisibleEntry()->GetUniqueID(),
u"abcdef"),
}));
}
TEST_F(PageTextObserverTest, AMPRequestedOnNonOOPIF) {
TestConsumer consumer;
observer()->AddConsumer(&consumer);
consumer.PopulateRequest(
/*max_size=*/1024,
/*events=*/{mojom::TextDumpEvent::kFirstLayout},
/*request_amp=*/true);
FakePageTextService fake_renderer_service;
fake_renderer_service.SetRemoteResponsesForEvent(
mojom::TextDumpEvent::kFirstLayout, {
u"abc",
u"def",
std::nullopt,
});
blink::AssociatedInterfaceProvider* remote_interfaces =
main_rfh()->GetRemoteAssociatedInterfaces();
remote_interfaces->OverrideBinderForTesting(
mojom::PageTextService::Name_,
base::BindRepeating(&FakePageTextService::BindPendingReceiver,
base::Unretained(&fake_renderer_service)));
content::NavigationSimulator::NavigateAndCommitFromBrowser(
web_contents(), GURL("http://test.com"));
EXPECT_TRUE(consumer.was_called());
// Add a non-OOPIF subframe.
content::RenderFrameHost* subframe =
content::RenderFrameHostTester::For(main_rfh())->AppendChild("subframe");
observer()->SetIsOOPIF(subframe, false);
FakePageTextService subframe_fake_renderer_service;
blink::AssociatedInterfaceProvider* subframe_remote_interfaces =
subframe->GetRemoteAssociatedInterfaces();
subframe_remote_interfaces->OverrideBinderForTesting(
mojom::PageTextService::Name_,
base::BindRepeating(&FakePageTextService::BindPendingReceiver,
base::Unretained(&subframe_fake_renderer_service)));
subframe_fake_renderer_service.SetRemoteResponsesForEvent(
mojom::TextDumpEvent::kFinishedLoad, {
u"\n",
u"amp",
std::nullopt,
});
observer()->RenderFrameCreated(subframe);
observer()->CallDidFinishLoad();
base::RunLoop().RunUntilIdle();
EXPECT_THAT(
fake_renderer_service.requests(),
::testing::UnorderedElementsAreArray({
mojom::PageTextDumpRequest(1024U, mojom::TextDumpEvent::kFirstLayout),
}));
EXPECT_TRUE(subframe_fake_renderer_service.requests().empty());
ASSERT_TRUE(consumer.result());
EXPECT_THAT(
consumer.result()->frame_results(),
::testing::UnorderedElementsAreArray({
MakeFrameDump(
mojom::TextDumpEvent::kFirstLayout, main_rfh()->GetGlobalId(),
/*amp_frame=*/false,
web_contents()->GetController().GetVisibleEntry()->GetUniqueID(),
u"abcdef"),
}));
}
class PageTextObserverWithPrerenderTest : public PageTextObserverTest {
public:
PageTextObserverWithPrerenderTest() = default;
content::RenderFrameHost* AddPrerender(const GURL& prerender_url) {
content::RenderFrameHost* prerender_frame =
content::WebContentsTester::For(web_contents())
->AddPrerenderAndCommitNavigation(prerender_url);
DCHECK(prerender_frame);
EXPECT_EQ(prerender_frame->GetLifecycleState(),
content::RenderFrameHost::LifecycleState::kPrerendering);
EXPECT_EQ(prerender_frame->GetLastCommittedURL(), prerender_url);
return prerender_frame;
}
private:
content::test::ScopedPrerenderFeatureList prerender_feature_list_;
};
TEST_F(PageTextObserverWithPrerenderTest,
PrerenderingShouldNotResetOutstandingRequest) {
content::test::ScopedPrerenderWebContentsDelegate web_contents_delegate(
*web_contents());
TestConsumer consumer;
observer()->AddConsumer(&consumer);
EXPECT_FALSE(consumer.was_called());
consumer.PopulateRequest(
/*max_size=*/1024,
/*events=*/{mojom::TextDumpEvent::kFirstLayout},
/*request_amp=*/true);
EXPECT_EQ(observer()->outstanding_requests(), 0U);
NavigateAndCommit(GURL("http://www.test.com"));
EXPECT_EQ(observer()->outstanding_requests(), 1U);
consumer.Reset();
// Add a prerender page.
const GURL prerender_url = GURL("http://www.test.com");
content::RenderFrameHost* prerender_frame = AddPrerender(prerender_url);
EXPECT_FALSE(consumer.was_called());
// |outstanding_requests_| should not be reset to 0 by prerendering.
EXPECT_EQ(observer()->outstanding_requests(), 1U);
consumer.Reset();
// Activate the prerendered page.
content::NavigationSimulator::NavigateAndCommitFromDocument(
prerender_url, web_contents()->GetPrimaryMainFrame());
EXPECT_EQ(prerender_frame->GetLifecycleState(),
content::RenderFrameHost::LifecycleState::kActive);
EXPECT_TRUE(consumer.was_called());
// |outstanding_requests_| should be reset to 0 after activating.
EXPECT_EQ(observer()->outstanding_requests(), 0U);
}
TEST_F(PageTextObserverWithPrerenderTest, AMPRequestedOnOOPIFInPrerendering) {
content::test::ScopedPrerenderWebContentsDelegate web_contents_delegate(
*web_contents());
TestConsumer consumer;
observer()->AddConsumer(&consumer);
consumer.PopulateRequest(
/*max_size=*/1024,
/*events=*/{mojom::TextDumpEvent::kFirstLayout},
/*request_amp=*/true);
NavigateAndCommit(GURL("http://www.test.com"));
consumer.Reset();
// Add a prerender page.
const GURL prerender_url = GURL("http://www.test.com/?prerender");
content::RenderFrameHost* prerender_frame = AddPrerender(prerender_url);
FakePageTextService fake_renderer_service;
fake_renderer_service.SetRemoteResponsesForEvent(
mojom::TextDumpEvent::kFirstLayout, {
u"abc",
u"def",
std::nullopt,
});
blink::AssociatedInterfaceProvider* remote_interfaces =
prerender_frame->GetRemoteAssociatedInterfaces();
remote_interfaces->OverrideBinderForTesting(
mojom::PageTextService::Name_,
base::BindRepeating(&FakePageTextService::BindPendingReceiver,
base::Unretained(&fake_renderer_service)));
EXPECT_FALSE(consumer.was_called());
// Add an OOPIF subframe.
content::RenderFrameHost* oopif_subframe =
content::RenderFrameHostTester::For(prerender_frame)
->AppendChild("subframe");
observer()->SetIsOOPIF(oopif_subframe, true);
FakePageTextService subframe_fake_renderer_service;
blink::AssociatedInterfaceProvider* subframe_remote_interfaces =
oopif_subframe->GetRemoteAssociatedInterfaces();
subframe_remote_interfaces->OverrideBinderForTesting(
mojom::PageTextService::Name_,
base::BindRepeating(&FakePageTextService::BindPendingReceiver,
base::Unretained(&subframe_fake_renderer_service)));
subframe_fake_renderer_service.SetRemoteResponsesForEvent(
mojom::TextDumpEvent::kFinishedLoad, {
u"amp",
std::nullopt,
});
observer()->RenderFrameCreated(oopif_subframe);
observer()->CallDidFinishLoad();
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(consumer.was_called());
EXPECT_FALSE(consumer.result());
}
class PageTextObserverFencedFramesTest : public PageTextObserverTest {
public:
PageTextObserverFencedFramesTest() {
scoped_feature_list_.InitAndEnableFeatureWithParameters(
blink::features::kFencedFrames, {{"implementation_type", "mparch"}});
}
~PageTextObserverFencedFramesTest() override = default;
private:
base::test::ScopedFeatureList scoped_feature_list_;
};
TEST_F(PageTextObserverFencedFramesTest, AMPRequestedOnOOPIFInFencedFrame) {
TestConsumer consumer;
observer()->AddConsumer(&consumer);
consumer.PopulateRequest(
/*max_size=*/1024,
/*events=*/{mojom::TextDumpEvent::kFirstLayout},
/*request_amp=*/true);
FakePageTextService fake_renderer_service;
fake_renderer_service.SetRemoteResponsesForEvent(
mojom::TextDumpEvent::kFirstLayout, {
u"abc",
u"def",
std::nullopt,
});
blink::AssociatedInterfaceProvider* remote_interfaces =
main_rfh()->GetRemoteAssociatedInterfaces();
remote_interfaces->OverrideBinderForTesting(
mojom::PageTextService::Name_,
base::BindRepeating(&FakePageTextService::BindPendingReceiver,
base::Unretained(&fake_renderer_service)));
content::NavigationSimulator::NavigateAndCommitFromBrowser(
web_contents(), GURL("http://test.com"));
EXPECT_TRUE(consumer.was_called());
content::RenderFrameHost* fenced_frame_rfh =
content::RenderFrameHostTester::For(main_rfh())->AppendFencedFrame();
GURL kFencedFrameUrl("http://fencedframe.com");
std::unique_ptr<content::NavigationSimulator> navigation_simulator =
content::NavigationSimulator::CreateRendererInitiated(kFencedFrameUrl,
fenced_frame_rfh);
navigation_simulator->Commit();
fenced_frame_rfh = navigation_simulator->GetFinalRenderFrameHost();
// Add an OOPIF subframe.
content::RenderFrameHost* oopif_subframe =
content::RenderFrameHostTester::For(fenced_frame_rfh)
->AppendChild("subframe");
observer()->SetIsOOPIF(oopif_subframe, true);
FakePageTextService subframe_fake_renderer_service;
blink::AssociatedInterfaceProvider* subframe_remote_interfaces =
oopif_subframe->GetRemoteAssociatedInterfaces();
subframe_remote_interfaces->OverrideBinderForTesting(
mojom::PageTextService::Name_,
base::BindRepeating(&FakePageTextService::BindPendingReceiver,
base::Unretained(&subframe_fake_renderer_service)));
subframe_fake_renderer_service.SetRemoteResponsesForEvent(
mojom::TextDumpEvent::kFinishedLoad, {
u"amp",
std::nullopt,
});
observer()->RenderFrameCreated(oopif_subframe);
observer()->CallDidFinishLoad();
consumer.WaitForPageText();
EXPECT_THAT(
fake_renderer_service.requests(),
::testing::UnorderedElementsAreArray({
mojom::PageTextDumpRequest(1024U, mojom::TextDumpEvent::kFirstLayout),
}));
EXPECT_TRUE(subframe_fake_renderer_service.requests().empty());
ASSERT_TRUE(consumer.result());
EXPECT_THAT(
consumer.result()->frame_results(),
::testing::UnorderedElementsAreArray({
MakeFrameDump(
mojom::TextDumpEvent::kFirstLayout, main_rfh()->GetGlobalId(),
/*amp_frame=*/false,
web_contents()->GetController().GetVisibleEntry()->GetUniqueID(),
u"abcdef"),
}));
}
} // namespace optimization_guide