blob: 1501f9799c1870d4147fbdb2af100a1b78cc1a3e [file] [log] [blame]
// Copyright 2010 Google Inc. All Rights Reserved.
// Author: sligocki@google.com (Shawn Ligocki)
#include "net/instaweb/rewriter/public/css_move_to_head_filter.h"
#include <cstddef>
#include "base/logging.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 "pagespeed/kernel/base/gtest.h"
#include "pagespeed/kernel/html/html_parse_test_base.h"
namespace net_instaweb {
namespace {
class CssMoveToHeadFilterTest : public RewriteTestBase {
protected:
};
TEST_F(CssMoveToHeadFilterTest, MovesCssToHead) {
AddFilter(RewriteOptions::kMoveCssToHead);
static const char html_input[] =
"<head>\n"
" <title>Example</title>\n"
"</head>\n"
"<body>\n"
" Hello,\n"
" <link rel='stylesheet' href='a.css' type='text/css'>" // no newline
"<link rel='stylesheet' href='b.css' type='text/css'>\n" // no indent
" <style type='text/css'>a {color: red }</style>\n"
" World!\n"
" <link rel='stylesheet' href='c.css' type='text/css'>\n"
"</body>\n";
static const char expected_output[] =
"<head>\n"
" <title>Example</title>\n"
"<link rel='stylesheet' href='a.css' type='text/css'>" // no newline
"<link rel='stylesheet' href='b.css' type='text/css'>" // no newline
"<style type='text/css'>a {color: red }</style>" // no newline
"<link rel='stylesheet' href='c.css' type='text/css'>" // no newline
"</head>\n"
"<body>\n"
" Hello,\n"
" \n"
" \n"
" World!\n"
" \n"
"</body>\n";
ValidateExpected("move_css_to_head", html_input, expected_output);
}
TEST_F(CssMoveToHeadFilterTest, DoesntMoveOutOfNoScript) {
AddFilter(RewriteOptions::kMoveCssToHead);
static const char input_html[] =
"<head>\n"
" <title>Example</title>\n"
"</head>\n"
"<body>\n"
" <style>a {color: red}</style>\n"
" <noscript>\n"
" <link rel='stylesheet' href='a.css'>\n"
" </noscript>\n"
" <style>p {color: blue}</style>\n"
"</body>\n";
static const char expected_html[] =
"<head>\n"
" <title>Example</title>\n"
"<style>a {color: red}</style>"
"</head>\n"
"<body>\n"
" \n"
" <noscript>\n"
" <link rel='stylesheet' href='a.css'>\n"
" </noscript>\n"
" <style>p {color: blue}</style>\n"
"</body>\n";
ValidateExpected("noscript", input_html, expected_html);
}
TEST_F(CssMoveToHeadFilterTest, DoesntMoveScopedStyle) {
AddFilter(RewriteOptions::kMoveCssToHead);
static const char input_html[] =
"<head>\n"
" <title>Example</title>\n"
"</head>\n"
"<body>\n"
" <style>div {color: green}</style>\n"
" <style scoped>a {color: red}</style>\n"
" <style scoped='scoped'>p {color: blue}</style>\n"
" <link rel='stylesheet' href='a.css'>\n"
" <p>Blue with a <a href='scoped_style.html'>red link</a>"
"</body>\n";
static const char expected_html[] =
"<head>\n"
" <title>Example</title>\n"
"<style>div {color: green}</style>"
"</head>\n"
"<body>\n"
" \n"
" <style scoped>a {color: red}</style>\n"
" <style scoped='scoped'>p {color: blue}</style>\n"
" <link rel='stylesheet' href='a.css'>\n"
" <p>Blue with a <a href='scoped_style.html'>red link</a>"
"</body>\n";
ValidateExpected("scoped_style", input_html, expected_html);
}
TEST_F(CssMoveToHeadFilterTest, MovePastScopedDiv) {
AddFilter(RewriteOptions::kMoveCssToHead);
static const char input_html[] =
"<head>\n"
" <title>Example</title>\n"
"</head>\n"
"<body>\n"
" <style>div {color: green}</style>\n"
" <div scoped>Just a div, move along!</div>\n"
" <style>p {color: blue}</style>\n"
" <link rel='stylesheet' href='a.css'>\n"
"</body>\n";
static const char expected_html[] =
"<head>\n"
" <title>Example</title>\n"
"<style>div {color: green}</style>"
"<style>p {color: blue}</style>"
"<link rel='stylesheet' href='a.css'>"
"</head>\n"
"<body>\n"
" \n"
" <div scoped>Just a div, move along!</div>\n"
" \n"
" \n"
"</body>\n";
ValidateExpected("scoped_div", input_html, expected_html);
}
TEST_F(CssMoveToHeadFilterTest, DoesntReorderCss) {
AddFilter(RewriteOptions::kMoveCssToHead);
static const char html[] =
"<head>\n"
" <title>Example</title>\n"
"</head>\n"
"<body>\n"
" <link rel='stylesheet' href='a.css' type='text/css'>\n"
" <link rel='stylesheet' href='b.css' type='text/css'>\n"
" <style type='text/css'>a { color: red }</style>\n"
" <link rel='stylesheet' href='d.css' type='text/css'>\n"
"</body>\n";
Parse("no_reorder_css", html);
LOG(INFO) << "output_buffer_ = " << output_buffer_;
size_t a_loc = output_buffer_.find("href='a.css'");
size_t b_loc = output_buffer_.find("href='b.css'");
size_t c_loc = output_buffer_.find("a { color: red }");
size_t d_loc = output_buffer_.find("href='d.css'");
// Make sure that all attributes are in output_buffer_ ...
EXPECT_NE(output_buffer_.npos, a_loc);
EXPECT_NE(output_buffer_.npos, b_loc);
EXPECT_NE(output_buffer_.npos, c_loc);
EXPECT_NE(output_buffer_.npos, d_loc);
// ... and that they are still in the right order (specifically, that
// the last link wasn't moved above the style).
EXPECT_LE(a_loc, b_loc);
EXPECT_LE(b_loc, c_loc);
EXPECT_LE(c_loc, d_loc);
}
TEST_F(CssMoveToHeadFilterTest, MovesAboveFirstScript) {
AddFilter(RewriteOptions::kMoveCssAboveScripts);
static const char input[] =
"<head>\n"
" <title>Example</title>\n"
" <link rel='stylesheet' type='text/css' href='a.css'>\n"
" <meta name='application-name' content='Foo'>\n"
" <script src='b.js'></script>\n"
" <!-- Comment -->\n"
" <style>.foo { color: red }</style>\n"
" <script src='c.js'></script>\n"
" <link rel='icon' href='d.png'>\n"
" <link rel='stylesheet' href='e.css'>\n"
"</head>\n"
"<body>\n"
" <link rel='stylesheet' type='text/css' href='f.css'>\n"
"</body>\n";
static const char expected_output[] =
"<head>\n"
" <title>Example</title>\n"
" <link rel='stylesheet' type='text/css' href='a.css'>\n"
" <meta name='application-name' content='Foo'>\n"
" "
"<style>.foo { color: red }</style>"
"<link rel='stylesheet' href='e.css'>"
"<link rel='stylesheet' type='text/css' href='f.css'>"
"<script src='b.js'></script>\n"
" <!-- Comment -->\n"
" \n"
" <script src='c.js'></script>\n"
" <link rel='icon' href='d.png'>\n"
" \n"
"</head>\n"
"<body>\n"
" \n"
"</body>\n";
ValidateExpected("move_above_first_script", input, expected_output);
}
TEST_F(CssMoveToHeadFilterTest, MovesAboveScriptAfterHead) {
AddFilter(RewriteOptions::kMoveCssAboveScripts);
static const char input[] =
"<head>\n"
" <title>Example</title>\n"
" <link rel='stylesheet' type='text/css' href='a.css'>\n"
" <meta name='application-name' content='Foo'>\n"
" <!-- Comment -->\n"
" <style>.foo { color: red }</style>\n"
" <link rel='icon' href='d.png'>\n"
" <link rel='stylesheet' href='e.css'>\n"
"</head>\n"
"<body>\n"
" <script src='b.js'></script>\n"
" <link rel='stylesheet' type='text/css' href='f.css'>\n"
"</body>\n";
static const char expected_output[] =
"<head>\n"
" <title>Example</title>\n"
" <link rel='stylesheet' type='text/css' href='a.css'>\n"
" <meta name='application-name' content='Foo'>\n"
" <!-- Comment -->\n"
" <style>.foo { color: red }</style>\n"
" <link rel='icon' href='d.png'>\n"
" <link rel='stylesheet' href='e.css'>\n"
"</head>\n"
"<body>\n"
" <link rel='stylesheet' type='text/css' href='f.css'>"
"<script src='b.js'></script>\n"
" \n"
"</body>\n";
ValidateExpected("move_above_script_after_head", input, expected_output);
}
TEST_F(CssMoveToHeadFilterTest, MovesToHeadEvenIfScriptAfter) {
options()->EnableFilter(RewriteOptions::kMoveCssToHead);
options()->EnableFilter(RewriteOptions::kMoveCssAboveScripts);
rewrite_driver_->AddFilters();
static const char input[] =
"<head>\n"
" <title>Example</title>\n"
" <link rel='stylesheet' type='text/css' href='a.css'>\n"
" <meta name='application-name' content='Foo'>\n"
" <!-- Comment -->\n"
" <style>.foo { color: red }</style>\n"
" <link rel='icon' href='d.png'>\n"
" <link rel='stylesheet' href='e.css'>\n"
"</head>\n"
"<body>\n"
" <script src='b.js'></script>\n"
" <link rel='stylesheet' type='text/css' href='f.css'>\n"
"</body>\n";
static const char expected_output[] =
"<head>\n"
" <title>Example</title>\n"
" <link rel='stylesheet' type='text/css' href='a.css'>\n"
" <meta name='application-name' content='Foo'>\n"
" <!-- Comment -->\n"
" <style>.foo { color: red }</style>\n"
" <link rel='icon' href='d.png'>\n"
" <link rel='stylesheet' href='e.css'>\n"
"<link rel='stylesheet' type='text/css' href='f.css'>"
"</head>\n"
"<body>\n"
" <script src='b.js'></script>\n"
" \n"
"</body>\n";
ValidateExpected("move_above_first_script", input, expected_output);
}
TEST_F(CssMoveToHeadFilterTest, MoveToHeadFlushEdge) {
AddFilter(RewriteOptions::kMoveCssToHead);
SetupWriter();
rewrite_driver()->StartParse(kTestDomain);
rewrite_driver()->ParseText("<html>\n"
" <head>\n"
" <title>Example</title>");
rewrite_driver()->Flush();
// Make it so that the </head> is the first thing in this flush window.
// Test to make sure we don't break this corner case.
rewrite_driver()->ParseText(
// NOTE: It is important there are not spaces, etc. before the <script>
// tag, those would become the first event.
"</head>\n"
" <body>\n"
" <link rel='stylesheet' type='text/css' href='f.css'>");
rewrite_driver()->Flush();
rewrite_driver()->ParseText("\n"
" </body>\n"
"</html>\n");
rewrite_driver()->FinishParse();
// Check that we do still move the <link> tag to the edge of the flush window.
// And more importantly that we don't lose the <link> or crash, etc.
EXPECT_EQ("<html>\n"
" <head>\n"
" <title>Example</title>"
"<link rel='stylesheet' type='text/css' href='f.css'>"
"</head>\n"
" <body>\n"
" \n"
" </body>\n"
"</html>\n", output_buffer_);
}
TEST_F(CssMoveToHeadFilterTest, MoveToHeadOverFlushEdge) {
AddFilter(RewriteOptions::kMoveCssToHead);
SetupWriter();
rewrite_driver()->StartParse(kTestDomain);
rewrite_driver()->ParseText("<html>\n"
" <head>\n"
" <title>Example</title>"
"</head>");
rewrite_driver()->Flush();
rewrite_driver()->ParseText(
"\n"
" <body>\n"
" <link rel='stylesheet' type='text/css' href='f.css'>");
rewrite_driver()->Flush();
rewrite_driver()->ParseText("\n"
" </body>\n"
"</html>\n");
rewrite_driver()->FinishParse();
// </head> is out of flush window at rewrite time, so we don't move anything.
EXPECT_EQ("<html>\n"
" <head>\n"
" <title>Example</title>"
"</head>\n"
" <body>\n"
" <link rel='stylesheet' type='text/css' href='f.css'>\n"
" </body>\n"
"</html>\n", output_buffer_);
}
TEST_F(CssMoveToHeadFilterTest, MoveAboveScriptsFlushEdge) {
AddFilter(RewriteOptions::kMoveCssAboveScripts);
SetupWriter();
rewrite_driver()->StartParse(kTestDomain);
rewrite_driver()->ParseText("<html>\n"
" <head>\n"
" <title>Example</title>");
rewrite_driver()->Flush();
// Make it so that the <script> is the first thing in this flush window.
// Test to make sure we don't break this corner case.
rewrite_driver()->ParseText(
// NOTE: It is important there are not spaces, etc. before the <script>
// tag, those would become the first event.
"<script src='b.js'></script>\n"
" </head>\n"
" <body>\n"
" <link rel='stylesheet' type='text/css' href='f.css'>");
rewrite_driver()->Flush();
rewrite_driver()->ParseText("\n"
" </body>\n"
"</html>\n");
rewrite_driver()->FinishParse();
// Check that we do still move the <link> tag to the edge of the flush window.
// And more importantly that we don't lose the <link> or crash, etc.
EXPECT_EQ("<html>\n"
" <head>\n"
" <title>Example</title>"
"<link rel='stylesheet' type='text/css' href='f.css'>"
"<script src='b.js'></script>\n"
" </head>\n"
" <body>\n"
" \n"
" </body>\n"
"</html>\n", output_buffer_);
}
TEST_F(CssMoveToHeadFilterTest, MoveAboveScriptsOverFlushEdge) {
AddFilter(RewriteOptions::kMoveCssAboveScripts);
SetupWriter();
rewrite_driver()->StartParse(kTestDomain);
rewrite_driver()->ParseText("<html>\n"
" <head>\n"
" <title>Example</title>"
"<script src='b.js'></script>");
rewrite_driver()->Flush();
rewrite_driver()->ParseText(
"\n"
" </head>\n"
" <body>\n"
" <link rel='stylesheet' type='text/css' href='f.css'>");
rewrite_driver()->Flush();
rewrite_driver()->ParseText("\n"
" </body>\n"
"</html>\n");
rewrite_driver()->FinishParse();
// <script> is out of flush window at rewrite time, so we don't move anything.
// TODO(sligocki): Technically, we could move it into <head> still, but
// I'm guessing this situation won't come up too much.
EXPECT_EQ("<html>\n"
" <head>\n"
" <title>Example</title>"
"<script src='b.js'></script>\n"
" </head>\n"
" <body>\n"
" <link rel='stylesheet' type='text/css' href='f.css'>\n"
" </body>\n"
"</html>\n", output_buffer_);
}
} // namespace
} // namespace net_instaweb