blob: 127d6c211706629fc3048740e3c4965fc10745ba [file] [log] [blame]
/*
* 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 "net/instaweb/rewriter/public/css_outline_filter.h"
#include "net/instaweb/rewriter/public/debug_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/support_noscript_filter.h"
#include "pagespeed/kernel/base/gtest.h"
#include "pagespeed/kernel/base/hasher.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/google_url.h"
#include "pagespeed/kernel/http/response_headers.h"
namespace net_instaweb {
namespace {
class CssOutlineFilterTest : public RewriteTestBase {
protected:
void SetupOutliner() {
options()->set_css_outline_min_bytes(0);
options()->SoftEnableFilterForTesting(RewriteOptions::kOutlineCss);
rewrite_driver()->AddFilters();
}
void SetupDebug(StringPiece debug_message) {
options()->EnableFilter(RewriteOptions::kDebug);
SetupOutliner();
// For some reason SupportNoScript filter is disabled here.
StringVector expected_disabled_filters;
SupportNoscriptFilter support_noscript_filter(rewrite_driver());
expected_disabled_filters.push_back(support_noscript_filter.Name());
debug_message.CopyToString(&debug_message_);
debug_suffix_ = DebugFilter::FormatEndDocumentMessage(
0, 0, 0, 0, 0, false, StringSet(),
expected_disabled_filters);
}
void TestOutlineCss(StringPiece html_url,
StringPiece base_ref,
StringPiece css_original_body,
bool expect_outline,
StringPiece css_rewritten_body,
// css_url_base only needed if different from html_url,
// e.g. domain rewriting.
StringPiece css_url_base) {
// TODO(sligocki): Test with outline threshold > 0.
// Figure out outline_url.
GoogleString hash = hasher()->Hash(css_rewritten_body);
GoogleUrl css_gurl_base;
if (css_url_base.empty()) {
css_gurl_base.Reset(html_url);
} else {
css_gurl_base.Reset(css_url_base);
}
GoogleUrl base_ref_gurl;
if (base_ref.empty()) {
base_ref_gurl.Reset(html_url);
} else {
base_ref_gurl.Reset(base_ref);
}
GoogleString css_gurl_base_origin = StrCat(css_gurl_base.Origin(), "/");
GoogleString base_ref_gurl_origin = StrCat(base_ref_gurl.Origin(), "/");
GoogleString outline_url = EncodeWithBase(base_ref_gurl_origin,
css_gurl_base_origin,
CssOutlineFilter::kFilterId,
hash, "_", "css");
// Add a base href to the HTML iff specified.
GoogleString other_content;
if (!base_ref.empty()) {
other_content = StrCat(" <base href=\"", base_ref, "\">\n");
}
const GoogleString html_input = StrCat(
"<head>\n",
other_content,
" <style>", css_original_body, "</style>\n"
"</head>\n"
"<body>Hello, world!</body>");
// Check output HTML.
GoogleString expected_output;
if (expect_outline) {
expected_output = StrCat(
"<head>\n",
other_content,
" <link rel=\"stylesheet\" href=\"", outline_url, "\">\n"
"</head>\n"
"<body>Hello, world!</body>");
} else {
expected_output = StrCat(
"<head>\n",
other_content,
" <style>", css_original_body, "</style>", debug_message_, "\n"
"</head>\n"
"<body>Hello, world!</body>");
}
ParseUrl(html_url, html_input);
EXPECT_HAS_SUBSTR(expected_output, output_buffer_);
if (!debug_suffix_.empty()) {
EXPECT_HAS_SUBSTR(debug_suffix_, output_buffer_);
}
if (expect_outline) {
// Expected headers.
GoogleString expected_headers;
AppendDefaultHeaders(kContentTypeCss, &expected_headers);
// Check fetched resource.
GoogleString actual_outline;
ResponseHeaders actual_headers;
EXPECT_TRUE(FetchResourceUrl(outline_url, &actual_outline,
&actual_headers));
EXPECT_EQ(expected_headers, actual_headers.ToString());
EXPECT_EQ(css_rewritten_body, actual_outline);
}
}
void OutlineStyle(const StringPiece& id) {
GoogleString html_url = StrCat("http://outline_style.test/", id, ".html");
GoogleString style_text = "background_blue { background-color: blue; }\n"
"foreground_yellow { color: yellow; }\n";
TestOutlineCss(html_url, "", style_text, true, style_text, "");
}
GoogleString debug_message_;
GoogleString debug_suffix_;
};
// Tests for Outlining styles.
TEST_F(CssOutlineFilterTest, OutlineStyle) {
SetupOutliner();
OutlineStyle("outline_styles_no_hash");
}
TEST_F(CssOutlineFilterTest, OutlineStyleMD5) {
SetupOutliner();
UseMd5Hasher();
OutlineStyle("outline_styles_md5");
}
TEST_F(CssOutlineFilterTest, CssOutlinePreserveURLsOn) {
options()->set_css_preserve_urls(true);
options()->set_css_outline_min_bytes(0);
SetupOutliner();
const char kStyleText[] = "background_blue { background-color: blue; }\n"
"foreground_yellow { color: yellow; }\n";
TestOutlineCss("http://outline_style.test/outline_styles_md5.html", "",
kStyleText, false, "", "");
}
TEST_F(CssOutlineFilterTest, NoAbsolutifySameDir) {
SetupOutliner();
const GoogleString css = "body { background-image: url('bg.png'); }";
TestOutlineCss("http://outline_style.test/index.html", "",
css, true, css, "");
}
TEST_F(CssOutlineFilterTest, AbsolutifyDifferentDir) {
SetupOutliner();
const GoogleString css1 = "body { background-image: url('bg.png'); }";
const GoogleString css2 =
"body { background-image: url('http://other_site.test/foo/bg.png'); }";
TestOutlineCss("http://outline_style.test/index.html",
"http://other_site.test/foo/", css1, true, css2, "");
}
TEST_F(CssOutlineFilterTest, ShardSubresources) {
SetupOutliner();
UseMd5Hasher();
AddShard("outline_style.test", "shard1.com,shard2.com");
const GoogleString css_in =
".p1 { background-image: url('b1.png'); }"
".p2 { background-image: url('b2.png'); }";
const GoogleString css_out =
".p1 { background-image: url('http://shard2.com/b1.png'); }"
".p2 { background-image: url('http://shard1.com/b2.png'); }";
TestOutlineCss("http://outline_style.test/index.html", "",
css_in, true, css_out, "http://shard1.com/");
}
TEST_F(CssOutlineFilterTest, UrlTooLong) {
GoogleString html_url = "http://outline_style.test/url_size_test.html";
GoogleString style_text = "background_blue { background-color: blue; }\n"
"foreground_yellow { color: yellow; }\n";
// By default we succeed at outlining.
SetupDebug(""); // No debug message.
TestOutlineCss(html_url, "", style_text, true, style_text, "");
// But if we set max_url_size too small, it will fail cleanly.
options()->ClearSignatureForTesting();
options()->set_max_url_size(0);
server_context()->ComputeSignature(options());
// Now we have a debug message.
debug_message_ = "<!--Rewritten URL too long: "
"http://outline_style.test/_.pagespeed.co.#.-->";
TestOutlineCss(html_url, "", style_text, false, style_text, "");
}
// Test our behavior with CDATA blocks.
TEST_F(CssOutlineFilterTest, CdataInContents) {
SetupOutliner();
SetXhtmlMimetype();
// TODO(sligocki): Fix. The outlined file should be "foo bar ".
GoogleString css = "foo <![CDATA[ bar ]]>";
TestOutlineCss("http://outline_css.test/cdata.html", "", css, true, css, "");
}
// Make sure we deal well with no Charactors() node between StartElement()
// and EndElement().
TEST_F(CssOutlineFilterTest, EmptyStyle) {
SetupOutliner();
ValidateNoChanges("empty_style", "<style></style>");
}
TEST_F(CssOutlineFilterTest, DoNotOutlineScoped) {
SetupOutliner();
// <style scoped> exists (with very limited support) but <link scoped>
// doesn't, so we shouldn't be outlining scoped styles.
ValidateNoChanges("scoped", "<style scoped>* {display: none;}</style>");
}
// http://code.google.com/p/modpagespeed/issues/detail?id=416
TEST_F(CssOutlineFilterTest, RewriteDomain) {
SetupOutliner();
AddRewriteDomainMapping("cdn.com", kTestDomain);
// Check that CSS gets outlined to the rewritten domain.
GoogleString expected_url = Encode("http://cdn.com/", "co", "0", "_", "css");
ValidateExpected("rewrite_domain",
"<style>.a { color: red; }</style>",
StrCat("<link rel=\"stylesheet\" href=\"", expected_url,
"\">"));
// And check that it serves correctly from that domain.
GoogleString content;
ASSERT_TRUE(FetchResourceUrl(expected_url, &content));
EXPECT_EQ(".a { color: red; }", content);
}
} // namespace
} // namespace net_instaweb