Commit eec217fc authored by Hiroki Nakagawa's avatar Hiroki Nakagawa Committed by Chromium LUCI CQ

Prerender: Disallow activation of prerendered pages for pop-up windows

This CL disallows activation of prerendered pages for pop-up windows.
The previous CL (https://crrev.com/c/2494291) already did the same thing
for iframes. In addition, this adds tests for both iframes and pop-up
windows.

We might also need to disallow both the contexts to trigger navigation
for prerendering (trigger <link rel=prerender>), but it is out of the
scope of this CL.

Change-Id: I741b07983e2e8d9f9f3af88ca618129c5d52674f
Bug: 1138711, 1138723
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2560362Reviewed-by: default avatarMatt Falkenhagen <falken@chromium.org>
Reviewed-by: default avatarKinuko Yasuda <kinuko@chromium.org>
Reviewed-by: default avatarAlexander Timin <altimin@chromium.org>
Commit-Queue: Hiroki Nakagawa <nhiroki@chromium.org>
Cr-Commit-Position: refs/heads/master@{#835109}
parent 73fa6f61
...@@ -116,7 +116,9 @@ class PrerenderBrowserTest : public ContentBrowserTest, ...@@ -116,7 +116,9 @@ class PrerenderBrowserTest : public ContentBrowserTest,
base::test::ScopedFeatureList feature_list_; base::test::ScopedFeatureList feature_list_;
}; };
INSTANTIATE_TEST_SUITE_P(All, PrerenderBrowserTest, testing::Bool()); INSTANTIATE_TEST_SUITE_P(All,
PrerenderBrowserTest,
/*disable_activation=*/testing::Bool());
IN_PROC_BROWSER_TEST_P(PrerenderBrowserTest, LinkRelPrerender) { IN_PROC_BROWSER_TEST_P(PrerenderBrowserTest, LinkRelPrerender) {
const GURL kInitialUrl = GetUrl("/prerender/add_prerender.html"); const GURL kInitialUrl = GetUrl("/prerender/add_prerender.html");
...@@ -240,6 +242,68 @@ IN_PROC_BROWSER_TEST_P(PrerenderBrowserTest, LinkRelPrerender_Duplicate) { ...@@ -240,6 +242,68 @@ IN_PROC_BROWSER_TEST_P(PrerenderBrowserTest, LinkRelPrerender_Duplicate) {
} }
} }
// Makes sure that activations on navigations for iframes don't happen.
IN_PROC_BROWSER_TEST_P(PrerenderBrowserTest, Activation_iFrame) {
const GURL kInitialUrl = GetUrl("/prerender/add_prerender.html");
const GURL kPrerenderingUrl = GetUrl("/empty.html");
// Navigate to an initial page.
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
ASSERT_EQ(shell()->web_contents()->GetURL(), kInitialUrl);
// Add <link rel=prerender> that will prerender `kPrerenderingUrl`.
ASSERT_EQ(GetRequestCount(kPrerenderingUrl), 0);
AddPrerender(kPrerenderingUrl);
EXPECT_EQ(GetRequestCount(kPrerenderingUrl), 1);
// A prerender host for the URL should be registered.
PrerenderHostRegistry& registry = GetPrerenderHostRegistry();
PrerenderHost* prerender_host =
registry.FindHostByUrlForTesting(kPrerenderingUrl);
EXPECT_TRUE(prerender_host);
// Attempt to activate the prerendered page for an iframe. This should fail
// and fallback to network request.
EXPECT_EQ("LOADED", EvalJs(shell()->web_contents(),
JsReplace("add_iframe($1)", kPrerenderingUrl)));
// Activation shouldn't happen, so the prerender host should not be consumed,
// and navigation for the iframe should issue a request again.
EXPECT_EQ(registry.FindHostByUrlForTesting(kPrerenderingUrl), prerender_host);
EXPECT_EQ(GetRequestCount(kPrerenderingUrl), 2);
}
// Makes sure that activations on navigations for pop-up windows don't happen.
IN_PROC_BROWSER_TEST_P(PrerenderBrowserTest, Activation_PopUpWindow) {
const GURL kInitialUrl = GetUrl("/prerender/add_prerender.html");
const GURL kPrerenderingUrl = GetUrl("/empty.html");
// Navigate to an initial page.
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
ASSERT_EQ(shell()->web_contents()->GetURL(), kInitialUrl);
// Add <link rel=prerender> that will prerender `kPrerenderingUrl`.
ASSERT_EQ(GetRequestCount(kPrerenderingUrl), 0);
AddPrerender(kPrerenderingUrl);
EXPECT_EQ(GetRequestCount(kPrerenderingUrl), 1);
// A prerender host for the URL should be registered.
PrerenderHostRegistry& registry = GetPrerenderHostRegistry();
PrerenderHost* prerender_host =
registry.FindHostByUrlForTesting(kPrerenderingUrl);
EXPECT_TRUE(prerender_host);
// Attempt to activate the prerendered page for a pop-up window. This should
// fail and fallback to network request.
EXPECT_EQ("LOADED", EvalJs(shell()->web_contents(),
JsReplace("open_window($1)", kPrerenderingUrl)));
// Activation shouldn't happen, so the prerender host should not be consumed,
// and navigation for the pop-up window should issue a request again.
EXPECT_EQ(registry.FindHostByUrlForTesting(kPrerenderingUrl), prerender_host);
EXPECT_EQ(GetRequestCount(kPrerenderingUrl), 2);
}
// TODO(https://crbug.com/1132746): Test canceling prerendering. // TODO(https://crbug.com/1132746): Test canceling prerendering.
// TODO(https://crbug.com/1132746): Test prerendering for 404 page, redirection, // TODO(https://crbug.com/1132746): Test prerendering for 404 page, redirection,
......
...@@ -8,6 +8,8 @@ ...@@ -8,6 +8,8 @@
#include "base/feature_list.h" #include "base/feature_list.h"
#include "base/stl_util.h" #include "base/stl_util.h"
#include "content/browser/prerender/prerender_host.h" #include "content/browser/prerender/prerender_host.h"
#include "content/browser/renderer_host/frame_tree_node.h"
#include "content/browser/renderer_host/render_frame_host_impl.h"
#include "third_party/blink/public/common/features.h" #include "third_party/blink/public/common/features.h"
namespace content { namespace content {
...@@ -49,8 +51,27 @@ void PrerenderHostRegistry::AbandonHost(const GURL& prerendering_url) { ...@@ -49,8 +51,27 @@ void PrerenderHostRegistry::AbandonHost(const GURL& prerendering_url) {
} }
std::unique_ptr<PrerenderHost> PrerenderHostRegistry::SelectForNavigation( std::unique_ptr<PrerenderHost> PrerenderHostRegistry::SelectForNavigation(
const GURL& url) { const GURL& navigation_url,
auto found = prerender_host_by_url_.find(url); FrameTreeNode& frame_tree_node) {
RenderFrameHostImpl* render_frame_host = frame_tree_node.current_frame_host();
// Disallow activation when the render frame host is for a nested browsing
// context (e.g., iframes). This is because nested browsing contexts are
// supposed to be created in the parent's browsing context group and can
// script with the parent, but prerendered pages are created in new browsing
// context groups.
if (render_frame_host->GetParent())
return nullptr;
// Disallow activation when other auxiliary browsing contexts (e.g., pop-up
// windows) exist in the same browsing context group. This is because these
// browsing contexts should be able to script each other, but prerendered
// pages are created in new browsing context groups.
SiteInstance* site_instance = render_frame_host->GetSiteInstance();
if (site_instance->GetRelatedActiveContentsCount() != 1u)
return nullptr;
auto found = prerender_host_by_url_.find(navigation_url);
if (found == prerender_host_by_url_.end()) if (found == prerender_host_by_url_.end())
return nullptr; return nullptr;
......
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
namespace content { namespace content {
class FrameTreeNode;
class PrerenderHost; class PrerenderHost;
// Prerender2: // Prerender2:
...@@ -44,9 +45,11 @@ class CONTENT_EXPORT PrerenderHostRegistry { ...@@ -44,9 +45,11 @@ class CONTENT_EXPORT PrerenderHostRegistry {
// Destroys the host registered for `prerendering_url`. // Destroys the host registered for `prerendering_url`.
void AbandonHost(const GURL& prerendering_url); void AbandonHost(const GURL& prerendering_url);
// Selects the host for navigation to `url`. Returns nullptr if it's not found // Selects the host to activate for a navigation for the given FrameTreeNode.
// or not ready for activation yet. // Returns nullptr if it's not found or not ready for activation yet.
std::unique_ptr<PrerenderHost> SelectForNavigation(const GURL& url); std::unique_ptr<PrerenderHost> SelectForNavigation(
const GURL& navigation_url,
FrameTreeNode& frame_tree_node);
// Returns a prerender host for `prerendering_url`. Returns nullptr if the URL // Returns a prerender host for `prerendering_url`. Returns nullptr if the URL
// doesn't match any prerender host. // doesn't match any prerender host.
......
...@@ -75,7 +75,8 @@ TEST_F(PrerenderHostRegistryTest, CreateAndStartHost) { ...@@ -75,7 +75,8 @@ TEST_F(PrerenderHostRegistryTest, CreateAndStartHost) {
// the prerendered page. // the prerendered page.
prerender_host->DidFinishNavigation(nullptr); prerender_host->DidFinishNavigation(nullptr);
EXPECT_TRUE(registry->SelectForNavigation(kPrerenderingUrl)); EXPECT_TRUE(registry->SelectForNavigation(
kPrerenderingUrl, *render_frame_host->frame_tree_node()));
EXPECT_EQ(registry->FindHostByUrlForTesting(kPrerenderingUrl), nullptr); EXPECT_EQ(registry->FindHostByUrlForTesting(kPrerenderingUrl), nullptr);
} }
...@@ -112,7 +113,8 @@ TEST_F(PrerenderHostRegistryTest, CreateAndStartHostForSameURL) { ...@@ -112,7 +113,8 @@ TEST_F(PrerenderHostRegistryTest, CreateAndStartHostForSameURL) {
// the prerendered page. // the prerendered page.
prerender_host1->DidFinishNavigation(nullptr); prerender_host1->DidFinishNavigation(nullptr);
EXPECT_TRUE(registry->SelectForNavigation(kPrerenderingUrl)); EXPECT_TRUE(registry->SelectForNavigation(
kPrerenderingUrl, *render_frame_host->frame_tree_node()));
EXPECT_EQ(registry->FindHostByUrlForTesting(kPrerenderingUrl), nullptr); EXPECT_EQ(registry->FindHostByUrlForTesting(kPrerenderingUrl), nullptr);
} }
...@@ -148,14 +150,16 @@ TEST_F(PrerenderHostRegistryTest, CreateAndStartHostForDifferentURLs) { ...@@ -148,14 +150,16 @@ TEST_F(PrerenderHostRegistryTest, CreateAndStartHostForDifferentURLs) {
prerender_host2->DidFinishNavigation(nullptr); prerender_host2->DidFinishNavigation(nullptr);
// Select the first host. // Select the first host.
EXPECT_TRUE(registry->SelectForNavigation(kPrerenderingUrl1)); EXPECT_TRUE(registry->SelectForNavigation(
kPrerenderingUrl1, *render_frame_host->frame_tree_node()));
EXPECT_EQ(registry->FindHostByUrlForTesting(kPrerenderingUrl1), nullptr); EXPECT_EQ(registry->FindHostByUrlForTesting(kPrerenderingUrl1), nullptr);
// The second host should still be findable. // The second host should still be findable.
EXPECT_EQ(registry->FindHostByUrlForTesting(kPrerenderingUrl2), EXPECT_EQ(registry->FindHostByUrlForTesting(kPrerenderingUrl2),
prerender_host2); prerender_host2);
// Select the second host. // Select the second host.
EXPECT_TRUE(registry->SelectForNavigation(kPrerenderingUrl2)); EXPECT_TRUE(registry->SelectForNavigation(
kPrerenderingUrl2, *render_frame_host->frame_tree_node()));
EXPECT_EQ(registry->FindHostByUrlForTesting(kPrerenderingUrl2), nullptr); EXPECT_EQ(registry->FindHostByUrlForTesting(kPrerenderingUrl2), nullptr);
} }
...@@ -179,7 +183,8 @@ TEST_F(PrerenderHostRegistryTest, SelectForNavigationBeforeReadyForActivation) { ...@@ -179,7 +183,8 @@ TEST_F(PrerenderHostRegistryTest, SelectForNavigationBeforeReadyForActivation) {
// The prerender host is not ready for activation yet, so the registry // The prerender host is not ready for activation yet, so the registry
// shouldn't select the host and instead should abandon it. // shouldn't select the host and instead should abandon it.
ASSERT_FALSE(prerender_host->is_ready_for_activation()); ASSERT_FALSE(prerender_host->is_ready_for_activation());
EXPECT_FALSE(registry->SelectForNavigation(kPrerenderingUrl)); EXPECT_FALSE(registry->SelectForNavigation(
kPrerenderingUrl, *render_frame_host->frame_tree_node()));
EXPECT_EQ(registry->FindHostByUrlForTesting(kPrerenderingUrl), nullptr); EXPECT_EQ(registry->FindHostByUrlForTesting(kPrerenderingUrl), nullptr);
} }
......
...@@ -1487,31 +1487,27 @@ void NavigationRequest::BeginNavigation() { ...@@ -1487,31 +1487,27 @@ void NavigationRequest::BeginNavigation() {
// Prerender2: // Prerender2:
// Find an available prerendered page for the request URL. If it's found, // Find an available prerendered page for the request URL. If it's found,
// this navigation will activate it instead of loading a page via network. // this navigation will activate it instead of loading a page via network.
// Only the main frame is allowed to activate the prerendered page. if (base::FeatureList::IsEnabled(blink::features::kPrerender2)) {
RenderFrameHostImpl* current_frame_host =
frame_tree_node_->current_frame_host();
if (base::FeatureList::IsEnabled(blink::features::kPrerender2) &&
!current_frame_host->GetParent()) {
auto* storage_partition_impl = static_cast<StoragePartitionImpl*>( auto* storage_partition_impl = static_cast<StoragePartitionImpl*>(
current_frame_host->GetStoragePartition()); frame_tree_node_->current_frame_host()->GetStoragePartition());
PrerenderHostRegistry* prerender_host_registry = PrerenderHostRegistry* prerender_host_registry =
storage_partition_impl->GetPrerenderHostRegistry(); storage_partition_impl->GetPrerenderHostRegistry();
if (prerender_host_registry) { DCHECK(prerender_host_registry);
std::unique_ptr<PrerenderHost> prerender_host = std::unique_ptr<PrerenderHost> prerender_host =
prerender_host_registry->SelectForNavigation(common_params_->url); prerender_host_registry->SelectForNavigation(common_params_->url,
switch (blink::features::kPrerender2Param.Get()) { *frame_tree_node_);
case blink::features::Prerender2ActivationMode::kEnabled: switch (blink::features::kPrerender2Param.Get()) {
// If `prerender_host_` exists, this navigation will activate the case blink::features::Prerender2ActivationMode::kEnabled:
// prerendered page on navigation commit. // If `prerender_host_` exists, this navigation will activate the
prerender_host_ = std::move(prerender_host); // prerendered page on navigation commit.
break; prerender_host_ = std::move(prerender_host);
case blink::features::Prerender2ActivationMode::kDisabled: break;
// The feature param disallows activation of the prerendered page for case blink::features::Prerender2ActivationMode::kDisabled:
// testing. Destroy `prerender_host` to dispose of the prerendered // The feature param disallows activation of the prerendered page for
// page. // testing. Destroy `prerender_host` to dispose of the prerendered
prerender_host.reset(); // page.
break; prerender_host.reset();
} break;
} }
} }
......
...@@ -12,6 +12,24 @@ function add_prerender(url) { ...@@ -12,6 +12,24 @@ function add_prerender(url) {
document.head.appendChild(link); document.head.appendChild(link);
} }
// Creates a new iframe with the URL.
async function add_iframe(url) {
const frame = document.createElement('iframe');
frame.src = url;
document.body.appendChild(frame);
return await new Promise(resolve => {
frame.onload = e => resolve('LOADED');
});
}
// Opens a new pop-up window with the URL.
async function open_window(url) {
const win = window.open(url, '_blank');
return await new Promise(resolve => {
win.onload = e => resolve('LOADED');
});
}
</script> </script>
</body> </body>
</html> </html>
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment