prerender: Resending prerender activation for DevTools.
This CL stores prerender activation in WebContents and resend it
once DevTools is reopened as a workaround to the current unsupported
prerender activation in DevTools.
Design doc: https://docs.google.com/document/d/1l5S3DCMVsFfF_Thg95BT9292sDxas1wzKALz8J9FrB8/edit?usp=sharing
Change-Id: Ib2997325e73a7fbf9a0bfde0228006624d931c22
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3805129
Reviewed-by: Alex Moshchuk <alexmos@chromium.org>
Reviewed-by: Andrey Kosyakov <caseq@chromium.org>
Commit-Queue: Huanpo Lin <robertlin@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1041841}
diff --git a/content/browser/devtools/devtools_instrumentation.cc b/content/browser/devtools/devtools_instrumentation.cc
index 19cc663..919c3245 100644
--- a/content/browser/devtools/devtools_instrumentation.cc
+++ b/content/browser/devtools/devtools_instrumentation.cc
@@ -318,6 +318,11 @@
void DidActivatePrerender(const NavigationRequest& nav_request) {
FrameTreeNode* ftn = nav_request.frame_tree_node();
+ WebContentsImpl* web_contents = WebContentsImpl::FromFrameTreeNode(ftn);
+ // Record prerender activation here because users don't necessarily open
+ // DevTools when the activation is triggered. If the DevTools is not opened at
+ // the moment, recording the activation here will still preserve the signal.
+ web_contents->set_last_navigation_was_prerender_activation_for_devtools();
DispatchToAgents(ftn, &protocol::PageHandler::DidActivatePrerender,
nav_request);
}
diff --git a/content/browser/devtools/protocol/devtools_protocol_browsertest.cc b/content/browser/devtools/protocol/devtools_protocol_browsertest.cc
index acf3ede..34bf2fe 100644
--- a/content/browser/devtools/protocol/devtools_protocol_browsertest.cc
+++ b/content/browser/devtools/protocol/devtools_protocol_browsertest.cc
@@ -223,9 +223,18 @@
prerender_helper_->GetPrerenderedMainFrameHost(host_id));
}
- private:
+ void NavigatePrimaryPage(const GURL& url) {
+ prerender_helper_->NavigatePrimaryPage(url);
+ }
+
+ // WebContentsDelegate overrides.
+ bool IsPrerender2Supported(WebContents& web_contents) override {
+ return true;
+ }
+
WebContents* web_contents() const { return shell()->web_contents(); }
+ private:
std::unique_ptr<test::PrerenderTestHelper> prerender_helper_;
};
@@ -3387,4 +3396,63 @@
PrerenderHost::FinalStatus::kMojoBinderPolicy, 1);
}
+IN_PROC_BROWSER_TEST_F(PrerenderDevToolsProtocolTest,
+ RemoveStoredPrerenderActivationIfNavigateAway) {
+ base::HistogramTester histogram_tester;
+ ASSERT_TRUE(embedded_test_server()->Start());
+ const GURL kInitialUrl = GetUrl("/empty.html");
+ const GURL kPrerenderingUrl = GetUrl("/empty.html?prerender");
+ WebContentsImpl* web_contents_impl =
+ static_cast<WebContentsImpl*>(web_contents());
+
+ // Navigate to an initial page.
+ ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
+
+ // Make a prerendered page.
+ AddPrerender(kPrerenderingUrl);
+
+ Attach();
+ SendCommandSync("Page.enable");
+ SendCommandSync("Runtime.enable");
+ NavigatePrimaryPage(kPrerenderingUrl);
+
+ WaitForNotification("Page.prerenderAttemptCompleted", true);
+
+ // Navigate away from the prerendered page, and this should trigger the
+ // mechanism of removing the stored prerender activation.
+ ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
+ ASSERT_FALSE(web_contents_impl
+ ->last_navigation_was_prerender_activation_for_devtools());
+}
+
+IN_PROC_BROWSER_TEST_F(PrerenderDevToolsProtocolTest,
+ NewPrerenderActivationOverrideTheOldOne) {
+ base::HistogramTester histogram_tester;
+ ASSERT_TRUE(embedded_test_server()->Start());
+ const GURL kInitialUrl = GetUrl("/empty.html");
+ const GURL kPrerenderingUrl = GetUrl("/empty.html?prerender");
+ const GURL kPrerenderingUrl2 = GetUrl("/title1.html?prerender2");
+ WebContentsImpl* web_contents_impl =
+ static_cast<WebContentsImpl*>(web_contents());
+
+ // Navigate to an initial page.
+ ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
+
+ // Make a prerendered page.
+ AddPrerender(kPrerenderingUrl);
+
+ Attach();
+ SendCommandSync("Page.enable");
+ SendCommandSync("Runtime.enable");
+ NavigatePrimaryPage(kPrerenderingUrl);
+
+ WaitForNotification("Page.prerenderAttemptCompleted", true);
+
+ // Trigger another prerender activation.
+ AddPrerender(kPrerenderingUrl2);
+ NavigatePrimaryPage(kPrerenderingUrl2);
+ ASSERT_TRUE(web_contents_impl
+ ->last_navigation_was_prerender_activation_for_devtools());
+}
+
} // namespace content
diff --git a/content/browser/devtools/protocol/page_handler.cc b/content/browser/devtools/protocol/page_handler.cc
index c5017a4..090ab72 100644
--- a/content/browser/devtools/protocol/page_handler.cc
+++ b/content/browser/devtools/protocol/page_handler.cc
@@ -346,6 +346,7 @@
Response PageHandler::Enable() {
enabled_ = true;
+ RetrievePrerenderActivationFromWebContents();
return Response::FallThrough();
}
@@ -1939,6 +1940,7 @@
}
void PageHandler::DidActivatePrerender(const NavigationRequest& nav_request) {
+ has_dispatched_stored_prerender_activation_ = false;
if (!enabled_)
return;
FrameTreeNode* ftn = nav_request.frame_tree_node();
@@ -1953,6 +1955,7 @@
const std::string& initiating_frame_id,
PrerenderHost::FinalStatus status,
const std::string& reason_details) {
+ has_dispatched_stored_prerender_activation_ = false;
if (!enabled_)
return;
DCHECK_NE(status, PrerenderHost::FinalStatus::kActivated);
@@ -1968,5 +1971,21 @@
return enabled_ && bypass_csp_;
}
+void PageHandler::RetrievePrerenderActivationFromWebContents() {
+ if (!host_)
+ return;
+ WebContentsImpl* web_contents =
+ WebContentsImpl::FromRenderFrameHostImpl(host_);
+ if (web_contents->last_navigation_was_prerender_activation_for_devtools() &&
+ !has_dispatched_stored_prerender_activation_) {
+ std::string frame_token =
+ host_->frame_tree_node()->devtools_frame_token().ToString();
+ has_dispatched_stored_prerender_activation_ = true;
+ frontend_->PrerenderAttemptCompleted(
+ frame_token, host_->GetLastCommittedURL().spec(),
+ Page::PrerenderFinalStatusEnum::Activated);
+ }
+}
+
} // namespace protocol
} // namespace content
diff --git a/content/browser/devtools/protocol/page_handler.h b/content/browser/devtools/protocol/page_handler.h
index 2a263de..3e38acc 100644
--- a/content/browser/devtools/protocol/page_handler.h
+++ b/content/browser/devtools/protocol/page_handler.h
@@ -223,6 +223,8 @@
using ResponseOrWebContents = absl::variant<Response, WebContentsImpl*>;
ResponseOrWebContents GetWebContentsForTopLevelActiveFrame();
+ void RetrievePrerenderActivationFromWebContents();
+
const bool allow_unsafe_operations_;
const bool is_trusted_;
const absl::optional<url::Origin> navigation_initiator_origin_;
@@ -241,6 +243,10 @@
int frame_counter_;
int frames_in_flight_;
+ // Whether stored prerender activation has been dispatched to Devtools. Reset
+ // whenever a new prerender event received.
+ bool has_dispatched_stored_prerender_activation_ = false;
+
// |video_consumer_| consumes video frames from FrameSinkVideoCapturerImpl,
// and provides PageHandler with these frames via OnFrameFromVideoConsumer.
// This is only used if Viz is enabled and if OS is not Android.
diff --git a/content/browser/web_contents/web_contents_impl.cc b/content/browser/web_contents/web_contents_impl.cc
index 29e19a8..8f0afba 100644
--- a/content/browser/web_contents/web_contents_impl.cc
+++ b/content/browser/web_contents/web_contents_impl.cc
@@ -5717,6 +5717,15 @@
was_ever_audible_ = false;
}
+ // Clear the stored prerender activation result if this is not a prerender
+ // activation. If this is another prerender activation, it will override
+ // the old result in DevTools.
+ if (!navigation_handle->IsPrerenderedPageActivation() &&
+ !navigation_handle->IsSameDocument() &&
+ navigation_handle->IsInPrimaryMainFrame()) {
+ last_navigation_was_prerender_activation_for_devtools_ = false;
+ }
+
if (!navigation_handle->IsSameDocument())
last_screen_orientation_change_time_ = base::TimeTicks();
}
diff --git a/content/browser/web_contents/web_contents_impl.h b/content/browser/web_contents/web_contents_impl.h
index 335ed04c..345eba7 100644
--- a/content/browser/web_contents/web_contents_impl.h
+++ b/content/browser/web_contents/web_contents_impl.h
@@ -1344,6 +1344,16 @@
return mouse_lock_widget_;
}
+ // Record a prerender activation for DevTools.
+ void set_last_navigation_was_prerender_activation_for_devtools() {
+ last_navigation_was_prerender_activation_for_devtools_ = true;
+ }
+
+ // Check if prerender was just activated.
+ bool last_navigation_was_prerender_activation_for_devtools() {
+ return last_navigation_was_prerender_activation_for_devtools_;
+ }
+
private:
using FrameTreeIterationCallback = base::RepeatingCallback<void(FrameTree*)>;
using RenderViewHostIterationCallback =
@@ -2349,6 +2359,12 @@
VisibleTimeRequestTrigger visible_time_request_trigger_;
+ // Stores the information whether last navigation was prerender activation for
+ // DevTools. Set when a prerender activation completes, and cleared when
+ // either DevTools is opened and consults this value or when a non-prerendered
+ // navigation commits in the primary main frame.
+ bool last_navigation_was_prerender_activation_for_devtools_ = false;
+
bool prerender2_disabled_ = false;
base::WeakPtrFactory<WebContentsImpl> loading_weak_factory_{this};
diff --git a/third_party/blink/web_tests/http/tests/inspector-protocol/prerender/report-prerender-resent-activation-expected.txt b/third_party/blink/web_tests/http/tests/inspector-protocol/prerender/report-prerender-resent-activation-expected.txt
new file mode 100644
index 0000000..549faa6
--- /dev/null
+++ b/third_party/blink/web_tests/http/tests/inspector-protocol/prerender/report-prerender-resent-activation-expected.txt
@@ -0,0 +1,20 @@
+Test that prerender navigations report the resent activation.
+{
+ method : Page.prerenderAttemptCompleted
+ params : {
+ finalStatus : Activated
+ initiatingFrameId : <string>
+ prerenderingUrl : http://127.0.0.1:8000/inspector-protocol/prerender/resources/empty.html
+ }
+ sessionId : <string>
+}
+{
+ method : Page.prerenderAttemptCompleted
+ params : {
+ finalStatus : Activated
+ initiatingFrameId : <string>
+ prerenderingUrl : http://127.0.0.1:8000/inspector-protocol/prerender/resources/empty.html
+ }
+ sessionId : <string>
+}
+
diff --git a/third_party/blink/web_tests/http/tests/inspector-protocol/prerender/report-prerender-resent-activation.js b/third_party/blink/web_tests/http/tests/inspector-protocol/prerender/report-prerender-resent-activation.js
new file mode 100644
index 0000000..187596d
--- /dev/null
+++ b/third_party/blink/web_tests/http/tests/inspector-protocol/prerender/report-prerender-resent-activation.js
@@ -0,0 +1,17 @@
+(async function(testRunner) {
+ const {page, session, dp} = await testRunner.startBlank(
+ `Test that prerender navigations report the resent activation.`);
+ await dp.Page.enable();
+
+ // Navigate to speculation rules Prerender Page.
+ await page.navigate('resources/simple-prerender.html');
+ session.evaluate(`document.getElementById('link').click()`);
+ const statusReport = await dp.Page.oncePrerenderAttemptCompleted();
+ testRunner.log(statusReport, '', ['initiatingFrameId', 'sessionId']);
+ await dp.Page.disable();
+ dp.Page.enable();
+ const resentStatusReport = await dp.Page.oncePrerenderAttemptCompleted();
+ testRunner.log(resentStatusReport, '', ['initiatingFrameId', 'sessionId']);
+
+ testRunner.completeTest();
+});