Skip to content

Commit

Permalink
PreloadingDevTools: Disable prerendering for clients other than DevTo…
Browse files Browse the repository at this point in the history
…ols frontend

Prerendering introduced switching of FrameTreeNodes in the browser side
and frame targets in the CDP client side. If a CDP client unaware of
this logic and a prerendered page activated, it will cause unexpected
behaviors. For example, Puppeteer loses control. To mitigate this, this
CL disables prerendering if a target without tab target support is
attached. For more details, see [1].

[1] https://docs.google.com/document/d/12HVmFxYj5Jc-eJr5OmWsa2bqTJsbgGLKI6ZIyx0_wpA

Bug: 1440085
Change-Id: I80bf9234b60722112baf21c4fa2ee1f0d0cb3221
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4495911
Reviewed-by: Danil Somsikov <dsv@chromium.org>
Commit-Queue: Ken Okada <kenoss@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1158723}
  • Loading branch information
kenoss authored and Chromium LUCI CQ committed Jun 16, 2023
1 parent f3cd15d commit 9d392f3
Show file tree
Hide file tree
Showing 6 changed files with 181 additions and 42 deletions.
13 changes: 11 additions & 2 deletions content/browser/devtools/devtools_instrumentation.cc
Expand Up @@ -503,9 +503,18 @@ void OnFrameTreeNodeDestroyed(FrameTreeNode& frame_tree_node) {
}

bool IsPrerenderAllowed(FrameTree& frame_tree) {
FrameTreeNode* ftn = frame_tree.root();

auto* render_frame_agent_host = static_cast<RenderFrameDevToolsAgentHost*>(
RenderFrameDevToolsAgentHost::GetFor(ftn));
if (render_frame_agent_host &&
render_frame_agent_host->HasSessionsWithoutTabTargetSupport()) {
return false;
}

bool is_allowed = true;
DispatchToAgents(frame_tree.root(),
&protocol::PageHandler::IsPrerenderingAllowed, is_allowed);
DispatchToAgents(ftn, &protocol::PageHandler::IsPrerenderingAllowed,
is_allowed);
return is_allowed;
}

Expand Down
92 changes: 86 additions & 6 deletions content/browser/devtools/protocol/devtools_protocol_browsertest.cc
Expand Up @@ -247,6 +247,41 @@ class PrerenderDevToolsProtocolTest : public DevToolsProtocolTest {

WebContents* web_contents() const { return shell()->web_contents(); }

std::string AttachToTabTargetAndGetSessionId() {
AttachToTabTarget(shell()->web_contents());
shell()->web_contents()->SetDelegate(this);

{
base::Value::Dict params;
params.Set("discover", true);
SendCommandSync("Target.setDiscoverTargets", std::move(params));
}

std::string frame_target_id;
for (int targetCount = 1; true; targetCount++) {
base::Value::Dict result;
result = WaitForNotification("Target.targetCreated", true);
if (*result.FindStringByDottedPath("targetInfo.type") == "page") {
frame_target_id =
std::string(*result.FindStringByDottedPath("targetInfo.targetId"));
break;
}
CHECK_LT(targetCount, 2);
}

{
base::Value::Dict params;
params.Set("targetId", frame_target_id);
params.Set("flatten", true);
const base::Value::Dict* result =
SendCommandSync("Target.attachToTarget", std::move(params));
CHECK(result);
std::string session_id(*result->FindString("sessionId"));
CHECK(session_id != "");
return session_id;
}
}

private:
std::unique_ptr<test::PrerenderTestHelper> prerender_helper_;
};
Expand Down Expand Up @@ -2645,6 +2680,41 @@ class DevToolsProtocolDeviceEmulationPrerenderTest

WebContents* GetWebContents() const { return shell()->web_contents(); }

std::string AttachToTabTargetAndGetSessionId() {
AttachToTabTarget(shell()->web_contents());
shell()->web_contents()->SetDelegate(this);

{
base::Value::Dict params;
params.Set("discover", true);
SendCommandSync("Target.setDiscoverTargets", std::move(params));
}

std::string frame_target_id;
for (int targetCount = 1; true; targetCount++) {
base::Value::Dict result;
result = WaitForNotification("Target.targetCreated", true);
if (*result.FindStringByDottedPath("targetInfo.type") == "page") {
frame_target_id =
std::string(*result.FindStringByDottedPath("targetInfo.targetId"));
break;
}
CHECK_LT(targetCount, 2);
}

{
base::Value::Dict params;
params.Set("targetId", frame_target_id);
params.Set("flatten", true);
const base::Value::Dict* result =
SendCommandSync("Target.attachToTarget", std::move(params));
CHECK(result);
std::string session_id(*result->FindString("sessionId"));
CHECK(session_id != "");
return session_id;
}
}

protected:
test::PrerenderTestHelper prerender_helper_;
};
Expand All @@ -2662,13 +2732,22 @@ IN_PROC_BROWSER_TEST_F(DevToolsProtocolDeviceEmulationPrerenderTest,

GURL test_url = embedded_test_server()->GetURL("/devtools/navigation.html");
NavigateToURLBlockUntilNavigationsComplete(shell(), test_url, 1);
Attach();
std::string session_id = AttachToTabTargetAndGetSessionId();

const gfx::Size original_size = GetViewSize();
const gfx::Size emulated_size =
gfx::Size(original_size.width() - 50, original_size.height() - 50);

EmulateDeviceSize(emulated_size);
{
const gfx::Size size = emulated_size;
base::Value::Dict params;
params.Set("width", size.width());
params.Set("height", size.height());
params.Set("deviceScaleFactor", 0);
params.Set("mobile", false);
SendSessionCommand("Emulation.setDeviceMetricsOverride", std::move(params),
session_id, true);
}
EXPECT_EQ(emulated_size, GetViewSize());

// Start a prerender and ensure frame size isn't changed.
Expand All @@ -2681,7 +2760,8 @@ IN_PROC_BROWSER_TEST_F(DevToolsProtocolDeviceEmulationPrerenderTest,
prerender_helper_.NavigatePrimaryPage(prerender_url);
EXPECT_EQ(emulated_size, GetViewSize());

SendCommandSync("Emulation.clearDeviceMetricsOverride");
SendSessionCommand("Emulation.clearDeviceMetricsOverride",
base::Value::Dict(), session_id, true);
EXPECT_EQ(original_size, GetViewSize());
}

Expand Down Expand Up @@ -3941,9 +4021,9 @@ IN_PROC_BROWSER_TEST_F(PrerenderHoldbackDevToolsProtocolTest,
// Navigate to an initial page.
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));

Attach();
SendCommandSync("Preload.enable");
SendCommandSync("Runtime.enable");
std::string session_id = AttachToTabTargetAndGetSessionId();
SendSessionCommand("Preload.enable", base::Value::Dict(), session_id, true);
SendSessionCommand("Runtime.enable", base::Value::Dict(), session_id, true);

AddPrerender(kPrerenderingUrl);

Expand Down
@@ -0,0 +1,15 @@
Test that prerender fails if a frame target without tab target is attached.
{
method : Preload.prerenderStatusUpdated
params : {
key : {
action : Prerender
loaderId : <string>
url : http://127.0.0.1:8000/inspector-protocol/prerender/resources/empty.html
}
prerenderStatus : PrerenderingDisabledByDevTools
status : Failure
}
sessionId : <string>
}

@@ -0,0 +1,23 @@
(async function(testRunner) {
await testRunner.log('Test that prerender fails if a frame target without tab target is attached.');

const targetId = (await testRunner.browserP().Target.createTarget({
url: 'about:blank',
forTab: false,
})).result.targetId;
const sessionId = (await testRunner.browserP().Target.attachToTarget({
targetId,
flatten: true
})).result.sessionId;
const session = testRunner.browserSession().createChild(sessionId);
const dp = session.protocol;

await dp.Page.enable();
await dp.Preload.enable();

session.navigate('resources/simple-prerender.html');

testRunner.log(await dp.Preload.oncePrerenderStatusUpdated());

testRunner.completeTest();
});
@@ -1,22 +1,33 @@
(async function(testRunner) {
const {dp, session} = await testRunner.startBlank(
`Tests that prerender targets get auto-attached properly.`);
testRunner.log(`Tests that prerender targets get auto-attached properly.`);

const pageUrl = 'http://devtools.oopif-a.test:8000/inspector-protocol/prerender/resources/simple-prerender.html';

const bp = testRunner.browserP();
await bp.Target.setAutoAttach({autoAttach: true, waitForDebuggerOnStart: true, flatten: true});

const tabs = (await bp.Target.getTargets({filter: [{type: "tab"}]})).result.targetInfos;
const params = {url: 'about:blank', forTab: true};
const tabTargetId = (await bp.Target.createTarget(params)).result.targetId;
const tabTargetSessionId =
(await bp.Target.attachToTarget({targetId: tabTargetId, flatten: true}))
.result.sessionId;
const tabTargetSession =
new TestRunner.Session(testRunner, tabTargetSessionId);
const tp = tabTargetSession.protocol;

const tabUnderTest = tabs.filter(target => target.url.endsWith('/inspector-protocol-page.html'))[0];
const tp = (await testRunner.browserSession().attachChild(tabUnderTest.targetId)).protocol;
const events = [];
tp.Target.onAttachedToTarget(event => {
events.push(event);
});
await tp.Target.setAutoAttach(
{autoAttach: true, waitForDebuggerOnStart: true, flatten: true});

const autoAttachDone = tp.Target.setAutoAttach({autoAttach: true, waitForDebuggerOnStart: true, flatten: true});
const targets = [];
tp.Target.onAttachedToTarget(event => { targets.push(event.params.targetInfo); });
await autoAttachDone;
const session = tabTargetSession.createChild(events[0].params.sessionId);

session.protocol.Page.enable();
session.protocol.Preload.enable();

const prerenderReady = session.protocol.Preload.oncePrerenderStatusUpdated(
e => e.params.status == 'Ready');
const navigateDone = session.navigate(pageUrl);

const attached = (await tp.Target.onceAttachedToTarget()).params;
Expand All @@ -31,13 +42,17 @@
testRunner.log(`${navigated.type}: ${navigated.frame.url}`);

await navigateDone;
await prerenderReady;

tp.Target.onTargetInfoChanged(event => testRunner.log(event.params));

// Now activate prerender and make sure old target detaches.
session.evaluate(`document.getElementById('link').click()`);
const detached = (await tp.Target.onceDetachedFromTarget()).params;
testRunner.log(`Detached from ${targets[0].targetId === detached.targetId ? 'correct' : 'incorrect'} target`);
testRunner.log(`Detached from ${
events[0].params.targetInfo.targetId === detached.targetId ?
'correct' :
'incorrect'} target`);

const responseReceived = prerenderSession.protocol.Network.onceResponseReceived();

Expand Down
@@ -1,37 +1,34 @@
(async function(testRunner) {
const pageUrl =
'http://127.0.0.1:8000/inspector-protocol/prerender/resources/inspector-protocol-page.html';
const {page, session, dp} = await testRunner.startURL(
pageUrl, 'Test that prerender page is included in the trace events');

const bp = testRunner.browserP();
const tabs = (await bp.Target.getTargets({
filter: [{type: 'tab'}]
})).result.targetInfos;
const tabUnderTest = tabs.find(target => target.url === pageUrl);

const tabSessionId = (await bp.Target.attachToTarget({
targetId: tabUnderTest.targetId,
flatten: true
})).result.sessionId;
const tabSession = testRunner.browserSession().createChild(tabSessionId);
const tp = tabSession.protocol;
const {tabTargetSession} = await testRunner.startBlankWithTabTarget(
'Test that prerender page is included in the trace events');

const tp = tabTargetSession.protocol;

const TracingHelper =
await testRunner.loadScript('../resources/tracing-test.js');
const tracingHelper = new TracingHelper(testRunner, tabSession);
const tracingHelper = new TracingHelper(testRunner, tabTargetSession);

const childTargetManager =
new TestRunner.ChildTargetManager(testRunner, tabTargetSession);
await childTargetManager.startAutoAttach();
const primarySession =
childTargetManager.findAttachedSessionPrimaryMainFrame();
const dp = primarySession.protocol;

primarySession.navigate(pageUrl);

await dp.Preload.enable();
await tracingHelper.startTracing();

const prerenderReadyPromise = dp.Preload.oncePrerenderStatusUpdated(e => e.params.status == 'Ready');
tp.Target.setAutoAttach({autoAttach: true, flatten: true, waitForDebuggerOnStart: false});
page.navigate('../prerender/resources/simple-prerender.html');
const prerenderSessionId = (await tp.Target.onceAttachedToTarget(
e => e.params.targetInfo.subtype === 'prerender')).params.sessionId;
const pp = session.createChild(prerenderSessionId).protocol;
await Promise.all([pp.Preload.enable(), prerenderReadyPromise]);
session.evaluate(`document.getElementById('link').click()`);
await primarySession.navigate('../prerender/resources/simple-prerender.html');
const prerenderSession = childTargetManager.findAttachedSessionPrerender();
const pp = prerenderSession.protocol;
await pp.Preload.enable();
await prerenderReadyPromise;
primarySession.evaluate(`document.getElementById('link').click()`);

await pp.Preload.oncePrerenderAttemptCompleted();

Expand Down

0 comments on commit 9d392f3

Please sign in to comment.