blob: c76a9505fd20044f236c0315db12df9579442332 [file] [log] [blame]
/*
* 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