| /* |
| * Copyright 2010 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: sligocki@google.com (Shawn Ligocki) |
| |
| #include "base/logging.h" |
| #include "net/instaweb/http/public/http_cache.h" |
| #include "net/instaweb/http/public/http_value.h" |
| #include "net/instaweb/rewriter/public/css_rewrite_test_base.h" |
| #include "net/instaweb/rewriter/public/domain_lawyer.h" |
| #include "net/instaweb/rewriter/public/image_rewrite_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/test_rewrite_driver_factory.h" |
| #include "net/instaweb/rewriter/public/test_url_namer.h" |
| #include "pagespeed/kernel/base/basictypes.h" |
| #include "pagespeed/kernel/base/dynamic_annotations.h" // RunningOnValgrind |
| #include "pagespeed/kernel/base/gtest.h" |
| #include "pagespeed/kernel/base/hasher.h" |
| #include "pagespeed/kernel/base/mock_message_handler.h" |
| #include "pagespeed/kernel/base/null_mutex.h" |
| #include "pagespeed/kernel/base/statistics.h" |
| #include "pagespeed/kernel/base/stdio_file_system.h" |
| #include "pagespeed/kernel/base/string.h" |
| #include "pagespeed/kernel/base/string_util.h" |
| #include "pagespeed/kernel/cache/lru_cache.h" |
| #include "pagespeed/kernel/html/html_parse_test_base.h" |
| #include "pagespeed/kernel/http/content_type.h" |
| #include "pagespeed/kernel/http/data_url.h" |
| #include "pagespeed/kernel/http/response_headers.h" |
| #include "pagespeed/kernel/http/user_agent_matcher_test_base.h" |
| #include "pagespeed/kernel/image/jpeg_utils.h" |
| |
| using net_instaweb::MockMessageHandler; |
| using net_instaweb::NullMutex; |
| using pagespeed::image_compression::JpegUtils; |
| |
| namespace net_instaweb { |
| |
| namespace { |
| |
| // Filenames of resource files. |
| const char kBikePngFile[] = "BikeCrashIcn.png"; |
| const char kCuppaPngFile[] = "Cuppa.png"; |
| const char kPuzzleJpgFile[] = "Puzzle.jpg"; |
| |
| const char kDummyContent[] = "Invalid PNG but it does not matter for this test"; |
| |
| const ContentType kContentTypeTtf = |
| { "application/octet-stream", ".ttf", ContentType::kOther }; |
| const ContentType kContentTypeEot = |
| { "application/vnd.ms-fontobject", ".eot", ContentType::kOther }; |
| const ContentType kContentTypeHtc = |
| { "text/x-component", ".htc", ContentType::kOther }; |
| |
| class CssImageRewriterTest : public CssRewriteTestBase { |
| protected: |
| virtual void SetUp() { |
| // We setup the options before the upcall so that the |
| // CSS filter is created aware of these. |
| options()->SoftEnableFilterForTesting(RewriteOptions::kExtendCacheImages); |
| options()->SoftEnableFilterForTesting( |
| RewriteOptions::kFallbackRewriteCssUrls); |
| options()->SoftEnableFilterForTesting(RewriteOptions::kRewriteCss); |
| CssRewriteTestBase::SetUp(); |
| } |
| |
| int RewriteCssImageCheckForQuality(const char* user_agent) { |
| const char kCssFile[] = "a.css"; |
| const char kCssTemplate[] = "div{background-image:url(%s)}"; |
| AddFileToMockFetcher(StrCat(kTestDomain, kPuzzleJpgFile), kPuzzleJpgFile, |
| kContentTypeJpeg, 100); |
| GoogleString in_css = StringPrintf(kCssTemplate, kPuzzleJpgFile); |
| SetResponseWithDefaultHeaders(kCssFile, kContentTypeCss, in_css, 100); |
| SetCurrentUserAgent(user_agent); |
| |
| // Using a "0" hash would result in the rewritten url having the same hash |
| // for mobile and non-mobile UAs. Hence, using Md5 hasher. |
| UseMd5Hasher(); |
| Parse("image_in_css", CssLinkHref(kCssFile)); |
| StringVector css_links; |
| CollectCssLinks("collect", output_buffer_, &css_links); |
| EXPECT_EQ(1, css_links.size()); |
| GoogleString out_css; |
| ResponseHeaders headers; |
| EXPECT_TRUE(FetchResourceUrl(StrCat(kTestDomain, css_links[0]), |
| &out_css, &headers)); |
| |
| // Find the image URL embedded in the CSS output. |
| GoogleString image_url = ExtractCssBackgroundImage(out_css); |
| if (image_url.empty()) { |
| return -1; |
| } |
| // FetchResourceUrl clears the rewrite driver. Add back the mobile UA. |
| SetCurrentUserAgent(user_agent); |
| |
| StringPiece out_image; |
| HTTPValue value_out; |
| ResponseHeaders headers_out; |
| EXPECT_EQ(kFoundResult, |
| HttpBlockingFind(StrCat(kTestDomain, image_url), http_cache(), |
| &value_out, &headers_out)); |
| value_out.ExtractContents(&out_image); |
| MockMessageHandler message_handler(new NullMutex); |
| int quality = JpegUtils::GetImageQualityFromImage(out_image.data(), |
| out_image.size(), |
| &message_handler); |
| return quality; |
| } |
| }; |
| |
| TEST_F(CssImageRewriterTest, CacheExtendsImagesSimple) { |
| // Simplified version of CacheExtendsImages, which doesn't have many copies of |
| // the same URL. |
| SetResponseWithDefaultHeaders("foo.png", kContentTypePng, kDummyContent, 100); |
| |
| static const char css_before[] = |
| "body {\n" |
| " background-image: url(foo.png);\n" |
| "}\n"; |
| const GoogleString css_after = |
| StrCat("body{background-image:url(", |
| Encode("", "ce", "0", "foo.png", "png"), |
| ")}"); |
| |
| ValidateRewrite("cache_extends_images", css_before, css_after, |
| kExpectSuccess | kNoClearFetcher); |
| } |
| |
| TEST_F(CssImageRewriterTest, CacheExtendsImagesEmbeddedComma) { |
| // Makes sure image-URL rewriting doesn't corrupt URLs with embedded |
| // commas. Earlier, we were escaping commas in URLs by backslashing |
| // the "," and IE8 interprets those backslashes as forward slashes, |
| // making the URL incorrect. |
| static const char kImageUrl[] = "foo,bar.png"; |
| SetResponseWithDefaultHeaders(kImageUrl, kContentTypePng, kDummyContent, 100); |
| |
| static const char css_before[] = |
| "body {\n" |
| " background-image: url(foo,bar.png);\n" |
| "}\n"; |
| const GoogleString css_after = |
| StrCat("body{background-image:url(", |
| Encode("", "ce", "0", kImageUrl, "png"), |
| ")}"); |
| |
| ValidateRewrite("cache_extends_images", css_before, css_after, |
| kExpectSuccess | kNoClearFetcher); |
| } |
| |
| TEST_F(CssImageRewriterTest, CacheExtendsImagesEmbeddedSpace) { |
| // Note that GoogleUrl will, internal to our system, convert the space to |
| // a %20, so we'll be fetching the percentified form. |
| SetResponseWithDefaultHeaders("foo%20bar.png", kContentTypePng, |
| kDummyContent, 100); |
| |
| static const char css_before[] = |
| "body {\n" |
| " background-image: url('foo bar.png');\n" |
| "}\n"; |
| const GoogleString css_after = |
| StrCat("body{background-image:url(", |
| Encode("", "ce", "0", "foo%20bar.png", "png"), |
| ")}"); |
| |
| ValidateRewrite("cache_extends_images", css_before, css_after, |
| kExpectSuccess | kNoClearFetcher); |
| } |
| |
| TEST_F(CssImageRewriterTest, MinifyImagesEmbeddedSpace) { |
| options()->ClearSignatureForTesting(); |
| options()->DisableFilter(RewriteOptions::kExtendCacheImages); |
| server_context()->ComputeSignature(options()); |
| |
| ValidateRewrite("minify", |
| MakeIndentedCssWithImage("'foo bar.png'"), |
| MakeMinifiedCssWithImage("foo\\ bar.png"), |
| kExpectSuccess | kNoClearFetcher); |
| } |
| |
| TEST_F(CssImageRewriterTest, RewriteCssImagesVerifyQuality) { |
| options()->ClearSignatureForTesting(); |
| options()->SoftEnableFilterForTesting(RewriteOptions::kRecompressJpeg); |
| options()->SoftEnableFilterForTesting(RewriteOptions::kRewriteCss); |
| options()->set_image_max_rewrites_at_once(1); |
| options()->set_always_rewrite_css(true); |
| options()->set_image_jpeg_recompress_quality(85); |
| options()->set_image_jpeg_recompress_quality_for_small_screens(60); |
| server_context()->ComputeSignature(options()); |
| |
| // Recompress quality set for desktop is 85 and mobile is 60. Verify this |
| // applied correctly by verifying the quality of the output image. |
| int mobile_quality = RewriteCssImageCheckForQuality( |
| UserAgentMatcherTestBase::kIPhoneUserAgent); |
| EXPECT_EQ(60, mobile_quality); |
| int non_mobile_quality = RewriteCssImageCheckForQuality( |
| UserAgentMatcherTestBase::kChrome15UserAgent); |
| EXPECT_EQ(85, non_mobile_quality); |
| } |
| |
| TEST_F(CssImageRewriterTest, CacheExtendsWhenCssGrows) { |
| // We run most tests with set_always_rewrite_css(true) which bypasses |
| // checks on whether rewriting is worthwhile or not. Test to make sure we make |
| // the right decision when we do do the check in the case where the produced |
| // CSS is actually larger, but contains rewritten resources. |
| // (We want to rewrite the CSS in that case) |
| options()->ClearSignatureForTesting(); |
| options()->set_always_rewrite_css(false); |
| server_context()->ComputeSignature(options()); |
| SetResponseWithDefaultHeaders("foo.png", kContentTypePng, kDummyContent, 100); |
| ValidateRewrite("cache_extends_images_growcheck", |
| MakeIndentedCssWithImage("foo.png"), |
| MakeMinifiedCssWithImage( |
| Encode("", "ce", "0", "foo.png", "png")), |
| kExpectSuccess | kNoClearFetcher); |
| } |
| |
| TEST_F(CssImageRewriterTest, CacheExtendsRepeatedTopLevel) { |
| // Test to make sure that if we cache extend inside CSS we can do it |
| // for the same image in HTML at the same time. |
| const char kImg[] = "img.png"; |
| const GoogleString kExtendedImg = Encode("", "ce", "0", "img.png", "png"); |
| |
| const char kCss[] = "stylesheet.css"; |
| const GoogleString kRewrittenCss = |
| Encode("", "cf", "0", "stylesheet.css", "css"); |
| |
| SetResponseWithDefaultHeaders(kImg, kContentTypePng, kDummyContent, 100); |
| SetResponseWithDefaultHeaders( |
| kCss, kContentTypeCss, MakeMinifiedCssWithImage(kImg), 100); |
| |
| const char kHtmlTemplate[] = |
| "<link rel='stylesheet' href='%s'>" |
| "<img src='%s'>"; |
| |
| ValidateExpected("repeated_top_level", |
| StringPrintf(kHtmlTemplate, kCss, kImg), |
| StringPrintf(kHtmlTemplate, |
| kRewrittenCss.c_str(), |
| kExtendedImg.c_str())); |
| |
| GoogleString css_out; |
| EXPECT_TRUE(FetchResourceUrl(StrCat(kTestDomain, kRewrittenCss), &css_out)); |
| EXPECT_EQ(MakeMinifiedCssWithImage(kExtendedImg), css_out); |
| } |
| |
| TEST_F(CssImageRewriterTest, CacheExtendsImages) { |
| SetResponseWithDefaultHeaders("foo.png", kContentTypePng, kDummyContent, 100); |
| SetResponseWithDefaultHeaders("bar.png", kContentTypePng, kDummyContent, 100); |
| SetResponseWithDefaultHeaders("baz.png", kContentTypePng, kDummyContent, 100); |
| |
| static const char css_before[] = |
| "body {\n" |
| " background-image: url(foo.png);\n" |
| " list-style-image: url('bar.png');\n" |
| "}\n" |
| ".titlebar p.cfoo, #end p {\n" |
| " background: url(\"baz.png\");\n" |
| " list-style: url('foo.png');\n" |
| "}\n" |
| ".other {\n" |
| " background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAA" |
| "AUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4" |
| "OHwAAAABJRU5ErkJggg==);" |
| " -proprietary-background-property: url(foo.png);\n" |
| "}"; |
| const GoogleString css_after = StrCat( |
| "body{background-image:url(", Encode("", "ce", "0", "foo.png", "png"), |
| ");list-style-image:url(", Encode("", "ce", "0", "bar.png", "png"), |
| ")}.titlebar p.cfoo,#end p{background:url(", |
| Encode("", "ce", "0", "baz.png", "png"), ");list-style:url(", |
| Encode("", "ce", "0", "foo.png", "png"), |
| ")}.other{" // data: URLs and unknown properties are not rewritten. |
| "background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAA" |
| "AUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4" |
| "OHwAAAABJRU5ErkJggg==);" |
| "-proprietary-background-property:url(foo.png)}"); |
| |
| ValidateRewrite("cache_extends_images", css_before, css_after, |
| kExpectSuccess | kNoClearFetcher); |
| } |
| |
| // See TrimsImageUrls below: change one, change them both! |
| TEST_F(CssImageRewriterTest, TrimsImageUrls) { |
| options()->ClearSignatureForTesting(); |
| options()->EnableFilter(RewriteOptions::kLeftTrimUrls); |
| server_context()->ComputeSignature(options()); |
| SetResponseWithDefaultHeaders("foo.png", kContentTypePng, kDummyContent, 100); |
| ValidateRewriteExternalCss("trims_css_urls", |
| MakeIndentedCssWithImage("foo.png"), |
| MakeMinifiedCssWithImage( |
| Encode("", "ce", "0", "foo.png", "png")), |
| kExpectSuccess | kNoClearFetcher); |
| } |
| |
| class CssImageRewriterTestUrlNamer : public CssImageRewriterTest { |
| public: |
| CssImageRewriterTestUrlNamer() { |
| SetUseTestUrlNamer(true); |
| } |
| }; |
| |
| // See TrimsImageUrls above: change one, change them both! |
| TEST_F(CssImageRewriterTestUrlNamer, TrimsImageUrls) { |
| // Check that we really are using TestUrlNamer and not UrlNamer. |
| EXPECT_NE(Encode(kTestDomain, "ce", "0", "foo.png", "png"), |
| EncodeNormal(kTestDomain, "ce", "0", "foo.png", "png")); |
| |
| // A verbatim copy of the test above but using TestUrlNamer. |
| options()->ClearSignatureForTesting(); |
| options()->EnableFilter(RewriteOptions::kLeftTrimUrls); |
| server_context()->ComputeSignature(options()); |
| SetResponseWithDefaultHeaders("foo.png", kContentTypePng, kDummyContent, 100); |
| ValidateRewriteExternalCss("trims_css_urls", |
| MakeIndentedCssWithImage("foo.png"), |
| MakeMinifiedCssWithImage( |
| Encode("", "ce", "0", "foo.png", "png")), |
| kExpectSuccess | kNoClearFetcher); |
| } |
| |
| TEST_F(CssImageRewriterTest, InlinePaths) { |
| // Make sure we properly handle CSS relative references when we have the same |
| // inline CSS in different places. This is also a regression test for a bug |
| // during development of async + inline case which caused us to do |
| // null rewrites from cache. |
| options()->ClearSignatureForTesting(); |
| options()->SoftEnableFilterForTesting(RewriteOptions::kLeftTrimUrls); |
| server_context()->ComputeSignature(options()); |
| SetResponseWithDefaultHeaders("dir/foo.png", kContentTypePng, |
| kDummyContent, 100); |
| |
| static const char kCssBefore[] = |
| "body {\n" |
| " background-image: url(http://test.com/dir/foo.png);\n" |
| "}\n"; |
| |
| // Force all URL encoding to use normal encoding so that the relative URL |
| // trimming logic can work and give us a relative URL result as expected. |
| TestUrlNamer::UseNormalEncoding(true); |
| |
| // Note: Original URL was absolute, so rewritten one is as well. |
| const GoogleString kCssAfter = StrCat( |
| "body{background-image:url(", |
| Encode("dir/", "ce", "0", "foo.png", "png"), |
| ")}"); |
| ValidateRewriteInlineCss("nosubdir", kCssBefore, kCssAfter, kExpectSuccess); |
| |
| const GoogleString kCssAfterRel = StrCat( |
| "body{background-image:url(", |
| Encode("", "ce", "0", "foo.png", "png"), |
| ")}"); |
| ValidateRewriteInlineCss("dir/yessubdir", kCssBefore, kCssAfterRel, |
| kExpectSuccess); |
| } |
| |
| TEST_F(CssImageRewriterTest, RewriteCached) { |
| // Make sure we produce the same output from cache. |
| options()->ClearSignatureForTesting(); |
| options()->SoftEnableFilterForTesting(RewriteOptions::kLeftTrimUrls); |
| server_context()->ComputeSignature(options()); |
| SetResponseWithDefaultHeaders("dir/foo.png", kContentTypePng, |
| kDummyContent, 100); |
| |
| static const char kCssBefore[] = |
| "body {\n" |
| " background-image: url(http://test.com/dir/foo.png);\n" |
| "}\n"; |
| |
| CHECK(!factory()->use_test_url_namer()); |
| |
| const GoogleString kCssAfter = StrCat( |
| "body{background-image:url(", |
| Encode("dir/", "ce", "0", "foo.png", "png"), |
| ")}"); |
| ValidateRewriteInlineCss("nosubdir", |
| kCssBefore, kCssAfter, |
| kExpectSuccess); |
| |
| statistics()->Clear(); |
| ValidateRewriteInlineCss("nosubdir2", |
| kCssBefore, kCssAfter, |
| kExpectSuccess | kNoStatCheck); |
| // Should not re-serialize. Works only under the new flow... |
| EXPECT_EQ(0, total_bytes_saved_->Get()); |
| } |
| |
| // Test that we remember parse failures. |
| TEST_F(CssImageRewriterTest, CacheInlineParseFailures) { |
| const char kInvalidCss[] = " div{"; |
| |
| ValidateRewriteInlineCss("inline-invalid", kInvalidCss, kInvalidCss, |
| kExpectFallback); |
| EXPECT_EQ(1, num_parse_failures_->Get()); |
| |
| // kNoStatCheck because we are explicitly depending on an extra failure |
| // not being recorded. |
| ValidateRewriteInlineCss("inline-invalid2", kInvalidCss, kInvalidCss, |
| kExpectFallback | kNoStatCheck); |
| // Shouldn't reparse -- and stats are reset between runs. |
| EXPECT_EQ(0, num_parse_failures_->Get()); |
| } |
| |
| TEST_F(CssImageRewriterTest, RecompressImages) { |
| options()->ClearSignatureForTesting(); |
| options()->SoftEnableFilterForTesting(RewriteOptions::kRecompressPng); |
| server_context()->ComputeSignature(options()); |
| AddFileToMockFetcher(StrCat(kTestDomain, "foo.png"), kBikePngFile, |
| kContentTypePng, 100); |
| static const char kCss[] = |
| "body {\n" |
| " background-image: url(foo.png);\n" |
| "}\n"; |
| |
| const GoogleString kCssAfter = StrCat( |
| "body{background-image:url(", |
| Encode("", "ic", "0", "foo.png", "png"), |
| ")}"); |
| |
| ValidateRewriteExternalCss("recompress_css_images", kCss, kCssAfter, |
| kExpectSuccess | kNoClearFetcher); |
| } |
| |
| TEST_F(CssImageRewriterTest, CssImagePreserveUrls) { |
| options()->ClearSignatureForTesting(); |
| options()->SoftEnableFilterForTesting(RewriteOptions::kRecompressPng); |
| options()->set_image_preserve_urls(true); |
| server_context()->ComputeSignature(options()); |
| AddFileToMockFetcher(StrCat(kTestDomain, "foo.png"), kBikePngFile, |
| kContentTypePng, 100); |
| static const char kCss[] = |
| "body {\n" |
| " background-image: url(foo.png);\n" |
| "}\n"; |
| |
| const GoogleString kCssAfter = "body{background-image:url(foo.png)}"; |
| // The CSS should minify but the URL shouldn't change. |
| ValidateRewriteExternalCss("compress_preserve_css_images", kCss, kCssAfter, |
| kExpectSuccess | kNoClearFetcher); |
| |
| // We should have optimized the image even though we didn't render the URL. |
| ClearStats(); |
| GoogleString out_img_url = Encode(kTestDomain, "ic", "0", "foo.png", "png"); |
| GoogleString out_img; |
| EXPECT_TRUE(FetchResourceUrl(out_img_url, &out_img)); |
| EXPECT_EQ(1, http_cache()->cache_hits()->Get()); |
| EXPECT_EQ(0, http_cache()->cache_misses()->Get()); |
| EXPECT_EQ(0, http_cache()->cache_inserts()->Get()); |
| EXPECT_EQ(1, static_cast<int>(lru_cache()->num_hits())); |
| EXPECT_EQ(0, static_cast<int>(lru_cache()->num_misses())); |
| EXPECT_EQ(0, static_cast<int>(lru_cache()->num_inserts())); |
| } |
| |
| TEST_F(CssImageRewriterTest, CssImagePreserveUrlsNoPreemptiveRewrite) { |
| options()->ClearSignatureForTesting(); |
| options()->SoftEnableFilterForTesting(RewriteOptions::kRecompressPng); |
| options()->set_image_preserve_urls(true); |
| options()->set_in_place_preemptive_rewrite_css_images(false); |
| server_context()->ComputeSignature(options()); |
| AddFileToMockFetcher(StrCat(kTestDomain, "foo.png"), kBikePngFile, |
| kContentTypePng, 100); |
| static const char kCss[] = |
| "body {\n" |
| " background-image: url(foo.png);\n" |
| "}\n"; |
| |
| const GoogleString kCssAfter = "body{background-image:url(foo.png)}"; |
| // The CSS should minify but the URL shouldn't change. |
| ValidateRewriteExternalCss("compress_preserve_css_images", kCss, kCssAfter, |
| kExpectSuccess | kNoClearFetcher); |
| |
| // We should not find a cache hit when requesting the image, indicating it |
| // has not been optimized, in contrast with the CssImagePreserveUrls test. |
| ClearStats(); |
| GoogleString out_img_url = Encode(kTestDomain, "ic", "0", "foo.png", "png"); |
| GoogleString out_img; |
| EXPECT_TRUE(FetchResourceUrl(out_img_url, &out_img)); |
| EXPECT_EQ(0, http_cache()->cache_hits()->Get()); |
| EXPECT_EQ(0, static_cast<int>(lru_cache()->num_hits())); |
| } |
| |
| class InlineCssImageRewriterTest : public CssImageRewriterTest { |
| protected: |
| int NumTestImageBytes() { return test_image_file_contents_.size(); } |
| |
| GoogleString TestImageFileName() { return kCuppaPngFile; } |
| |
| GoogleString TestImageDataUrl() { |
| GoogleString data_url; |
| DataUrl(kContentTypePng, BASE64, test_image_file_contents_, &data_url); |
| return data_url; |
| } |
| |
| void SetMaxBytes(int image_inline_max_bytes, |
| int css_image_inline_max_bytes) { |
| options()->ClearSignatureForTesting(); |
| options()->set_image_inline_max_bytes(image_inline_max_bytes); |
| options()->set_css_image_inline_max_bytes(css_image_inline_max_bytes); |
| EXPECT_EQ(image_inline_max_bytes, options()->ImageInlineMaxBytes()); |
| EXPECT_EQ(css_image_inline_max_bytes, options()->CssImageInlineMaxBytes()); |
| server_context()->ComputeSignature(options()); |
| } |
| |
| virtual void SetUp() { |
| options()->EnableFilter(RewriteOptions::kInlineImages); |
| CssImageRewriterTest::SetUp(); |
| SetUpTestImageFile(); |
| EXPECT_FALSE(test_image_file_contents_.empty()); |
| } |
| |
| private: |
| void SetUpTestImageFile() { |
| GoogleString file_name = TestImageFileName(); |
| AddFileToMockFetcher(StrCat(kTestDomain, file_name), |
| file_name, kContentTypePng, 100); |
| |
| StdioFileSystem stdio_file_system; |
| GoogleString file_path = StrCat(GTestSrcDir(), kTestData, file_name); |
| EXPECT_TRUE(stdio_file_system.ReadFile( |
| file_path.c_str(), &test_image_file_contents_, message_handler())); |
| } |
| |
| GoogleString test_image_file_contents_; |
| }; |
| |
| TEST_F(InlineCssImageRewriterTest, InlineImages) { |
| SetMaxBytes(NumTestImageBytes() + 1, // image_inline_max_bytes |
| NumTestImageBytes() + 1); // css_image_inline_max_bytes |
| GoogleString input_css = StrCat( |
| "body {\n" |
| " background-image: url(", TestImageFileName(), ");\n" |
| "}\n"); |
| GoogleString expected_css = StrCat( |
| "body{background-image:url(", TestImageDataUrl(), ")}"); |
| |
| // Skip the stat check because inlining *increases* the CSS size and |
| // causes the check to fail. Inlining eliminates a resource fetch, so |
| // it should normally be a net win in practice. |
| ValidateRewrite("inline_css_images", input_css, expected_css, |
| kExpectSuccess | kNoClearFetcher | kNoStatCheck); |
| } |
| |
| TEST_F(InlineCssImageRewriterTest, InlineImagesInFallbackMode) { |
| SetMaxBytes(NumTestImageBytes() + 1, // image_inline_max_bytes |
| NumTestImageBytes() + 1); // css_image_inline_max_bytes |
| // This ought to not parse. |
| static const char kCssTemplate[] = |
| "body {\n" |
| " background-image: url(%s);\n" |
| "}}}}}\n"; |
| GoogleString input_css = StringPrintf( |
| kCssTemplate, TestImageFileName().c_str()); |
| GoogleString expected_css = StringPrintf( |
| kCssTemplate, TestImageDataUrl().c_str()); |
| |
| // Skip the stat check because inlining *increases* the CSS size and |
| // causes the check to fail. Inlining eliminates a resource fetch, so |
| // it should normally be a net win in practice. |
| ValidateRewrite("inline_images_in_fallback_mode", input_css, expected_css, |
| kExpectFallback | kNoClearFetcher | kNoStatCheck); |
| } |
| |
| TEST_F(InlineCssImageRewriterTest, NoInlineWhenImageTooLargeForCss) { |
| SetMaxBytes(NumTestImageBytes() + 1, // image_inline_max_bytes |
| NumTestImageBytes()); // css_image_inline_max_bytes |
| GoogleString file_name = TestImageFileName(); |
| GoogleString input_css = StrCat( |
| "body {\n" |
| " background-image: url(", file_name, ");\n" |
| "}\n"); |
| GoogleString expected_css = StrCat( |
| "body{background-image:url(", |
| Encode("", "ce", "0", file_name, "png"), ")}"); |
| |
| ValidateRewrite("no_inline_when_image_too_large_for_css", |
| input_css, expected_css, kExpectSuccess | kNoClearFetcher); |
| } |
| |
| TEST_F(InlineCssImageRewriterTest, InlineInExternalCssOnly) { |
| SetMaxBytes(NumTestImageBytes(), // image_inline_max_bytes |
| NumTestImageBytes() + 1); // css_image_inline_max_bytes |
| GoogleString file_name = TestImageFileName(); |
| GoogleString input_css = StrCat( |
| "body {\n" |
| " background-image: url(", file_name, ");\n" |
| "}\n"); |
| GoogleString expected_inline_css = StrCat( |
| "body{background-image:url(", |
| Encode("", "ce", "0", file_name, "png"), ")}"); |
| GoogleString expected_outline_css = StrCat( |
| "body{background-image:url(", TestImageDataUrl(), ")}"); |
| |
| ValidateRewriteInlineCss( |
| "no_inline_in_inline", input_css, expected_inline_css, |
| kExpectSuccess | kNoClearFetcher); |
| ValidateRewriteExternalCss( |
| "inline_in_external", input_css, expected_outline_css, |
| kExpectSuccess | kNoClearFetcher | kNoStatCheck); |
| } |
| |
| TEST_F(CssImageRewriterTest, UseCorrectBaseUrl) { |
| // Initialize resources. |
| static const char css_url[] = "http://www.example.com/bar/style.css"; |
| static const char css_before[] = "body { background: url(image.png); }"; |
| SetResponseWithDefaultHeaders(css_url, kContentTypeCss, css_before, 100); |
| static const char image_url[] = "http://www.example.com/bar/image.png"; |
| SetResponseWithDefaultHeaders(image_url, kContentTypePng, kDummyContent, 100); |
| |
| // Construct URL for rewritten image. |
| GoogleString expected_image_url = |
| Encode("", RewriteOptions::kCacheExtenderId, |
| hasher()->Hash(kDummyContent), "image.png", |
| // + 1 Needed to exclude "." from the extension. |
| kContentTypePng.file_extension_ + 1); |
| |
| GoogleString css_after = StrCat( |
| "body{background:url(", expected_image_url, ")}"); |
| |
| // Construct URL for rewritten CSS. |
| GoogleString expected_css_url = |
| Encode("bar/", RewriteOptions::kCssFilterId, |
| hasher()->Hash(css_after), "style.css", |
| // + 1 Needed to exclude "." from the extension. |
| kContentTypeCss.file_extension_ + 1); |
| |
| static const char html_before[] = |
| "<head>\n" |
| " <link rel='stylesheet' href='bar/style.css'>\n" |
| "</head>"; |
| GoogleString html_after = StrCat( |
| "<head>\n" |
| " <link rel='stylesheet' href='", expected_css_url, "'>\n" |
| "</head>"); |
| |
| // Make sure that image.png uses http://www.example.com/bar/style.css as |
| // base URL instead of http://www.example.com/. |
| ValidateExpectedUrl("http://www.example.com/", html_before, html_after); |
| |
| GoogleString actual_css_after; |
| FetchResourceUrl(StrCat("http://www.example.com/", expected_css_url), |
| &actual_css_after); |
| EXPECT_EQ(css_after, actual_css_after); |
| } |
| |
| TEST_F(CssImageRewriterTest, CacheExtendsImagesInStyleAttributes) { |
| SetResponseWithDefaultHeaders("foo.png", kContentTypePng, kDummyContent, 100); |
| SetResponseWithDefaultHeaders("bar.png", kContentTypePng, kDummyContent, 100); |
| SetResponseWithDefaultHeaders("baz.png", kContentTypePng, kDummyContent, 100); |
| |
| options()->ClearSignatureForTesting(); |
| options()->SoftEnableFilterForTesting( |
| RewriteOptions::kRewriteStyleAttributes); |
| server_context()->ComputeSignature(options()); |
| |
| ValidateExpected("cache_extend_images_simple", |
| "<div style=\"" |
| " background-image: url(foo.png);\n" |
| " list-style-image: url('bar.png');\n" |
| "\"/>", |
| StrCat( |
| "<div style=\"" |
| "background-image:" |
| "url(", Encode("", "ce", "0", "foo.png", "png"), ");", |
| "list-style-image:" |
| "url(", Encode("", "ce", "0", "bar.png", "png"), ")", |
| "\"/>")); |
| |
| ValidateExpected("cache_extend_images", |
| "<div style=\"" |
| " background: url(baz.png);\n" |
| " list-style: url("foo.png");\n" |
| "\"/>", |
| StrCat( |
| "<div style=\"" |
| "background:" |
| "url(", Encode("", "ce", "0", "baz.png", "png"), ");" |
| "list-style:" |
| "url(", Encode("", "ce", "0", "foo.png", "png"), ")" |
| "\"/>")); |
| |
| ValidateExpected("dont_cache_extend_data_urls", |
| "<div style=\"" |
| " background-image:url(data:image/png;base64," |
| "iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P" |
| "4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==);" |
| " -proprietary-background-property: url(foo.png);\n" |
| "\"/>", |
| "<div style=\"" |
| "background-image:url(data:image/png;base64," |
| "iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P" |
| "4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==);" |
| "-proprietary-background-property:url(foo.png)" |
| "\"/>"); |
| } |
| |
| // Fallback rewriter tests |
| |
| TEST_F(CssImageRewriterTest, CacheExtendsImagesSimpleFallback) { |
| SetResponseWithDefaultHeaders("foo.png", kContentTypePng, kDummyContent, 100); |
| |
| // Note: Extra }s cause parse failure. |
| static const char css_template[] = |
| "body {\n" |
| " background-image: url(%s);\n" |
| "}}}}}\n"; |
| const GoogleString css_before = StringPrintf(css_template, "foo.png"); |
| const GoogleString css_after = StringPrintf( |
| css_template, Encode("", "ce", "0", "foo.png", "png").c_str()); |
| |
| ValidateRewrite("unparseable", css_before, css_after, |
| kExpectFallback | kNoClearFetcher); |
| } |
| |
| TEST_F(CssImageRewriterTest, CacheExtendsRepeatedTopLevelFallback) { |
| // Test to make sure that if we cache extend inside CSS we can do it |
| // for the same image in HTML at the same time. |
| const char kImg[] = "img.png"; |
| const GoogleString kExtendedImg = Encode("", "ce", "0", "img.png", "png"); |
| |
| const char kCss[] = "stylesheet.css"; |
| const GoogleString kRewrittenCss = |
| Encode("", "cf", "0", "stylesheet.css", "css"); |
| // Note: Extra }s cause parse failure. |
| const char kCssTemplate[] = "body{background-image:url(%s)}}}}}"; |
| |
| SetResponseWithDefaultHeaders(kImg, kContentTypePng, kDummyContent, 100); |
| SetResponseWithDefaultHeaders( |
| kCss, kContentTypeCss, StringPrintf(kCssTemplate, kImg), 100); |
| |
| const char kHtmlTemplate[] = |
| "<link rel='stylesheet' href='%s'>" |
| "<img src='%s'>"; |
| |
| ValidateExpected("repeated_top_level", |
| StringPrintf(kHtmlTemplate, kCss, kImg), |
| StringPrintf(kHtmlTemplate, |
| kRewrittenCss.c_str(), |
| kExtendedImg.c_str())); |
| |
| GoogleString css_out; |
| EXPECT_TRUE(FetchResourceUrl(StrCat(kTestDomain, kRewrittenCss), &css_out)); |
| EXPECT_EQ(StringPrintf(kCssTemplate, kExtendedImg.c_str()), css_out); |
| } |
| |
| TEST_F(CssImageRewriterTest, CacheExtendsImagesFallback) { |
| SetResponseWithDefaultHeaders("foo.png", kContentTypePng, kDummyContent, 100); |
| SetResponseWithDefaultHeaders("bar.png", kContentTypePng, kDummyContent, 100); |
| SetResponseWithDefaultHeaders("baz.png", kContentTypePng, kDummyContent, 100); |
| |
| static const char css_template[] = |
| "body {\n" |
| " background-image: url(%s);\n" |
| " list-style-image: url('%s');\n" |
| "}\n" |
| ".titlebar p.cfoo, #end p {\n" |
| " background: url(\"%s\");\n" |
| " list-style: url('%s');\n" |
| "}\n" |
| ".other {\n" |
| " background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAA" |
| "AUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4" |
| "OHwAAAABJRU5ErkJggg==);" |
| " -proprietary-background-property: url(%s);\n" |
| // Note: Extra }s cause parse failure. |
| "}}}}}}"; |
| const GoogleString css_before = StringPrintf( |
| css_template, "foo.png", "bar.png", "baz.png", "foo.png", "foo.png"); |
| const GoogleString css_after = StringPrintf( |
| css_template, |
| Encode("", "ce", "0", "foo.png", "png").c_str(), |
| Encode("", "ce", "0", "bar.png", "png").c_str(), |
| Encode("", "ce", "0", "baz.png", "png").c_str(), |
| Encode("", "ce", "0", "foo.png", "png").c_str(), |
| Encode("", "ce", "0", "foo.png", "png").c_str()); |
| |
| |
| ValidateRewrite("cache_extends_images", css_before, css_after, |
| kExpectFallback | kNoClearFetcher); |
| } |
| |
| TEST_F(CssImageRewriterTest, RecompressImagesFallback) { |
| options()->ClearSignatureForTesting(); |
| options()->SoftEnableFilterForTesting(RewriteOptions::kRecompressPng); |
| server_context()->ComputeSignature(options()); |
| AddFileToMockFetcher(StrCat(kTestDomain, "foo.png"), kBikePngFile, |
| kContentTypePng, 100); |
| // Note: Extra }s cause parse failure. |
| static const char css_template[] = |
| "body {\n" |
| " background-image: url(%s);\n" |
| "}}}}}\n"; |
| const GoogleString css_before = StringPrintf(css_template, "foo.png"); |
| const GoogleString css_after = StringPrintf( |
| css_template, Encode("", "ic", "0", "foo.png", "png").c_str()); |
| |
| ValidateRewriteExternalCss("recompress_css_images", css_before, css_after, |
| kExpectFallback | kNoClearFetcher); |
| } |
| |
| // Make sure we don't break import URLs or other non-image URLs. |
| TEST_F(CssImageRewriterTest, FallbackImportsAndUnknownContentType) { |
| options()->ClearSignatureForTesting(); |
| options()->SoftEnableFilterForTesting(RewriteOptions::kRecompressPng); |
| server_context()->ComputeSignature(options()); |
| |
| AddFileToMockFetcher(StrCat(kTestDomain, "image.png"), kBikePngFile, |
| kContentTypePng, 100); |
| SetResponseWithDefaultHeaders("style.css", kContentTypeCss, |
| kDummyContent, 100); |
| SetResponseWithDefaultHeaders("zero.css", kContentTypeCss, kDummyContent, 0); |
| SetResponseWithDefaultHeaders("doc.html", kContentTypeHtml, |
| kDummyContent, 100); |
| |
| SetResponseWithDefaultHeaders("behavior.htc", kContentTypeHtc, |
| kDummyContent, 100); |
| SetResponseWithDefaultHeaders("font.ttf", kContentTypeTtf, |
| kDummyContent, 100); |
| SetResponseWithDefaultHeaders("font.eot", kContentTypeEot, |
| kDummyContent, 100); |
| |
| static const char css_template[] = |
| "@import '%s';" |
| // zero.css doesn't get cache extended because it has 0 TTL. |
| "@import url(zero.css);" |
| "@font-face {\n" |
| " font-family: name;\n" |
| // Unapproved Content-Types do not get cache extended. |
| " src: url('font.ttf'), url(font.eot);\n" |
| "}\n" |
| "body {\n" |
| " background-image: url(%s);\n" |
| // Unapproved Content-Types do not get cache extended. |
| " behavior: url(behavior.htc);\n" |
| " -moz-content-file: url(doc.html);\n" |
| // Note: Extra }s cause parse failure. |
| "}}}}}\n"; |
| const GoogleString css_before = StringPrintf( |
| css_template, "style.css", "image.png"); |
| const GoogleString css_after = StringPrintf( |
| css_template, |
| Encode("", "ce", "0", "style.css", "css").c_str(), |
| Encode("", "ic", "0", "image.png", "png").c_str()); |
| |
| ValidateRewriteExternalCss("recompress_css_images", css_before, css_after, |
| kExpectFallback | kNoClearFetcher); |
| } |
| |
| // Test that the fallback fetcher fails smoothly. |
| TEST_F(CssImageRewriterTest, FallbackFails) { |
| // Note: //// is not a valid URL leading to fallback rewrite failure. |
| static const char bad_css[] = ".foo { url(////); }}}}}}"; |
| ValidateRewrite("fallback_fails", bad_css, bad_css, kExpectFailure); |
| } |
| |
| // Check that we absolutify URLs when moving CSS. |
| TEST_F(CssImageRewriterTest, FallbackAbsolutify) { |
| options()->ClearSignatureForTesting(); |
| DomainLawyer* lawyer = options()->WriteableDomainLawyer(); |
| lawyer->AddRewriteDomainMapping("http://new_domain.com", kTestDomain, |
| &message_handler_); |
| // Turn off trimming to make sure we can see full absolutifications. |
| options()->DisableFilter(RewriteOptions::kLeftTrimUrls); |
| server_context()->ComputeSignature(options()); |
| |
| SetResponseWithDefaultHeaders("foo.png", kContentTypePng, kDummyContent, 0); |
| |
| // Note: Extra }s cause parse failure. |
| const char css_template[] = ".foo { background: url(%s); }}}}"; |
| const GoogleString css_before = StringPrintf(css_template, "foo.png"); |
| const GoogleString css_after = StringPrintf(css_template, |
| "http://new_domain.com/foo.png"); |
| |
| // We only test inline CSS because ValidateRewriteExternalCss doesn't work |
| // with AddRewriteDomainMapping. |
| ValidateRewriteInlineCss("change_domain", css_before, css_after, |
| kExpectFallback | kNoClearFetcher); |
| |
| // Test loading from other domains. |
| SetResponseWithDefaultHeaders("other_domain.css", kContentTypeCss, |
| css_before, 100); |
| |
| const char rewritten_url[] = |
| "http://test.com/I.other_domain.css.pagespeed.cf.0.css"; |
| ServeResourceFromManyContexts(rewritten_url, css_after); |
| } |
| |
| // Check that we don't absolutify URLs when not moving them. |
| TEST_F(CssImageRewriterTest, FallbackNoAbsolutify) { |
| options()->ClearSignatureForTesting(); |
| // Turn off trimming to make sure we can see full absolutifications. |
| options()->DisableFilter(RewriteOptions::kLeftTrimUrls); |
| server_context()->ComputeSignature(options()); |
| |
| SetResponseWithDefaultHeaders("foo.png", kContentTypePng, kDummyContent, 0); |
| |
| // Note: Extra }s cause parse failure. |
| const char css[] = ".foo { background: url(foo.png); }}}}"; |
| |
| ValidateRewrite("change_domain", css, css, kExpectFallback | kNoClearFetcher); |
| } |
| |
| // Check that we still absolutify URLs even if we fail to parse CSS while |
| // rewriting on a fetch. This can come up if you have different rewrite |
| // options on the HTML and resources-serving servers or if the resource |
| // changes between the HTML and resource servers (race condition during push). |
| TEST_F(CssImageRewriterTest, FetchRewriteFailure) { |
| options()->ClearSignatureForTesting(); |
| DomainLawyer* lawyer = options()->WriteableDomainLawyer(); |
| lawyer->AddRewriteDomainMapping("http://new_domain.com", kTestDomain, |
| &message_handler_); |
| // Turn off trimming to make sure we can see full absolutifications. |
| options()->DisableFilter(RewriteOptions::kLeftTrimUrls); |
| options()->DisableFilter(RewriteOptions::kFallbackRewriteCssUrls); |
| server_context()->ComputeSignature(options()); |
| |
| SetResponseWithDefaultHeaders("foo.png", kContentTypePng, kDummyContent, 0); |
| |
| // Note: Extra }s cause parse failure. |
| const char css_template[] = ".foo { background: url(%s); }}}}"; |
| const GoogleString css_before = StringPrintf(css_template, "foo.png"); |
| const GoogleString css_after = StringPrintf(css_template, |
| "http://new_domain.com/foo.png"); |
| |
| // Test loading from other domains. |
| SetResponseWithDefaultHeaders("other_domain.css", kContentTypeCss, |
| css_before, 100); |
| |
| GoogleString content; |
| FetchResource(kTestDomain, "cf", "other_domain.css", "css", &content); |
| EXPECT_STREQ(css_after, content); |
| EXPECT_EQ(0, num_fallback_rewrites_->Get()); |
| EXPECT_EQ(1, num_parse_failures_->Get()); |
| |
| // Check that this still works correctly the second time (this loads the |
| // result from cache and so goes through a different code path). |
| content.clear(); |
| FetchResource(kTestDomain, "cf", "other_domain.css", "css", &content); |
| EXPECT_STREQ(css_after, content); |
| } |
| |
| TEST_F(CssImageRewriterTest, DummyRuleset) { |
| // Simplified version of CacheExtendsImages, which doesn't have many copies of |
| // the same URL. |
| SetResponseWithDefaultHeaders("foo.png", kContentTypePng, kDummyContent, 100); |
| |
| static const char css_before[] = |
| "@font-face { font-family: 'Robotnik'; font-style: normal }\n" |
| "body {\n" |
| " background-image: url(foo.png);\n" |
| "}\n" |
| "@to-infinity and beyond;\n"; |
| const GoogleString css_after = |
| StrCat("@font-face{font-family:'Robotnik';font-style:normal}" |
| "body{background-image:url(", |
| Encode("", "ce", "0", "foo.png", "png"), |
| ")}@to-infinity and beyond;"); |
| |
| ValidateRewrite("cache_extends_images", css_before, css_after, |
| kExpectSuccess | kNoClearFetcher); |
| } |
| |
| class CssRecompressImagesInStyleAttributes : public RewriteTestBase { |
| protected: |
| CssRecompressImagesInStyleAttributes() |
| : div_before_( |
| "<div style=\"" |
| "background-image:url(foo.png)" |
| "\"/>") {} |
| |
| virtual void SetUp() { |
| RewriteTestBase::SetUp(); |
| options()->EnableFilter(RewriteOptions::kRewriteCss); |
| options()->EnableFilter(RewriteOptions::kFallbackRewriteCssUrls); |
| options()->set_always_rewrite_css(true); |
| AddFileToMockFetcher(StrCat(kTestDomain, "foo.png"), kBikePngFile, |
| kContentTypePng, 100); |
| div_after_ = StrCat( |
| "<div style=\"" |
| "background-image:url(", |
| Encode("", "ic", "0", "foo.png", "png"), |
| ")" |
| "\"/>"); |
| } |
| |
| // Rewrites HTML, scans the rewritten HTML for a substring, and returns |
| // the number of image rewrites that were executed. |
| int64 TestInlineWithUA(StringPiece id, const GoogleString& html_input, |
| const GoogleString& expected_substring, |
| StringPiece user_agent) { |
| Variable* image_rewrite_count = statistics()->GetVariable( |
| ImageRewriteFilter::kImageRewrites); |
| int64 start_image_rewrites = image_rewrite_count->Get(); |
| output_buffer_.clear(); |
| ClearRewriteDriver(); |
| if (user_agent == "webp") { |
| SetupForWebp(); |
| } else { |
| SetCurrentUserAgent(user_agent); |
| } |
| Parse(id, html_input); |
| EXPECT_TRUE(output_buffer_.find(expected_substring) != GoogleString::npos) |
| << expected_substring; |
| return image_rewrite_count->Get() - start_image_rewrites; |
| } |
| |
| GoogleString div_before_; |
| GoogleString div_after_; |
| }; |
| |
| // No rewriting if neither option is enabled. |
| TEST_F(CssRecompressImagesInStyleAttributes, NeitherEnabled) { |
| ValidateNoChanges("options_disabled", div_before_); |
| } |
| |
| // No rewriting if only 'style' is enabled. |
| TEST_F(CssRecompressImagesInStyleAttributes, OnlyStyleEnabled) { |
| AddFilter(RewriteOptions::kRewriteStyleAttributesWithUrl); |
| ValidateNoChanges("recompress_images_disabled", div_before_); |
| } |
| |
| // No rewriting if only 'recompress' is enabled. |
| TEST_F(CssRecompressImagesInStyleAttributes, OnlyRecompressEnabled) { |
| AddRecompressImageFilters(); |
| rewrite_driver()->AddFilters(); |
| ValidateNoChanges("recompress_images_disabled", div_before_); |
| } |
| |
| // Rewrite iff both options are enabled. |
| TEST_F(CssRecompressImagesInStyleAttributes, RecompressAndStyleEnabled) { |
| options()->EnableFilter(RewriteOptions::kRecompressPng); |
| options()->EnableFilter(RewriteOptions::kRewriteStyleAttributesWithUrl); |
| rewrite_driver()->AddFilters(); |
| ValidateExpected("options_enabled", div_before_, div_after_); |
| } |
| |
| TEST_F(CssRecompressImagesInStyleAttributes, RecompressAndWebpAndStyleEnabled) { |
| if (RunningOnValgrind()) { // Too slow under vg. |
| return; |
| } |
| |
| AddFileToMockFetcher(StrCat(kTestDomain, "foo.jpg"), kPuzzleJpgFile, |
| kContentTypeJpeg, 100); |
| options()->EnableFilter(RewriteOptions::kConvertJpegToWebp); |
| options()->EnableFilter(RewriteOptions::kRecompressJpeg); |
| options()->EnableFilter(RewriteOptions::kRewriteStyleAttributesWithUrl); |
| options()->set_image_jpeg_recompress_quality(85); |
| SetupForWebp(); |
| rewrite_driver()->AddFilters(); |
| ValidateExpected("webp", |
| "<div style=\"background-image:url(foo.jpg)\"/>", |
| "<div style=\"background-image:url(xfoo.jpg.pagespeed.ic.0.webp)\"/>"); |
| } |
| |
| TEST_F(CssRecompressImagesInStyleAttributes, |
| RecompressAndWebpLosslessAndStyleEnabled) { |
| if (RunningOnValgrind()) { // Too slow under vg. |
| return; |
| } |
| |
| AddFileToMockFetcher(StrCat(kTestDomain, "foo.jpg"), kPuzzleJpgFile, |
| kContentTypeJpeg, 100); |
| options()->EnableFilter(RewriteOptions::kConvertJpegToWebp); |
| options()->EnableFilter(RewriteOptions::kRecompressJpeg); |
| options()->EnableFilter(RewriteOptions::kRewriteStyleAttributesWithUrl); |
| options()->set_image_jpeg_recompress_quality(85); |
| SetupForWebpLossless(); |
| rewrite_driver()->AddFilters(); |
| ValidateExpected("webp-lossless", |
| "<div style=\"background-image:url(foo.jpg)\"/>", |
| "<div style=\"background-image:url(xfoo.jpg.pagespeed.ic.0.webp)\"/>"); |
| } |
| |
| // TODO(huibao) Remove test cases RecompressAndWebpAndStyleEnabledWithMaxCssSize |
| // and RecompressAndWebpLosslessAndStyleEnabledWithMaxCssSize, when |
| // option max_image_bytes_for_webp_in_css is removed. |
| TEST_F(CssRecompressImagesInStyleAttributes, |
| RecompressAndWebpAndStyleEnabledWithMaxCssSize) { |
| AddFileToMockFetcher(StrCat(kTestDomain, "foo.jpg"), kPuzzleJpgFile, |
| kContentTypeJpeg, 100); |
| options()->EnableFilter(RewriteOptions::kConvertJpegToWebp); |
| options()->EnableFilter(RewriteOptions::kRecompressJpeg); |
| options()->EnableFilter(RewriteOptions::kRewriteStyleAttributesWithUrl); |
| options()->set_image_jpeg_recompress_quality(85); |
| options()->set_max_image_bytes_for_webp_in_css(1); // No effect |
| SetupForWebp(); |
| rewrite_driver()->AddFilters(); |
| ValidateExpected("webp", |
| "<div style=\"background-image:url(foo.jpg)\"/>", |
| "<div style=\"background-image:url(xfoo.jpg.pagespeed.ic.0.webp)\"/>"); |
| } |
| |
| TEST_F(CssRecompressImagesInStyleAttributes, |
| RecompressAndWebpLosslessAndStyleEnabledWithMaxCssSize) { |
| AddFileToMockFetcher(StrCat(kTestDomain, "foo.jpg"), kPuzzleJpgFile, |
| kContentTypeJpeg, 100); |
| options()->EnableFilter(RewriteOptions::kConvertJpegToWebp); |
| options()->EnableFilter(RewriteOptions::kRecompressJpeg); |
| options()->EnableFilter(RewriteOptions::kRewriteStyleAttributesWithUrl); |
| options()->set_image_jpeg_recompress_quality(85); |
| options()->set_max_image_bytes_for_webp_in_css(1); // No effect |
| SetupForWebpLossless(); |
| rewrite_driver()->AddFilters(); |
| ValidateExpected("webp-lossless", |
| "<div style=\"background-image:url(foo.jpg)\"/>", |
| "<div style=\"background-image:url(xfoo.jpg.pagespeed.ic.0.webp)\"/>"); |
| } |
| |
| // https://code.google.com/p/modpagespeed/issues/detail?id=781 |
| TEST_F(CssRecompressImagesInStyleAttributes, ServeCssToDifferentUA) { |
| AddFileToMockFetcher(StrCat(kTestDomain, "bike.png"), kBikePngFile, |
| kContentTypePng, 100); |
| options()->EnableFilter(RewriteOptions::kInlineImages); |
| options()->EnableFilter(RewriteOptions::kConvertPngToJpeg); |
| options()->EnableFilter(RewriteOptions::kConvertJpegToWebp); |
| options()->EnableFilter(RewriteOptions::kRecompressJpeg); |
| options()->EnableFilter(RewriteOptions::kRewriteStyleAttributesWithUrl); |
| options()->set_image_jpeg_recompress_quality(85); |
| options()->set_image_inline_max_bytes(10000); |
| options()->set_max_image_bytes_for_webp_in_css(100000); |
| options()->set_css_image_inline_max_bytes(10000); |
| rewrite_driver()->AddFilters(); |
| |
| const char kHtmlInput[] = "<div style=\"background-image:url(bike.png)\"/>"; |
| const char kJpegFile[] = "<div style=\"background-image:url(" |
| "xbike.png.pagespeed.ic.0.jpg)\"/>"; |
| const char kJpegInline[] = "background-image:url(data:image/jpeg;base64"; |
| const char kWebpInline[] = "background-image:url(data:image/webp;base64"; |
| const char* kIE7 = UserAgentMatcherTestBase::kIe7UserAgent; |
| const char* kIE9 = UserAgentMatcherTestBase::kIe9UserAgent; |
| const char* kFF = UserAgentMatcherTestBase::kFirefoxUserAgent; |
| const char* kWebp = UserAgentMatcherTestBase::kTestingWebp; |
| |
| // Test to make sure we don't poison the cache with metadata from |
| // the wrong UA by making sure we get the expected results in each |
| // order. The return value of TestInlineWithUA is the number of |
| // image-rewrites that were executed. Note that ie7, ie9, and ff |
| // each share the same rewritten jpeg file, though ie7 can't inline |
| // it. Thus after clearing cache, we should expect to see exactly |
| // two new image rewrites: 1 for jpeg, one for webp. |
| EXPECT_EQ(1, TestInlineWithUA("ie7", kHtmlInput, kJpegFile, kIE7)); |
| EXPECT_EQ(0, TestInlineWithUA("ff", kHtmlInput, kJpegInline, kFF)); |
| EXPECT_EQ(0, TestInlineWithUA("ie9", kHtmlInput, kJpegInline, kIE9)); |
| EXPECT_EQ(1, TestInlineWithUA("webp", kHtmlInput, kWebpInline, kWebp)); |
| |
| lru_cache()->Clear(); |
| EXPECT_EQ(1, TestInlineWithUA("ie7", kHtmlInput, kJpegFile, kIE7)); |
| EXPECT_EQ(1, TestInlineWithUA("webp", kHtmlInput, kWebpInline, kWebp)); |
| EXPECT_EQ(0, TestInlineWithUA("ff", kHtmlInput, kJpegInline, kFF)); |
| EXPECT_EQ(0, TestInlineWithUA("ie9", kHtmlInput, kJpegInline, kIE9)); |
| |
| lru_cache()->Clear(); |
| EXPECT_EQ(1, TestInlineWithUA("ff", kHtmlInput, kJpegInline, kFF)); |
| EXPECT_EQ(0, TestInlineWithUA("ie9", kHtmlInput, kJpegInline, kIE9)); |
| EXPECT_EQ(0, TestInlineWithUA("ie7", kHtmlInput, kJpegFile, kIE7)); |
| EXPECT_EQ(1, TestInlineWithUA("webp", kHtmlInput, kWebpInline, kWebp)); |
| |
| lru_cache()->Clear(); |
| EXPECT_EQ(1, TestInlineWithUA("ff", kHtmlInput, kJpegInline, kFF)); |
| EXPECT_EQ(0, TestInlineWithUA("ie9", kHtmlInput, kJpegInline, kIE9)); |
| EXPECT_EQ(1, TestInlineWithUA("webp", kHtmlInput, kWebpInline, kWebp)); |
| EXPECT_EQ(0, TestInlineWithUA("ie7", kHtmlInput, kJpegFile, kIE7)); |
| |
| lru_cache()->Clear(); |
| EXPECT_EQ(1, TestInlineWithUA("webp", kHtmlInput, kWebpInline, kWebp)); |
| EXPECT_EQ(1, TestInlineWithUA("ie7", kHtmlInput, kJpegFile, kIE7)); |
| EXPECT_EQ(0, TestInlineWithUA("ff", kHtmlInput, kJpegInline, kFF)); |
| EXPECT_EQ(0, TestInlineWithUA("ie9", kHtmlInput, kJpegInline, kIE9)); |
| |
| lru_cache()->Clear(); |
| EXPECT_EQ(1, TestInlineWithUA("webp", kHtmlInput, kWebpInline, kWebp)); |
| EXPECT_EQ(1, TestInlineWithUA("ff", kHtmlInput, kJpegInline, kFF)); |
| EXPECT_EQ(0, TestInlineWithUA("ie9", kHtmlInput, kJpegInline, kIE9)); |
| EXPECT_EQ(0, TestInlineWithUA("ie7", kHtmlInput, kJpegFile, kIE7)); |
| } |
| |
| TEST_F(CssImageRewriterTest, DebugMessage) { |
| options()->ClearSignatureForTesting(); |
| options()->set_image_inline_max_bytes(20000); |
| options()->SoftEnableFilterForTesting(RewriteOptions::kDebug); |
| options()->SoftEnableFilterForTesting(RewriteOptions::kInlineImages); |
| options()->SoftEnableFilterForTesting(RewriteOptions::kRecompressPng); |
| server_context()->ComputeSignature(options()); |
| AddFileToMockFetcher(StrCat(kTestDomain, "foo.png"), kBikePngFile, |
| kContentTypePng, 100); |
| static const char kCss[] = |
| "body {\n" |
| " background-image: url(foo.png);\n" |
| "}\n"; |
| |
| const GoogleString kCssAfter = StrCat( |
| "body{background-image:url(", |
| Encode("", "ic", "0", "foo.png", "png"), |
| ")}"); |
| |
| debug_message_ = |
| "<!--Image does not appear to need resizing.-->" |
| "<!--Image has no transparent pixels, is not sensitive to compression " |
| "noise, and has no animation.-->" |
| "<!--The image was not inlined because it has too many bytes.-->"; |
| ValidateRewriteInlineCss("recompress_css_images", kCss, kCssAfter, |
| kNoStatCheck | kExpectCached); |
| } |
| |
| // This inherits off CssRewriteTestBase and not CssImageRewriterTest to |
| // skip the weird filter twiddling that does to test preserve_urls(), |
| // which doesn't really work for an experimental flag. |
| class CssImageRewriterMetricsTest : public CssRewriteTestBase { |
| public: |
| virtual void SetUp() { |
| options()->EnableFilter(RewriteOptions::kExperimentCollectMobImageInfo); |
| options()->EnableFilter(RewriteOptions::kRecompressPng); |
| options()->EnableFilter(RewriteOptions::kRecompressJpeg); |
| // Recursively process other CSS but not actually flatten. |
| options()->EnableFilter(RewriteOptions::kFlattenCssImports); |
| options()->set_css_flatten_max_bytes(1); |
| CssRewriteTestBase::SetUp(); |
| } |
| }; |
| |
| TEST_F(CssImageRewriterMetricsTest, ReportDimensionsToJs) { |
| AddFileToMockFetcher(StrCat(kTestDomain, "a.png"), kBikePngFile, |
| kContentTypePng, 100); |
| AddFileToMockFetcher(StrCat(kTestDomain, "b.jpg"), kPuzzleJpgFile, |
| kContentTypeJpeg, 100); |
| |
| const char kOuterCss[] = |
| "@import url(inner.css); * { background-image: url(a.png) }"; |
| const char kInnerCss[] = |
| "* { background-image: url(b.jpg) }"; |
| SetResponseWithDefaultHeaders( |
| "outer.css", kContentTypeCss, kOuterCss, 100); |
| SetResponseWithDefaultHeaders( |
| "inner.css", kContentTypeCss, kInnerCss, 100); |
| GoogleString css_out_url(Encode("", "cf", "0", "outer.css", "css")); |
| GoogleString a_out_url(Encode(kTestDomain, "ic", "0", "a.png", "png")); |
| GoogleString b_out_url(Encode(kTestDomain, "ic", "0", "b.jpg", "jpg")); |
| GoogleString js = StrCat( |
| "psMobStaticImageInfo = {" |
| "\"", a_out_url, "\":{w:100,h:100}," |
| "\"", b_out_url, "\":{w:1023,h:766},}"); |
| |
| // We write directly since default framework makes assumptions involving |
| // newlines it appends that don't work with things inserted at body end. |
| SetupWriter(); |
| rewrite_driver()->StartParse(StrCat(kTestDomain, "dims.html")); |
| rewrite_driver()->ParseText(CssLinkHref("outer.css")); |
| rewrite_driver()->FinishParse(); |
| |
| EXPECT_EQ(StrCat(CssLinkHref(css_out_url), "<script>", js, "</script>"), |
| output_buffer_); |
| } |
| |
| } // namespace |
| |
| } // namespace net_instaweb |