| /* |
| * Copyright 2013 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: jmarantz@google.com (Joshua Marantz) |
| |
| #include <cstddef> |
| |
| #include "net/instaweb/rewriter/public/css_rewrite_test_base.h" |
| #include "net/instaweb/rewriter/public/rewrite_options.h" |
| #include "pagespeed/kernel/base/dynamic_annotations.h" // RunningOnValgrind |
| #include "pagespeed/kernel/base/gtest.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/response_headers.h" |
| |
| namespace { |
| |
| const char kBikePngFile[] = "BikeCrashIcn.png"; |
| const char kCuppaPngFile[] = "Cuppa.png"; |
| const char kDummyContent[] = "Invalid PNG but it does not matter for this test"; |
| const char kPuzzleJpgFile[] = "Puzzle.jpg"; |
| const char kEmbedCss[] = "embed.css"; |
| |
| } // namespace |
| |
| namespace net_instaweb { |
| |
| // Test infrastructure for css files with encoded options. Note that all |
| // the image-related options can affect the output hash of the CSS |
| // file. |
| // |
| // Note that it is not practical to test the hash of the images or the |
| // CSS that references them because the image algorithms produce |
| // different bits on different platforms. |
| class CssEmbeddedConfigTest : public CssRewriteTestBase { |
| protected: |
| virtual void SetUp() { |
| // Don't call CssRewriteTestBase::SetUp() here because that calls AddFilter |
| // and makes it inconvenient for us to add more. Instead each test method |
| // should call AddFilterAndSetup. |
| options()->set_add_options_to_urls(true); |
| |
| SetResponseWithDefaultHeaders("foo.png", kContentTypePng, kDummyContent, |
| 100); |
| css_url_ = StrCat(kTestDomain, kEmbedCss); |
| html_input_ = MakeHtmlWithExternalCssLink(css_url_, 0, false); |
| } |
| |
| // Rewrites an image in a CSS file and returns the resulting CSS filename. |
| GoogleString RewriteImageInCss(StringPiece image_url_in) { |
| SetResponseWithDefaultHeaders(css_url_, kContentTypeCss, |
| MakeIndentedCssWithImage(image_url_in), 300); |
| |
| ParseUrl(StrCat(kTestDomain, "embed_config.html"), html_input_); |
| StringVector css_links; |
| CollectCssLinks("embedded_config", output_buffer_, &css_links); |
| if (css_links.size() != 1) { |
| return ""; |
| } |
| return css_links[0]; |
| } |
| |
| GoogleString ExtractImageFromCssFilename(StringPiece css_link) { |
| // Fetch the resultant CSS file. |
| GoogleString css_out; |
| ResponseHeaders css_headers, image_headers; |
| ClearStats(); |
| EXPECT_TRUE(FetchResourceUrl(css_link, &css_out, &css_headers)); |
| EXPECT_EQ(1, lru_cache()->num_hits()); |
| EXPECT_EQ(0, lru_cache()->num_misses()); |
| ClearStats(); |
| |
| // Find the image URL in the css output. |
| size_t pos = css_out.find("url("); |
| if (pos != GoogleString::npos) { |
| pos += 4; // skip past "url(". |
| size_t end_pos = css_out.find(")", pos); |
| if (end_pos != GoogleString::npos) { |
| return css_out.substr(pos, end_pos - pos); |
| } |
| } |
| return ""; |
| } |
| |
| GoogleString FetchImageFromCache(StringPiece image_url) { |
| ResponseHeaders image_headers; |
| GoogleString image; |
| EXPECT_TRUE(FetchResourceUrl(image_url, &image, &image_headers)); |
| bool is_on_the_fly = image_url.find(".ce.") != StringPiece::npos; |
| if (is_on_the_fly) { |
| EXPECT_EQ(2, lru_cache()->num_hits()); |
| } else { |
| EXPECT_EQ(1, lru_cache()->num_hits()); |
| } |
| EXPECT_EQ(0, lru_cache()->num_misses()); |
| return image; |
| } |
| |
| void AddFilterAndSetup(RewriteOptions::Filter filter) { |
| // We setup the options before the upcall so that the |
| // CSS filter is created aware of these. |
| options()->EnableFilter(filter); |
| CssRewriteTestBase::SetUp(); |
| } |
| |
| GoogleString EncodedImageUrl(StringPiece image_name, |
| StringPiece ext, |
| StringPiece option_segment) { |
| GoogleString image_url = Encode("", "ic", "0", image_name, ext); |
| return AddOptionsToEncodedUrl(image_url, option_segment); |
| } |
| |
| GoogleString EncodedCssUrl(StringPiece option_segment) { |
| // Original CSS file had absolute path, so rewritten one does too. |
| GoogleString css_link = Encode(kTestDomain, "cf", "0", kEmbedCss, "css"); |
| return AddOptionsToEncodedUrl(css_link, option_segment); |
| } |
| |
| GoogleString css_url_; |
| GoogleString html_input_; |
| }; |
| |
| TEST_F(CssEmbeddedConfigTest, CacheExtend) { |
| AddFilterAndSetup(RewriteOptions::kExtendCacheImages); |
| GoogleString css_link = RewriteImageInCss("foo.png"); |
| EXPECT_STREQ(EncodedCssUrl("ei"), css_link); |
| GoogleString image_url = ExtractImageFromCssFilename(css_link); |
| EXPECT_STREQ(Encode("", "ce", "0", "foo.png", "png"), image_url); |
| EXPECT_STREQ(kDummyContent, |
| FetchImageFromCache(StrCat(kTestDomain, image_url))); |
| } |
| |
| TEST_F(CssEmbeddedConfigTest, RewriteJpeg) { |
| options()->set_image_jpeg_recompress_quality(81); |
| AddFilterAndSetup(RewriteOptions::kRecompressJpeg); |
| AddFileToMockFetcher(StrCat(kTestDomain, kPuzzleJpgFile), kPuzzleJpgFile, |
| kContentTypeJpeg, 100); |
| GoogleString css_link = RewriteImageInCss(kPuzzleJpgFile); |
| EXPECT_STREQ(EncodedCssUrl("rj+iq=81"), css_link); |
| GoogleString image_url = ExtractImageFromCssFilename(css_link); |
| EXPECT_STREQ(EncodedImageUrl(kPuzzleJpgFile, "jpg", "rj+iq=81"), |
| image_url); |
| EXPECT_GE(103704, FetchImageFromCache(StrCat(kTestDomain, image_url)).size()); |
| } |
| |
| TEST_F(CssEmbeddedConfigTest, RewriteJpegProgressive) { |
| options()->set_image_jpeg_recompress_quality(81); |
| options()->EnableFilter(RewriteOptions::kConvertJpegToProgressive); |
| AddFilterAndSetup(RewriteOptions::kRecompressJpeg); |
| AddFileToMockFetcher(StrCat(kTestDomain, kPuzzleJpgFile), kPuzzleJpgFile, |
| kContentTypeJpeg, 100); |
| GoogleString css_link = RewriteImageInCss(kPuzzleJpgFile); |
| EXPECT_STREQ(EncodedCssUrl("jp+rj+iq=81"), css_link); |
| GoogleString image_url = ExtractImageFromCssFilename(css_link); |
| EXPECT_STREQ(EncodedImageUrl(kPuzzleJpgFile, "jpg", "jp+rj+iq=81"), |
| image_url); |
| EXPECT_GE(100349, FetchImageFromCache(StrCat(kTestDomain, image_url)).size()); |
| } |
| |
| TEST_F(CssEmbeddedConfigTest, InlineImageToCss) { |
| options()->set_css_image_inline_max_bytes(2048); |
| AddFilterAndSetup(RewriteOptions::kInlineImages); |
| AddFileToMockFetcher(StrCat(kTestDomain, kCuppaPngFile), kCuppaPngFile, |
| kContentTypePng, 100); |
| GoogleString css_link = RewriteImageInCss(kCuppaPngFile); |
| EXPECT_STREQ(EncodedCssUrl("ii+cii=2048"), css_link); |
| GoogleString image_url = ExtractImageFromCssFilename(css_link); |
| EXPECT_TRUE(StringPiece(image_url).starts_with("data:image/png;base64,")); |
| } |
| |
| TEST_F(CssEmbeddedConfigTest, InlineImageToCssSmallThresholdExtend) { |
| options()->set_css_image_inline_max_bytes(5); // prevents inlining cuppa.png |
| options()->EnableFilter(RewriteOptions::kExtendCacheImages); |
| AddFilterAndSetup(RewriteOptions::kInlineImages); |
| AddFileToMockFetcher(StrCat(kTestDomain, kCuppaPngFile), kCuppaPngFile, |
| kContentTypePng, 100); |
| GoogleString css_link = RewriteImageInCss(kCuppaPngFile); |
| EXPECT_STREQ(EncodedCssUrl("ei+ii+cii=5"), css_link); |
| GoogleString image_url = ExtractImageFromCssFilename(css_link); |
| EXPECT_STREQ(Encode("", "ce", "0", kCuppaPngFile, "png"), image_url); |
| EXPECT_EQ(1763, FetchImageFromCache(StrCat(kTestDomain, image_url)).size()); |
| } |
| |
| TEST_F(CssEmbeddedConfigTest, InlineImageToCssSmallThresholdCompress) { |
| options()->set_css_image_inline_max_bytes(5); // prevents inlining |
| options()->set_image_jpeg_recompress_quality(60); |
| options()->EnableFilter(RewriteOptions::kRecompressJpeg); |
| AddFilterAndSetup(RewriteOptions::kInlineImages); |
| AddFileToMockFetcher(StrCat(kTestDomain, kPuzzleJpgFile), kPuzzleJpgFile, |
| kContentTypeJpeg, 100); |
| GoogleString css_link = RewriteImageInCss(kPuzzleJpgFile); |
| EXPECT_STREQ(EncodedCssUrl("ii+rj+cii=5+iq=60"), css_link); |
| GoogleString image_url = ExtractImageFromCssFilename(css_link); |
| EXPECT_STREQ(EncodedImageUrl(kPuzzleJpgFile, "jpg", "rj+iq=60"), image_url); |
| EXPECT_GE(67113, FetchImageFromCache(StrCat(kTestDomain, image_url)).size()); |
| } |
| |
| TEST_F(CssEmbeddedConfigTest, InlineImageToCssSmallTranscode) { |
| options()->set_image_webp_recompress_quality(60); |
| options()->EnableFilter(RewriteOptions::kConvertJpegToWebp); |
| AddFilterAndSetup(RewriteOptions::kInlineImages); |
| if (RunningOnValgrind()) { // Too slow under vg; must call Setup first. |
| return; |
| } |
| SetupForWebp(); |
| AddFileToMockFetcher(StrCat(kTestDomain, kPuzzleJpgFile), kPuzzleJpgFile, |
| kContentTypeJpeg, 100); |
| GoogleString css_link = RewriteImageInCss(kPuzzleJpgFile); |
| EXPECT_STREQ(EncodedCssUrl("jw+ii+iw=60"), css_link); |
| GoogleString image_url = ExtractImageFromCssFilename(css_link); |
| EXPECT_STREQ(EncodedImageUrl(kPuzzleJpgFile, "webp", "jw+iw=60"), image_url); |
| EXPECT_GE(36350, FetchImageFromCache(StrCat(kTestDomain, image_url)).size()); |
| } |
| |
| } // namespace net_instaweb |