blob: 19c6bf67dab3055fc4d328caf346f8d54d27b3fe [file] [log] [blame]
/*
* Copyright 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Author: sligocki@google.com (Shawn Ligocki)
#include <algorithm>
#include <memory>
#include "net/instaweb/rewriter/public/css_rewrite_test_base.h"
#include "net/instaweb/rewriter/public/rewrite_options.h"
#include "pagespeed/kernel/base/gtest.h"
#include "pagespeed/kernel/base/string.h"
#include "pagespeed/kernel/base/string_util.h"
#include "pagespeed/kernel/html/html_parse_test_base.h"
#include "pagespeed/kernel/http/content_type.h"
#include "pagespeed/kernel/http/http_names.h"
#include "pagespeed/kernel/http/response_headers.h"
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 kChefGifFile[] = "IronChef2.gif";
const char* kHtmlTemplate3Divs = "<head><style>"
"#div1{background:url(%s) 0 0;width:10px;height:10px}"
"#div2{background:url(%s) 0 %s;width:%s;height:10px}"
"#div3{background:url(%s) 0 %s;width:10px;height:10px}"
"</style></head>";
// Image spriting tests.
class CssImageCombineTest : public CssRewriteTestBase {
protected:
virtual void SetUp() {
// We setup the options before the upcall so that the
// CSS filter is created aware of these.
options()->EnableFilter(RewriteOptions::kSpriteImages);
CssRewriteTestBase::SetUp();
AddFileToMockFetcher(StrCat(kTestDomain, kBikePngFile), kBikePngFile,
kContentTypePng, 100);
AddFileToMockFetcher(StrCat(kTestDomain, kCuppaPngFile), kCuppaPngFile,
kContentTypePng, 100);
AddFileToMockFetcher(StrCat(kTestDomain, kPuzzleJpgFile), kPuzzleJpgFile,
kContentTypeJpeg, 100);
AddFileToMockFetcher(StrCat(kTestDomain, kChefGifFile), kChefGifFile,
kContentTypeGif, 100);
}
void TestSpriting(const char* bike_position, const char* expected_position,
bool should_sprite) {
const GoogleString sprite_string =
Encode("", "is", "0", MultiUrl(kCuppaPngFile, kBikePngFile), "png");
const char* sprite = sprite_string.c_str();
// The JPEG will not be included in the sprite because we only handle PNGs.
const char* html = "<head><style>"
"#div1{background-image:url(%s);"
"background-position:0 0;width:10px;height:10px}"
"#div2{background:transparent url(%s);"
"background-position:%s;width:10px;height:10px}"
"#div3{background-image:url(%s);width:10px;height:10px}"
"</style></head>";
GoogleString before = StringPrintf(
html, kCuppaPngFile, kBikePngFile, bike_position, kPuzzleJpgFile);
GoogleString after = StringPrintf(
html, sprite, sprite, expected_position, kPuzzleJpgFile);
ValidateExpected("sprites_images", before, should_sprite ? after : before);
// Try it again, this time using the background shorthand with a couple
// different orderings
const char* html2 = "<head><style>"
"#div1{background:0 0 url(%s) no-repeat transparent scroll;"
"width:10px;height:10px}"
"#div2{background:url(%s) %s repeat fixed;width:10px;height:10px}"
"#div3{background-image:url(%s);width:10px;height:10px}"
"</style></head>";
before = StringPrintf(
html2, kCuppaPngFile, kBikePngFile, bike_position, kPuzzleJpgFile);
after = StringPrintf(
html2, sprite, sprite, expected_position, kPuzzleJpgFile);
ValidateExpected("sprites_images", before, should_sprite ? after : before);
}
};
TEST_F(CssImageCombineTest, SpritesImages) {
// For each of these, expect the following:
// If spriting is possible, the first image (Cuppa.png)
// ends up on top and the second image (BikeCrashIcn.png) ends up on the
// bottom.
// Cuppa.png 65px wide by 70px high.
// BikeCrashIcn.png is 100px wide by 100px high.
// Therefore if you want to see just BikeCrashIcn.png, you need to
// align the image 70px above the div (i.e. -70px).
// All the divs are 10px by 10px (which affects the resulting
// alignments).
TestSpriting("0 0", "0 -70px", true);
TestSpriting("left top", "0 -70px", true);
TestSpriting("top 10px", "10px -70px", true);
// TODO(nforman): Have spriting reject this since the 5px will
// display part of the image above this one.
TestSpriting("-5px 5px", "-5px -65px", true);
// We want pixels 45 to 55 out of the image, therefore align the image
// 45 pixels to the left of the div.
TestSpriting("center top", "-45px -70px", true);
// Same as above, but this time select the middle 10 pixels vertically,
// as well (45 to 55, but offset by 70 for the image above).
TestSpriting("center center", "-45px -115px", true);
// We want the bottom, right corner of the image, i.e. pixels
// 90 to 100 (both vertically and horizontally), so align the image
// 90 pixels to the left and 160 pixels (70 from Cuppa.png) above.
TestSpriting("right bottom", "-90px -160px", true);
// Here we need the vertical center (45 to 55, plus the 70 offset),
// and the horizontal right (90 to 100).
TestSpriting("center right", "-90px -115px", true);
// This is equivalent to "center right".
TestSpriting("right", "-90px -115px", true);
// This is equivalent to "top center".
TestSpriting("top", "-45px -70px", true);
}
// Image spriting tests with debug enabled.
class CssImageCombineUnauthorizedTest : public CssRewriteTestBase {
protected:
virtual void SetUp() {
options()->EnableFilter(RewriteOptions::kDebug);
options()->EnableFilter(RewriteOptions::kSpriteImages);
CssRewriteTestBase::SetUp();
AddFileToMockFetcher(StrCat("http://unauth.com/", kBikePngFile),
kBikePngFile, kContentTypePng, 100);
AddFileToMockFetcher(StrCat(kTestDomain, kCuppaPngFile), kCuppaPngFile,
kContentTypePng, 100);
AddFileToMockFetcher(StrCat(kTestDomain, kPuzzleJpgFile), kPuzzleJpgFile,
kContentTypeJpeg, 100);
}
};
TEST_F(CssImageCombineTest, UnauthorizedDomain) {
const GoogleString bike_path = StrCat("http://unauth.com/", kBikePngFile);
AddFileToMockFetcher(bike_path, kBikePngFile, kContentTypePng, 100);
options()->ClearSignatureForTesting();
options()->EnableFilter(RewriteOptions::kDebug);
options()->ComputeSignature();
const GoogleString kDebugMessage = StrCat(
"<!--Flattening failed: Cannot rewrite ", bike_path,
" as it is on an unauthorized domain-->");
const char kDebugStatistics[] = "";
const char* html = "<head><style>"
"#div2{background:transparent url(%s);"
"background-position:0 0;width:10px;height:10px}"
"</style>%s</head>%s";
GoogleString before = StringPrintf(html, bike_path.c_str(), "", "");
GoogleString after = StringPrintf(html, bike_path.c_str(),
kDebugMessage.c_str(), kDebugStatistics);
ValidateExpected("unauthorized_domain", before, after);
}
class CssImageCombineTestCustomOptions : public CssImageCombineTest {
protected:
// Derived classes should set their options and then call
// CssImageCombineTest::SetUp().
virtual void SetUp() {}
};
TEST_F(CssImageCombineTest, SpritesMultiple) {
GoogleString before, after, sprite;
// With the same image present 3 times, there should be no sprite.
before = StringPrintf(kHtmlTemplate3Divs, kBikePngFile,
kBikePngFile, "0", "10px", kBikePngFile, "0");
ValidateNoChanges("no_sprite_3_bikes", before);
// With 2 of the same and 1 different, there should be a sprite without
// duplication.
before = StringPrintf(kHtmlTemplate3Divs, kBikePngFile,
kBikePngFile, "0", "10px", kCuppaPngFile, "0");
sprite = Encode("", "is", "0",
MultiUrl(kBikePngFile, kCuppaPngFile), "png").c_str();
after = StringPrintf(kHtmlTemplate3Divs, sprite.c_str(),
sprite.c_str(), "0", "10px", sprite.c_str(), "-100px");
ValidateExpected("sprite_2_bikes_1_cuppa", before, after);
// If the second occurrence of the image is unspriteable (e.g. if the div is
// larger than the image), then don't sprite anything.
before = StringPrintf(kHtmlTemplate3Divs, kBikePngFile,
kBikePngFile, "0", "999px", kCuppaPngFile, "0");
ValidateNoChanges("sprite_none_dimensions", before);
}
// Try the last test from SpritesMultiple with a cold cache.
TEST_F(CssImageCombineTest, NoSpritesMultiple) {
// If the second occurrence of the image is unspriteable (e.g. if the div is
// larger than the image), then don't sprite anything.
GoogleString in_text =
StringPrintf(kHtmlTemplate3Divs, kBikePngFile,
kBikePngFile, "0", "999px", kCuppaPngFile, "0");
ValidateNoChanges("no_sprite", in_text);
}
TEST_F(CssImageCombineTest, NoCrashUnknownType) {
// Make sure we don't crash trying to sprite an image with an unknown mimetype
ResponseHeaders response_headers;
SetDefaultLongCacheHeaders(&kContentTypePng, &response_headers);
response_headers.Replace(HttpAttributes::kContentType, "image/x-bewq");
response_headers.ComputeCaching();
SetFetchResponse(StrCat(kTestDomain, "bar.bewq"),
response_headers, "unused payload");
SetResponseWithDefaultHeaders("foo.png", kContentTypePng,
"unused payload", 100);
const GoogleString before =
"<head><style>"
"#div1 { background-image:url('bar.bewq');"
"width:10px;height:10px}"
"#div2 { background:transparent url('foo.png');width:10px;height:10px}"
"</style></head>";
ParseUrl(kTestDomain, before);
}
TEST_F(CssImageCombineTest, SpritesImagesExternal) {
SetupWaitFetcher();
const GoogleString beforeCss = StrCat(" " // extra whitespace allows rewrite
"#div1{background-image:url(", kCuppaPngFile, ");"
"width:10px;height:10px}"
"#div2{background:transparent url(", kBikePngFile,
");width:10px;height:10px}");
GoogleString cssUrl(kTestDomain);
cssUrl += "style.css";
// At first try, not even the CSS gets loaded, so nothing gets
// changed at all.
ValidateRewriteExternalCss("wip", beforeCss, beforeCss,
kExpectNoChange | kNoClearFetcher);
// Allow the images to load
CallFetcherCallbacks();
// On the second run, we get spriting.
const GoogleString sprite =
Encode("", "is", "0", MultiUrl(kCuppaPngFile, kBikePngFile), "png");
const GoogleString spriteCss = StrCat(
"#div1{background-image:url(", sprite, ");"
"width:10px;height:10px;"
"background-position:0 0}"
"#div2{background:transparent url(", sprite,
");width:10px;height:10px;background-position:0 -70px}");
// kNoStatCheck because ImageCombineFilter uses different stats.
ValidateRewriteExternalCss("wip", beforeCss, spriteCss,
kExpectSuccess | kNoClearFetcher | kNoStatCheck);
}
TEST_F(CssImageCombineTest, SpritesOkAfter404) {
// Make sure the handling of a 404 is correct, and doesn't interrupt spriting
// (nor check fail, as it used to before).
AddFileToMockFetcher(StrCat(kTestDomain, "bike2.png"), kBikePngFile,
kContentTypePng, 100);
AddFileToMockFetcher(StrCat(kTestDomain, "bike3.png"), kBikePngFile,
kContentTypePng, 100);
SetFetchResponse404("404.png");
const char kHtmlTemplate[] = "<head><style>"
"#div1{background:url(%s);width:10px;height:10px}"
"#div2{background:url(%s);width:10px;height:10px}"
"#div3{background:url(%s);width:10px;height:10px}"
"#div4{background:url(%s);width:10px;height:10px}"
"#div5{background:url(%s);width:10px;height:10px}"
"</style></head>";
GoogleString html = StringPrintf(kHtmlTemplate,
kBikePngFile,
kCuppaPngFile,
"404.png",
"bike2.png",
"bike3.png");
Parse("sprite_with_404", html); // Parse
EXPECT_NE(GoogleString::npos,
output_buffer_.find(
Encode("", "is", "0",
MultiUrl(kBikePngFile, kCuppaPngFile,
"bike2.png", "bike3.png"),
"png")));
}
TEST_F(CssImageCombineTest, SpritesMultiSite) {
// Make sure we do something sensible when we're forced to split into multiple
// partitions due to different host names -- at lest when it doesn't require
// us to keep track of multiple partitions intelligently.
const char kAltDomain[] = "http://images.example.com/";
AddDomain(kAltDomain);
AddFileToMockFetcher(StrCat(kAltDomain, kBikePngFile), kBikePngFile,
kContentTypePng, 100);
AddFileToMockFetcher(StrCat(kAltDomain, kCuppaPngFile), kCuppaPngFile,
kContentTypePng, 100);
const char kHtmlTemplate[] = "<head><style>"
"#div1{background:url(%s);width:10px;height:10px%s}"
"#div2{background:url(%s);width:10px;height:10px%s}"
"#div3{background:url(%s);width:10px;height:10px%s}"
"#div4{background:url(%s);width:10px;height:10px%s}"
"</style></head>";
GoogleString test_bike = StrCat(kTestDomain, kBikePngFile);
GoogleString alt_bike = StrCat(kAltDomain, kBikePngFile);
GoogleString test_cup = StrCat(kTestDomain, kCuppaPngFile);
GoogleString alt_cup = StrCat(kAltDomain, kCuppaPngFile);
GoogleString test_sprite = Encode(
kTestDomain, "is", "0", MultiUrl(kBikePngFile, kCuppaPngFile), "png");
GoogleString alt_sprite = Encode(
kAltDomain, "is", "0", MultiUrl(kBikePngFile, kCuppaPngFile), "png");
GoogleString before = StringPrintf(kHtmlTemplate,
test_bike.c_str(), "",
alt_bike.c_str(), "",
test_cup.c_str(), "",
alt_cup.c_str(), "");
GoogleString after = StringPrintf(
kHtmlTemplate,
test_sprite.c_str(), ";background-position:0 0",
alt_sprite.c_str(), ";background-position:0 0",
test_sprite.c_str(), ";background-position:0 -100px",
alt_sprite.c_str(), ";background-position:0 -100px");
ValidateExpected("multi_site", before, after);
// For this test, a partition should get created for the alt_bike image,
// but it should end up getting canceled and deleted since the partition
// will have only one image in it.
before = StringPrintf(kHtmlTemplate,
alt_bike.c_str(), "",
test_bike.c_str(), "",
test_cup.c_str(), "",
test_bike.c_str(), "");
after = StringPrintf(kHtmlTemplate,
alt_bike.c_str(), "",
test_sprite.c_str(), ";background-position:0 0",
test_sprite.c_str(), ";background-position:0 -100px",
test_sprite.c_str(), ";background-position:0 0");
ValidateExpected("multi_site_one_sprite", before, after);
}
// TODO(nforman): Add a testcase that synthesizes a spriting situation where
// the total size of the constructed segment (not including the domain or
// .pagespeed.* parts) is larger than RewriteOptions::kDefaultMaxUrlSegmentSize
// (1024).
TEST_F(CssImageCombineTest, ServeFiles) {
GoogleString sprite_str =
Encode(kTestDomain, "is", "0",
MultiUrl(kCuppaPngFile, kBikePngFile), "png");
GoogleString output;
EXPECT_TRUE(FetchResourceUrl(sprite_str, &output));
ServeResourceFromManyContexts(sprite_str, output);
}
// FYI: Takes ~10000 ms to run under Valgrind.
TEST_F(CssImageCombineTest, CombineManyFiles) {
// Prepare an HTML fragment with too many image files to combine,
// exceeding the char limit.
const int kNumImages = 100;
const int kImagesInCombination = 47;
GoogleString html = "<head><style>";
for (int i = 0; i < kNumImages; ++i) {
GoogleString url = StringPrintf("%s%.02d%s", kTestDomain, i, kBikePngFile);
AddFileToMockFetcher(url, kBikePngFile, kContentTypePng, 100);
html.append(StringPrintf(
"#div%d{background:url(%s) 0 0;width:10px;height:10px}",
i, url.c_str()));
}
html.append("</style></head>");
// We expect 3 combinations: 0-46, 47-93, 94-99
StringVector combinations;
int image_index = 0;
while (image_index < kNumImages) {
StringVector combo;
int end_index = std::min(image_index + kImagesInCombination, kNumImages);
while (image_index < end_index) {
combo.push_back(StringPrintf("%.02d%s", image_index, kBikePngFile));
++image_index;
}
// Original URL is absolute, so rewritten one is as well.
combinations.push_back(Encode(kTestDomain, "is", "0", combo, "png"));
}
image_index = 0;
int combo_index = 0;
GoogleString result = "<head><style>";
while (image_index < kNumImages) {
int offset = (image_index - (combo_index * kImagesInCombination)) * -100;
GoogleString offset_str;
if (offset == 0) {
// Minification artifact.
offset_str = "0";
} else {
offset_str = StringPrintf("%dpx", offset);
}
result.append(StringPrintf(
"#div%d{background:url(%s) 0 %s;width:10px;height:10px}",
image_index, combinations[combo_index].c_str(), offset_str.c_str()));
++image_index;
if (image_index % kImagesInCombination == 0) {
++combo_index;
}
}
result.append("</style></head>");
ValidateExpected("manymanyimages", html, result);
}
TEST_F(CssImageCombineTest, SpritesBrokenUp) {
// Make sure we include all spritable images, even if there are
// un-spritable images in between.
GoogleString before, after;
before = StringPrintf(kHtmlTemplate3Divs, kBikePngFile,
kPuzzleJpgFile, "0", "10px", kCuppaPngFile, "0");
const GoogleString sprite_string =
Encode("", "is", "0", MultiUrl(kBikePngFile, kCuppaPngFile), "png");
const char* sprite = sprite_string.c_str();
after = StringPrintf(kHtmlTemplate3Divs, sprite,
kPuzzleJpgFile, "0", "10px", sprite, "-100px");
ValidateExpected("sprite_broken_up", before, after);
}
TEST_F(CssImageCombineTest, SpritesGifsWithPngs) {
// Make sure we include all spritable images, even if there are
// un-spritable images in between.
GoogleString before, after;
before = StringPrintf(kHtmlTemplate3Divs, kBikePngFile,
kChefGifFile, "0", "10px", kCuppaPngFile, "0");
const GoogleString sprite_string =
Encode("", "is", "0", MultiUrl(kBikePngFile, kChefGifFile, kCuppaPngFile),
"png");
const char* sprite = sprite_string.c_str();
// The BikePng is 100px tall, the ChefGif is 256px tall, so we
// expect the Chef to be offset by -100, and the CuppaPng to be
// offset by -356.
after = StringPrintf(kHtmlTemplate3Divs, sprite,
sprite, "-100px", "10px", sprite, "-356px");
ValidateExpected("sprite_with_gif", before, after);
}
TEST_F(CssImageCombineTest, SpriteWrongMime) {
// Make sure that a server messing up the content-type doesn't prevent
// spriting.
GoogleString wrong_bike = StrCat(kTestDomain, "w", kBikePngFile);
GoogleString wrong_cuppa = StrCat(kTestDomain, "w", kCuppaPngFile);
AddFileToMockFetcher(wrong_bike, kBikePngFile, kContentTypeJpeg, 100);
AddFileToMockFetcher(wrong_cuppa, kCuppaPngFile,
kContentTypeJpeg, 100);
GoogleString rel_sprite =
Encode("", "is", "0",
MultiUrl(StrCat("w", kBikePngFile),
StrCat("w", kCuppaPngFile),
kCuppaPngFile),
"png");
GoogleString abs_sprite = StrCat(kTestDomain, rel_sprite);
GoogleString before, after;
before = StringPrintf(kHtmlTemplate3Divs, wrong_bike.c_str(),
wrong_cuppa.c_str(), "0", "10px", kCuppaPngFile, "0");
// The BikePng is 100px tall, the cuppa is 70px tall, so we
// expect the cuppa to be offset by -100, and the right-path cuppa to be
// offset by -170.
//
// First 2 original URLs were absolute, so rewritten ones are as well.
// Last was relative, so it is preserved as relative.
after = StringPrintf(kHtmlTemplate3Divs,
abs_sprite.c_str(), abs_sprite.c_str(), "-100px", "10px",
rel_sprite.c_str(), "-170px");
ValidateExpected("wrong_mime", before, after);
}
class CssImageMultiFilterTest : public CssImageCombineTest {
// Users must call CssImageCombineTest::SetUp().
virtual void SetUp() {}
};
TEST_F(CssImageMultiFilterTest, SpritesAndNonSprites) {
// We setup the options before the upcall so that the
// CSS filter is created aware of these.
options()->EnableFilter(RewriteOptions::kExtendCacheImages);
CssImageCombineTest::SetUp();
GoogleString before, after, encoded, cuppa_encoded, sprite;
// With the same image present 3 times, there should be no sprite.
before = StringPrintf(kHtmlTemplate3Divs, kBikePngFile,
kBikePngFile, "0", "10px", kBikePngFile, "0");
encoded = Encode("", "ce", "0", kBikePngFile, "png");
after = StringPrintf(kHtmlTemplate3Divs, encoded.c_str(),
encoded.c_str(), "0", "10px", encoded.c_str(), "0");
ValidateExpected("no_sprite_3_bikes", before, after);
// With 2 of the same and 1 different, there should be a sprite without
// duplication.
before = StringPrintf(kHtmlTemplate3Divs, kBikePngFile,
kBikePngFile, "0", "10px", kCuppaPngFile, "0");
sprite = Encode("", "is", "0",
MultiUrl(kBikePngFile, kCuppaPngFile), "png").c_str();
after = StringPrintf(kHtmlTemplate3Divs, sprite.c_str(),
sprite.c_str(), "0", "10px", sprite.c_str(), "-100px");
ValidateExpected("sprite_2_bikes_1_cuppa", before, after);
// If the second occurrence of the image is unspriteable (e.g. if the div is
// larger than the image), we shouldn't sprite any of them.
before = StringPrintf(kHtmlTemplate3Divs, kBikePngFile,
kBikePngFile, "0", "999px", kCuppaPngFile, "0");
cuppa_encoded = Encode("", "ce", "0", kCuppaPngFile, "png");
after = StringPrintf(kHtmlTemplate3Divs, encoded.c_str(), encoded.c_str(),
"0", "999px", cuppa_encoded.c_str(), "0");
ValidateExpected("sprite_none_dimmensions", before, after);
}
// A test in which base URL inside CSS is different than inside HTML.
// Specifically CSS base URL is inside subdir/.
// This might also be the only test for external stylesheets.
TEST_F(CssImageCombineTest, CssDifferentBase) {
// Set up resources.
AddFileToMockFetcher("subdir/Cuppa.png", kCuppaPngFile, kContentTypePng, 100);
AddFileToMockFetcher("subdir/BikeCrashIcn.png", kBikePngFile,
kContentTypePng, 100);
GoogleString css_before =
".a {background: 0 0 url(Cuppa.png) no-repeat;"
" width:10px; height:10px}"
".b {background: 0 0 url(BikeCrashIcn.png) no-repeat;"
" width:10px; height:10px}";
SetResponseWithDefaultHeaders("subdir/foo.css", kContentTypeCss,
css_before, 100);
GoogleString expected_css_after =
".a{background:0 0"
" url(Cuppa.png+BikeCrashIcn.png.pagespeed.is.0.png)"
" no-repeat;width:10px;height:10px}"
".b{background:0 -70px"
" url(Cuppa.png+BikeCrashIcn.png.pagespeed.is.0.png)"
" no-repeat;width:10px;height:10px}";
GoogleString rewritten_url = Encode("subdir/", "cf", "0", "foo.css", "css");
ValidateExpected("diff_base",
CssLinkHref("subdir/foo.css"),
CssLinkHref(rewritten_url));
GoogleString actual_css_after;
ASSERT_TRUE(FetchResourceUrl(StrCat(kTestDomain, rewritten_url),
&actual_css_after));
EXPECT_EQ(expected_css_after, actual_css_after);
}
TEST_F(CssImageMultiFilterTest, WithFlattening) {
// We setup the options before the upcall so that the
// CSS filter is created aware of these.
options()->EnableFilter(RewriteOptions::kFlattenCssImports);
CssImageCombineTest::SetUp();
AddFileToMockFetcher("dir/Cuppa.png", kCuppaPngFile, kContentTypePng, 100);
AddFileToMockFetcher("dir/BikeCrashIcn.png", kBikePngFile,
kContentTypePng, 100);
const char kLeafCss[] =
".a {background: 0 0 url(Cuppa.png) no-repeat;"
" width:10px; height:10px}"
".b {background: 0 0 url(BikeCrashIcn.png) no-repeat;"
" width:10px; height:10px}";
SetResponseWithDefaultHeaders("dir/a.css", kContentTypeCss, kLeafCss, 100);
const char kBeforeHtml[] = "<style>@import url(dir/a.css);</style>";
// Note: This is flattened and combined.
// TODO(sligocki): Perhaps http://test.com/dir/Cuppa.png should be relative
// given that the original URL in the original stylesheet was relative.
const char kAfterHtml[] =
"<style>"
".a{background:0 0"
" url(http://test.com/dir/Cuppa.png+BikeCrashIcn.png.pagespeed.is.0.png)"
" no-repeat;width:10px;height:10px}"
".b{background:0 -70px"
" url(http://test.com/dir/Cuppa.png+BikeCrashIcn.png.pagespeed.is.0.png)"
" no-repeat;width:10px;height:10px}"
"</style>";
ValidateExpected("with_flattening", kBeforeHtml, kAfterHtml);
}
TEST_F(CssImageMultiFilterTest, NoCombineAcrossFlattening) {
// We setup the options before the upcall so that the
// CSS filter is created aware of these.
options()->EnableFilter(RewriteOptions::kFlattenCssImports);
CssImageCombineTest::SetUp();
AddFileToMockFetcher("dir/Cuppa.png", kCuppaPngFile, kContentTypePng, 100);
const char kLeafCss[] =
".a {background: 0 0 url(Cuppa.png) no-repeat;"
" width:10px; height:10px}";
SetResponseWithDefaultHeaders("dir/a.css", kContentTypeCss, kLeafCss, 100);
const char kBeforeHtml[] =
"<style>\n"
"@import url(dir/a.css);\n"
".b {background: 0 0 url(BikeCrashIcn.png) no-repeat;"
" width:10px; height:10px}\n"
"</style>";
// TODO(sligocki): Any reason not to combine images across flattening
// boundaries? Currently we don't seem to.
// TODO(sligocki): Perhaps http://test.com/dir/Cuppa.png should be relative
// given that the original URL in the original stylesheet was relative.
const char kAfterHtml[] =
"<style>"
".a{background:0 0 url(http://test.com/dir/Cuppa.png) no-repeat;"
"width:10px;height:10px}"
".b{background:0 0 url(BikeCrashIcn.png) no-repeat;"
"width:10px;height:10px}"
"</style>";
ValidateExpected("with_flattening", kBeforeHtml, kAfterHtml);
}
TEST_F(CssImageCombineTest, ContentTypeValidation) {
ValidateFallbackHeaderSanitization("is");
}
} // namespace
} // namespace net_instaweb