| /* |
| * Copyright 2012 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: rahulbansal@google.com (Rahul Bansal) |
| |
| #include "net/instaweb/rewriter/public/split_html_filter.h" |
| |
| #include "net/instaweb/http/public/logging_proto_impl.h" |
| #include "net/instaweb/http/public/request_context.h" |
| #include "net/instaweb/rewriter/critical_line_info.pb.h" |
| #include "net/instaweb/rewriter/flush_early.pb.h" |
| #include "net/instaweb/rewriter/public/add_instrumentation_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/split_html_helper_filter.h" |
| #include "net/instaweb/rewriter/public/static_asset_manager.h" |
| #include "net/instaweb/rewriter/public/test_rewrite_driver_factory.h" |
| #include "pagespeed/kernel/base/gtest.h" |
| #include "pagespeed/kernel/base/mock_timer.h" |
| #include "pagespeed/kernel/base/ref_counted_ptr.h" |
| #include "pagespeed/kernel/base/string_writer.h" |
| #include "pagespeed/kernel/html/html_keywords.h" |
| #include "pagespeed/kernel/html/html_name.h" |
| #include "pagespeed/kernel/html/html_writer_filter.h" |
| #include "pagespeed/kernel/http/http_names.h" |
| #include "pagespeed/kernel/http/response_headers.h" |
| #include "pagespeed/kernel/http/user_agent_matcher_test_base.h" |
| |
| namespace net_instaweb { |
| |
| namespace { |
| |
| const char kRequestUrl[] = "http://www.test.com"; |
| |
| const char kHtmlInputPart1[] = |
| "<html>" |
| "<head>\n" |
| "<script orig_index=1>blah</script>" |
| "<script orig_index=2>blah2</script>" |
| "</head>\n" |
| "<body>\n" |
| "<div id=\"header\"> This is the header </div>" |
| "<div id=\"container\" class>" |
| "<h2 id=\"beforeItems\"> This is before Items </h2>" |
| "<div id=\"item\">" |
| "<img src=\"image1\" data-pagespeed-high-res-src=\"image1_high_res\"" |
| " onload=\"func\">" |
| "<img src=\"image2\" data-pagespeed-high-res-src=\"image2_high_res\">" |
| "</div>" |
| "<span id=\"between\"> This is in between </span>" |
| "<div id=\"inspiration\">" |
| "<script orig_index=3></script>" |
| "<img src=\"image11\">" |
| "</div>"; |
| |
| const char kHtmlInputPart2[] = |
| "<h3 id=\"afterInspirations\"> This is after Inspirations </h3>" |
| "</div>" |
| "<img id=\"image\" src=\"image_panel.1\">" |
| "<script data-pagespeed-no-defer></script>" |
| "<h1 id=\"footer\" name style>" |
| "This is the footer" |
| "</h1>" |
| "</body></html>"; |
| |
| const char kSplitHtmlPrefix[] = |
| "<html><head>" |
| "\n<script orig_index=1>blah</script>" |
| "<script orig_index=2>blah2</script>"; |
| |
| const char kSplitHtmlMiddle[] = |
| "</head>\n" |
| "<body>\n" |
| "<div id=\"header\"> This is the header </div>" |
| "<div id=\"container\" class>" |
| "<h2 id=\"beforeItems\"> This is before Items </h2>" |
| "<div id=\"item\">" |
| "<img src=\"image1\" data-pagespeed-high-res-src=\"image1_high_res\">" |
| "<img src=\"image2\" data-pagespeed-high-res-src=\"image2_high_res\">" |
| "</div>" |
| "<span id=\"between\"> This is in between </span>" |
| "<!--GooglePanel begin panel-id.0--><!--GooglePanel end panel-id.0-->" |
| "</div>" |
| "<!--GooglePanel begin panel-id.1--><!--GooglePanel end panel-id.1-->" |
| "<script data-pagespeed-no-defer></script>" |
| "<h1 id=\"footer\" name style>" |
| "This is the footer" |
| "</h1>" |
| "</body></html>"; |
| |
| const char kSplitHtmlMiddleWithoutPanelStubs[] = |
| "</head>\n" |
| "<body>\n" |
| "<div id=\"header\"> This is the header </div>" |
| "<div id=\"container\" class>" |
| "<h2 id=\"beforeItems\"> This is before Items </h2>" |
| "<div id=\"item\">" |
| "<img src=\"image1\" data-pagespeed-high-res-src=\"image1_high_res\">" |
| "<img src=\"image2\" data-pagespeed-high-res-src=\"image2_high_res\">" |
| "</div>" |
| "<span id=\"between\"> This is in between </span>" |
| "<div id=\"inspiration\">" |
| "<script orig_index=3></script>" |
| "<img src=\"image11\">" |
| "</div>"; |
| |
| const char kSplitHtmlBelowTheFoldData[] = |
| "{\"panel-id.0\":[{\"instance_html\":\"<div id=\\\"inspiration\\\" " |
| "panel-id=\\\"panel-id.0\\\"><script orig_index=3>" |
| "<\\/script><img src=\\\"image11\\\"></div>" |
| "<h3 id=\\\"afterInspirations\\\" panel-id=\\\"panel-id.0\\\"> " |
| "This is after Inspirations </h3>\"}]," |
| "\"panel-id.1\":[{\"instance_html\":\"<img id=\\\"image\\\" " |
| "src=\\\"image_panel.1\\\" panel-id=\\\"panel-id.1\\\">\"}]}"; |
| |
| const char kHtmlInputForLazyload[] = "<html><head></head><body></body></html>"; |
| |
| const char kHtmlInputForIgnoreScript[] = |
| "<html><body>%s<h1></h1>%s<h1></h1></body></html>"; |
| |
| const char kHtmlExpectedOutputForIgnoreScript1[] = |
| "<html><head>%s</head><body>%s<h1></h1>%s" |
| "<!--GooglePanel begin panel-id.0--><!--GooglePanel end panel-id.0-->" |
| "</body></html>%s"; |
| |
| const char kHtmlExpectedOutputForIgnoreScript2[] = |
| "<html><head></head><body>%s" |
| "<!--GooglePanel begin panel-id.0--><!--GooglePanel end panel-id.0-->" |
| "</body></html>%s"; |
| |
| class SplitHtmlFilterTest : public RewriteTestBase { |
| public: |
| SplitHtmlFilterTest(): writer_(&output_) {} |
| |
| virtual bool AddHtmlTags() const { return false; } |
| |
| protected: |
| virtual void SetUp() { |
| delete options_; |
| options_ = new RewriteOptions(factory()->thread_system()); |
| options_->DisableFilter(RewriteOptions::kHtmlWriterFilter); |
| RewriteTestBase::SetUp(); |
| |
| rewrite_driver()->SetWriter(&writer_); |
| SplitHtmlFilter* filter = new SplitHtmlFilter(rewrite_driver()); |
| html_writer_filter_.reset(filter); |
| html_writer_filter_->set_writer(&writer_); |
| rewrite_driver()->AddFilter(html_writer_filter_.get()); |
| SetCurrentUserAgent( |
| UserAgentMatcherTestBase::kChrome18UserAgent); |
| |
| response_headers_.set_status_code(HttpStatus::kOK); |
| response_headers_.SetDateAndCaching( |
| MockTimer::kApr_5_2010_ms, 10000, ",no-cache"); |
| response_headers_.Add(HttpAttributes::kPragma, "no-cache"); |
| response_headers_.Add(HttpAttributes::kAge, "1000"); |
| rewrite_driver()->set_response_headers_ptr(&response_headers_); |
| output_.clear(); |
| StaticAssetManager* static_asset_manager = |
| rewrite_driver()->server_context()->static_asset_manager(); |
| blink_js_url_ = static_asset_manager->GetAssetUrl( |
| StaticAssetEnum::BLINK_JS, options_).c_str(); |
| nodefer_str_ = |
| HtmlKeywords::KeywordToString(HtmlName::kDataPagespeedNoDefer); |
| } |
| |
| // TODO(marq): This looks reusable enough to go into RewriteTestBase. Perhaps |
| // it should also know the rewriter-under-test's ID so there's less |
| // boilerplate? |
| void VerifyAppliedRewriters(GoogleString expected_rewriters) { |
| EXPECT_STREQ(expected_rewriters, AppliedRewriterStringFromLog()); |
| } |
| |
| void VerifyJsonSize(int64 expected_size) { |
| int64 actual_size = 0; |
| if (logging_info()->has_split_html_info()) { |
| actual_size = logging_info()->split_html_info().json_size(); |
| } |
| EXPECT_EQ(expected_size, actual_size); |
| } |
| |
| void SetBtfRequest() { |
| rewrite_driver()->request_context()->set_split_request_type( |
| RequestContext::SPLIT_BELOW_THE_FOLD); |
| } |
| |
| void SetAtfRequest() { |
| rewrite_driver()->request_context()->set_split_request_type( |
| RequestContext::SPLIT_ABOVE_THE_FOLD); |
| } |
| |
| GoogleString output_; |
| const char* blink_js_url_; |
| ResponseHeaders response_headers_; |
| const StringPiece* nodefer_str_; |
| |
| private: |
| StringWriter writer_; |
| SplitHtmlFilter* split_html_filter_; |
| }; |
| |
| TEST_F(SplitHtmlFilterTest, SplitHtmlWithDriverHavingCriticalLineInfo) { |
| CriticalLineInfo* config = new CriticalLineInfo; |
| Panel* panel = config->add_panels(); |
| panel->set_start_xpath("div[@id = \"container\"]/div[4]"); |
| panel = config->add_panels(); |
| panel->set_start_xpath("img[3]"); |
| panel->set_end_marker_xpath("h1[@id = \"footer\"]"); |
| rewrite_driver()->set_critical_line_info(config); |
| |
| Parse("split_with_pcache", StrCat(kHtmlInputPart1, kHtmlInputPart2)); |
| GoogleString suffix(StringPrintf(SplitHtmlFilter::kSplitSuffixJsFormatString, |
| blink_js_url_, |
| SplitHtmlFilter::kLoadHiResImages, 2, |
| kSplitHtmlBelowTheFoldData, "false")); |
| EXPECT_EQ(StrCat(kSplitHtmlPrefix, |
| kSplitHtmlMiddle, suffix), |
| output_); |
| VerifyAppliedRewriters( |
| RewriteOptions::FilterId(RewriteOptions::kSplitHtml)); |
| VerifyJsonSize(strlen(kSplitHtmlBelowTheFoldData)); |
| } |
| |
| TEST_F(SplitHtmlFilterTest, SplitHtmlAddMetaReferer) { |
| options_->set_hide_referer_using_meta(true); |
| CriticalLineInfo* config = new CriticalLineInfo; |
| Panel* panel = config->add_panels(); |
| panel->set_start_xpath("div[@id = \"container\"]/div[4]"); |
| panel = config->add_panels(); |
| panel->set_start_xpath("img[3]"); |
| panel->set_end_marker_xpath("h1[@id = \"footer\"]"); |
| rewrite_driver()->set_critical_line_info(config); |
| |
| Parse("split_with_pcache", StrCat(kHtmlInputPart1, kHtmlInputPart2)); |
| GoogleString suffix(StringPrintf(SplitHtmlFilter::kSplitSuffixJsFormatString, |
| blink_js_url_, |
| SplitHtmlFilter::kLoadHiResImages, 2, |
| kSplitHtmlBelowTheFoldData, "false")); |
| EXPECT_EQ(StrCat("<html><head>", |
| SplitHtmlFilter::kMetaReferer, |
| "\n<script orig_index=1>blah</script>" |
| "<script orig_index=2>blah2</script>", |
| kSplitHtmlMiddle, suffix), |
| output_); |
| VerifyAppliedRewriters( |
| RewriteOptions::FilterId(RewriteOptions::kSplitHtml)); |
| VerifyJsonSize(strlen(kSplitHtmlBelowTheFoldData)); |
| } |
| |
| TEST_F(SplitHtmlFilterTest, |
| SplitTwoChunksHtmlWithDriverHavingCriticalLineInfoATF) { |
| options_->set_serve_split_html_in_two_chunks(true); |
| SetAtfRequest(); |
| CriticalLineInfo* config = new CriticalLineInfo; |
| Panel* panel = config->add_panels(); |
| panel->set_start_xpath("div[@id = \"container\"]/div[4]"); |
| panel = config->add_panels(); |
| panel->set_start_xpath("img[3]"); |
| panel->set_end_marker_xpath("h1[@id = \"footer\"]"); |
| rewrite_driver()->set_critical_line_info(config); |
| rewrite_driver()->AddOwnedEarlyPreRenderFilter( |
| new SplitHtmlHelperFilter(rewrite_driver())); |
| |
| Parse("split_with_pcache?\"test", |
| StrCat(kHtmlInputPart1, kHtmlInputPart2)); |
| GoogleString suffix( |
| StringPrintf( |
| SplitHtmlFilter::kSplitTwoChunkSuffixJsFormatString, |
| HttpAttributes::kXPsaSplitConfig, |
| "div[@id = \"container\"]/div[4],img[3]:h1[@id = \"footer\"],", "1", |
| SplitHtmlFilter::kLoadHiResImages, blink_js_url_, 2)); |
| EXPECT_EQ(StrCat(kSplitHtmlPrefix, |
| kSplitHtmlMiddle, suffix), |
| output_); |
| EXPECT_STREQ("1000", response_headers_.Lookup1(HttpAttributes::kAge)); |
| EXPECT_STREQ("no-cache", response_headers_.Lookup1(HttpAttributes::kPragma)); |
| ConstStringStarVector values; |
| EXPECT_TRUE(response_headers_.Lookup(HttpAttributes::kCacheControl, &values)); |
| EXPECT_STREQ("max-age=10,no-cache", JoinStringStar(values, ",")); |
| } |
| |
| TEST_F(SplitHtmlFilterTest, |
| SplitTwoChunksHtmlWithDriverHavingCriticalLineInfoATFAndCacheTime) { |
| options_->set_max_html_cache_time_ms(30000); |
| options_->set_serve_split_html_in_two_chunks(true); |
| SetAtfRequest(); |
| CriticalLineInfo* config = new CriticalLineInfo; |
| Panel* panel = config->add_panels(); |
| panel->set_start_xpath("div[@id = \"container\"]/div[4]"); |
| panel = config->add_panels(); |
| panel->set_start_xpath("img[3]"); |
| panel->set_end_marker_xpath("h1[@id = \"footer\"]"); |
| rewrite_driver()->set_critical_line_info(config); |
| |
| Parse("split_with_pcache", StrCat(kHtmlInputPart1, kHtmlInputPart2)); |
| GoogleString suffix( |
| StringPrintf( |
| SplitHtmlFilter::kSplitTwoChunkSuffixJsFormatString, |
| HttpAttributes::kXPsaSplitConfig, |
| "div[@id = \"container\"]/div[4],img[3]:h1[@id = \"footer\"],", "1", |
| SplitHtmlFilter::kLoadHiResImages, blink_js_url_, 2)); |
| EXPECT_EQ(StrCat(kSplitHtmlPrefix, |
| kSplitHtmlMiddle, suffix), |
| output_); |
| EXPECT_EQ(NULL, response_headers_.Lookup1( |
| HttpAttributes::kAccessControlAllowOrigin)); |
| EXPECT_EQ(NULL, response_headers_.Lookup1( |
| HttpAttributes::kAccessControlAllowCredentials)); |
| EXPECT_EQ(NULL, response_headers_.Lookup1(HttpAttributes::kAge)); |
| EXPECT_EQ(NULL, response_headers_.Lookup1(HttpAttributes::kPragma)); |
| ConstStringStarVector values; |
| EXPECT_TRUE(response_headers_.Lookup(HttpAttributes::kCacheControl, &values)); |
| EXPECT_STREQ("max-age=30,private", JoinStringStar(values, ",")); |
| } |
| |
| TEST_F(SplitHtmlFilterTest, SplitTwoChunksHtmlATFAndNoBTF) { |
| options_->set_serve_split_html_in_two_chunks(true); |
| SetAtfRequest(); |
| CriticalLineInfo* config = new CriticalLineInfo; |
| Panel* panel = config->add_panels(); |
| // Use a non-existent xpath. |
| panel->set_start_xpath("div[@id = \"abcd\"]/div[4]"); |
| rewrite_driver()->set_critical_line_info(config); |
| |
| Parse("split_with_pcache", StrCat(kHtmlInputPart1, kHtmlInputPart2)); |
| GoogleString expected_output(kSplitHtmlPrefix); |
| GoogleString suffix( |
| StringPrintf(SplitHtmlFilter::kSplitTwoChunkSuffixJsFormatString, |
| HttpAttributes::kXPsaSplitConfig, |
| "div[@id = \"abcd\"]/div[4],", "", |
| SplitHtmlFilter::kLoadHiResImages, |
| blink_js_url_, 3)); |
| StrAppend(&expected_output, |
| kSplitHtmlMiddleWithoutPanelStubs, |
| kHtmlInputPart2, suffix); |
| |
| EXPECT_EQ(expected_output, output_); |
| } |
| |
| TEST_F(SplitHtmlFilterTest, SplitTwoChunksHtmlATFWithFlushAndHelper) { |
| options_->set_serve_split_html_in_two_chunks(true); |
| SetAtfRequest(); |
| options_->set_critical_line_config("div[@id = \"abcd\"]/div[4]"); |
| rewrite_driver()->AddOwnedEarlyPreRenderFilter( |
| new SplitHtmlHelperFilter(rewrite_driver())); |
| |
| html_parse()->SetWriter(&write_to_string_); |
| html_parse()->StartParse("http://example.com"); |
| html_parse()->ParseText(kHtmlInputPart1); |
| html_parse()->Flush(); |
| html_parse()->ParseText(kHtmlInputPart2); |
| html_parse()->FinishParse(); |
| |
| GoogleString expected_output(kSplitHtmlPrefix); |
| GoogleString suffix( |
| StringPrintf(SplitHtmlFilter::kSplitTwoChunkSuffixJsFormatString, |
| HttpAttributes::kXPsaSplitConfig, |
| "div[@id = \"abcd\"]/div[4],", "", |
| SplitHtmlFilter::kLoadHiResImages, |
| blink_js_url_, 3)); |
| StrAppend(&expected_output, |
| kSplitHtmlMiddleWithoutPanelStubs, |
| kHtmlInputPart2, suffix); |
| |
| EXPECT_EQ(expected_output, output_buffer_); |
| } |
| |
| TEST_F(SplitHtmlFilterTest, FlushBeforeParse) { |
| options_->set_serve_split_html_in_two_chunks(true); |
| SetAtfRequest(); |
| options_->set_critical_line_config("div[@id = \"abcd\"]/div[4]"); |
| rewrite_driver()->AddOwnedEarlyPreRenderFilter( |
| new SplitHtmlHelperFilter(rewrite_driver())); |
| |
| html_parse()->SetWriter(&write_to_string_); |
| html_parse()->StartParse("http://example.com"); |
| html_parse()->Flush(); |
| html_parse()->ParseText(kHtmlInputPart1); |
| html_parse()->ParseText(kHtmlInputPart2); |
| html_parse()->FinishParse(); |
| |
| GoogleString expected_output(kSplitHtmlPrefix); |
| GoogleString suffix( |
| StringPrintf(SplitHtmlFilter::kSplitTwoChunkSuffixJsFormatString, |
| HttpAttributes::kXPsaSplitConfig, |
| "div[@id = \"abcd\"]/div[4],", "", |
| SplitHtmlFilter::kLoadHiResImages, |
| blink_js_url_, 3)); |
| StrAppend(&expected_output, |
| kSplitHtmlMiddleWithoutPanelStubs, |
| kHtmlInputPart2, suffix); |
| |
| EXPECT_EQ(expected_output, output_buffer_); |
| } |
| |
| TEST_F(SplitHtmlFilterTest, ATFHeadersWithAllowAllOrigins) { |
| AddRequestAttribute(HttpAttributes::kOrigin, "abc.com"); |
| options_->set_serve_split_html_in_two_chunks(true); |
| SetAtfRequest(); |
| options_->set_serve_xhr_access_control_headers(true); |
| options_->set_access_control_allow_origins("*"); |
| CriticalLineInfo* config = new CriticalLineInfo; |
| rewrite_driver()->set_critical_line_info(config); |
| |
| Parse("split_with_pcache", StrCat(kHtmlInputPart1, kHtmlInputPart2)); |
| GoogleString expected_output(kSplitHtmlPrefix); |
| GoogleString suffix( |
| StringPrintf(SplitHtmlFilter::kSplitTwoChunkSuffixJsFormatString, |
| HttpAttributes::kXPsaSplitConfig, "", "", |
| SplitHtmlFilter::kLoadHiResImages, |
| blink_js_url_, 3)); |
| StrAppend(&expected_output, |
| kSplitHtmlMiddleWithoutPanelStubs, |
| kHtmlInputPart2, suffix); |
| EXPECT_EQ(expected_output, output_); |
| EXPECT_STREQ("abc.com", response_headers_.Lookup1( |
| HttpAttributes::kAccessControlAllowOrigin)); |
| EXPECT_STREQ("true", response_headers_.Lookup1( |
| HttpAttributes::kAccessControlAllowCredentials)); |
| } |
| |
| TEST_F(SplitHtmlFilterTest, ATFHeadersCrossOriginAllowed) { |
| AddRequestAttribute(HttpAttributes::kOrigin, "http://cross-domain.com"); |
| options_->set_serve_split_html_in_two_chunks(true); |
| SetAtfRequest(); |
| options_->set_serve_xhr_access_control_headers(true); |
| options_->set_access_control_allow_origins( |
| "example.com, *cross-domain.com, abc.com"); |
| CriticalLineInfo* config = new CriticalLineInfo; |
| rewrite_driver()->set_critical_line_info(config); |
| |
| Parse("split_with_pcache", StrCat(kHtmlInputPart1, kHtmlInputPart2)); |
| GoogleString expected_output(kSplitHtmlPrefix); |
| GoogleString suffix( |
| StringPrintf(SplitHtmlFilter::kSplitTwoChunkSuffixJsFormatString, |
| HttpAttributes::kXPsaSplitConfig, "", "", |
| SplitHtmlFilter::kLoadHiResImages, |
| blink_js_url_, 3)); |
| StrAppend(&expected_output, |
| kSplitHtmlMiddleWithoutPanelStubs, |
| kHtmlInputPart2, suffix); |
| EXPECT_EQ(expected_output, output_); |
| EXPECT_STREQ("http://cross-domain.com", response_headers_.Lookup1( |
| HttpAttributes::kAccessControlAllowOrigin)); |
| EXPECT_STREQ("true", response_headers_.Lookup1( |
| HttpAttributes::kAccessControlAllowCredentials)); |
| } |
| |
| TEST_F(SplitHtmlFilterTest, ATFHeadersCrossOriginDisAllowed) { |
| AddRequestAttribute(HttpAttributes::kOrigin, "disallowed-domain.com"); |
| options_->set_serve_split_html_in_two_chunks(true); |
| SetAtfRequest(); |
| options_->set_serve_xhr_access_control_headers(true); |
| options_->set_access_control_allow_origins( |
| "example.com, cross-domain.com, http://disallowed-domain.com, abc.com"); |
| CriticalLineInfo* config = new CriticalLineInfo; |
| rewrite_driver()->set_critical_line_info(config); |
| |
| Parse("split_with_pcache", StrCat(kHtmlInputPart1, kHtmlInputPart2)); |
| GoogleString expected_output(kSplitHtmlPrefix); |
| GoogleString suffix( |
| StringPrintf(SplitHtmlFilter::kSplitTwoChunkSuffixJsFormatString, |
| HttpAttributes::kXPsaSplitConfig, "", "", |
| SplitHtmlFilter::kLoadHiResImages, |
| blink_js_url_, 3)); |
| StrAppend(&expected_output, |
| kSplitHtmlMiddleWithoutPanelStubs, |
| kHtmlInputPart2, suffix); |
| EXPECT_EQ(expected_output, output_); |
| EXPECT_EQ(NULL, response_headers_.Lookup1( |
| HttpAttributes::kAccessControlAllowOrigin)); |
| EXPECT_STREQ(NULL, response_headers_.Lookup1( |
| HttpAttributes::kAccessControlAllowCredentials)); |
| } |
| |
| TEST_F(SplitHtmlFilterTest, |
| SplitTwoChunksHtmlWithDriverHavingCriticalLineInfoBTF) { |
| options_->set_serve_split_html_in_two_chunks(true); |
| SetBtfRequest(); |
| CriticalLineInfo* config = new CriticalLineInfo; |
| Panel* panel = config->add_panels(); |
| panel->set_start_xpath("div[@id = \"container\"]/div[4]"); |
| panel = config->add_panels(); |
| panel->set_start_xpath("img[3]"); |
| panel->set_end_marker_xpath("h1[@id = \"footer\"]"); |
| rewrite_driver()->set_critical_line_info(config); |
| |
| Parse("split_with_pcache", StrCat(kHtmlInputPart1, kHtmlInputPart2)); |
| EXPECT_EQ(kSplitHtmlBelowTheFoldData, output_); |
| } |
| |
| TEST_F(SplitHtmlFilterTest, |
| SplitTwoChunksHtmlWithRequestHeaderDriverHavingNoCriticalLineInfoBTF) { |
| options_->set_serve_split_html_in_two_chunks(true); |
| SetBtfRequest(); |
| rewrite_driver()->set_critical_line_info(NULL); |
| AddRequestAttribute( |
| HttpAttributes::kXPsaSplitConfig, |
| "div[@id = \"container\"]/div[4],img[3]:h1[@id = \"footer\"],"); |
| Parse("split_with_pcache", StrCat(kHtmlInputPart1, kHtmlInputPart2)); |
| EXPECT_EQ(kSplitHtmlBelowTheFoldData, output_); |
| } |
| |
| TEST_F(SplitHtmlFilterTest, |
| SplitTwoChunksHtmlWithRequestHeaderDriverHavingCriticalLineInfoBTF) { |
| options_->set_serve_split_html_in_two_chunks(true); |
| SetBtfRequest(); |
| CriticalLineInfo* config = new CriticalLineInfo; |
| Panel* panel = config->add_panels(); |
| panel->set_start_xpath("div[@id = \"blah\"]/div[5]"); |
| rewrite_driver()->set_critical_line_info(config); |
| AddRequestAttribute( |
| HttpAttributes::kXPsaSplitConfig, |
| "div[@id = \"container\"]/div[4],img[3]:h1[@id = \"footer\"],"); |
| |
| Parse("split_with_pcache", StrCat(kHtmlInputPart1, kHtmlInputPart2)); |
| EXPECT_EQ("{}", output_); |
| } |
| |
| TEST_F(SplitHtmlFilterTest, SplitHtmlWithFlushingCachedHtml) { |
| CriticalLineInfo* config = new CriticalLineInfo; |
| Panel* panel = config->add_panels(); |
| panel->set_start_xpath("div[@id = \"container\"]/div[4]"); |
| panel = config->add_panels(); |
| panel->set_start_xpath("img[3]"); |
| panel->set_end_marker_xpath("h1[@id = \"footer\"]"); |
| rewrite_driver()->set_critical_line_info(config); |
| rewrite_driver()->set_flushing_cached_html(true); |
| |
| Parse("split_with_pcache", StrCat(kHtmlInputPart1, kHtmlInputPart2)); |
| GoogleString suffix(StringPrintf(SplitHtmlFilter::kSplitSuffixJsFormatString, |
| blink_js_url_, |
| SplitHtmlFilter::kLoadHiResImages, 2, |
| kSplitHtmlBelowTheFoldData, "true")); |
| EXPECT_EQ(StrCat(kSplitHtmlPrefix, |
| kSplitHtmlMiddle, suffix), |
| output_); |
| VerifyAppliedRewriters( |
| RewriteOptions::FilterId(RewriteOptions::kSplitHtml)); |
| VerifyJsonSize(strlen(kSplitHtmlBelowTheFoldData)); |
| } |
| |
| TEST_F(SplitHtmlFilterTest, SplitHtmlWithOptions) { |
| options_->set_critical_line_config( |
| "div[@id = \"container\"]/div[4]," |
| "img[3]:h1[@id = \"footer\"]"); |
| Parse("split_with_options", StrCat(kHtmlInputPart1, kHtmlInputPart2)); |
| GoogleString suffix(StringPrintf(SplitHtmlFilter::kSplitSuffixJsFormatString, |
| blink_js_url_, |
| SplitHtmlFilter::kLoadHiResImages, 2, |
| kSplitHtmlBelowTheFoldData, "false")); |
| EXPECT_EQ(StrCat(kSplitHtmlPrefix, |
| kSplitHtmlMiddle, suffix), |
| output_); |
| VerifyAppliedRewriters( |
| RewriteOptions::FilterId(RewriteOptions::kSplitHtml)); |
| VerifyJsonSize(strlen(kSplitHtmlBelowTheFoldData)); |
| } |
| |
| TEST_F(SplitHtmlFilterTest, SplitHtmlWithFlushes) { |
| options_->set_critical_line_config( |
| "div[@id = \"container\"]/div[4]," |
| "img[3]:h1[@id = \"footer\"]"); |
| html_parse()->StartParse("http://test.com/"); |
| html_parse()->ParseText(kHtmlInputPart1); |
| html_parse()->Flush(); |
| html_parse()->ParseText(kHtmlInputPart2); |
| html_parse()->FinishParse(); |
| GoogleString suffix(StringPrintf(SplitHtmlFilter::kSplitSuffixJsFormatString, |
| blink_js_url_, |
| SplitHtmlFilter::kLoadHiResImages, 2, |
| kSplitHtmlBelowTheFoldData, "false")); |
| EXPECT_EQ(StrCat(kSplitHtmlPrefix, |
| kSplitHtmlMiddle, suffix), |
| output_); |
| VerifyAppliedRewriters( |
| RewriteOptions::FilterId(RewriteOptions::kSplitHtml)); |
| VerifyJsonSize(strlen(kSplitHtmlBelowTheFoldData)); |
| } |
| |
| TEST_F(SplitHtmlFilterTest, FlushEarlyHeadSuppress) { |
| options_->ForceEnableFilter( |
| RewriteOptions::RewriteOptions::kFlushSubresources); |
| options_->set_critical_line_config( |
| "div[@id = \"container\"]/div[4]," |
| "img[3]:h1[@id = \"footer\"]"); |
| |
| GoogleString pre_head_input = "<!DOCTYPE html><html><head>"; |
| GoogleString post_head_input = |
| "<link type=\"text/css\" rel=\"stylesheet\" href=\"a.css\"/>" |
| "<script src=\"b.js\"></script>" |
| "</head>" |
| "<body></body></html>"; |
| GoogleString suffix(StringPrintf(SplitHtmlFilter::kSplitSuffixJsFormatString, |
| blink_js_url_, |
| SplitHtmlFilter::kLoadHiResImages, |
| -1, "{}", "false")); |
| GoogleString post_head_output = StrCat( |
| "<link type=\"text/css\" rel=\"stylesheet\" href=\"a.css\"/>" |
| "<script src=\"b.js\"></script>", |
| "</head><body></body></html>", suffix); |
| GoogleString html_input = StrCat(pre_head_input, post_head_input); |
| |
| Parse("not_flushed_early", html_input); |
| EXPECT_EQ(StrCat(pre_head_input, post_head_output), output_); |
| VerifyAppliedRewriters(""); |
| VerifyJsonSize(0); |
| |
| // SuppressPreheadFilter should have populated the flush_early_proto with the |
| // appropriate pre head information. |
| EXPECT_EQ(pre_head_input, |
| rewrite_driver()->flush_early_info()->pre_head()); |
| |
| // pre head is suppressed if the dummy head was flushed early. |
| output_.clear(); |
| rewrite_driver()->set_flushed_early(true); |
| Parse("flushed_early", html_input); |
| EXPECT_EQ(post_head_output, output_); |
| VerifyAppliedRewriters(""); |
| VerifyJsonSize(0); |
| } |
| |
| TEST_F(SplitHtmlFilterTest, FlushEarlyDisabled) { |
| options_->set_critical_line_config( |
| "div[@id = \"container\"]/div[4]," |
| "img[3]:h1[@id = \"footer\"]"); |
| |
| const char pre_head_input[] = "<!DOCTYPE html><html>"; |
| const char post_head_input[] = |
| "<head>" |
| "<link type=\"text/css\" rel=\"stylesheet\" href=\"a.css\"/>" |
| "<script src=\"b.js\"></script>" |
| "</head>" |
| "<body></body></html>"; |
| GoogleString html_input = StrCat(pre_head_input, post_head_input); |
| |
| Parse("not_flushed_early", html_input); |
| |
| // SuppressPreheadFilter should not have populated the flush_early_proto. |
| EXPECT_EQ("", rewrite_driver()->flush_early_info()->pre_head()); |
| VerifyAppliedRewriters(""); |
| VerifyJsonSize(0); |
| } |
| |
| TEST_F(SplitHtmlFilterTest, SplitHtmlNoXpaths) { |
| CriticalLineInfo* info = new CriticalLineInfo; |
| rewrite_driver()->set_critical_line_info(info); |
| options_->set_critical_line_config(""); |
| Parse("split_without_xpaths", StrCat(kHtmlInputPart1, kHtmlInputPart2)); |
| GoogleString expected_output(kSplitHtmlPrefix); |
| GoogleString suffix(StringPrintf(SplitHtmlFilter::kSplitSuffixJsFormatString, |
| blink_js_url_, |
| SplitHtmlFilter::kLoadHiResImages, |
| 3, "{}", "false")); |
| StrAppend(&expected_output, |
| kSplitHtmlMiddleWithoutPanelStubs, |
| kHtmlInputPart2, suffix); |
| EXPECT_EQ(expected_output, output_); |
| VerifyAppliedRewriters(""); |
| VerifyJsonSize(0); |
| } |
| |
| TEST_F(SplitHtmlFilterTest, SplitHtmlNoXpathsTwoChunksATF) { |
| CriticalLineInfo* info = new CriticalLineInfo; |
| rewrite_driver()->set_critical_line_info(info); |
| options_->set_critical_line_config(""); |
| options_->set_serve_split_html_in_two_chunks(true); |
| SetAtfRequest(); |
| Parse("split_without_xpaths", StrCat(kHtmlInputPart1, kHtmlInputPart2)); |
| GoogleString expected_output(kSplitHtmlPrefix); |
| GoogleString suffix( |
| StringPrintf(SplitHtmlFilter::kSplitTwoChunkSuffixJsFormatString, |
| HttpAttributes::kXPsaSplitConfig, "", "", |
| SplitHtmlFilter::kLoadHiResImages, |
| blink_js_url_, 3)); |
| StrAppend(&expected_output, |
| kSplitHtmlMiddleWithoutPanelStubs, |
| kHtmlInputPart2, suffix); |
| EXPECT_EQ(expected_output, output_); |
| VerifyAppliedRewriters(""); |
| VerifyJsonSize(0); |
| } |
| |
| TEST_F(SplitHtmlFilterTest, SplitHtmlNoXpathsTwoChunksBTF) { |
| CriticalLineInfo* info = new CriticalLineInfo; |
| rewrite_driver()->set_critical_line_info(info); |
| options_->set_critical_line_config(""); |
| SetBtfRequest(); |
| options_->set_serve_split_html_in_two_chunks(true); |
| Parse("split_without_xpaths", StrCat(kHtmlInputPart1, kHtmlInputPart2)); |
| EXPECT_EQ("{}", output_); |
| VerifyAppliedRewriters(""); |
| VerifyJsonSize(0); |
| } |
| |
| TEST_F(SplitHtmlFilterTest, SplitHtmlNoInfoTwoChunksATF) { |
| rewrite_driver()->set_critical_line_info(NULL); |
| options_->set_serve_split_html_in_two_chunks(true); |
| const GoogleString html(StrCat(kHtmlInputPart1, kHtmlInputPart2)); |
| Parse("split_cache_miss", html); |
| EXPECT_EQ(html, output_); |
| VerifyAppliedRewriters(""); |
| VerifyJsonSize(0); |
| } |
| |
| TEST_F(SplitHtmlFilterTest, SplitHtmlNoInfoTwoChunksBTF) { |
| rewrite_driver()->set_critical_line_info(NULL); |
| SetBtfRequest(); |
| options_->set_serve_split_html_in_two_chunks(true); |
| const GoogleString html(StrCat(kHtmlInputPart1, kHtmlInputPart2)); |
| Parse("split_cache_miss", html); |
| EXPECT_EQ(html, output_); |
| VerifyAppliedRewriters(""); |
| VerifyJsonSize(0); |
| } |
| |
| TEST_F(SplitHtmlFilterTest, SplitHtmlNoInfo) { |
| rewrite_driver()->set_critical_line_info(NULL); |
| const GoogleString html(StrCat(kHtmlInputPart1, kHtmlInputPart2)); |
| Parse("split_cache_miss", html); |
| GoogleString expected_output(kSplitHtmlPrefix); |
| GoogleString suffix(StringPrintf(SplitHtmlFilter::kSplitSuffixJsFormatString, |
| blink_js_url_, |
| SplitHtmlFilter::kLoadHiResImages, |
| 3, "{}", "false")); |
| StrAppend(&expected_output, |
| kSplitHtmlMiddleWithoutPanelStubs, |
| kHtmlInputPart2, suffix); |
| EXPECT_EQ(expected_output, output_); |
| VerifyAppliedRewriters(""); |
| VerifyJsonSize(0); |
| } |
| |
| TEST_F(SplitHtmlFilterTest, SplitHtmlWithUnsupportedUserAgent) { |
| options_->set_critical_line_config( |
| "div[@id = \"container\"]/div[4]," |
| "img[3]:h1[@id = \"footer\"]"); |
| SetCurrentUserAgent("BlackListUserAgent"); |
| SetDriverRequestHeaders(); |
| Parse("split_with_options", StrCat(kHtmlInputPart1, kHtmlInputPart2)); |
| EXPECT_EQ(StrCat(kHtmlInputPart1, kHtmlInputPart2), output_); |
| VerifyAppliedRewriters(""); |
| VerifyJsonSize(0); |
| } |
| |
| TEST_F(SplitHtmlFilterTest, SplitHtmlIgnoreScriptNoscript1) { |
| options_->set_critical_line_config("h1[2]"); |
| GoogleString expected_output_suffix( |
| StringPrintf(SplitHtmlFilter::kSplitSuffixJsFormatString, |
| blink_js_url_, |
| SplitHtmlFilter::kLoadHiResImages, -1, |
| "{\"panel-id.0\":[{\"instance_html\":" |
| "\"<h1 panel-id=\\\"panel-id.0\\\">" |
| "</h1>\"}]}", "false")); |
| GoogleString input(StringPrintf(kHtmlInputForIgnoreScript, "", "")); |
| Parse("split_ignore_script1", input); |
| EXPECT_EQ(StringPrintf(kHtmlExpectedOutputForIgnoreScript1, |
| "", "", "", |
| expected_output_suffix.c_str()).c_str(), output_); |
| VerifyAppliedRewriters("sh"); |
| } |
| |
| TEST_F(SplitHtmlFilterTest, SplitHtmlIgnoreScriptNoscript2) { |
| options_->set_critical_line_config("h1[2]"); |
| GoogleString expected_output_suffix( |
| StringPrintf(SplitHtmlFilter::kSplitSuffixJsFormatString, |
| blink_js_url_, |
| SplitHtmlFilter::kLoadHiResImages, -1, |
| "{\"panel-id.0\":[{\"instance_html\":" |
| "\"<h1 panel-id=\\\"panel-id.0\\\">" |
| "</h1>\"}]}", "false")); |
| GoogleString input = StringPrintf(kHtmlInputForIgnoreScript, "", |
| "<script></script><noscript></noscript>"); |
| Parse("split_ignore_script2", input); |
| EXPECT_EQ(StringPrintf(kHtmlExpectedOutputForIgnoreScript1, |
| "", "", |
| "<script></script><noscript></noscript>", |
| expected_output_suffix.c_str()).c_str(), output_); |
| VerifyAppliedRewriters("sh"); |
| } |
| |
| TEST_F(SplitHtmlFilterTest, SplitHtmlIgnoreScriptNoscript3) { |
| options_->set_critical_line_config("h1[2]"); |
| GoogleString expected_output_suffix( |
| StringPrintf(SplitHtmlFilter::kSplitSuffixJsFormatString, |
| blink_js_url_, |
| SplitHtmlFilter::kLoadHiResImages, -1, |
| "{\"panel-id.0\":[{\"instance_html\":" |
| "\"<h1 panel-id=\\\"panel-id.0\\\">" |
| "</h1>\"}]}", "false")); |
| GoogleString input = StringPrintf(kHtmlInputForIgnoreScript, |
| "<script></script><noscript></noscript>", |
| "<script></script><noscript></noscript>"); |
| Parse("split_ignore_script3", input); |
| EXPECT_EQ(StringPrintf(kHtmlExpectedOutputForIgnoreScript1, |
| "", "<script></script><noscript></noscript>", |
| "<script></script><noscript></noscript>", |
| expected_output_suffix.c_str()).c_str(), output_); |
| VerifyAppliedRewriters("sh"); |
| } |
| |
| TEST_F(SplitHtmlFilterTest, SplitHtmlIgnoreScriptNoscript4) { |
| options_->set_critical_line_config("h1[1]"); |
| GoogleString expected_output_suffix( |
| StringPrintf(SplitHtmlFilter::kSplitSuffixJsFormatString, |
| blink_js_url_, |
| SplitHtmlFilter::kLoadHiResImages, -1, |
| "{\"panel-id.0\":[{\"instance_html\":" |
| "\"<h1 panel-id=\\\"panel-id.0\\\">" |
| "</h1>" |
| "<h1 panel-id=\\\"panel-id.0\\\">" |
| "</h1>\"}]}", "false")); |
| GoogleString input = StringPrintf(kHtmlInputForIgnoreScript, "", ""); |
| Parse("split_ignore_script4", input); |
| EXPECT_EQ(StringPrintf(kHtmlExpectedOutputForIgnoreScript2, |
| "", |
| expected_output_suffix.c_str()).c_str(), output_); |
| VerifyAppliedRewriters("sh"); |
| } |
| |
| TEST_F(SplitHtmlFilterTest, SplitHtmlIgnoreScriptNoscript5) { |
| options_->set_critical_line_config("h1[1]"); |
| GoogleString expected_output_suffix( |
| StringPrintf(SplitHtmlFilter::kSplitSuffixJsFormatString, |
| blink_js_url_, |
| SplitHtmlFilter::kLoadHiResImages, -1, |
| "{\"panel-id.0\":[{\"instance_html\":" |
| "\"<h1 panel-id=\\\"panel-id.0\\\">" |
| "</h1>" |
| "<h1 panel-id=\\\"panel-id.0\\\">" |
| "</h1>\"}]}", "false")); |
| GoogleString input = StringPrintf( |
| kHtmlInputForIgnoreScript, |
| "<script></script><noscript></noscript>" |
| "<style></style><link href=\"http://a.com/\">", ""); |
| Parse("split_ignore_script5", input); |
| EXPECT_EQ(StringPrintf(kHtmlExpectedOutputForIgnoreScript2, |
| "<script></script><noscript></noscript>" |
| "<style></style><link href=\"http://a.com/\">", |
| expected_output_suffix.c_str()).c_str(), output_); |
| VerifyAppliedRewriters("sh"); |
| } |
| |
| TEST_F(SplitHtmlFilterTest, SplitHtmlWithGhostClickBuster) { |
| options_->ClearSignatureForTesting(); |
| options_->set_serve_ghost_click_buster_with_split_html(true); |
| options_->set_critical_line_config("h1[2]"); |
| GoogleString expected_output_suffix( |
| StringPrintf(SplitHtmlFilter::kSplitSuffixJsFormatString, |
| blink_js_url_, |
| SplitHtmlFilter::kLoadHiResImages, -1, |
| "{\"panel-id.0\":[{\"instance_html\":" |
| "\"<h1 panel-id=\\\"panel-id.0\\\">" |
| "</h1>\"}]}", "false")); |
| GoogleString input(StringPrintf(kHtmlInputForIgnoreScript, "", "")); |
| Parse("split_ignore_script1", input); |
| StaticAssetManager* static_asset_manager = |
| rewrite_driver_->server_context()->static_asset_manager(); |
| |
| EXPECT_EQ(StringPrintf(kHtmlExpectedOutputForIgnoreScript1, |
| StrCat("<script type=\"text/javascript\">", |
| static_asset_manager->GetAsset( |
| StaticAssetEnum::GHOST_CLICK_BUSTER_JS, |
| options_), "</script>").c_str(), |
| "", "", |
| expected_output_suffix.c_str()).c_str(), output_); |
| VerifyAppliedRewriters("sh"); |
| } |
| |
| TEST_F(SplitHtmlFilterTest, SplitHtmlWithNestedPanels) { |
| GoogleString input_html = |
| "<html><head></head><body>" |
| "<div id=\"outer\">" |
| "<div id=\"inner\"></div>" |
| "</div>" |
| "</body></html>"; |
| options_->set_critical_line_config( |
| "div[@id = \"outer\"]," |
| "div[@id = \"inner\"]"); |
| GoogleString expected_output_suffix( |
| StringPrintf( |
| SplitHtmlFilter::kSplitSuffixJsFormatString, |
| blink_js_url_, |
| SplitHtmlFilter::kLoadHiResImages, -1, |
| "{\"panel-id.0\":[{\"instance_html\":" |
| "\"<div id=\\\"outer\\\" panel-id=\\\"panel-id.0\\\">" |
| "<div id=\\\"inner\\\"></div></div>\"}]}", |
| "false")); |
| Parse("split_with_options", input_html); |
| EXPECT_EQ(StrCat("<html><head></head><body>" |
| "<!--GooglePanel begin panel-id.0-->" |
| "<!--GooglePanel end panel-id.0-->" |
| "</body></html>", |
| expected_output_suffix), |
| output_); |
| VerifyAppliedRewriters("sh"); |
| } |
| |
| // When an ATF request is received, we defer the instrumentation script. |
| TEST_F(SplitHtmlFilterTest, Instrumentation1) { |
| options()->set_critical_line_config("div[@id=\"god\"]"); |
| options_->set_serve_split_html_in_two_chunks(true); |
| SetAtfRequest(); |
| rewrite_driver()->AddOwnedEarlyPreRenderFilter( |
| new AddInstrumentationFilter(rewrite_driver())); |
| Parse("defer_instrumentation", |
| "<html><head>" |
| "</head><body>" |
| "<div id='1'/>" |
| "<div id='god'/><div id='2'/>" |
| "</body></html>"); |
| GoogleString expected = |
| "<html><head>" |
| "<script type='text/javascript'>" |
| "window.mod_pagespeed_start = Number(new Date());</script>" |
| "</head><body>" |
| "<div id='1'/>" |
| "<!--GooglePanel begin panel-id.0--><!--GooglePanel end panel-id.0-->" |
| "</body></html>"; |
| EXPECT_TRUE(output_.find(expected) != GoogleString::npos); |
| EXPECT_TRUE(output_.find(nodefer_str_->as_string()) == GoogleString::npos); |
| } |
| |
| // When an ATF request is received but the useragent is unsupported, we don't |
| // defer the instrumentation script. |
| TEST_F(SplitHtmlFilterTest, Instrumentation2) { |
| SetCurrentUserAgent("BlackListUserAgent"); |
| options()->set_critical_line_config("div[@id=\"god\"]"); |
| options_->set_serve_split_html_in_two_chunks(true); |
| SetAtfRequest(); |
| rewrite_driver()->AddOwnedEarlyPreRenderFilter( |
| new AddInstrumentationFilter(rewrite_driver())); |
| Parse("nodefer_instrumentation", |
| "<html><head>" |
| "</head><body>" |
| "<div id='1'/>" |
| "<div id='god'/><div id='2'/>" |
| "</body></html>"); |
| GoogleString expected = |
| "<html><head>" |
| "<script type='text/javascript'>" |
| "window.mod_pagespeed_start = Number(new Date());</script>" |
| "</head><body>" |
| "<div id='1'/>" |
| "<div id='god'/><div id='2'/>"; |
| EXPECT_TRUE(output_.find(expected) != GoogleString::npos); |
| EXPECT_TRUE(output_.find(nodefer_str_->as_string()) != GoogleString::npos); |
| } |
| |
| } // namespace |
| |
| } // namespace net_instaweb |