blob: 5e30fc2b6a4671eb88549f1038e7112d7cfca3e4 [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/common_filter.h"
#include "net/instaweb/rewriter/public/domain_lawyer.h"
#include "net/instaweb/rewriter/public/resource.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/base/mock_message_handler.h"
#include "pagespeed/kernel/base/scoped_ptr.h"
#include "pagespeed/kernel/base/string.h"
#include "pagespeed/kernel/base/string_util.h"
#include "pagespeed/kernel/html/html_node.h"
#include "pagespeed/kernel/http/google_url.h"
namespace net_instaweb {
class HtmlElement;
namespace {
class CountingFilter : public CommonFilter {
public:
explicit CountingFilter(RewriteDriver* driver) : CommonFilter(driver),
start_doc_calls_(0),
start_element_calls_(0),
end_element_calls_(0) {}
virtual void StartDocumentImpl() { ++start_doc_calls_; }
virtual void StartElementImpl(HtmlElement* element) {
++start_element_calls_;
}
virtual void EndElementImpl(HtmlElement* element) { ++end_element_calls_; }
virtual const char* Name() const { return "CommonFilterTest.CountingFilter"; }
int start_doc_calls_;
int start_element_calls_;
int end_element_calls_;
};
class CommonFilterTest : public RewriteTestBase {
protected:
virtual void SetUp() {
RewriteTestBase::SetUp();
filter_.reset(new CountingFilter(rewrite_driver()));
rewrite_driver()->AddFilter(filter_.get());
}
void ExpectUrl(const GoogleString& expected_url,
const GoogleUrl& actual_gurl) {
EXPECT_EQ(expected_url, actual_gurl.Spec());
}
bool CanRewriteResource(CommonFilter* filter, const StringPiece& url) {
bool unused;
ResourcePtr resource(filter->CreateInputResource(url, &unused));
return (resource.get() != NULL);
}
CommonFilter* MakeFilter(const StringPiece& base_url,
const StringPiece& domain,
RewriteOptions* options,
RewriteDriver* driver) {
options->WriteableDomainLawyer()->AddDomain(domain, message_handler());
CountingFilter* filter = new CountingFilter(driver);
driver->AddOwnedPostRenderFilter(filter);
driver->StartParse(base_url);
driver->Flush();
return filter;
}
scoped_ptr<CountingFilter> filter_;
};
TEST_F(CommonFilterTest, DoesCallImpls) {
EXPECT_EQ(0, filter_->start_doc_calls_);
filter_->StartDocument();
EXPECT_EQ(1, filter_->start_doc_calls_);
RewriteDriver* driver = rewrite_driver();
HtmlElement* element = driver->NewElement(NULL, "foo");
EXPECT_EQ(0, filter_->start_element_calls_);
filter_->StartElement(element);
EXPECT_EQ(1, filter_->start_element_calls_);
EXPECT_EQ(0, filter_->end_element_calls_);
filter_->EndElement(element);
EXPECT_EQ(1, filter_->end_element_calls_);
}
TEST_F(CommonFilterTest, StoresCorrectBaseUrl) {
GoogleString doc_url = "http://www.example.com/";
RewriteDriver* driver = rewrite_driver();
driver->StartParse(doc_url);
driver->Flush();
// Base URL starts out as document URL.
ExpectUrl(doc_url, driver->google_url());
ExpectUrl(doc_url, filter_->base_url());
driver->ParseText(
"<html><head><link rel='stylesheet' href='foo.css'>");
driver->Flush();
ExpectUrl(doc_url, filter_->base_url());
GoogleString base_url = "http://www.baseurl.com/foo/";
driver->ParseText("<base href='");
driver->ParseText(base_url);
driver->ParseText("' />");
driver->Flush();
// Update to base URL.
ExpectUrl(base_url, filter_->base_url());
// Make sure we didn't change the document URL.
ExpectUrl(doc_url, driver->google_url());
driver->ParseText("<link rel='stylesheet' href='foo.css'>");
driver->Flush();
ExpectUrl(base_url, filter_->base_url());
GoogleString new_base_url = "http://www.somewhere-else.com/";
driver->ParseText("<base href='");
driver->ParseText(new_base_url);
driver->ParseText("' />");
driver->Flush();
EXPECT_EQ(1, message_handler()->TotalMessages());
// Uses old base URL.
ExpectUrl(base_url, filter_->base_url());
driver->ParseText("</head></html>");
driver->Flush();
ExpectUrl(base_url, filter_->base_url());
driver->FinishParse();
ExpectUrl(doc_url, driver->google_url());
}
TEST_F(CommonFilterTest, ResolveUrl) {
GoogleUrl out;
// Normal parse, no <base>
GoogleString doc_url = "http://www.example.com/";
RewriteDriver* driver = rewrite_driver();
driver->StartParse(doc_url);
filter_->ResolveUrl("a.css", &out);
ExpectUrl("http://www.example.com/a.css", out);
driver->FinishParse();
// Refs from base
driver->StartParse(doc_url);
driver->ParseText("<base href='https://www.example.org/' >");
driver->Flush();
filter_->ResolveUrl("a.css", &out);
ExpectUrl("https://www.example.org/a.css", out);
driver->FinishParse();
// Nasty case: refs before base.
driver->StartParse(doc_url);
driver->set_refs_before_base();
driver->Flush();
filter_->ResolveUrl("a.css", &out);
EXPECT_FALSE(out.IsAnyValid());
driver->ParseText("<base href='https://www.example.org/' >");
driver->Flush();
filter_->ResolveUrl("a.css", &out);
ExpectUrl("https://www.example.org/a.css", out);
driver->FinishParse();
}
TEST_F(CommonFilterTest, DetectsNoScriptCorrectly) {
GoogleString doc_url = "http://www.example.com/";
RewriteDriver* driver = rewrite_driver();
driver->StartParse(doc_url);
driver->Flush();
EXPECT_TRUE(filter_->noscript_element() == NULL);
driver->ParseText("<html><head><title>Example Site");
driver->Flush();
EXPECT_TRUE(filter_->noscript_element() == NULL);
driver->ParseText("</title><noscript>");
driver->Flush();
EXPECT_TRUE(filter_->noscript_element() != NULL);
// Nested <noscript> elements
driver->ParseText("Blah blah blah <noscript><noscript> do-de-do-do ");
driver->Flush();
EXPECT_TRUE(filter_->noscript_element() != NULL);
driver->ParseText("<link href='style.css'>");
driver->Flush();
EXPECT_TRUE(filter_->noscript_element() != NULL);
// Close inner <noscript>s
driver->ParseText("</noscript></noscript>");
driver->Flush();
EXPECT_TRUE(filter_->noscript_element() != NULL);
// Close outter <noscript>
driver->ParseText("</noscript>");
driver->Flush();
EXPECT_TRUE(filter_->noscript_element() == NULL);
driver->ParseText("</head></html>");
driver->FinishParse();
EXPECT_TRUE(filter_->noscript_element() == NULL);
}
TEST_F(CommonFilterTest, TestTwoDomainLawyers) {
static const char kBaseUrl[] = "http://www.base.com/";
CommonFilter* a = MakeFilter(kBaseUrl, "a.com", options(), rewrite_driver());
CommonFilter* b = MakeFilter(kBaseUrl, "b.com", other_options(),
other_rewrite_driver());
// Either filter can rewrite resources from the base URL
EXPECT_TRUE(CanRewriteResource(a, StrCat(kBaseUrl, "base.css")));
EXPECT_TRUE(CanRewriteResource(b, StrCat(kBaseUrl, "base.css")));
// But the other domains are specific to the two different drivers/filters
EXPECT_TRUE(CanRewriteResource(a, "http://a.com/a.css"));
EXPECT_FALSE(CanRewriteResource(a, "http://b.com/b.css"));
EXPECT_FALSE(CanRewriteResource(b, "http://a.com/a.css"));
EXPECT_TRUE(CanRewriteResource(b, "http://b.com/b.css"));
}
const char kEndDocumentComment[] = "<!--test comment-->";
class EndDocumentInserterFilter : public CommonFilter {
public:
explicit EndDocumentInserterFilter(RewriteDriver* driver)
: CommonFilter(driver)
{}
virtual void EndDocument() {
InsertNodeAtBodyEnd(driver()->NewCommentNode(NULL, "test comment"));
}
virtual void StartDocumentImpl() {}
virtual void StartElementImpl(HtmlElement* element) {}
virtual void EndElementImpl(HtmlElement* element) {}
virtual const char* Name() const {
return "CommonFilterTest.EndDocumentInserterFilter";
}
};
class CommonFilterInsertNodeAtBodyEndTest : public RewriteTestBase {
protected:
virtual void SetUp() {
RewriteTestBase::SetUp();
filter_.reset(new EndDocumentInserterFilter(rewrite_driver()));
rewrite_driver()->AddFilter(filter_.get());
SetupWriter();
}
void StartTest(StringPiece pre_comment) {
GoogleString url = "http://www.example.com/";
rewrite_driver()->StartParse(url);
rewrite_driver()->ParseText(pre_comment);
}
const GoogleString FinishTest(StringPiece pre_comment,
StringPiece post_comment) {
const GoogleString expected_html =
StrCat(pre_comment, kEndDocumentComment, post_comment);
rewrite_driver()->ParseText(post_comment);
rewrite_driver()->FinishParse();
return expected_html;
}
const GoogleString FullTest(StringPiece pre_comment,
StringPiece post_comment) {
StartTest(pre_comment);
return FinishTest(pre_comment, post_comment);
}
const GoogleString FlushTest(StringPiece pre_flush, StringPiece pre_comment,
StringPiece post_comment) {
StartTest(pre_flush);
rewrite_driver()->Flush();
rewrite_driver()->ParseText(pre_comment);
GoogleString full_pre_comment = StrCat(pre_flush, pre_comment);
return FinishTest(full_pre_comment, post_comment);
}
scoped_ptr<EndDocumentInserterFilter> filter_;
};
TEST_F(CommonFilterInsertNodeAtBodyEndTest, OneBody) {
GoogleString expected =
FullTest("<html><head></head><body>", "</body></html>");
EXPECT_STREQ(expected, output_buffer_);
}
TEST_F(CommonFilterInsertNodeAtBodyEndTest, WhiteSpace) {
GoogleString expected =
FullTest("<html><head></head><body>", "</body>\n</html>");
EXPECT_STREQ(expected, output_buffer_);
}
TEST_F(CommonFilterInsertNodeAtBodyEndTest, NoBody) {
GoogleString expected =
FullTest("some content without body tag\n</html>", "");
EXPECT_STREQ(expected, output_buffer_);
}
TEST_F(CommonFilterInsertNodeAtBodyEndTest, NoCloseBody) {
GoogleString expected =
FullTest("<html><head></head><body><img src=\"a.jpg\">", "</html>");
EXPECT_STREQ(expected, output_buffer_);
}
TEST_F(CommonFilterInsertNodeAtBodyEndTest, FlushInBody) {
GoogleString expected =
FlushTest("<html><head></head><body>", "", "</body></html>");
EXPECT_STREQ(expected, output_buffer_);
}
TEST_F(CommonFilterInsertNodeAtBodyEndTest, FlushBeforeBody) {
GoogleString expected =
FlushTest("<html><head></head>", "<body>", "</body></html>");
EXPECT_STREQ(expected, output_buffer_);
}
TEST_F(CommonFilterInsertNodeAtBodyEndTest, FlushAfterCloseBody) {
// kEndDocumentComment gets inserted after </body> since both the open and
// close tags have been flushed already.
GoogleString expected =
FlushTest("<html><head></head><body></body>", "", "</html>");
EXPECT_STREQ(expected, output_buffer_);
}
TEST_F(CommonFilterInsertNodeAtBodyEndTest, FlushAtEnd) {
// This causes us to append to the end of document after the flush.
GoogleString expected =
FlushTest("<html><head></head><body></body></html>", "", "");
EXPECT_STREQ(expected, output_buffer_);
}
TEST_F(CommonFilterInsertNodeAtBodyEndTest, TwoBodies) {
GoogleString expected =
FullTest("<html><head></head><body></body><body>", "</body></html>");
EXPECT_STREQ(expected, output_buffer_);
}
TEST_F(CommonFilterInsertNodeAtBodyEndTest, TextAfterCloseBody) {
GoogleString expected =
FullTest("<html><head></head><body></body>extra text", "</html>");
EXPECT_STREQ(expected, output_buffer_);
}
TEST_F(CommonFilterInsertNodeAtBodyEndTest, TextAfterCloseHtml) {
GoogleString expected =
FullTest("<html><head></head><body></body></html>extra text", "");
EXPECT_STREQ(expected, output_buffer_);
}
TEST_F(CommonFilterInsertNodeAtBodyEndTest, BodyInNoscript) {
GoogleString expected = FullTest(
"<html><head></head><noscript><body></body></noscript>", "</html>");
EXPECT_STREQ(expected, output_buffer_);
}
} // namespace
} // namespace net_instaweb