Commit 40cb6df4 authored by Tsuyoshi Horo's avatar Tsuyoshi Horo Committed by Commit Bot

Support imagesrcset and imagesizes for signed exchange prefetching

Bug: 935267
Change-Id: Ia465f59ed333d66ebda5fd2f6eeaaa5682ceeb75
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1630385Reviewed-by: default avatarKunihiko Sakamoto <ksakamoto@chromium.org>
Reviewed-by: default avatarKinuko Yasuda <kinuko@chromium.org>
Commit-Queue: Tsuyoshi Horo <horo@chromium.org>
Cr-Commit-Position: refs/heads/master@{#664145}
parent ad64b102
...@@ -5,12 +5,16 @@ ...@@ -5,12 +5,16 @@
#include <string> #include <string>
#include <vector> #include <vector>
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/path_service.h"
#include "base/run_loop.h" #include "base/run_loop.h"
#include "base/stl_util.h" #include "base/stl_util.h"
#include "base/strings/stringprintf.h" #include "base/strings/stringprintf.h"
#include "base/task_runner.h" #include "base/task_runner.h"
#include "base/test/scoped_feature_list.h" #include "base/test/scoped_feature_list.h"
#include "base/test/test_timeouts.h" #include "base/test/test_timeouts.h"
#include "base/threading/thread_restrictions.h"
#include "base/threading/thread_task_runner_handle.h" #include "base/threading/thread_task_runner_handle.h"
#include "content/browser/blob_storage/chrome_blob_storage_context.h" #include "content/browser/blob_storage/chrome_blob_storage_context.h"
#include "content/browser/frame_host/render_frame_host_impl.h" #include "content/browser/frame_host/render_frame_host_impl.h"
...@@ -22,6 +26,7 @@ ...@@ -22,6 +26,7 @@
#include "content/public/browser/render_view_host.h" #include "content/public/browser/render_view_host.h"
#include "content/public/browser/web_contents.h" #include "content/public/browser/web_contents.h"
#include "content/public/common/content_features.h" #include "content/public/common/content_features.h"
#include "content/public/common/content_paths.h"
#include "content/public/test/browser_test_utils.h" #include "content/public/test/browser_test_utils.h"
#include "content/public/test/content_browser_test.h" #include "content/public/test/content_browser_test.h"
#include "content/public/test/content_browser_test_utils.h" #include "content/public/test/content_browser_test_utils.h"
...@@ -475,6 +480,157 @@ IN_PROC_BROWSER_TEST_P(SignedExchangeSubresourcePrefetchBrowserTest, ...@@ -475,6 +480,157 @@ IN_PROC_BROWSER_TEST_P(SignedExchangeSubresourcePrefetchBrowserTest,
EXPECT_EQ(0, script_fetch_count); EXPECT_EQ(0, script_fetch_count);
} }
IN_PROC_BROWSER_TEST_P(SignedExchangeSubresourcePrefetchBrowserTest,
PrefetchAlternativeSubresourceSXG_ImageSrcsetAndSizes) {
int image1_sxg_fetch_count = 0;
int image1_fetch_count = 0;
int image2_sxg_fetch_count = 0;
int image2_fetch_count = 0;
const char* prefetch_path = "/prefetch.html";
const char* target_sxg_path = "/target.sxg";
const char* target_path = "/target.html";
const char* image1_sxg_path = "/image1_png.sxg";
const char* image1_path = "/image1.png";
const char* image2_sxg_path = "/image2_png.sxg";
const char* image2_path = "/image2.png";
base::RunLoop image1_sxg_prefetch_waiter;
RegisterRequestMonitor(embedded_test_server(), image1_sxg_path,
&image1_sxg_fetch_count, &image1_sxg_prefetch_waiter);
base::RunLoop image1_prefetch_waiter;
RegisterRequestMonitor(embedded_test_server(), image1_path,
&image1_fetch_count, &image1_prefetch_waiter);
base::RunLoop image2_sxg_prefetch_waiter;
RegisterRequestMonitor(embedded_test_server(), image2_sxg_path,
&image2_sxg_fetch_count, &image2_sxg_prefetch_waiter);
base::RunLoop image2_prefetch_waiter;
RegisterRequestMonitor(embedded_test_server(), image2_path,
&image2_fetch_count, &image2_prefetch_waiter);
RegisterRequestHandler(embedded_test_server());
ASSERT_TRUE(embedded_test_server()->Start());
const GURL target_sxg_url = embedded_test_server()->GetURL(target_sxg_path);
const GURL target_url = embedded_test_server()->GetURL(target_path);
const GURL image1_sxg_url = embedded_test_server()->GetURL(image1_sxg_path);
const GURL image1_url = embedded_test_server()->GetURL(image1_path);
const GURL image2_sxg_url = embedded_test_server()->GetURL(image2_sxg_path);
const GURL image2_url = embedded_test_server()->GetURL(image2_path);
std::string image_contents;
{
base::ScopedAllowBlockingForTesting allow_blocking;
base::FilePath path;
ASSERT_TRUE(base::PathService::Get(content::DIR_TEST_DATA, &path));
path = path.AppendASCII("loader/empty16x16.png");
ASSERT_TRUE(base::PathExists(path));
ASSERT_TRUE(base::ReadFileToString(path, &image_contents));
}
const net::SHA256HashValue target_header_integrity = {{0x01}};
const net::SHA256HashValue image1_header_integrity = {{0x02}};
const net::SHA256HashValue image2_header_integrity = {{0x03}};
const std::string image1_header_integrity_string =
GetHeaderIntegrityString(image1_header_integrity);
const std::string image2_header_integrity_string =
GetHeaderIntegrityString(image2_header_integrity);
const std::string outer_link_header = base::StringPrintf(
"<%s>;"
"rel=\"alternate\";"
"type=\"application/signed-exchange;v=b3\";"
"anchor=\"%s\","
"<%s>;"
"rel=\"alternate\";"
"type=\"application/signed-exchange;v=b3\";"
"anchor=\"%s\"",
image1_sxg_url.spec().c_str(), image1_url.spec().c_str(),
image2_sxg_url.spec().c_str(), image2_url.spec().c_str());
const std::string inner_link_headers = base::StringPrintf(
"Link: "
"<%s>;rel=\"allowed-alt-sxg\";header-integrity=\"%s\","
"<%s>;rel=\"allowed-alt-sxg\";header-integrity=\"%s\","
"<%s>;rel=\"preload\";as=\"image\";"
// imagesrcset says the size of image1 is 320, and the size of image2 is
// 160.
"imagesrcset=\"%s 320w, %s 160w\";"
// imagesizes says the size of the image is 320. So image1 is selected.
"imagesizes=\"320px\"",
image1_url.spec().c_str(), image1_header_integrity_string.c_str(),
image2_url.spec().c_str(), image2_header_integrity_string.c_str(),
image2_url.spec().c_str(), image1_url.spec().c_str(),
image2_url.spec().c_str());
RegisterResponse(
prefetch_path,
ResponseEntry(base::StringPrintf(
"<body><link rel='prefetch' href='%s'></body>", target_sxg_path)));
RegisterResponse(image1_path, ResponseEntry(image_contents, "image/png"));
RegisterResponse(image2_path, ResponseEntry(image_contents, "image/png"));
RegisterResponse(
target_sxg_path,
// We mock the SignedExchangeHandler, so just return a HTML
// content as "application/signed-exchange;v=b3".
ResponseEntry(base::StringPrintf(
"<head>"
"<title>Prefetch Target (SXG)</title>"
"</head>"
"<img src=\"%s\" onload=\"document.title='done'\">",
image1_url.spec().c_str()),
"application/signed-exchange;v=b3",
{{"x-content-type-options", "nosniff"},
{"link", outer_link_header}}));
RegisterResponse(
image1_sxg_path,
// We mock the SignedExchangeHandler, so just return a JS
// content as "application/signed-exchange;v=b3".
ResponseEntry(image_contents, "application/signed-exchange;v=b3",
{{"x-content-type-options", "nosniff"}}));
MockSignedExchangeHandlerFactory factory(
{MockSignedExchangeHandlerParams(
target_sxg_url, SignedExchangeLoadResult::kSuccess, net::OK,
target_url, "text/html", {inner_link_headers},
target_header_integrity),
MockSignedExchangeHandlerParams(
image1_sxg_url, SignedExchangeLoadResult::kSuccess, net::OK,
image1_url, "image/png", {}, image1_header_integrity),
MockSignedExchangeHandlerParams(
image2_sxg_url, SignedExchangeLoadResult::kSuccess, net::OK,
image2_url, "image/png", {}, image2_header_integrity)});
ScopedSignedExchangeHandlerFactory scoped_factory(&factory);
NavigateToURL(shell(), embedded_test_server()->GetURL(prefetch_path));
image1_sxg_prefetch_waiter.Run();
EXPECT_EQ(1, image1_sxg_fetch_count);
EXPECT_EQ(0, image1_fetch_count);
EXPECT_EQ(0, image2_sxg_fetch_count);
EXPECT_EQ(0, image2_fetch_count);
WaitUntilLoaded(target_sxg_url);
WaitUntilLoaded(image1_sxg_url);
const auto cached_exchanges = GetCachedExchanges();
EXPECT_EQ(2u, cached_exchanges.size());
const auto target_it = cached_exchanges.find(target_sxg_url);
ASSERT_TRUE(target_it != cached_exchanges.end());
EXPECT_EQ(target_sxg_url, target_it->second->outer_url());
EXPECT_EQ(target_url, target_it->second->inner_url());
EXPECT_EQ(target_header_integrity, *target_it->second->header_integrity());
const auto image1_it = cached_exchanges.find(image1_sxg_url);
ASSERT_TRUE(image1_it != cached_exchanges.end());
EXPECT_EQ(image1_sxg_url, image1_it->second->outer_url());
EXPECT_EQ(image1_url, image1_it->second->inner_url());
EXPECT_EQ(image1_header_integrity, *image1_it->second->header_integrity());
NavigateToURLAndWaitTitle(target_sxg_url, "done");
EXPECT_EQ(1, image1_sxg_fetch_count);
EXPECT_EQ(0, image1_fetch_count);
EXPECT_EQ(0, image2_sxg_fetch_count);
EXPECT_EQ(0, image2_fetch_count);
}
IN_PROC_BROWSER_TEST_P(SignedExchangeSubresourcePrefetchBrowserTest, IN_PROC_BROWSER_TEST_P(SignedExchangeSubresourcePrefetchBrowserTest,
PrefetchAlternativeSubresourceSXG_CrossOrigin) { PrefetchAlternativeSubresourceSXG_CrossOrigin) {
int script_sxg_fetch_count = 0; int script_sxg_fetch_count = 0;
......
...@@ -99,6 +99,25 @@ MediaValues* CreateMediaValues( ...@@ -99,6 +99,25 @@ MediaValues* CreateMediaValues(
return media_values; return media_values;
} }
bool MediaMatches(const String& media, MediaValues* media_values) {
scoped_refptr<MediaQuerySet> media_queries = MediaQuerySet::Create(media);
MediaQueryEvaluator evaluator(*media_values);
return evaluator.Eval(*media_queries);
}
KURL GetBestFitImageURL(const Document& document,
const KURL& base_url,
MediaValues* media_values,
const KURL& href,
const String& image_srcset,
const String& image_sizes) {
float source_size = SizesAttributeParser(media_values, image_sizes).length();
ImageCandidate candidate = BestFitSourceForImageAttributes(
media_values->DevicePixelRatio(), source_size, href, image_srcset);
return base_url.IsNull() ? document.CompleteURL(candidate.ToString())
: KURL(base_url, candidate.ToString());
}
} // namespace } // namespace
void PreloadHelper::DnsPrefetchIfNeeded( void PreloadHelper::DnsPrefetchIfNeeded(
...@@ -195,12 +214,6 @@ base::Optional<ResourceType> PreloadHelper::GetResourceTypeFromAsAttribute( ...@@ -195,12 +214,6 @@ base::Optional<ResourceType> PreloadHelper::GetResourceTypeFromAsAttribute(
return base::nullopt; return base::nullopt;
} }
static bool MediaMatches(const String& media, MediaValues* media_values) {
scoped_refptr<MediaQuerySet> media_queries = MediaQuerySet::Create(media);
MediaQueryEvaluator evaluator(*media_values);
return evaluator.Eval(*media_queries);
}
// |base_url| is used in Link HTTP Header based preloads to resolve relative // |base_url| is used in Link HTTP Header based preloads to resolve relative
// URLs in srcset, which should be based on the resource's URL, not the // URLs in srcset, which should be based on the resource's URL, not the
// document's base URL. If |base_url| is a null URL, relative URLs are resolved // document's base URL. If |base_url| is a null URL, relative URLs are resolved
...@@ -223,13 +236,8 @@ Resource* PreloadHelper::PreloadIfNeeded( ...@@ -223,13 +236,8 @@ Resource* PreloadHelper::PreloadIfNeeded(
if (resource_type == ResourceType::kImage && !params.image_srcset.IsEmpty()) { if (resource_type == ResourceType::kImage && !params.image_srcset.IsEmpty()) {
UseCounter::Count(document, WebFeature::kLinkRelPreloadImageSrcset); UseCounter::Count(document, WebFeature::kLinkRelPreloadImageSrcset);
media_values = CreateMediaValues(document, viewport_description); media_values = CreateMediaValues(document, viewport_description);
float source_size = url = GetBestFitImageURL(document, base_url, media_values, params.href,
SizesAttributeParser(media_values, params.image_sizes).length(); params.image_srcset, params.image_sizes);
ImageCandidate candidate = BestFitSourceForImageAttributes(
media_values->DevicePixelRatio(), source_size, params.href,
params.image_srcset);
url = base_url.IsNull() ? document.CompleteURL(candidate.ToString())
: KURL(base_url, candidate.ToString());
} else { } else {
url = params.href; url = params.href;
} }
...@@ -486,9 +494,23 @@ void PreloadHelper::LoadLinksFromHeader( ...@@ -486,9 +494,23 @@ void PreloadHelper::LoadLinksFromHeader(
if (alternate_resource_info && params.rel.IsLinkPreload()) { if (alternate_resource_info && params.rel.IsLinkPreload()) {
DCHECK( DCHECK(
RuntimeEnabledFeatures::SignedExchangeSubresourcePrefetchEnabled()); RuntimeEnabledFeatures::SignedExchangeSubresourcePrefetchEnabled());
// TODO(crbug.com/935267): Support image_srcset and image_sizes. KURL url = params.href;
base::Optional<ResourceType> resource_type =
PreloadHelper::GetResourceTypeFromAsAttribute(params.as);
if (document && resource_type == ResourceType::kImage &&
!params.image_srcset.IsEmpty()) {
// |media_values| is created based on the viewport dimensions of the
// current page that prefetched SXGs, not on the viewport of the SXG
// content.
// TODO(crbug/935267): Consider supporting Viewport HTTP response
// header. https://discourse.wicg.io/t/proposal-viewport-http-header/
MediaValues* media_values =
CreateMediaValues(*document, viewport_description);
url = GetBestFitImageURL(*document, base_url, media_values, params.href,
params.image_srcset, params.image_sizes);
}
const auto* alternative_resource = const auto* alternative_resource =
alternate_resource_info->FindMatchingEntry(params.href); alternate_resource_info->FindMatchingEntry(url);
if (alternative_resource && if (alternative_resource &&
alternative_resource->alternative_url().IsValid()) { alternative_resource->alternative_url().IsValid()) {
params.href = alternative_resource->alternative_url(); params.href = alternative_resource->alternative_url();
......
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