| /* |
| * 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&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, 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 |