| /* |
| * 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 |