blob: 5f476075e80cc30495e05e987eb71b5cba29b0cd [file] [log] [blame]
/*
* Copyright 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Author: pulkitg@google.com (Pulkit Goyal)
#include "net/instaweb/http/public/log_record.h"
#include "net/instaweb/http/public/log_record_test_helper.h"
#include "net/instaweb/http/public/logging_proto.h"
#include "net/instaweb/http/public/logging_proto_impl.h"
#include "net/instaweb/public/global_constants.h"
#include "net/instaweb/rewriter/public/critical_images_beacon_filter.h"
#include "net/instaweb/rewriter/public/critical_images_finder.h"
#include "net/instaweb/rewriter/public/critical_images_finder_test_base.h"
#include "net/instaweb/rewriter/public/delay_images_filter.h"
#include "net/instaweb/rewriter/public/rewrite_driver.h"
#include "net/instaweb/rewriter/public/rewrite_options.h"
#include "net/instaweb/rewriter/public/rewrite_test_base.h"
#include "net/instaweb/rewriter/public/server_context.h"
#include "net/instaweb/rewriter/public/static_asset_manager.h"
#include "pagespeed/kernel/base/abstract_mutex.h"
#include "pagespeed/kernel/base/gmock.h"
#include "pagespeed/kernel/base/gtest.h"
#include "pagespeed/kernel/base/string.h"
#include "pagespeed/kernel/base/string_util.h"
#include "pagespeed/kernel/base/wildcard.h"
#include "pagespeed/kernel/http/content_type.h"
#include "pagespeed/kernel/http/image_types.pb.h"
#include "pagespeed/kernel/http/semantic_type.h"
#include "pagespeed/kernel/http/user_agent_matcher_test_base.h"
#include "pagespeed/opt/logging/enums.pb.h"
namespace {
const char kSampleJpgFile[] = "Sample.jpg";
const char kSampleWebpFile[] = "Sample_webp.webp";
const char kLargeJpgFile[] = "Puzzle.jpg";
const char kSmallPngFile[] = "BikeCrashIcn.png";
// Generated html is matched approximately because different versions of
// libjpeg are yielding different low_res_image_data.
const char kSampleJpegData[] = "data:image/jpeg;base64*";
const char kSampleWebpData[] = "data:image/webp;base64*";
const char kSamplePngData[] = "data:image/png;base64*";
const char kHeadHtml[] = "<head></head>";
const char kScriptTemplate[] =
"<script data-pagespeed-no-defer type=\"text/javascript\">%s</script>";
} // namespace
namespace net_instaweb {
class DelayImagesFilterTest : public RewriteTestBase {
public:
DelayImagesFilterTest() {
options()->set_min_image_size_low_resolution_bytes(1 * 1024);
options()->set_max_inlined_preview_images_index(-1);
}
protected:
// TODO(matterbury): Delete this method as it should be redundant.
virtual void SetUp() {
RewriteTestBase::SetUp();
SetHtmlMimetype(); // Prevent insertion of CDATA tags to static JS.
}
virtual bool AddHtmlTags() const { return false; }
// Match rewritten html content and return its byte count.
int MatchOutputAndCountBytes(const GoogleString& html_input,
const GoogleString& expected) {
Parse("inline_preview_images", html_input);
GoogleString full_html = doctype_string_ + AddHtmlBody(expected);
EXPECT_TRUE(Wildcard(full_html).Match(output_buffer_)) <<
"Expected:\n" << full_html << "\n\nGot:\n" << output_buffer_;
int output_size = output_buffer_.size();
output_buffer_.clear();
return output_size;
}
GoogleString GetImageOnloadScriptBlock() const {
return StrCat("<script data-pagespeed-no-defer type=\"text/javascript\">",
DelayImagesFilter::kImageOnloadJsSnippet,
"</script>");
}
GoogleString GetNoscript() const {
return StringPrintf(
kNoScriptRedirectFormatter,
"http://test.com/inline_preview_images.html?PageSpeed=noscript",
"http://test.com/inline_preview_images.html?PageSpeed=noscript");
}
GoogleString GenerateAddLowResScript(const GoogleString& url,
const GoogleString& image_data) {
return StringPrintf(
kScriptTemplate,
StrCat("\npagespeed.delayImagesInline.addLowResImages(\'", url,
"\', \'", image_data, "\');\n"
"pagespeed.delayImagesInline.replaceWithLowRes();\n").c_str());
}
GoogleString GenerateRewrittenImageTag(const GoogleString& url,
const GoogleString& low_res_src) {
return StrCat(
"<img data-pagespeed-high-res-src=\"", url, "\" src=\"", low_res_src,
"\" onload=\"", DelayImagesFilter::kImageOnloadCode,
"\" onerror=\"this.onerror=null;",
DelayImagesFilter::kImageOnloadCode, "\"/>");
}
GoogleString GetInlineScript() {
return StringPrintf(
kScriptTemplate,
StrCat(GetDelayImagesInlineCode(),
GetJsCode(StaticAssetEnum::DELAY_IMAGES_JS,
DelayImagesFilter::kDelayImagesSuffix)).c_str());
}
GoogleString GetHighResScript() {
return StringPrintf(kScriptTemplate,
"\npagespeed.delayImages.replaceWithHighRes();\n");
}
GoogleString GetLazyHighResScript() {
return StringPrintf(kScriptTemplate,
"\npagespeed.delayImages.registerLazyLoadHighRes();\n");
}
GoogleString GetDelayImagesInlineCode() {
return GetJsCode(StaticAssetEnum::DELAY_IMAGES_INLINE_JS,
DelayImagesFilter::kDelayImagesInlineSuffix);
}
GoogleString GetJsCode(StaticAssetEnum::StaticAsset module,
const StringPiece& call) {
StringPiece code =
server_context()->static_asset_manager()->GetAsset(module, options());
return StrCat(code, call);
}
void SetupUserAgentTest(StringPiece user_agent) {
ClearRewriteDriver();
SetCurrentUserAgent(user_agent);
SetHtmlMimetype(); // Prevent insertion of CDATA tags to static JS.
SetDriverRequestHeaders();
}
void ExpectLogRecord(int index, const RewriterInfo& expected_info) {
ScopedMutex lock(rewrite_driver()->log_record()->mutex());
ASSERT_LT(index, rewrite_driver()->log_record()->logging_info()
->rewriter_info_size());
const RewriterInfo& actual_info = rewrite_driver()->log_record()
->logging_info()->rewriter_info(index);
EXPECT_EQ(expected_info.id(), actual_info.id());
EXPECT_EQ(expected_info.status(), actual_info.status());
EXPECT_EQ(expected_info.has_rewrite_resource_info(),
actual_info.has_rewrite_resource_info());
EXPECT_EQ(expected_info.has_image_rewrite_resource_info(),
actual_info.has_image_rewrite_resource_info());
if (expected_info.has_rewrite_resource_info()) {
EXPECT_EQ(expected_info.rewrite_resource_info().is_inlined(),
actual_info.rewrite_resource_info().is_inlined());
EXPECT_EQ(expected_info.rewrite_resource_info().is_critical(),
actual_info.rewrite_resource_info().is_critical());
}
if (expected_info.has_image_rewrite_resource_info()) {
const ImageRewriteResourceInfo& expected_image_info =
expected_info.image_rewrite_resource_info();
const ImageRewriteResourceInfo& actual_image_info =
actual_info.image_rewrite_resource_info();
EXPECT_EQ(expected_image_info.is_low_res_src_inserted(),
actual_image_info.is_low_res_src_inserted());
EXPECT_GE(expected_image_info.low_res_size(),
actual_image_info.low_res_size());
}
}
};
TEST_F(DelayImagesFilterTest, DelayImagesAcrossDifferentFlushWindow) {
options()->set_lazyload_highres_images(true);
AddFilter(RewriteOptions::kDelayImages);
AddFileToMockFetcher("http://test.com/1.webp", kSampleWebpFile,
kContentTypeWebp, 100);
AddFileToMockFetcher("http://test.com/1.jpeg", kSampleJpgFile,
kContentTypeJpeg, 100);
GoogleString flush1 = "<head></head>"
"<body>"
"<img src=\"http://test.com/1.webp\" />";
GoogleString flush2 = "<img src=\"http://test.com/1.jpeg\" />"
"</body>";
SetMockLogRecord();
MockLogRecord* log = mock_log_record();
EXPECT_CALL(*log,
MockLogImageRewriteActivity(LogImageRewriteActivityMatcher(
StrEq("ic"),
StrEq("http://test.com/1.webp"),
RewriterApplication::NOT_APPLIED,
false /* is_image_inlined */,
true /* is_critical_image */,
false /* is_url_rewritten */,
1780 /* original size */,
true /* try_low_res_src_insertion */,
true /* low_res_src_inserted */,
IMAGE_WEBP,
_ /* low_res_data_size */)));
EXPECT_CALL(*log,
MockLogImageRewriteActivity(LogImageRewriteActivityMatcher(
StrEq("ic"),
StrEq("http://test.com/1.jpeg"),
RewriterApplication::NOT_APPLIED,
false /* is_image_inlined */,
true /* is_critical_image */,
false /* is_url_rewritten */,
8010 /* original size */,
true /* try_low_res_src_insertion */,
true /* low_res_src_inserted */,
IMAGE_JPEG,
_ /* low_res_data_size */)));
SetupWriter();
html_parse()->StartParse("http://test.com/inline_preview_images.html");
html_parse()->ParseText(flush1);
html_parse()->Flush();
html_parse()->ParseText(flush2);
html_parse()->FinishParse();
GoogleString output_html = StrCat(
"<head></head><body>",
GetNoscript(),
GetImageOnloadScriptBlock(),
GenerateRewrittenImageTag("http://test.com/1.webp",
kSampleWebpData),
GenerateRewrittenImageTag("http://test.com/1.jpeg",
kSampleJpegData),
"</body>");
EXPECT_TRUE(Wildcard(output_html).Match(output_buffer_));
EXPECT_TRUE(AppliedRewriterStringFromLog().find("di") !=
GoogleString::npos);
RewriterInfo expected;
expected.set_id("di");
expected.set_status(RewriterApplication::APPLIED_OK);
ExpectLogRecord(0, expected);
ExpectLogRecord(1, expected);
rewrite_driver_->log_record()->WriteLog();
LoggingInfo* logging_info = rewrite_driver_->log_record()->logging_info();
for (int i = 0; i < logging_info->rewriter_stats_size(); i++) {
if (logging_info->rewriter_stats(i).id() == "di" &&
logging_info->rewriter_stats(i).has_html_status()) {
EXPECT_EQ(RewriterHtmlApplication::ACTIVE,
logging_info->rewriter_stats(i).html_status());
return;
}
}
FAIL();
}
TEST_F(DelayImagesFilterTest, DelayImagesPreserveURLsOn) {
// Make sure that we don't delay images when preserve urls is on.
options()->set_image_preserve_urls(true);
options()->SoftEnableFilterForTesting(RewriteOptions::kDelayImages);
rewrite_driver()->AddFilters();
AddFileToMockFetcher("http://test.com/1.jpeg", kSampleJpgFile,
kContentTypeJpeg, 100);
const char kInputHtml[] =
"<html><head></head><body>"
"<img src=\"http://test.com/1.jpeg\"/>"
"</body></html>";
MatchOutputAndCountBytes(kInputHtml, kInputHtml);
}
TEST_F(DelayImagesFilterTest, DelayImageInsideNoscript) {
AddFilter(RewriteOptions::kDelayImages);
AddFileToMockFetcher("http://test.com/1.webp", kSampleWebpFile,
kContentTypeWebp, 100);
GoogleString input_html = "<head></head>"
"<body>"
"<noscript><img src=\"http://test.com/1.webp\" /></noscript>"
"</body>";
GoogleString output_html = StrCat(
"<head></head>",
StrCat("<body>", GetNoscript(), "<noscript>"
"<img src=\"http://test.com/1.webp\"/></noscript></body>"));
MatchOutputAndCountBytes(input_html, output_html);
}
TEST_F(DelayImagesFilterTest, DelayImageWithUnsupportedUserAgent) {
AddFilter(RewriteOptions::kDelayImages);
SetupUserAgentTest("unsupported");
AddFileToMockFetcher("http://test.com/1.webp", kSampleWebpFile,
kContentTypeWebp, 100);
GoogleString input_html = "<head></head>"
"<body>"
"<img src=\"http://test.com/1.webp\"/>"
"</body>";
MatchOutputAndCountBytes(input_html, input_html);
rewrite_driver_->log_record()->WriteLog();
LoggingInfo* logging_info = rewrite_driver_->log_record()->logging_info();
for (int i = 0; i < logging_info->rewriter_stats_size(); i++) {
if (logging_info->rewriter_stats(i).id() == "di" &&
logging_info->rewriter_stats(i).has_html_status()) {
EXPECT_EQ(RewriterHtmlApplication::USER_AGENT_NOT_SUPPORTED,
logging_info->rewriter_stats(i).html_status());
return;
}
}
FAIL();
}
TEST_F(DelayImagesFilterTest, DelayImageWithQueryParam) {
options()->DisableFilter(RewriteOptions::kInlineImages);
AddFilter(RewriteOptions::kDelayImages);
AddFileToMockFetcher("http://test.com/1.webp?a=b&c=d", kSampleWebpFile,
kContentTypeWebp, 100);
GoogleString input_html = "<head></head>"
"<body>"
"<img src=\"http://test.com/1.webp?a=b&amp;c=d\" />"
"</body>";
GoogleString output_html = StrCat(
"<head></head><body>",
GetNoscript(),
GetImageOnloadScriptBlock(),
GenerateRewrittenImageTag("http://test.com/1.webp?a=b&amp;c=d",
kSampleWebpData),
"</body>");
MatchOutputAndCountBytes(input_html, output_html);
}
TEST_F(DelayImagesFilterTest, DelayImageWithUnescapedQueryParam) {
options()->DisableFilter(RewriteOptions::kInlineImages);
AddFilter(RewriteOptions::kDelayImages);
AddFileToMockFetcher("http://test.com/1.webp?a=b&c=d", kSampleWebpFile,
kContentTypeWebp, 100);
GoogleString input_html = "<head></head>"
"<body>"
"<img src=\"http://test.com/1.webp?a=b&c=d\" />"
"</body>";
GoogleString output_html = StrCat(
"<head></head><body>",
GetNoscript(),
GetImageOnloadScriptBlock(),
GenerateRewrittenImageTag("http://test.com/1.webp?a=b&c=d",
kSampleWebpData),
"</body>");
MatchOutputAndCountBytes(input_html, output_html);
}
TEST_F(DelayImagesFilterTest, DelayImageWithOnlyUrlValuedAttribute) {
options()->AddUrlValuedAttribute("img", "data-src", semantic_type::kImage);
AddFilter(RewriteOptions::kDelayImages);
AddFileToMockFetcher("http://test.com/1.webp", kSampleWebpFile,
kContentTypeWebp, 100);
GoogleString input_html = "<head></head><body>"
"<img data-src=\"http://test.com/1.webp\"/>"
"</body>";
// No change made.
GoogleString output_html = StrCat(
"<head></head><body>",
GetNoscript(),
"<img data-src=\"http://test.com/1.webp\"/>",
"</body>");
MatchOutputAndCountBytes(input_html, output_html);
}
TEST_F(DelayImagesFilterTest, DelayImageWithSrcAndUrlValuedAttribute) {
options()->AddUrlValuedAttribute("img", "data-src", semantic_type::kImage);
AddFilter(RewriteOptions::kDelayImages);
AddFileToMockFetcher("http://test.com/1.webp", kSampleWebpFile,
kContentTypeWebp, 100);
AddFileToMockFetcher("http://test.com/2.jpeg", kSampleJpgFile,
kContentTypeJpeg, 100);
GoogleString input_html = "<head></head><body>"
"<img src=\"http://test.com/1.webp\""
" data-src=\"http://test.com/2.jpeg\"/>"
"</body>";
// Inlined image will be a blank png instead of a low res webp.
GoogleString output_html = StrCat(
"<head></head><body>", GetNoscript(),
GetImageOnloadScriptBlock(),
"<img data-pagespeed-high-res-src=\"http://test.com/1.webp\" "
"data-src=\"http://test.com/2.jpeg\" src=\"", kSampleWebpData,
StrCat("\" onload=\"", DelayImagesFilter::kImageOnloadCode,
"\" onerror=\"this.onerror=null;",
DelayImagesFilter::kImageOnloadCode, "\"/></body>"));
MatchOutputAndCountBytes(input_html, output_html);
}
// Verify that delay_images does not get applied on image elements that have an
// onload handler defined for them and the onload handler does not match the
// one added by CriticalImagesBeaconFilter.
TEST_F(DelayImagesFilterTest, NoDelayImagesWithOnloadAttribute) {
options()->DisableFilter(RewriteOptions::kInlineImages);
AddFilter(RewriteOptions::kDelayImages);
AddFileToMockFetcher("http://test.com/2.jpeg", kSampleJpgFile,
kContentTypeJpeg, 100);
GoogleString input_html = "<head></head><body>"
"<img src=\"http://test.com/2.jpeg\""
" onload=\"do_something();\"/>"
"</body>";
GoogleString output_html = StrCat(
"<head></head><body>", GetNoscript(),
"<img src=\"http://test.com/2.jpeg\""
" onload=\"do_something();\"/>"
"</body>");
MatchOutputAndCountBytes(input_html, output_html);
}
// Verify that delay_images gets applied on image elements that have an
// onload handler and the onload handler matches the one added by
// CriticalImagesBeaconFilter.
TEST_F(DelayImagesFilterTest, DelayImagesWithPagespeedAddedOnloadAttribute) {
options()->DisableFilter(RewriteOptions::kInlineImages);
AddFilter(RewriteOptions::kDelayImages);
AddFileToMockFetcher("http://test.com/2.jpeg", kSampleJpgFile,
kContentTypeJpeg, 100);
GoogleString input_html = StrCat("<head></head><body>"
"<img src=\"http://test.com/2.jpeg\""
" onload=\"",
CriticalImagesBeaconFilter::kImageOnloadCode,
"\"/></body>");
GoogleString output_html = StrCat(
"<head></head><body>", GetNoscript(),
GetImageOnloadScriptBlock(),
GenerateRewrittenImageTag(
"http://test.com/2.jpeg",
kSampleJpegData),
"</body>");
MatchOutputAndCountBytes(input_html, output_html);
}
TEST_F(DelayImagesFilterTest, DelayImageWithBlankImage) {
options()->set_use_blank_image_for_inline_preview(true);
AddFilter(RewriteOptions::kDelayImages);
AddFileToMockFetcher("http://test.com/1.webp", kSampleWebpFile,
kContentTypeWebp, 100);
GoogleString input_html = "<head></head><body>"
"<img src=\"http://test.com/1.webp\"/>"
"</body>";
// Inlined image will be a blank png instead of a low res webp.
GoogleString output_html = StrCat(
"<head></head><body>",
GetNoscript(),
GetImageOnloadScriptBlock(),
GenerateRewrittenImageTag("http://test.com/1.webp", kSamplePngData),
"</body>");
MatchOutputAndCountBytes(input_html, output_html);
}
TEST_F(DelayImagesFilterTest, DelayImageWithBlankImageOnMobile) {
options()->set_enable_aggressive_rewriters_for_mobile(true);
options()->set_use_blank_image_for_inline_preview(true);
AddFilter(RewriteOptions::kDelayImages);
SetupUserAgentTest(UserAgentMatcherTestBase::kAndroidICSUserAgent);
AddFileToMockFetcher("http://test.com/1.jpeg", kSampleJpgFile,
kContentTypeJpeg, 100);
GoogleString input_html = "<head></head><body>"
"<img src=\"http://test.com/1.jpeg\"/>"
"</body>";
// Inlined image will be a blank png instead of a low res Jpeg.
// Even for the mobile user agent, the image is inlined if its a blank image.
GoogleString output_html = StrCat(
"<head></head><body>",
GetNoscript(),
GetImageOnloadScriptBlock(),
GenerateRewrittenImageTag("http://test.com/1.jpeg", kSamplePngData),
"</body>");
MatchOutputAndCountBytes(input_html, output_html);
}
TEST_F(DelayImagesFilterTest, DelayImageWithMobileAggressiveEnabled) {
options()->set_enable_aggressive_rewriters_for_mobile(true);
AddFilter(RewriteOptions::kDelayImages);
SetupUserAgentTest(UserAgentMatcherTestBase::kAndroidICSUserAgent);
AddFileToMockFetcher("http://test.com/1.jpeg", kSampleJpgFile,
kContentTypeJpeg, 100);
GoogleString input_html = "<head></head>"
"<body>"
"<img src=\"http://test.com/1.jpeg\" />"
"</body>";
GoogleString output_html = StrCat(
"<head></head><body>",
GetNoscript(),
"<img data-pagespeed-high-res-src=\"http://test.com/1.jpeg\"/>",
StrCat(GetInlineScript(),
GenerateAddLowResScript("http://test.com/1.jpeg", kSampleJpegData),
GetHighResScript(), "</body>"));
MatchOutputAndCountBytes(input_html, output_html);
}
TEST_F(DelayImagesFilterTest, DelayImageMobileWithUrlValuedAttribute) {
options()->set_enable_aggressive_rewriters_for_mobile(true);
options()->AddUrlValuedAttribute("img", "data-src", semantic_type::kImage);
SetupUserAgentTest(UserAgentMatcherTestBase::kAndroidICSUserAgent);
AddFilter(RewriteOptions::kDelayImages);
AddFileToMockFetcher("http://test.com/1.webp", kSampleWebpFile,
kContentTypeWebp, 100);
GoogleString input_html = "<head></head><body>"
"<img data-src=\"http://test.com/1.webp\"/>"
"</body>";
// No inlining.
GoogleString output_html = StrCat(
"<head></head><body>",
GetNoscript(),
"<img data-src=\"http://test.com/1.webp\"/>"
"</body>");
MatchOutputAndCountBytes(input_html, output_html);
}
TEST_F(DelayImagesFilterTest, DelayImageWithMobileLazyLoad) {
options()->set_enable_aggressive_rewriters_for_mobile(true);
options()->set_lazyload_highres_images(true);
AddFilter(RewriteOptions::kDelayImages);
SetupUserAgentTest(UserAgentMatcherTestBase::kAndroidICSUserAgent);
AddFileToMockFetcher("http://test.com/1.jpeg", kSampleJpgFile,
kContentTypeJpeg, 100);
GoogleString input_html = "<head></head>"
"<body>"
"<img src=\"http://test.com/1.jpeg\" />"
"</body>";
GoogleString output_html = StrCat(
"<head></head><body>",
GetNoscript(),
"<img data-pagespeed-high-res-src=\"http://test.com/1.jpeg\"/>",
GetInlineScript(),
GenerateAddLowResScript("http://test.com/1.jpeg", kSampleJpegData),
GetLazyHighResScript(), "</body>");
MatchOutputAndCountBytes(input_html, output_html);
}
TEST_F(DelayImagesFilterTest, DelayJpegImageOnInputElement) {
options()->set_enable_aggressive_rewriters_for_mobile(true);
AddFilter(RewriteOptions::kDelayImages);
SetupUserAgentTest(UserAgentMatcherTestBase::kAndroidICSUserAgent);
AddFileToMockFetcher("http://test.com/1.jpeg", kSampleJpgFile,
kContentTypeJpeg, 100);
GoogleString input_html = "<head></head>"
"<body>"
"<input type=\"image\" src=\"http://test.com/1.jpeg\" />"
"</body>";
GoogleString output_html = StrCat(
"<head></head><body>",
GetNoscript(),
"<input type=\"image\""
" data-pagespeed-high-res-src=\"http://test.com/1.jpeg\"/>",
StrCat(GetInlineScript(),
GenerateAddLowResScript("http://test.com/1.jpeg", kSampleJpegData),
GetHighResScript(), "</body>"));
MatchOutputAndCountBytes(input_html, output_html);
}
TEST_F(DelayImagesFilterTest, TestMinImageSizeLowResolutionBytesFlag) {
options()->set_min_image_size_low_resolution_bytes(2 * 1024);
AddFilter(RewriteOptions::kDelayImages);
AddFileToMockFetcher("http://test.com/1.webp", kSampleWebpFile,
kContentTypeWebp, 100);
AddFileToMockFetcher("http://test.com/1.jpeg", kSampleJpgFile,
kContentTypeJpeg, 100);
// Size of 1.webp is 1780 and size of 1.jpeg is 6245. As
// MinImageSizeLowResolutionBytes is set to 2 KB only jpeg low quality image
// will be generated.
GoogleString input_html = "<head></head>"
"<body>"
"<img src=\"http://test.com/1.webp\" />"
"<img src=\"http://test.com/1.jpeg\" />"
"</body>";
GoogleString output_html = StrCat(
"<head></head><body>",
GetNoscript(),
"<img src=\"http://test.com/1.webp\"/>",
GetImageOnloadScriptBlock(),
GenerateRewrittenImageTag("http://test.com/1.jpeg", kSampleJpegData),
"</body>");
MatchOutputAndCountBytes(input_html, output_html);
}
TEST_F(DelayImagesFilterTest, TestMaxImageSizeLowResolutionBytesFlag) {
options()->set_max_image_size_low_resolution_bytes(4 * 1024);
AddFilter(RewriteOptions::kDelayImages);
AddFileToMockFetcher("http://test.com/1.webp", kSampleWebpFile,
kContentTypeWebp, 100);
AddFileToMockFetcher("http://test.com/1.jpeg", kSampleJpgFile,
kContentTypeJpeg, 100);
// Size of 1.webp is 1780 and size of 1.jpeg is 6245. As
// MaxImageSizeLowResolutionBytes is set to 4 KB only webp low quality image
// will be generated.
GoogleString input_html = "<head></head>"
"<body>"
"<img src=\"http://test.com/1.webp\" />"
"<img src=\"http://test.com/1.jpeg\" />"
"</body>";
GoogleString output_html = StrCat(
"<head></head><body>",
GetNoscript(),
GetImageOnloadScriptBlock(),
GenerateRewrittenImageTag("http://test.com/1.webp", kSampleWebpData),
"<img src=\"http://test.com/1.jpeg\"/>",
"</body>");
MatchOutputAndCountBytes(input_html, output_html);
}
TEST_F(DelayImagesFilterTest, TestMaxInlinedPreviewImagesIndexFlag) {
options()->set_max_inlined_preview_images_index(1);
AddFilter(RewriteOptions::kDelayImages);
AddFileToMockFetcher("http://test.com/1.webp", kSampleWebpFile,
kContentTypeWebp, 100);
AddFileToMockFetcher("http://test.com/1.jpeg", kSampleJpgFile,
kContentTypeJpeg, 100);
GoogleString input_html = "<head></head>"
"<body>"
"<img src=\"http://test.com/1.jpeg\" />"
"<img src=\"http://test.com/1.webp\" />"
"</body>";
GoogleString output_html = StrCat(
"<head></head><body>",
GetNoscript(),
GetImageOnloadScriptBlock(),
GenerateRewrittenImageTag("http://test.com/1.jpeg", kSampleJpegData),
"<img src=\"http://test.com/1.webp\"/>",
"</body>");
MatchOutputAndCountBytes(input_html, output_html);
}
TEST_F(DelayImagesFilterTest, DelayMultipleSameImage) {
options()->set_enable_aggressive_rewriters_for_mobile(true);
AddFilter(RewriteOptions::kDelayImages);
SetupUserAgentTest(UserAgentMatcherTestBase::kAndroidICSUserAgent);
AddFileToMockFetcher("http://test.com/1.jpeg", kSampleJpgFile,
kContentTypeWebp, 100);
// pagespeed_inline_map size will be 1. For same images, delay_images_filter
// make only one entry in pagespeed_inline_map.
GoogleString input_html = "<head></head>"
"<body>"
"<img src=\"http://test.com/1.jpeg\" />"
"<img src=\"http://test.com/1.jpeg\" />"
"</body>";
GoogleString output_html = StrCat(
"<head></head><body>",
GetNoscript(),
"<img data-pagespeed-high-res-src=\"http://test.com/1.jpeg\"/>",
"<img data-pagespeed-high-res-src=\"http://test.com/1.jpeg\"/>",
StrCat(GetInlineScript(),
GenerateAddLowResScript("http://test.com/1.jpeg", kSampleJpegData),
GetHighResScript(), "</body>"));
MatchOutputAndCountBytes(input_html, output_html);
}
TEST_F(DelayImagesFilterTest, NoHeadTag) {
AddFilter(RewriteOptions::kDelayImages);
AddFileToMockFetcher("http://test.com/1.webp", kSampleWebpFile,
kContentTypeWebp, 100);
GoogleString input_html = "<body>"
"<img src=\"http://test.com/1.webp\"/>"
"</body>";
GoogleString output_html = StrCat(
"<body>",
GetNoscript(),
GetImageOnloadScriptBlock(),
GenerateRewrittenImageTag("http://test.com/1.webp", kSampleWebpData),
"</body>");
MatchOutputAndCountBytes(input_html, output_html);
}
TEST_F(DelayImagesFilterTest, PcacheMiss) {
TestCriticalImagesFinder* finder =
new TestCriticalImagesFinder(NULL, statistics());
finder->set_available(CriticalImagesFinder::kNoDataYet);
server_context()->set_critical_images_finder(finder);
AddFilter(RewriteOptions::kDelayImages);
AddFileToMockFetcher("http://test.com/1.webp", kSampleWebpFile,
kContentTypeWebp, 100);
GoogleString input_html = "<head></head><body>"
"<img src=\"http://test.com/1.webp\"/>"
"</body>";
GoogleString output_html = StrCat(
"<head></head><body>",
GetNoscript(),
"<img src=\"http://test.com/1.webp\"/></body>");
MatchOutputAndCountBytes(input_html, output_html);
rewrite_driver_->log_record()->WriteLog();
ScopedMutex lock(rewrite_driver()->log_record()->mutex());
EXPECT_EQ(RewriterHtmlApplication::PROPERTY_CACHE_MISS,
logging_info()->rewriter_stats(0).html_status());
EXPECT_EQ("di", logging_info()->rewriter_stats(0).id());
}
TEST_F(DelayImagesFilterTest, MultipleBodyTags) {
options()->set_enable_aggressive_rewriters_for_mobile(true);
AddFilter(RewriteOptions::kDelayImages);
SetupUserAgentTest(UserAgentMatcherTestBase::kAndroidICSUserAgent);
AddFileToMockFetcher("http://test.com/1.webp", kSampleWebpFile,
kContentTypeWebp, 100);
AddFileToMockFetcher("http://test.com/2.jpeg", kSampleJpgFile,
kContentTypeJpeg, 100);
// No change in the subsequent body tags.
GoogleString input_html = "<head></head>"
"<body><img src=\"http://test.com/1.webp\"/></body>"
"<body><img src=\"http://test.com/2.jpeg\"/></body>";
GoogleString output_html = StrCat(
"<head></head><body>",
GetNoscript(),
"<img data-pagespeed-high-res-src=\"http://test.com/1.webp\"/>",
StrCat(GetInlineScript(),
GenerateAddLowResScript("http://test.com/1.webp", kSampleWebpData),
GetHighResScript(), "</body>"),
"<body>",
"<img data-pagespeed-high-res-src=\"http://test.com/2.jpeg\"/>",
StrCat(GenerateAddLowResScript("http://test.com/2.jpeg", kSampleJpegData),
GetHighResScript(), "</body>"));
MatchOutputAndCountBytes(input_html, output_html);
}
TEST_F(DelayImagesFilterTest, MultipleFlushWindowsForExperimental) {
options()->set_enable_aggressive_rewriters_for_mobile(true);
AddFilter(RewriteOptions::kDelayImages);
SetupUserAgentTest(UserAgentMatcherTestBase::kAndroidICSUserAgent);
AddFileToMockFetcher("http://test.com/1.webp", kSampleWebpFile,
kContentTypeWebp, 100);
AddFileToMockFetcher("http://test.com/2.jpeg", kSampleJpgFile,
kContentTypeJpeg, 100);
SetupWriter();
html_parse()->StartParse("http://test.com/inline_preview_images.html");
html_parse()->ParseText(
"<head></head><body><img src=\"http://test.com/1.webp\"/>");
html_parse()->Flush();
html_parse()->ParseText("<img src=\"http://test.com/2.jpeg\"/></body>");
html_parse()->FinishParse();
GoogleString output_html = StrCat(
"<head></head><body>",
GetNoscript(),
"<img data-pagespeed-high-res-src=\"http://test.com/1.webp\"/>",
StrCat(GetInlineScript(),
GenerateAddLowResScript("http://test.com/1.webp",
kSampleWebpData)),
"<img data-pagespeed-high-res-src=\"http://test.com/2.jpeg\"/>",
StrCat(GenerateAddLowResScript("http://test.com/2.jpeg", kSampleJpegData),
GetHighResScript(), "</body>"));
EXPECT_TRUE(Wildcard(output_html).Match(output_buffer_));
}
TEST_F(DelayImagesFilterTest, ResizeForResolution) {
options()->EnableFilter(RewriteOptions::kDelayImages);
options()->EnableFilter(RewriteOptions::kResizeMobileImages);
options()->set_enable_aggressive_rewriters_for_mobile(false);
rewrite_driver()->AddFilters();
AddFileToMockFetcher("http://test.com/1.jpeg", kLargeJpgFile,
kContentTypeJpeg, 100);
GoogleString input_html = "<head></head>"
"<body>"
"<img src=\"http://test.com/1.jpeg\"/>"
"</body>";
GoogleString output_html = StrCat(
kHeadHtml,
"<body>",
GetNoscript(),
GetImageOnloadScriptBlock(),
"<img data-pagespeed-high-res-src=\"http://test.com/1.jpeg\" "
"src=\"", kSampleJpegData,
StrCat("\" onload=\"", DelayImagesFilter::kImageOnloadCode,
"\" onerror=\"this.onerror=null;",
DelayImagesFilter::kImageOnloadCode, "\"/></body>"));
// Mobile output should be smaller than desktop because inlined low quality
// image is resized smaller for mobile.
// Do desktop and mobile rewriting twice. They should not affect each other.
SetupUserAgentTest("Safari");
int byte_count_desktop1 = MatchOutputAndCountBytes(input_html, output_html);
SetupUserAgentTest(UserAgentMatcherTestBase::kAndroidICSUserAgent);
int byte_count_android1 = MatchOutputAndCountBytes(input_html, output_html);
EXPECT_LT(byte_count_android1, byte_count_desktop1);
SetupUserAgentTest("MSIE 8.0");
int byte_count_desktop2 = MatchOutputAndCountBytes(input_html, output_html);
SetupUserAgentTest(UserAgentMatcherTestBase::kAndroidNexusSUserAgent);
int byte_count_android2 = MatchOutputAndCountBytes(input_html, output_html);
EXPECT_EQ(byte_count_android1, byte_count_android2);
EXPECT_EQ(byte_count_desktop1, byte_count_desktop2);
SetupUserAgentTest("iPhone OS");
int byte_count_iphone = MatchOutputAndCountBytes(input_html, output_html);
EXPECT_EQ(byte_count_iphone, byte_count_android1);
}
TEST_F(DelayImagesFilterTest, ResizeForResolutionWithSmallImage) {
options()->EnableFilter(RewriteOptions::kDelayImages);
options()->EnableFilter(RewriteOptions::kResizeMobileImages);
rewrite_driver()->AddFilters();
AddFileToMockFetcher("http://test.com/1.png", kSmallPngFile,
kContentTypePng, 100);
GoogleString input_html = "<head></head>"
"<body>"
"<img src=\"http://test.com/1.png\"/>"
"</body>";
GoogleString output_html = StrCat(
kHeadHtml,
"<body>",
GetNoscript(),
"<img src=\"http://test.com/1.png\"/>"
"</body>");
// No low quality data for an image smaller than kDelayImageWidthForMobile
// (in image_rewrite_filter.cc).
SetCurrentUserAgent(
UserAgentMatcherTestBase::kAndroidICSUserAgent);
MatchOutputAndCountBytes(input_html, output_html);
}
TEST_F(DelayImagesFilterTest, ResizeForResolutionNegative) {
options()->set_enable_aggressive_rewriters_for_mobile(false);
AddFilter(RewriteOptions::kDelayImages);
AddFileToMockFetcher("http://test.com/1.jpeg", kLargeJpgFile,
kContentTypeJpeg, 100);
GoogleString input_html = "<head></head>"
"<body>"
"<img src=\"http://test.com/1.jpeg\"/>"
"</body>";
GoogleString output_html = StrCat(
kHeadHtml,
"<body>",
GetNoscript(),
GetImageOnloadScriptBlock(),
"<img data-pagespeed-high-res-src=\"http://test.com/1.jpeg\" "
"src=\"", kSampleJpegData, "\"/></body>");
// If kResizeMobileImages is not explicitly enabled, desktop and mobile
// outputs will have the same size.
SetupUserAgentTest("Safari");
int byte_count_desktop = MatchOutputAndCountBytes(input_html, output_html);
SetupUserAgentTest(UserAgentMatcherTestBase::kAndroidICSUserAgent);
int byte_count_mobile = MatchOutputAndCountBytes(input_html, output_html);
EXPECT_EQ(byte_count_mobile, byte_count_desktop);
}
TEST_F(DelayImagesFilterTest, DelayImagesScriptOptimized) {
options()->set_enable_aggressive_rewriters_for_mobile(true);
AddFilter(RewriteOptions::kDelayImages);
SetupUserAgentTest(UserAgentMatcherTestBase::kAndroidICSUserAgent);
AddFileToMockFetcher("http://test.com/1.jpeg", kLargeJpgFile,
kContentTypeJpeg, 100);
Parse("optimized",
"<head></head><body><img src=\"http://test.com/1.jpeg\"/></body>");
EXPECT_EQ(GoogleString::npos, output_buffer_.find("/*"))
<< "There should be no comments in the optimized code";
}
TEST_F(DelayImagesFilterTest, DelayImagesScriptDebug) {
options()->EnableFilter(RewriteOptions::kDebug);
options()->set_enable_aggressive_rewriters_for_mobile(true);
AddFilter(RewriteOptions::kDelayImages);
SetupUserAgentTest(UserAgentMatcherTestBase::kAndroidICSUserAgent);
AddFileToMockFetcher("http://test.com/1.jpeg", kLargeJpgFile,
kContentTypeJpeg, 100);
Parse("debug",
"<head></head><body><img src=\"http://test.com/1.jpeg\"/></body>");
EXPECT_EQ(GoogleString::npos, output_buffer_.find("/*"))
<< "There should be no comments in the debug code";
}
TEST_F(DelayImagesFilterTest, DelayImageBasicTest) {
options()->DisableFilter(RewriteOptions::kInlineImages);
AddFilter(RewriteOptions::kDelayImages);
AddFileToMockFetcher("http://test.com/1.webp", kSampleWebpFile,
kContentTypeWebp, 100);
GoogleString input_html = "<head></head>"
"<body>"
"<img src=\"http://test.com/1.webp\"/>"
"</body>";
GoogleString output_html = StrCat(
"<head></head><body>",
GetNoscript(),
GetImageOnloadScriptBlock(),
GenerateRewrittenImageTag("http://test.com/1.webp", kSampleWebpData),
"</body>");
MatchOutputAndCountBytes(input_html, output_html);
}
TEST_F(DelayImagesFilterTest, DelayImageSizeLimitTest) {
options()->DisableFilter(RewriteOptions::kInlineImages);
// If the low res is small, the image is not inline previewed.
options()->set_max_low_res_image_size_bytes(615);
AddFilter(RewriteOptions::kDelayImages);
AddFileToMockFetcher("http://test.com/1.webp", kSampleWebpFile,
kContentTypeWebp, 100);
GoogleString input_html = "<head></head>"
"<body>"
"<img src=\"http://test.com/1.webp\"/>"
"</body>";
GoogleString output_html = StrCat(
"<head></head><body>",
GetNoscript(),
"<img src=\"http://test.com/1.webp\"/>"
"</body>");
MatchOutputAndCountBytes(input_html, output_html);
}
TEST_F(DelayImagesFilterTest, DelayImageSizePercentageLimitTest) {
options()->DisableFilter(RewriteOptions::kInlineImages);
// If the low-res-size / full-res-size > 0.3, the image is not inline
// previewed.
options()->set_max_low_res_to_full_res_image_size_percentage(30);
AddFilter(RewriteOptions::kDelayImages);
AddFileToMockFetcher("http://test.com/1.webp", kSampleWebpFile,
kContentTypeWebp, 100);
GoogleString input_html = "<head></head>"
"<body>"
"<img src=\"http://test.com/1.webp\"/>"
"</body>";
GoogleString output_html = StrCat(
"<head></head><body>",
GetNoscript(),
"<img src=\"http://test.com/1.webp\"/>"
"</body>");
MatchOutputAndCountBytes(input_html, output_html);
}
} // namespace net_instaweb