| /* |
| * Copyright 2011 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: mmohabey@google.com (Megha Mohabey) |
| |
| // Unit-tests for FlushEarlyFlow. |
| |
| #include "pagespeed/automatic/flush_early_flow.h" |
| |
| #include "net/instaweb/http/public/http_cache.h" |
| #include "net/instaweb/http/public/log_record.h" |
| #include "net/instaweb/http/public/logging_proto_impl.h" |
| #include "net/instaweb/http/public/mock_url_fetcher.h" |
| #include "net/instaweb/http/public/request_context.h" |
| #include "net/instaweb/http/public/request_timing_info.h" |
| #include "net/instaweb/public/global_constants.h" |
| #include "net/instaweb/rewriter/public/beacon_critical_line_info_finder.h" |
| #include "net/instaweb/rewriter/public/critical_css_filter.h" |
| #include "net/instaweb/rewriter/public/critical_selector_filter.h" |
| #include "net/instaweb/rewriter/public/critical_selector_finder.h" |
| #include "net/instaweb/rewriter/public/flush_early_content_writer_filter.h" |
| #include "net/instaweb/rewriter/public/js_disable_filter.h" |
| #include "net/instaweb/rewriter/public/lazyload_images_filter.h" |
| #include "net/instaweb/rewriter/public/mock_critical_css_finder.h" |
| #include "net/instaweb/rewriter/public/rewrite_driver.h" |
| #include "net/instaweb/rewriter/public/rewrite_options.h" |
| #include "net/instaweb/rewriter/public/server_context.h" |
| #include "net/instaweb/rewriter/public/split_html_filter.h" |
| #include "net/instaweb/rewriter/public/static_asset_manager.h" |
| #include "net/instaweb/rewriter/public/test_rewrite_driver_factory.h" |
| #include "net/instaweb/rewriter/public/test_url_namer.h" |
| #include "net/instaweb/rewriter/static_asset_config.pb.h" |
| #include "net/instaweb/util/public/mock_property_page.h" |
| #include "net/instaweb/util/public/property_cache.h" |
| #include "pagespeed/automatic/proxy_interface_test_base.h" |
| #include "pagespeed/kernel/base/gtest.h" |
| #include "pagespeed/kernel/base/mock_timer.h" |
| #include "pagespeed/kernel/base/scoped_ptr.h" |
| #include "pagespeed/kernel/base/statistics.h" |
| #include "pagespeed/kernel/base/string.h" |
| #include "pagespeed/kernel/base/string_util.h" |
| #include "pagespeed/kernel/base/time_util.h" |
| #include "pagespeed/kernel/base/timer.h" |
| #include "pagespeed/kernel/cache/lru_cache.h" |
| #include "pagespeed/kernel/html/html_parse_test_base.h" |
| #include "pagespeed/kernel/http/content_type.h" |
| #include "pagespeed/kernel/http/http_names.h" |
| #include "pagespeed/kernel/http/request_headers.h" |
| #include "pagespeed/kernel/http/response_headers.h" |
| #include "pagespeed/kernel/http/user_agent_matcher.h" |
| #include "pagespeed/kernel/http/user_agent_matcher_test_base.h" |
| #include "pagespeed/opt/logging/enums.pb.h" |
| |
| namespace net_instaweb { |
| |
| namespace { |
| |
| const char kMockHashValue[] = "MDAwMD"; |
| |
| const char kCssContent[] = "* { display: none; }"; |
| |
| const char kFlushEarlyHtml[] = |
| "<!doctype html PUBLIC \"HTML 4.0.1 Strict>" |
| "<html>" |
| "<head>" |
| "<meta http-equiv=\"content-type\" content=\"text/html;charset=utf-8\"/>" |
| "<meta http-equiv=\"last-modified\" content=\"2012-08-09T11:03:27Z\"/>" |
| "<meta charset=\"UTF-8\"/>" |
| "<title>Flush Subresources Early example</title>" |
| "<link rel=\"stylesheet\" type=\"text/css\" href=\"http://test.com/1.css\">" |
| "<link rel=\"stylesheet\" type=\"text/css\" href=\"http://test.com/2.css\">" |
| "<script src=\"http://test.com/1.js\"></script>" |
| "<script src=\"http://test.com/2.js\"></script>" |
| "<img src=\"http://test.com/1.jpg\"/>" |
| "<script src=\"http://test.com/private.js\"></script>" |
| "<script src=\"http://www.domain1.com/private.js\"></script>" |
| "</head>" |
| "<body>" |
| "Hello, mod_pagespeed!" |
| "<link rel=\"stylesheet\" type=\"text/css\" href=\"http://test.com/3.css\">" |
| "<script src=\"http://www.domain2.com/private.js\"></script>" |
| "<link rel=\"stylesheet\" type=\"text/css\"" |
| " href=\"http://www.domain3.com/3.css\">" |
| "</body>" |
| "</html>"; |
| const char kFlushEarlyMoreResourcesInputHtml[] = |
| "<!doctype html PUBLIC \"HTML 4.0.1 Strict>" |
| "<html>" |
| "<head>" |
| "<meta http-equiv=\"content-type\" content=\"text/html;charset=utf-8\"/>" |
| "<meta http-equiv=\"last-modified\" content=\"2012-08-09T11:03:27Z\"/>" |
| "<meta charset=\"UTF-8\"/>" |
| "<title>Flush Subresources Early example</title>" |
| "<link rel=\"stylesheet\" type=\"text/css\" href=\"http://test.com/1.css\">" |
| "</head>" |
| "<body>" |
| "<script src=\"http://test.com/1.js\"></script>" |
| "Hello, mod_pagespeed!" |
| "</body>" |
| "</html>"; |
| |
| const char kCookieScript[] = |
| "<script type=\"text/javascript\" data-pagespeed-no-defer>" |
| "(function(){" |
| "var data = [\"CG=US:CA:Mountain+View\",\"UA=chrome\",\"path=/\"];" |
| "for (var i = 0; i < data.length; i++) {" |
| "document.cookie = data[i];" |
| "}})()" |
| "</script>"; |
| |
| const char kPreHeadHtml[] = |
| "<!doctype html PUBLIC \"HTML 4.0.1 Strict>" |
| "<html>" |
| "<head>"; |
| |
| const char kRewrittenHtml[] = |
| "<meta http-equiv=\"content-type\" content=\"text/html;charset=utf-8\"/>" |
| "<meta http-equiv=\"last-modified\" content=\"2012-08-09T11:03:27Z\"/>" |
| "<meta charset=\"UTF-8\"/>" |
| "<title>Flush Subresources Early example</title>" |
| "<link rel=\"stylesheet\" type=\"text/css\" href=\"%s\">" |
| "<link rel=\"stylesheet\" type=\"text/css\" href=\"%s\">" |
| "<script src=\"%s\"></script>" |
| "<script src=\"%s\"></script>" |
| "<img src=\"%s\"/>" |
| "<script src=\"http://test.com/private.js\"></script>" |
| "<script src=\"http://www.domain1.com/private.js\"></script>" |
| "</head>" |
| "<body>%s" |
| "Hello, mod_pagespeed!" |
| "<link rel=\"stylesheet\" type=\"text/css\" href=\"%s\">" |
| "<script src=\"http://www.domain2.com/private.js\"></script>" |
| "<link rel=\"stylesheet\" type=\"text/css\"" |
| " href=\"http://www.domain3.com/3.css\">" |
| "</body>" |
| "</html>"; |
| |
| const char kRewrittenHtmlWithDeferJs[] = |
| "%s" |
| "<meta http-equiv=\"content-type\" content=\"text/html;charset=utf-8\"/>" |
| "<meta http-equiv=\"last-modified\" content=\"2012-08-09T11:03:27Z\"/>" |
| "<meta charset=\"UTF-8\"/>" |
| "<title>Flush Subresources Early example</title>" |
| "<link rel=\"stylesheet\" type=\"text/css\" href=\"%s\">" |
| "<link rel=\"stylesheet\" type=\"text/css\" href=\"%s\">" |
| "<script src=\"%s\" type=\"text/psajs\" orig_index=\"0\">" |
| "</script>" |
| "<script src=\"%s\" type=\"text/psajs\" orig_index=\"1\">" |
| "</script>" |
| "%s" |
| "<script src=\"http://test.com/private.js\"" |
| " type=\"text/psajs\"" |
| " orig_index=\"2\"></script>" |
| "<script src=\"http://www.domain1.com/private.js\"" |
| " type=\"text/psajs\" orig_index=\"3\"></script>" |
| "</head>" |
| "<body>%s" |
| "Hello, mod_pagespeed!" |
| "<link rel=\"stylesheet\" type=\"text/css\" href=\"%s\">" |
| "<script src=\"http://www.domain2.com/private.js\"" |
| " type=\"text/psajs\" orig_index=\"4\"></script>" |
| "<link rel=\"stylesheet\" type=\"text/css\"" |
| " href=\"http://www.domain3.com/3.css\">" |
| "%s" |
| "</body>" |
| "</html>%s"; |
| |
| const char kRewrittenHtmlForRedirect[] = |
| "<script type=\"text/javascript\">" |
| "window.location.replace(\"%s\")" |
| "</script>" |
| "</head><body></body></html>"; |
| |
| const char kFlushEarlyRewrittenHtmlImageTag[] = |
| "<script type=\"text/javascript\">(function(){" |
| "new Image().src=\"%s\";" |
| "new Image().src=\"%s\";})()</script>" |
| "<link rel=\"stylesheet\" href=\"%s\"/>\n" |
| "<link rel=\"stylesheet\" href=\"%s\"/>\n" |
| "<link rel=\"stylesheet\" href=\"%s\"/>\n" |
| "%s" |
| "<script type='text/javascript'>" |
| "window.mod_pagespeed_prefetch_start = Number(new Date());" |
| "window.mod_pagespeed_num_resources_prefetched = 5</script>"; |
| |
| const char kFlushEarlyRewrittenHtmlImageTagInsertDnsPrefetch[] = |
| "<script type=\"text/javascript\">(function(){" |
| "new Image().src=\"%s\";" |
| "new Image().src=\"%s\";})()</script>" |
| "<link rel=\"dns-prefetch\" href=\"//www.domain1.com\">" |
| "<link rel=\"dns-prefetch\" href=\"//www.domain2.com\">" |
| "<link rel=\"dns-prefetch\" href=\"//www.domain3.com\">" |
| "<link rel=\"stylesheet\" href=\"%s\"/>\n" |
| "<link rel=\"stylesheet\" href=\"%s\"/>\n" |
| "<link rel=\"stylesheet\" href=\"%s\"/>\n" |
| "%s" |
| "<script type='text/javascript'>" |
| "window.mod_pagespeed_prefetch_start = Number(new Date());" |
| "window.mod_pagespeed_num_resources_prefetched = 5</script>"; |
| |
| const char kFlushEarlyRewrittenHtmlLinkScript[] = |
| "<link rel=\"stylesheet\" href=\"%s\"/>\n" |
| "<link rel=\"stylesheet\" href=\"%s\"/>\n" |
| "<script type=\"psa_prefetch\" src=\"%s\"></script>\n" |
| "<script type=\"psa_prefetch\" src=\"%s\"></script>\n" |
| "<link rel=\"stylesheet\" href=\"%s\"/>\n" |
| "%s" |
| "<script type='text/javascript'>" |
| "window.mod_pagespeed_prefetch_start = Number(new Date());" |
| "window.mod_pagespeed_num_resources_prefetched = 5</script>"; |
| const char kFlushEarlyRewrittenHtmlWithLazyloadDeferJsScript[] = |
| "<link rel=\"stylesheet\" href=\"%s\"/>\n" |
| "<link rel=\"stylesheet\" href=\"%s\"/>\n" |
| "<link rel=\"stylesheet\" href=\"%s\"/>\n" |
| "%s" |
| "<script type='text/javascript'>" |
| "window.mod_pagespeed_prefetch_start = Number(new Date());" |
| "window.mod_pagespeed_num_resources_prefetched = %d</script>"; |
| |
| const char kFlushEarlyRewrittenLinkRelPrefetch[] = |
| "<link rel=\"prefetch\" href=\"%s\"/>\n" |
| "<link rel=\"prefetch\" href=\"%s\"/>\n" |
| "<link rel=\"prefetch\" href=\"%s\"/>\n" |
| "<link rel=\"prefetch\" href=\"%s\"/>\n" |
| "<link rel=\"prefetch\" href=\"%s\"/>\n" |
| "<script type='text/javascript'>" |
| "window.mod_pagespeed_prefetch_start = Number(new Date());" |
| "window.mod_pagespeed_num_resources_prefetched = 5</script>"; |
| |
| |
| const char kFlushEarlyPdf[] = "This is a dummy pdf text."; |
| |
| } // namespace |
| |
| class FlushEarlyFlowTest : public ProxyInterfaceTestBase { |
| protected: |
| static const int kHtmlCacheTimeSec = 5000; |
| |
| FlushEarlyFlowTest() |
| : start_time_ms_(0), |
| request_url_(kTestDomain), |
| noscript_redirect_url_(StrCat(kTestDomain, "?PageSpeed=noscript")), |
| max_age_300_("max-age=300"), |
| request_start_time_ms_(-1), |
| set_httponly_cookie_(false) { |
| ConvertTimeToString(MockTimer::kApr_5_2010_ms, &start_time_string_); |
| ConvertTimeToString(MockTimer::kApr_5_2010_ms + 5 * Timer::kMinuteMs, |
| &start_time_plus_300s_string_); |
| ConvertTimeToString(MockTimer::kApr_5_2010_ms - 2 * Timer::kDayMs, |
| &old_time_string_); |
| } |
| virtual ~FlushEarlyFlowTest() {} |
| |
| virtual void SetUp() { |
| SetMockHashValue("00000"); // Base64 encodes to kMockHashValue. |
| server_context_->set_enable_property_cache(true); |
| const PropertyCache::Cohort* dom_cohort = |
| SetupCohort(server_context_->page_property_cache(), |
| RewriteDriver::kDomCohort); |
| server_context_->set_dom_cohort(dom_cohort); |
| RewriteOptions* options = server_context()->global_options(); |
| options->ClearSignatureForTesting(); |
| options->set_max_html_cache_time_ms(kHtmlCacheTimeSec * Timer::kSecondMs); |
| options->set_in_place_rewriting_enabled(true); |
| server_context()->ComputeSignature(options); |
| ProxyInterfaceTestBase::SetUp(); |
| // The original url_async_fetcher() is still owned by RewriteDriverFactory. |
| background_fetch_fetcher_.reset(new BackgroundFetchCheckingUrlAsyncFetcher( |
| factory()->ComputeUrlAsyncFetcher())); |
| server_context()->set_default_system_fetcher( |
| background_fetch_fetcher_.get()); |
| |
| start_time_ms_ = timer()->NowMs(); |
| rewritten_css_url_1_ = Encode( |
| kTestDomain, "cf", kMockHashValue, "1.css", "css"); |
| rewritten_css_url_2_ = Encode( |
| kTestDomain, "cf", kMockHashValue, "2.css", "css"); |
| rewritten_css_url_3_ = Encode( |
| kTestDomain, "cf", kMockHashValue, "3.css", "css"); |
| rewritten_js_url_1_ = Encode( |
| kTestDomain, "jm", kMockHashValue, "1.js", "js"); |
| rewritten_js_url_2_ = Encode( |
| kTestDomain, "jm", kMockHashValue, "2.js", "js"); |
| rewritten_js_url_3_ = Encode( |
| kTestDomain, "ce", kMockHashValue, "1.js", "js"); |
| rewritten_img_url_1_ = Encode( |
| kTestDomain, "ce", kMockHashValue, "1.jpg", "jpg"); |
| } |
| |
| void SetupForFlushEarlyFlow() { |
| // Setup |
| ResponseHeaders headers; |
| headers.Add(HttpAttributes::kContentType, kContentTypeHtml.mime_type()); |
| headers.Add(HttpAttributes::kSetCookie, "CG=US:CA:Mountain+View"); |
| headers.Add(HttpAttributes::kSetCookie, "UA=chrome"); |
| headers.Add(HttpAttributes::kSetCookie, "path=/"); |
| if (set_httponly_cookie_) { |
| headers.Add(HttpAttributes::kSetCookie, "a=1; HttpOnly"); |
| } |
| |
| headers.SetStatusAndReason(HttpStatus::kOK); |
| mock_url_fetcher_.SetResponse(request_url_, headers, kFlushEarlyHtml); |
| |
| // Enable FlushSubresourcesFilter filter. |
| RewriteOptions* rewrite_options = server_context()->global_options(); |
| rewrite_options->ClearSignatureForTesting(); |
| rewrite_options->EnableFilter(RewriteOptions::kFlushSubresources); |
| rewrite_options->EnableFilter(RewriteOptions::kCombineCss); |
| rewrite_options->EnableFilter(RewriteOptions::kCombineJavascript); |
| rewrite_options->EnableExtendCacheFilters(); |
| // Disabling the inline filters so that the resources get flushed early |
| // else our dummy resources are too small and always get inlined. |
| rewrite_options->DisableFilter(RewriteOptions::kInlineCss); |
| rewrite_options->DisableFilter(RewriteOptions::kInlineJavascript); |
| rewrite_options->ComputeSignature(); |
| |
| SetResponseWithDefaultHeaders(StrCat(kTestDomain, "1.css"), kContentTypeCss, |
| kCssContent, kHtmlCacheTimeSec * 2); |
| SetResponseWithDefaultHeaders(StrCat(kTestDomain, "2.css"), kContentTypeCss, |
| kCssContent, kHtmlCacheTimeSec * 2); |
| SetResponseWithDefaultHeaders(StrCat(kTestDomain, "3.css"), kContentTypeCss, |
| kCssContent, kHtmlCacheTimeSec * 2); |
| const char kContent[] = "function f() {alert('foo');}"; |
| SetResponseWithDefaultHeaders(StrCat(kTestDomain, "1.js"), |
| kContentTypeJavascript, kContent, |
| kHtmlCacheTimeSec * 2); |
| SetResponseWithDefaultHeaders(StrCat(kTestDomain, "2.js"), |
| kContentTypeJavascript, kContent, |
| kHtmlCacheTimeSec * 2); |
| SetResponseWithDefaultHeaders(StrCat(kTestDomain, "1.jpg"), |
| kContentTypeJpeg, "image", |
| kHtmlCacheTimeSec * 2); |
| ResponseHeaders private_headers; |
| DefaultResponseHeaders(kContentTypeJavascript, kHtmlCacheTimeSec, |
| &private_headers); |
| private_headers.SetDateAndCaching(http_cache()->timer()->NowMs(), |
| 300 * Timer::kSecondMs, ", private"); |
| private_headers.ComputeCaching(); |
| SetFetchResponse(AbsolutifyUrl("private.js"), private_headers, "a"); |
| } |
| |
| void VerifyCharset(ResponseHeaders* headers) { |
| EXPECT_TRUE(StringCaseEqual(headers->Lookup1(HttpAttributes::kContentType), |
| "text/html; charset=utf-8")); |
| } |
| |
| GoogleString GetDeferJsCode() { |
| return StrCat("<script type=\"text/javascript\" src=\"", |
| server_context()->static_asset_manager()->GetAssetUrl( |
| StaticAssetEnum::DEFER_JS, options_), |
| "\"></script>"); |
| } |
| |
| GoogleString GetSplitHtmlSuffixCode() { |
| return StringPrintf(SplitHtmlFilter::kSplitSuffixJsFormatString, |
| "/psajs/blink.0.js", SplitHtmlFilter::kLoadHiResImages, |
| 4, "{}", "false"); |
| } |
| |
| GoogleString NoScriptRedirectHtml() { |
| return StringPrintf(kNoScriptRedirectFormatter, |
| noscript_redirect_url_.c_str(), |
| noscript_redirect_url_.c_str()); |
| } |
| |
| GoogleString GetJsDisableScriptSnippet() { |
| if (options_->enable_defer_js_experimental()) { |
| return StrCat("<script type=\"text/javascript\" data-pagespeed-no-defer>", |
| JsDisableFilter::kEnableJsExperimental, |
| "</script>"); |
| } else { |
| return ""; |
| } |
| } |
| |
| GoogleString RewrittenHtmlWithDeferJs(bool split_html_enabled, |
| const GoogleString& image_tag, |
| bool is_ie) { |
| GoogleString defer_js_injected_html3; |
| GoogleString defer_js_injected_html2 = GetJsDisableScriptSnippet(); |
| if (split_html_enabled) { |
| defer_js_injected_html3 = GetSplitHtmlSuffixCode(); |
| } else { |
| StrAppend(&defer_js_injected_html2, GetDeferJsCode()); |
| } |
| const char kCompatibleMetaTag[] = |
| "<meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">"; |
| |
| return StringPrintf( |
| kRewrittenHtmlWithDeferJs, (is_ie ? kCompatibleMetaTag : ""), |
| rewritten_css_url_1_.c_str(), rewritten_css_url_2_.c_str(), |
| rewritten_js_url_1_.c_str(), rewritten_js_url_2_.c_str(), |
| image_tag.c_str(), NoScriptRedirectHtml().c_str(), |
| rewritten_css_url_3_.c_str(), defer_js_injected_html2.c_str(), |
| defer_js_injected_html3.c_str()); |
| } |
| |
| GoogleString RewrittenHtml(const GoogleString& noscript_redirect) { |
| return StringPrintf( |
| kRewrittenHtml, rewritten_css_url_1_.c_str(), |
| rewritten_css_url_2_.c_str(), rewritten_js_url_1_.c_str(), |
| rewritten_js_url_2_.c_str(), rewritten_img_url_1_.c_str(), |
| noscript_redirect.c_str(), rewritten_css_url_3_.c_str()); |
| } |
| |
| GoogleString RewrittenHtmlForRedirect() { |
| return StringPrintf(kRewrittenHtmlForRedirect, |
| redirect_url_.c_str()); |
| } |
| |
| GoogleString FlushEarlyRewrittenHtml( |
| UserAgentMatcher::PrefetchMechanism value, |
| bool defer_js_enabled, bool insert_dns_prefetch, |
| bool ua_only_for_flush_early_html) { |
| return FlushEarlyRewrittenHtml(value, defer_js_enabled, |
| insert_dns_prefetch, false, false, false, |
| ua_only_for_flush_early_html, false); |
| } |
| |
| GoogleString GetLazyloadScriptFlushEarly() { |
| return StrCat( |
| "<script type=\"text/javascript\">", |
| LazyloadImagesFilter::GetLazyloadJsSnippet( |
| options_, server_context()->static_asset_manager()), |
| "</script>"); |
| } |
| |
| GoogleString FlushEarlyRewrittenHtml( |
| UserAgentMatcher::PrefetchMechanism value, |
| bool defer_js_enabled, bool insert_dns_prefetch, |
| bool lazyload_enabled, bool redirect_psa_off, bool split_html_enabled, |
| bool ua_only_for_flush_early_html, bool is_ie) { |
| GoogleString flush_early_html; |
| GoogleString cookie_script = kCookieScript; |
| GoogleString rewritten_html; |
| |
| GoogleString expected_deferjs_url = split_html_enabled ? |
| "/psajs/blink.0.js" : "/psajs/js_defer.0.js"; |
| |
| |
| // Get rewritten html. |
| if (defer_js_enabled && !ua_only_for_flush_early_html) { |
| if (lazyload_enabled) { |
| rewritten_html = RewrittenHtmlWithDeferJs( |
| split_html_enabled, |
| StrCat("<img data-pagespeed-lazy-src=\"", rewritten_img_url_1_, |
| "\" src=\"/psajs/1.0.gif\" onload=\"", |
| LazyloadImagesFilter::kImageOnloadCode, |
| "\" onerror=\"this.onerror=null;", |
| LazyloadImagesFilter::kImageOnloadCode, "\"/>" |
| "<script type=\"text/javascript\" data-pagespeed-no-defer>" |
| "pagespeed.lazyLoadImages.overrideAttributeFunctions();" |
| "</script>"), |
| is_ie); |
| } else { |
| rewritten_html = RewrittenHtmlWithDeferJs( |
| split_html_enabled, |
| StrCat("<img src=\"", rewritten_img_url_1_, "\"/>"), is_ie); |
| } |
| } else { |
| if (redirect_psa_off) { |
| rewritten_html = RewrittenHtmlForRedirect(); |
| cookie_script = ""; |
| } else if (value == UserAgentMatcher::kPrefetchNotSupported) { |
| rewritten_html = RewrittenHtml(""); |
| } else { |
| rewritten_html = RewrittenHtml(NoScriptRedirectHtml()); |
| } |
| } |
| |
| // Get FlushEarly html. |
| switch (value) { |
| case UserAgentMatcher::kPrefetchLinkScriptTag: |
| if (defer_js_enabled) { |
| flush_early_html = StringPrintf( |
| kFlushEarlyRewrittenHtmlWithLazyloadDeferJsScript, |
| rewritten_css_url_1_.c_str(), rewritten_css_url_2_.c_str(), |
| rewritten_css_url_3_.c_str(), |
| StrCat("<script type=\"psa_prefetch\" src=\"", |
| expected_deferjs_url, "\"></script>\n", |
| FlushEarlyContentWriterFilter::kDisableLinkTag).c_str(), |
| 4); |
| if (lazyload_enabled) { |
| StrAppend(&flush_early_html, GetLazyloadScriptFlushEarly()); |
| } |
| } else { |
| flush_early_html = StringPrintf( |
| kFlushEarlyRewrittenHtmlLinkScript, rewritten_css_url_1_.c_str(), |
| rewritten_css_url_2_.c_str(), rewritten_js_url_1_.c_str(), |
| rewritten_js_url_2_.c_str(), rewritten_css_url_3_.c_str(), |
| FlushEarlyContentWriterFilter::kDisableLinkTag); |
| } |
| break; |
| case UserAgentMatcher::kPrefetchImageTag: |
| if (defer_js_enabled) { |
| flush_early_html = StringPrintf( |
| kFlushEarlyRewrittenHtmlWithLazyloadDeferJsScript, |
| rewritten_css_url_1_.c_str(), rewritten_css_url_2_.c_str(), |
| rewritten_css_url_3_.c_str(), |
| FlushEarlyContentWriterFilter::kDisableLinkTag, 3); |
| } else { |
| GoogleString output_format; |
| if (insert_dns_prefetch) { |
| output_format = kFlushEarlyRewrittenHtmlImageTagInsertDnsPrefetch; |
| } else { |
| output_format = kFlushEarlyRewrittenHtmlImageTag; |
| } |
| flush_early_html = StringPrintf( |
| output_format.c_str(), rewritten_js_url_1_.c_str(), |
| rewritten_js_url_2_.c_str(), rewritten_css_url_1_.c_str(), |
| rewritten_css_url_2_.c_str(), rewritten_css_url_3_.c_str(), |
| FlushEarlyContentWriterFilter::kDisableLinkTag); |
| } |
| break; |
| case UserAgentMatcher::kPrefetchLinkRelPrefetchTag: |
| flush_early_html = StringPrintf( |
| kFlushEarlyRewrittenLinkRelPrefetch, rewritten_css_url_1_.c_str(), |
| rewritten_css_url_2_.c_str(), rewritten_js_url_1_.c_str(), |
| rewritten_js_url_2_.c_str(), rewritten_css_url_3_.c_str()); |
| break; |
| case UserAgentMatcher::kPrefetchNotSupported: |
| default: |
| flush_early_html = ""; |
| cookie_script = ""; |
| break; |
| } |
| |
| // Return combined html. |
| return StrCat(kPreHeadHtml, flush_early_html, cookie_script, |
| rewritten_html); |
| } |
| |
| void ExperimentalFlushEarlyFlowTestHelper( |
| const GoogleString& user_agent, |
| UserAgentMatcher::PrefetchMechanism mechanism, bool inject_error) { |
| ExperimentalFlushEarlyFlowTestHelperWithPropertyCache( |
| user_agent, mechanism, false, false, inject_error); |
| ExperimentalFlushEarlyFlowTestHelperWithPropertyCache( |
| user_agent, mechanism, false, true, inject_error); |
| ExperimentalFlushEarlyFlowTestHelperWithPropertyCache( |
| user_agent, mechanism, true, true, inject_error); |
| ExperimentalFlushEarlyFlowTestHelperWithPropertyCache( |
| user_agent, mechanism, true, false, inject_error); |
| } |
| |
| void ExperimentalFlushEarlyFlowTestHelperWithPropertyCache( |
| const GoogleString& user_agent, |
| UserAgentMatcher::PrefetchMechanism mechanism, |
| bool delay_pcache, bool thread_pcache, bool inject_error) { |
| lru_cache()->Clear(); |
| SetupForFlushEarlyFlow(); |
| GoogleString text; |
| RequestHeaders request_headers; |
| request_headers.Replace(HttpAttributes::kUserAgent, user_agent); |
| ResponseHeaders headers; |
| TestPropertyCacheWithHeadersAndOutput( |
| kTestDomain, delay_pcache, thread_pcache, true, false, false, |
| false, request_headers, &headers, &text); |
| |
| if (inject_error) { |
| ResponseHeaders error_headers; |
| error_headers.SetStatusAndReason(HttpStatus::kOK); |
| mock_url_fetcher_.SetResponse( |
| kTestDomain, error_headers, ""); |
| } |
| |
| // Fetch the url again. This time FlushEarlyFlow should not be triggered. |
| // None |
| TestPropertyCacheWithHeadersAndOutput( |
| kTestDomain, delay_pcache, thread_pcache, true, false, false, |
| inject_error, request_headers, &headers, &text); |
| GoogleString expected_output = FlushEarlyRewrittenHtml( |
| mechanism, false, false, true); |
| if (!inject_error) { |
| EXPECT_STREQ(expected_output, text); |
| VerifyCharset(&headers); |
| } |
| } |
| void TestFlushEarlyFlow(const StringPiece& user_agent, |
| UserAgentMatcher::PrefetchMechanism mechanism, |
| bool ua_only_for_flush_early_html) { |
| SetupForFlushEarlyFlow(); |
| GoogleString text; |
| RequestHeaders request_headers; |
| ResponseHeaders headers; |
| request_headers.Replace(HttpAttributes::kUserAgent, user_agent); |
| FetchFromProxy(kTestDomain, request_headers, true, &text, &headers); |
| // Check total number of cache inserts. |
| // 7 for 1.css, 2.css, 3.css, 1.js, 2.js, 1.jpg and private.js. |
| // 19 metadata cache entries - three for cf and jm, seven for ce and |
| // six for fs. |
| // 1 for DomCohort write in property cache. |
| EXPECT_EQ(27, lru_cache()->num_inserts()); |
| |
| // Fetch the url again. This time FlushEarlyFlow should be triggered with |
| // the appropriate prefetch mechanism. |
| FetchFromProxy(kTestDomain, request_headers, true, &text, &headers); |
| EXPECT_EQ(FlushEarlyRewrittenHtml(mechanism, false, false, |
| ua_only_for_flush_early_html), text); |
| VerifyCharset(&headers); |
| if (mechanism != UserAgentMatcher::kPrefetchNotSupported) { |
| EXPECT_STREQ("cf,ei,fs,jm", AppliedRewriterStringFromLog()); |
| EXPECT_STREQ("cf,ei,fs,jm", headers.Lookup1(kPsaRewriterHeader)); |
| } |
| } |
| |
| void TestFlushLazyLoadJsEarly(bool is_mobile) { |
| const char kInputHtml[] = |
| "<!doctype html PUBLIC \"HTML 4.0.1 Strict>" |
| "<html>" |
| "<head>" |
| "<title>Flush Subresources Early example</title>" |
| "</head>" |
| "<body>" |
| "<link rel=\"stylesheet\" type=\"text/css\"" |
| " href=\"http://test.com/1.css\">" |
| "<img src=http://test.com/1.jpg />" |
| "Hello, mod_pagespeed!" |
| "</body>" |
| "</html>"; |
| |
| GoogleString redirect_url = StrCat(kTestDomain, "?PageSpeed=noscript"); |
| GoogleString kNotMobileOutputHtml = StrCat( |
| "<!doctype html PUBLIC \"HTML 4.0.1 Strict>" |
| "<html>" |
| "<head>", |
| StringPrintf( |
| "<link rel=\"stylesheet\" href=\"%s\"/>\n" |
| "%s" |
| "<script type='text/javascript'>" |
| "window.mod_pagespeed_prefetch_start = Number(new Date());" |
| "window.mod_pagespeed_num_resources_prefetched = 1" |
| "</script>", rewritten_css_url_1_.c_str(), |
| FlushEarlyContentWriterFilter::kDisableLinkTag), |
| GetLazyloadScriptFlushEarly(), |
| "<title>Flush Subresources Early example</title>" |
| "</head>" |
| "<body>", |
| StringPrintf(kNoScriptRedirectFormatter, redirect_url.c_str(), |
| redirect_url.c_str()), |
| StringPrintf( |
| "<link rel=\"stylesheet\" type=\"text/css\" href=\"%s\">" |
| "<img data-pagespeed-lazy-src=http://test.com/1.jpg.pagespeed.ce.%s.jpg" |
| " src=\"/psajs/1.0.gif\"" |
| " onload=\"%s\" onerror=\"this.onerror=null;%s\"/>", |
| rewritten_css_url_1_.c_str(), kMockHashValue, |
| LazyloadImagesFilter::kImageOnloadCode, |
| LazyloadImagesFilter::kImageOnloadCode), |
| "Hello, mod_pagespeed!", |
| GetLazyloadPostscriptHtml(), |
| "</body></html>"); |
| |
| GoogleString kMobileOutputHtml = StrCat( |
| "<!doctype html PUBLIC \"HTML 4.0.1 Strict>" |
| "<html>" |
| "<head>", |
| StringPrintf( |
| "<link rel=\"stylesheet\" href=\"%s\"/>\n" |
| "%s" |
| "<script type='text/javascript'>" |
| "window.mod_pagespeed_prefetch_start = Number(new Date());" |
| "window.mod_pagespeed_num_resources_prefetched = 1" |
| "</script>", rewritten_css_url_1_.c_str(), |
| FlushEarlyContentWriterFilter::kDisableLinkTag), |
| "<title>Flush Subresources Early example</title>", |
| GetLazyloadScriptHtml(), |
| "</head>" |
| "<body>", |
| StringPrintf(kNoScriptRedirectFormatter, redirect_url.c_str(), |
| redirect_url.c_str()), |
| StringPrintf( |
| "<link rel=\"stylesheet\" type=\"text/css\" href=\"%s\">", |
| rewritten_css_url_1_.c_str()), |
| StringPrintf( |
| "<img data-pagespeed-lazy-src=http://test.com/1.jpg.pagespeed.ce.%s.jpg" |
| " src=\"/psajs/1.0.gif\"" |
| " onload=\"%s\" onerror=\"this.onerror=null;%s\"/>", |
| kMockHashValue, |
| LazyloadImagesFilter::kImageOnloadCode, |
| LazyloadImagesFilter::kImageOnloadCode), |
| "Hello, mod_pagespeed!", |
| GetLazyloadPostscriptHtml(), |
| "</body></html>"); |
| |
| ResponseHeaders headers; |
| headers.Add(HttpAttributes::kContentType, kContentTypeHtml.mime_type()); |
| headers.SetStatusAndReason(HttpStatus::kOK); |
| mock_url_fetcher_.SetResponse(kTestDomain, headers, kInputHtml); |
| |
| // Enable FlushSubresourcesFilter filter. |
| RewriteOptions* rewrite_options = server_context()->global_options(); |
| rewrite_options->ClearSignatureForTesting(); |
| rewrite_options->EnableFilter(RewriteOptions::kFlushSubresources); |
| rewrite_options->EnableExtendCacheFilters(); |
| // Disabling the inline filters so that the resources get flushed early |
| // else our dummy resources are too small and always get inlined. |
| rewrite_options->DisableFilter(RewriteOptions::kInlineCss); |
| rewrite_options->DisableFilter(RewriteOptions::kInlineJavascript); |
| rewrite_options->ComputeSignature(); |
| |
| SetResponseWithDefaultHeaders(StrCat(kTestDomain, "1.jpg"), |
| kContentTypeJpeg, "image", |
| kHtmlCacheTimeSec * 2); |
| SetResponseWithDefaultHeaders(StrCat(kTestDomain, "1.css"), kContentTypeCss, |
| kCssContent, kHtmlCacheTimeSec * 2); |
| |
| scoped_ptr<RewriteOptions> custom_options( |
| server_context()->global_options()->Clone()); |
| custom_options->EnableFilter(RewriteOptions::kLazyloadImages); |
| SetRewriteOptions(custom_options.get()); |
| |
| GoogleString text; |
| RequestHeaders request_headers; |
| if (is_mobile) { |
| request_headers.Replace( |
| HttpAttributes::kUserAgent, |
| UserAgentMatcherTestBase::kAndroidChrome21UserAgent); |
| } else { |
| request_headers.Replace(HttpAttributes::kUserAgent, "Chrome/ 9.0"); |
| } |
| |
| FetchFromProxy(kTestDomain, request_headers, true, &text, &headers); |
| |
| // Fetch the url again. This time FlushEarlyFlow should be triggered but no |
| // lazyload js will be flushed early as no resource is present in the html. |
| FetchFromProxy(kTestDomain, request_headers, true, &text, &headers); |
| if (is_mobile) { |
| EXPECT_STREQ(kMobileOutputHtml, text); |
| } else { |
| EXPECT_STREQ(kNotMobileOutputHtml, text); |
| } |
| } |
| |
| void TestFlushPreconnects(bool is_mobile) { |
| SetHeaderLatencyMs(200); |
| const char kInputHtml[] = |
| "<!doctype html PUBLIC \"HTML 4.0.1 Strict>" |
| "<html>" |
| "<head>" |
| "<title>Flush Subresources Early example</title>" |
| "<link rel=\"stylesheet\" type=\"text/css\"" |
| " href=\"http://test.com/1.css\">" |
| "</head>" |
| "<body>" |
| "<img src=1.jpg />" |
| "<img src=2.jpg />" |
| "<img src=3.jpg />" |
| "Hello, mod_pagespeed!" |
| "</body>" |
| "</html>"; |
| |
| GoogleString redirect_url = StrCat(kTestDomain, "?PageSpeed=noscript"); |
| const char pre_connect_tag[] = |
| "<link rel=\"stylesheet\" href=\"http://cdn.com/pre_connect?id=%s\"/>"; |
| const char image_tag[] = |
| "<img src=http://cdn.com/http/test.com/http/test.com/%s />"; |
| |
| GoogleString pre_connect_url = "http://cdn.com/pre_connect"; |
| GoogleString kOutputHtml = StringPrintf( |
| "<!doctype html PUBLIC \"HTML 4.0.1 Strict>" |
| "<html>" |
| "<head>" |
| "<link rel=\"stylesheet\" href=\"http://cdn.com/http/" |
| "test.com/http/test.com/A.1.css.pagespeed.cf.%s.css\"/>\n" |
| "%s" |
| "<script type='text/javascript'>" |
| "window.mod_pagespeed_prefetch_start = Number(" |
| "new Date());" |
| "window.mod_pagespeed_num_resources_prefetched = 1</script>", |
| kMockHashValue, FlushEarlyContentWriterFilter::kDisableLinkTag); |
| if (!is_mobile) { |
| StrAppend(&kOutputHtml, StringPrintf(pre_connect_tag, "0"), |
| StringPrintf(pre_connect_tag, "1")); |
| } |
| StrAppend(&kOutputHtml, |
| StringPrintf( |
| "<title>Flush Subresources Early example</title>" |
| "<link rel=\"stylesheet\" type=\"text/css\" href=\"http://cdn.com/http/" |
| "test.com/http/test.com/A.1.css.pagespeed.cf.%s.css\">" |
| "</head>" |
| "<body>", kMockHashValue), StrCat( |
| StringPrintf(kNoScriptRedirectFormatter, redirect_url.c_str(), |
| redirect_url.c_str()), |
| StringPrintf(image_tag, StringPrintf("1.jpg.pagespeed.ce.%s.jpg", |
| kMockHashValue).c_str()), |
| StringPrintf(image_tag, StringPrintf("2.jpg.pagespeed.ce.%s.jpg", |
| kMockHashValue).c_str()), |
| StringPrintf(image_tag, StringPrintf("3.jpg.pagespeed.ce.%s.jpg", |
| kMockHashValue).c_str()), |
| "Hello, mod_pagespeed!" |
| "</body>" |
| "</html>")); |
| |
| ResponseHeaders headers; |
| headers.Add(HttpAttributes::kContentType, kContentTypeHtml.mime_type()); |
| headers.SetStatusAndReason(HttpStatus::kOK); |
| mock_url_fetcher_.SetResponse(kTestDomain, headers, kInputHtml); |
| |
| // Enable FlushSubresourcesFilter filter. |
| RewriteOptions* rewrite_options = server_context()->global_options(); |
| rewrite_options->ClearSignatureForTesting(); |
| rewrite_options->EnableFilter(RewriteOptions::kFlushSubresources); |
| rewrite_options->EnableExtendCacheFilters(); |
| // Disabling the inline filters so that the resources get flushed early |
| // else our dummy resources are too small and always get inlined. |
| rewrite_options->DisableFilter(RewriteOptions::kInlineCss); |
| rewrite_options->DisableFilter(RewriteOptions::kInlineJavascript); |
| rewrite_options->set_pre_connect_url(pre_connect_url); |
| rewrite_options->ComputeSignature(); |
| |
| SetResponseWithDefaultHeaders(StrCat(kTestDomain, "1.css"), kContentTypeCss, |
| kCssContent, kHtmlCacheTimeSec * 2); |
| SetResponseWithDefaultHeaders(StrCat(kTestDomain, "1.jpg"), |
| kContentTypeJpeg, "image", |
| kHtmlCacheTimeSec * 2); |
| SetResponseWithDefaultHeaders(StrCat(kTestDomain, "2.jpg"), |
| kContentTypeJpeg, "image", |
| kHtmlCacheTimeSec * 2); |
| SetResponseWithDefaultHeaders(StrCat(kTestDomain, "3.jpg"), |
| kContentTypeJpeg, "image", |
| kHtmlCacheTimeSec * 2); |
| TestUrlNamer url_namer; |
| server_context()->set_url_namer(&url_namer); |
| url_namer.SetProxyMode(true); |
| |
| GoogleString text; |
| RequestHeaders request_headers; |
| if (is_mobile) { |
| request_headers.Replace( |
| HttpAttributes::kUserAgent, |
| UserAgentMatcherTestBase::kAndroidChrome21UserAgent); |
| } else { |
| request_headers.Replace(HttpAttributes::kUserAgent, "prefetch_image_tag"); |
| } |
| |
| FetchFromProxy(kTestDomain, request_headers, true, &text, &headers); |
| |
| // Fetch the url again. This time FlushEarlyFlow and pre connect should be |
| // triggered. |
| FetchFromProxy(kTestDomain, request_headers, true, &text, &headers); |
| EXPECT_STREQ(kOutputHtml, text); |
| } |
| |
| void SetHeaderLatencyMs(int64 latency_ms) { |
| RequestTimingInfo* timing_info = mutable_timing_info(); |
| timing_info->FetchStarted(); |
| AdvanceTimeMs(latency_ms); |
| timing_info->FetchHeaderReceived(); |
| } |
| |
| scoped_ptr<BackgroundFetchCheckingUrlAsyncFetcher> background_fetch_fetcher_; |
| int64 start_time_ms_; |
| GoogleString start_time_string_; |
| GoogleString start_time_plus_300s_string_; |
| GoogleString old_time_string_; |
| GoogleString rewritten_css_url_1_; |
| GoogleString rewritten_css_url_2_; |
| GoogleString rewritten_css_url_3_; |
| GoogleString rewritten_js_url_1_; |
| GoogleString rewritten_js_url_2_; |
| GoogleString rewritten_js_url_3_; |
| GoogleString rewritten_img_url_1_; |
| |
| GoogleString request_url_; |
| GoogleString redirect_url_; |
| GoogleString noscript_redirect_url_; |
| |
| const GoogleString max_age_300_; |
| int64 request_start_time_ms_; |
| bool set_httponly_cookie_; |
| }; |
| |
| // TODO(mpalem): Add tests for max_prefetch_js_elements and defer Js. |
| |
| TEST_F(FlushEarlyFlowTest, FlushEarlyFlowTest) { |
| TestFlushEarlyFlow(NULL, UserAgentMatcher::kPrefetchNotSupported, true); |
| } |
| |
| TEST_F(FlushEarlyFlowTest, FlushEarlyFlowTestPrefetch) { |
| TestFlushEarlyFlow("prefetch_link_script_tag", |
| UserAgentMatcher::kPrefetchLinkScriptTag, true); |
| rewrite_driver_->log_record()->WriteLog(); |
| EXPECT_EQ(5, logging_info()->rewriter_stats_size()); |
| EXPECT_STREQ("fs", logging_info()->rewriter_stats(2).id()); |
| const RewriterStats& stats = logging_info()->rewriter_stats(2); |
| EXPECT_EQ(RewriterHtmlApplication::ACTIVE, stats.html_status()); |
| EXPECT_EQ(2, stats.status_counts_size()); |
| const RewriteStatusCount& applied = stats.status_counts(0); |
| EXPECT_EQ(RewriterApplication::APPLIED_OK, applied.application_status()); |
| EXPECT_EQ(6, applied.count()); |
| const RewriteStatusCount& not_applied = stats.status_counts(1); |
| EXPECT_EQ(RewriterApplication::NOT_APPLIED, not_applied.application_status()); |
| EXPECT_EQ(2, not_applied.count()); |
| } |
| |
| TEST_F(FlushEarlyFlowTest, FlushEarlyFlowTestPcacheMiss) { |
| SetupForFlushEarlyFlow(); |
| GoogleString text; |
| RequestHeaders request_headers; |
| ResponseHeaders headers; |
| request_headers.Replace(HttpAttributes::kUserAgent, |
| "prefetch_link_script_tag"); |
| FetchFromProxy(kTestDomain, request_headers, true, &text, &headers); |
| |
| rewrite_driver_->log_record()->WriteLog(); |
| EXPECT_EQ(5, logging_info()->rewriter_stats_size()); |
| EXPECT_STREQ("fs", logging_info()->rewriter_stats(2).id()); |
| const RewriterStats& stats = logging_info()->rewriter_stats(2); |
| EXPECT_EQ(RewriterHtmlApplication::PROPERTY_CACHE_MISS, stats.html_status()); |
| EXPECT_EQ(0, stats.status_counts_size()); |
| } |
| |
| TEST_F(FlushEarlyFlowTest, FlushEarlyFlowTestFallbackPageUsage) { |
| SetupForFlushEarlyFlow(); |
| |
| // Enable UseFallbackPropertyCacheValues. |
| scoped_ptr<RewriteOptions> custom_options( |
| server_context()->global_options()->Clone()); |
| custom_options->set_use_fallback_property_cache_values(true); |
| SetRewriteOptions(custom_options.get()); |
| |
| // Setting up mock responses for the url and fallback url. |
| GoogleString url = StrCat(kTestDomain, "a.html?query=some"); |
| GoogleString fallback_url = |
| StrCat(kTestDomain, "a.html?different_query=some"); |
| ResponseHeaders response_headers; |
| response_headers.Add( |
| HttpAttributes::kContentType, kContentTypeHtml.mime_type()); |
| response_headers.SetStatusAndReason(HttpStatus::kOK); |
| mock_url_fetcher_.SetResponse(url, response_headers, kFlushEarlyHtml); |
| mock_url_fetcher_.SetResponse( |
| fallback_url, response_headers, kFlushEarlyHtml); |
| |
| GoogleString text; |
| RequestHeaders request_headers; |
| ResponseHeaders headers; |
| request_headers.Replace(HttpAttributes::kUserAgent, |
| "prefetch_link_script_tag"); |
| |
| FetchFromProxy(url, request_headers, true, &text, &headers); |
| |
| // Request another url with different query params so that fallback values |
| // will be used. |
| FetchFromProxy(fallback_url, request_headers, true, &text, &headers); |
| |
| rewrite_driver_->log_record()->WriteLog(); |
| EXPECT_EQ(5, logging_info()->rewriter_stats_size()); |
| EXPECT_STREQ("fs", logging_info()->rewriter_stats(2).id()); |
| const RewriterStats& fallback_stats = logging_info()->rewriter_stats(2); |
| EXPECT_EQ(RewriterHtmlApplication::ACTIVE, fallback_stats.html_status()); |
| EXPECT_EQ(2, fallback_stats.status_counts_size()); |
| } |
| |
| TEST_F(FlushEarlyFlowTest, FallBackWithNonHtmlResourceIsRedirected) { |
| SetupForFlushEarlyFlow(); |
| |
| // Enable UseFallbackPropertyCacheValues. |
| scoped_ptr<RewriteOptions> custom_options( |
| server_context()->global_options()->Clone()); |
| custom_options->set_use_fallback_property_cache_values(true); |
| SetRewriteOptions(custom_options.get()); |
| |
| // Setting up mock responses for the url and fallback url. |
| GoogleString url = |
| StrCat(kTestDomain, "a.html?different_query=some_pdf"); |
| GoogleString fallback_url = StrCat(kTestDomain, "a.html?query=some_html"); |
| ResponseHeaders response_headers; |
| response_headers.Add( |
| HttpAttributes::kContentType, kContentTypeHtml.mime_type()); |
| response_headers.SetStatusAndReason(HttpStatus::kOK); |
| mock_url_fetcher_.SetResponse(fallback_url, |
| response_headers, kFlushEarlyHtml); |
| // Set some non html response to the url. |
| response_headers.Replace( |
| HttpAttributes::kContentType, kContentTypePdf.mime_type()); |
| mock_url_fetcher_.SetResponse(url, response_headers, kFlushEarlyPdf); |
| |
| noscript_redirect_url_ = StrCat(fallback_url, "&PageSpeed=noscript"); |
| GoogleString kOutputHtml = StrCat( |
| kPreHeadHtml, |
| StringPrintf(kFlushEarlyRewrittenHtmlLinkScript, |
| rewritten_css_url_1_.c_str(), rewritten_css_url_2_.c_str(), |
| rewritten_js_url_1_.c_str(), rewritten_js_url_2_.c_str(), |
| rewritten_css_url_3_.c_str(), |
| FlushEarlyContentWriterFilter::kDisableLinkTag), |
| RewrittenHtml(NoScriptRedirectHtml())); |
| |
| GoogleString text; |
| RequestHeaders request_headers; |
| ResponseHeaders headers; |
| request_headers.Replace(HttpAttributes::kUserAgent, |
| "prefetch_link_script_tag"); |
| |
| FetchFromProxy(fallback_url, request_headers, true, &text, &headers); |
| |
| // Fetch the url again. This time FlushEarlyFlow should be triggered. |
| FetchFromProxy(fallback_url, request_headers, true, &text, &headers); |
| EXPECT_STREQ(kOutputHtml, text); |
| |
| EXPECT_EQ(0, TimedValue( |
| FlushEarlyFlow::kNumFlushEarlyRequestsRedirected)); |
| |
| // Request another url with different query params so that fallback values |
| // will be used. Since the content type is different here, this request |
| // should be redirected. |
| FetchFromProxy(url, request_headers, true, &text, &headers); |
| redirect_url_ = StrCat(url, "&PageSpeed=noscript"); |
| EXPECT_EQ(FlushEarlyRewrittenHtml( |
| UserAgentMatcher::kPrefetchLinkScriptTag, false, false, false, |
| true, false, true, false), text); |
| |
| EXPECT_EQ(1, TimedValue( |
| FlushEarlyFlow::kNumFlushEarlyRequestsRedirected)); |
| } |
| |
| TEST_F(FlushEarlyFlowTest, FlushEarlyFlowTestDisabled) { |
| // Adding a httponly cookie in the response causes flush early to be disabled |
| // for the second request. |
| set_httponly_cookie_ = true; |
| SetupForFlushEarlyFlow(); |
| GoogleString text; |
| RequestHeaders request_headers; |
| ResponseHeaders headers; |
| request_headers.Replace(HttpAttributes::kUserAgent, |
| "prefetch_link_script_tag"); |
| FetchFromProxy(kTestDomain, request_headers, true, &text, &headers); |
| EXPECT_FALSE(headers.Has(kPsaRewriterHeader)); |
| FetchFromProxy(kTestDomain, request_headers, true, &text, &headers); |
| EXPECT_FALSE(headers.Has(kPsaRewriterHeader)); |
| |
| rewrite_driver_->log_record()->WriteLog(); |
| EXPECT_EQ(5, logging_info()->rewriter_stats_size()); |
| EXPECT_STREQ("fs", logging_info()->rewriter_stats(2).id()); |
| const RewriterStats& stats = logging_info()->rewriter_stats(2); |
| EXPECT_EQ(RewriterHtmlApplication::DISABLED, stats.html_status()); |
| EXPECT_EQ(0, stats.status_counts_size()); |
| |
| // Change the fetcher's response to not set the http only cookie. We still |
| // don't flush early. |
| set_httponly_cookie_ = false; |
| SetupForFlushEarlyFlow(); |
| FetchFromProxy(kTestDomain, request_headers, true, &text, &headers); |
| EXPECT_FALSE(headers.Has(kPsaRewriterHeader)); |
| FetchFromProxy(kTestDomain, request_headers, true, &text, &headers); |
| EXPECT_FALSE(headers.Has(kPsaRewriterHeader)); |
| |
| // Clear all the caches. We don't flush early on the first request since we |
| // miss the pcache, but flush early on the second request. |
| lru_cache()->Clear(); |
| FetchFromProxy(kTestDomain, request_headers, true, &text, &headers); |
| EXPECT_FALSE(headers.Has(kPsaRewriterHeader)); |
| FetchFromProxy(kTestDomain, request_headers, true, &text, &headers); |
| EXPECT_TRUE(headers.Has(kPsaRewriterHeader)); |
| } |
| |
| TEST_F(FlushEarlyFlowTest, FlushEarlyFlowTestUnsupportedUserAgent) { |
| SetupForFlushEarlyFlow(); |
| GoogleString text; |
| RequestHeaders request_headers; |
| ResponseHeaders headers; |
| request_headers.Replace(HttpAttributes::kUserAgent, ""); |
| FetchFromProxy(kTestDomain, request_headers, true, &text, &headers); |
| |
| rewrite_driver_->log_record()->WriteLog(); |
| EXPECT_EQ(5, logging_info()->rewriter_stats_size()); |
| EXPECT_STREQ("fs", logging_info()->rewriter_stats(2).id()); |
| const RewriterStats& stats = logging_info()->rewriter_stats(2); |
| EXPECT_EQ(RewriterHtmlApplication::USER_AGENT_NOT_SUPPORTED, |
| stats.html_status()); |
| EXPECT_EQ(0, stats.status_counts_size()); |
| } |
| |
| TEST_F(FlushEarlyFlowTest, ConditionalRequestHeaders) { |
| SetupForFlushEarlyFlow(); |
| GoogleString text; |
| RequestHeaders request_headers; |
| ResponseHeaders headers; |
| request_headers.Add(HttpAttributes::kUserAgent, "prefetch_link_script_tag"); |
| request_headers.Add(HttpAttributes::kIfNoneMatch, "etag"); |
| FetchFromProxy(kTestDomain, request_headers, true, &text, &headers); |
| |
| rewrite_driver_->log_record()->WriteLog(); |
| EXPECT_EQ(5, logging_info()->rewriter_stats_size()); |
| EXPECT_STREQ("fs", logging_info()->rewriter_stats(2).id()); |
| const RewriterStats& stats = logging_info()->rewriter_stats(2); |
| EXPECT_EQ(RewriterHtmlApplication::DISABLED, stats.html_status()); |
| EXPECT_EQ(0, stats.status_counts_size()); |
| } |
| |
| TEST_F(FlushEarlyFlowTest, FlushEarlyFlowStatusCodeUnstable) { |
| // Test that the flush early flow is not triggered when the status code is |
| // unstable. |
| request_url_ = "http://test.com/?q=1"; |
| SetupForFlushEarlyFlow(); |
| redirect_url_ = StrCat(request_url_, "&PageSpeed=noscript"); |
| noscript_redirect_url_ = StrCat(request_url_, "&PageSpeed=noscript"); |
| GoogleString text; |
| RequestHeaders request_headers; |
| request_headers.Replace(HttpAttributes::kUserAgent, |
| "prefetch_link_script_tag"); |
| ResponseHeaders headers; |
| FetchFromProxy(request_url_, request_headers, true, &text, &headers); |
| |
| // Fetch the url again. This time FlushEarlyFlow should be triggered. |
| FetchFromProxy(request_url_, request_headers, true, &text, &headers); |
| EXPECT_EQ(FlushEarlyRewrittenHtml( |
| UserAgentMatcher::kPrefetchLinkScriptTag, false, false, true), |
| text); |
| EXPECT_EQ(0, TimedValue( |
| FlushEarlyFlow::kNumFlushEarlyRequestsRedirected)); |
| |
| SetFetchResponse404(request_url_); |
| // Fetch again so that 404 is populated in response headers. |
| // It should redirect to PageSpeed=noscript in this case. |
| FetchFromProxy(request_url_, request_headers, true, &text, &headers); |
| EXPECT_EQ(FlushEarlyRewrittenHtml( |
| UserAgentMatcher::kPrefetchLinkScriptTag, false, false, false, |
| true, false, true, false), text); |
| EXPECT_EQ(1, TimedValue( |
| FlushEarlyFlow::kNumFlushEarlyRequestsRedirected)); |
| |
| // Fetch the url again. This time FlushEarlyFlow should not be triggered as |
| // the status code is not stable. |
| FetchFromProxy(request_url_, request_headers, true, &text, &headers); |
| EXPECT_EQ(HttpStatus::kNotFound, headers.status_code()); |
| |
| // Delete the 404 from cache and again set up for 200 response. |
| lru_cache()->Delete(HttpCacheKey(request_url_)); |
| SetupForFlushEarlyFlow(); |
| |
| // Flush early flow is again not triggered as the status code is not |
| // stable for property_cache_http_status_stability_threshold number of |
| // requests. |
| for (int i = 0, n = server_context()->global_options()-> |
| property_cache_http_status_stability_threshold(); i < n; ++i) { |
| FetchFromProxy(request_url_, request_headers, true, &text, &headers); |
| EXPECT_TRUE(text.find("mod_pagespeed_num_resources_prefetched") == |
| GoogleString::npos); |
| } |
| // Fetch the url again. This time FlushEarlyFlow should be triggered. |
| FetchFromProxy(request_url_, request_headers, true, &text, &headers); |
| EXPECT_EQ(FlushEarlyRewrittenHtml( |
| UserAgentMatcher::kPrefetchLinkScriptTag, false, false, true), |
| text); |
| |
| // Fetch again so that 404 is populated in response headers. |
| // It should redirect to PageSpeed=noscript in this case. |
| SetFetchResponse404(request_url_); |
| FetchFromProxy(request_url_, request_headers, true, &text, &headers); |
| EXPECT_EQ(FlushEarlyRewrittenHtml( |
| UserAgentMatcher::kPrefetchLinkScriptTag, false, false, false, |
| true, false, true, false), text); |
| EXPECT_EQ(2, TimedValue( |
| FlushEarlyFlow::kNumFlushEarlyRequestsRedirected)); |
| } |
| |
| TEST_F(FlushEarlyFlowTest, FlushEarlyFlowTestMobile) { |
| TestFlushEarlyFlow(UserAgentMatcherTestBase::kAndroidChrome21UserAgent, |
| UserAgentMatcher::kPrefetchImageTag, false); |
| } |
| |
| TEST_F(FlushEarlyFlowTest, FlushEarlyFlowTestImageTag) { |
| TestFlushEarlyFlow("prefetch_image_tag", |
| UserAgentMatcher::kPrefetchImageTag, true); |
| } |
| |
| TEST_F(FlushEarlyFlowTest, FlushEarlyFlowTestLinkScript) { |
| TestFlushEarlyFlow("prefetch_link_script_tag", |
| UserAgentMatcher::kPrefetchLinkScriptTag, true); |
| } |
| |
| TEST_F(FlushEarlyFlowTest, FlushEarlyFlowTestWithDeferJsImageTag) { |
| SetupForFlushEarlyFlow(); |
| scoped_ptr<RewriteOptions> custom_options( |
| server_context()->global_options()->Clone()); |
| custom_options->EnableFilter(RewriteOptions::kDeferJavascript); |
| SetRewriteOptions(custom_options.get()); |
| |
| GoogleString text; |
| RequestHeaders request_headers; |
| request_headers.Replace(HttpAttributes::kUserAgent, "prefetch_image_tag"); |
| ResponseHeaders headers; |
| FetchFromProxy(kTestDomain, request_headers, true, &text, &headers); |
| |
| // Fetch the url again. This time FlushEarlyFlow should be triggered. |
| FetchFromProxy(kTestDomain, request_headers, true, &text, &headers); |
| EXPECT_STREQ(FlushEarlyRewrittenHtml( |
| UserAgentMatcher::kPrefetchImageTag, true, false, true), text); |
| VerifyCharset(&headers); |
| } |
| |
| TEST_F(FlushEarlyFlowTest, FlushEarlyFlowTestWithDeferJsPrefetch) { |
| SetupForFlushEarlyFlow(); |
| scoped_ptr<RewriteOptions> custom_options( |
| server_context()->global_options()->Clone()); |
| custom_options->EnableFilter(RewriteOptions::kDeferJavascript); |
| custom_options->set_max_prefetch_js_elements(0); |
| SetRewriteOptions(custom_options.get()); |
| |
| GoogleString text; |
| RequestHeaders request_headers; |
| request_headers.Replace(HttpAttributes::kUserAgent, "Firefox/ 9.0"); |
| |
| ResponseHeaders headers; |
| FetchFromProxy(kTestDomain, request_headers, true, &text, &headers); |
| |
| // Fetch the url again. This time FlushEarlyFlow should be triggered. |
| FetchFromProxy(kTestDomain, request_headers, true, &text, &headers); |
| EXPECT_STREQ(FlushEarlyRewrittenHtml( |
| UserAgentMatcher::kPrefetchLinkScriptTag, true, false, false), text); |
| VerifyCharset(&headers); |
| } |
| |
| TEST_F(FlushEarlyFlowTest, ExperimentalFlushEarlyFlowTest) { |
| ExperimentalFlushEarlyFlowTestHelper( |
| "", UserAgentMatcher::kPrefetchNotSupported, false); |
| } |
| |
| TEST_F(FlushEarlyFlowTest, ExperimentalFlushEarlyFlowTestError) { |
| ExperimentalFlushEarlyFlowTestHelper( |
| "", UserAgentMatcher::kPrefetchNotSupported, true); |
| } |
| |
| TEST_F(FlushEarlyFlowTest, ExperimentalFlushEarlyFlowTestImageTag) { |
| ExperimentalFlushEarlyFlowTestHelper( |
| "prefetch_image_tag", UserAgentMatcher::kPrefetchImageTag, false); |
| } |
| |
| TEST_F(FlushEarlyFlowTest, ExperimentalFlushEarlyFlowTestImageTagError) { |
| ExperimentalFlushEarlyFlowTestHelper( |
| "prefetch_image_tag", UserAgentMatcher::kPrefetchImageTag, true); |
| } |
| |
| TEST_F(FlushEarlyFlowTest, ExperimentalFlushEarlyFlowTestLinkScript) { |
| ExperimentalFlushEarlyFlowTestHelper( |
| "prefetch_link_script_tag", UserAgentMatcher::kPrefetchLinkScriptTag, |
| false); |
| } |
| |
| TEST_F(FlushEarlyFlowTest, ExperimentalFlushEarlyFlowTestLinkScriptError) { |
| ExperimentalFlushEarlyFlowTestHelper( |
| "prefetch_link_script_tag", UserAgentMatcher::kPrefetchLinkScriptTag, |
| true); |
| } |
| |
| TEST_F(FlushEarlyFlowTest, ExperimentalFlushEarlyFlowTestLinkRelPrefetch) { |
| ExperimentalFlushEarlyFlowTestHelper( |
| UserAgentMatcherTestBase::kChrome42UserAgent, |
| UserAgentMatcher::kPrefetchLinkRelPrefetchTag, |
| false); |
| } |
| |
| TEST_F(FlushEarlyFlowTest, |
| ExperimentalFlushEarlyFlowTestLinkRelPrefetchError) { |
| ExperimentalFlushEarlyFlowTestHelper( |
| UserAgentMatcherTestBase::kChrome42UserAgent, |
| UserAgentMatcher::kPrefetchLinkRelPrefetchTag, |
| true); |
| } |
| |
| TEST_F(FlushEarlyFlowTest, |
| ExperimentalFlushEarlyFlowTestWithInsertDnsPrefetch) { |
| SetupForFlushEarlyFlow(); |
| scoped_ptr<RewriteOptions> custom_options( |
| server_context()->global_options()->Clone()); |
| custom_options->EnableFilter(RewriteOptions::kInsertDnsPrefetch); |
| SetRewriteOptions(custom_options.get()); |
| |
| GoogleString text; |
| RequestHeaders request_headers; |
| request_headers.Replace(HttpAttributes::kUserAgent, "prefetch_image_tag"); |
| ResponseHeaders headers; |
| |
| FetchFromProxy(kTestDomain, request_headers, true, &text, &headers); |
| |
| // Fetch the url again. This time FlushEarlyFlow should be triggered but not |
| // insert dns prefetch filter as domains are not yet stable. |
| FetchFromProxy(kTestDomain, request_headers, true, &text, &headers); |
| |
| // Fetch the url again. This time InsertDnsPrefetch filter should applied. |
| FetchFromProxy(kTestDomain, request_headers, true, &text, &headers); |
| EXPECT_STREQ(FlushEarlyRewrittenHtml( |
| UserAgentMatcher::kPrefetchImageTag, false, true, true), text); |
| } |
| |
| TEST_F(FlushEarlyFlowTest, LazyloadAndDeferJsScriptFlushedEarly) { |
| SetHeaderLatencyMs(600); |
| SetupForFlushEarlyFlow(); |
| scoped_ptr<RewriteOptions> custom_options( |
| server_context()->global_options()->Clone()); |
| custom_options->EnableFilter(RewriteOptions::kDeferJavascript); |
| custom_options->EnableFilter(RewriteOptions::kLazyloadImages); |
| custom_options->set_max_prefetch_js_elements(0); |
| custom_options->set_flush_more_resources_early_if_time_permits(true); |
| SetRewriteOptions(custom_options.get()); |
| |
| GoogleString text; |
| RequestHeaders request_headers; |
| // Useragent is set to Firefox/ 9.0 because all flush early flow, defer |
| // javascript and lazyload filter are enabled for this user agent. |
| request_headers.Replace(HttpAttributes::kUserAgent, |
| "Firefox/ 9.0"); |
| ResponseHeaders headers; |
| FetchFromProxy(kTestDomain, request_headers, true, &text, &headers); |
| |
| // Fetch the url again. This time FlushEarlyFlow should be triggered. |
| FetchFromProxy(kTestDomain, request_headers, true, &text, &headers); |
| EXPECT_STREQ(FlushEarlyRewrittenHtml( |
| UserAgentMatcher::kPrefetchLinkScriptTag, true, false, true, |
| false, false, false, false), text); |
| } |
| |
| TEST_F(FlushEarlyFlowTest, NoLazyloadScriptFlushedOutIfNoImagePresent) { |
| // TODO(jmaessen): We now include the lazy load script (in <head>) even if |
| // there are no images present. We could tighten this up, but it will require |
| // a major redesign of the filter. |
| const char kInputHtml[] = |
| "<!doctype html PUBLIC \"HTML 4.0.1 Strict>" |
| "<html>" |
| "<head>" |
| "<meta http-equiv=\"content-type\" content=\"text/html;charset=utf-8\"/>" |
| "<meta http-equiv=\"last-modified\" content=\"2012-08-09T11:03:27Z\"/>" |
| "<meta charset=\"UTF-8\"/>" |
| "<title>Flush Subresources Early example</title>" |
| "<link rel=\"stylesheet\" type=\"text/css\"" |
| " href=\"http://test.com/1.css\">" |
| "</head>" |
| "<body>" |
| "Hello, mod_pagespeed!" |
| "</body>" |
| "</html>"; |
| |
| GoogleString redirect_url = StrCat(kTestDomain, "?PageSpeed=noscript"); |
| GoogleString kOutputHtml = StrCat( |
| "<!doctype html PUBLIC \"HTML 4.0.1 Strict>" |
| "<html>" |
| "<head>" |
| "<link rel=\"stylesheet\" href=\"", |
| rewritten_css_url_1_.c_str(), |
| "\"/>\n", |
| FlushEarlyContentWriterFilter::kDisableLinkTag, |
| "<script type='text/javascript'>" |
| "window.mod_pagespeed_prefetch_start = Number(new Date());" |
| "window.mod_pagespeed_num_resources_prefetched = 1" |
| "</script>", |
| GetLazyloadScriptFlushEarly(), |
| "<meta http-equiv=\"content-type\" content=\"text/html;charset=utf-8\"/>" |
| "<meta http-equiv=\"last-modified\" content=\"2012-08-09T11:03:27Z\"/>" |
| "<meta charset=\"UTF-8\"/>" |
| "<title>Flush Subresources Early example</title>" |
| "<link rel=\"stylesheet\" type=\"text/css\" href=\"", |
| rewritten_css_url_1_.c_str(), |
| "\"></head><body>", |
| StringPrintf(kNoScriptRedirectFormatter, redirect_url.c_str(), |
| redirect_url.c_str()), |
| "Hello, mod_pagespeed!</body></html>"); |
| |
| ResponseHeaders headers; |
| headers.Add(HttpAttributes::kContentType, kContentTypeHtml.mime_type()); |
| headers.SetStatusAndReason(HttpStatus::kOK); |
| mock_url_fetcher_.SetResponse(kTestDomain, headers, kInputHtml); |
| |
| // Enable FlushSubresourcesFilter filter. |
| RewriteOptions* rewrite_options = server_context()->global_options(); |
| rewrite_options->ClearSignatureForTesting(); |
| rewrite_options->EnableFilter(RewriteOptions::kFlushSubresources); |
| rewrite_options->EnableExtendCacheFilters(); |
| // Disabling the inline filters so that the resources get flushed early |
| // else our dummy resources are too small and always get inlined. |
| rewrite_options->DisableFilter(RewriteOptions::kInlineCss); |
| rewrite_options->DisableFilter(RewriteOptions::kInlineJavascript); |
| rewrite_options->ComputeSignature(); |
| |
| SetResponseWithDefaultHeaders(StrCat(kTestDomain, "1.css"), kContentTypeCss, |
| kCssContent, kHtmlCacheTimeSec * 2); |
| |
| scoped_ptr<RewriteOptions> custom_options( |
| server_context()->global_options()->Clone()); |
| custom_options->EnableFilter(RewriteOptions::kLazyloadImages); |
| SetRewriteOptions(custom_options.get()); |
| |
| GoogleString text; |
| RequestHeaders request_headers; |
| // Useragent is set to Firefox/ 9.0 because all flush early flow, defer |
| // javascript and lazyload filter is enabled for this user agent. |
| request_headers.Replace(HttpAttributes::kUserAgent, |
| "Firefox/ 9.0"); |
| |
| FetchFromProxy(kTestDomain, request_headers, true, &text, &headers); |
| |
| // Fetch the url again. This time FlushEarlyFlow should be triggered. |
| FetchFromProxy(kTestDomain, request_headers, true, &text, &headers); |
| EXPECT_STREQ(kOutputHtml, text); |
| } |
| |
| TEST_F(FlushEarlyFlowTest, FlushEarlyMoreResourcesIfTimePermits) { |
| SetHeaderLatencyMs(600); |
| StringSet* css_critical_images = new StringSet; |
| css_critical_images->insert(StrCat(kTestDomain, "1.jpg")); |
| SetCssCriticalImagesInFinder(css_critical_images); |
| GoogleString redirect_url = StrCat(kTestDomain, "?PageSpeed=noscript"); |
| |
| GoogleString kOutputHtml = StringPrintf( |
| "<!doctype html PUBLIC \"HTML 4.0.1 Strict>" |
| "<html>" |
| "<head>" |
| "<script type=\"text/javascript\">(function(){" |
| "new Image().src=\"http://test.com/1.jpg.pagespeed.ce.%s.jpg\";" |
| "new Image().src=\"%s\";})()</script>" |
| "<link rel=\"stylesheet\" href=\"%s\"/>\n" |
| "%s" |
| "<script type='text/javascript'>" |
| "window.mod_pagespeed_prefetch_start = Number(new Date());" |
| "window.mod_pagespeed_num_resources_prefetched = 3" |
| "</script>" |
| "<meta http-equiv=\"content-type\" content=\"text/html;charset=utf-8\"/>" |
| "<meta http-equiv=\"last-modified\" content=\"2012-08-09T11:03:27Z\"/>" |
| "<meta charset=\"UTF-8\"/>" |
| "<title>Flush Subresources Early example</title>" |
| "<link rel=\"stylesheet\" type=\"text/css\"" |
| " href=\"%s\"></head>" |
| "<body>%s" |
| "<script src=\"%s\"></script>" |
| "Hello, mod_pagespeed!</body></html>", |
| kMockHashValue, rewritten_js_url_3_.c_str(), |
| rewritten_css_url_1_.c_str(), |
| FlushEarlyContentWriterFilter::kDisableLinkTag, |
| rewritten_css_url_1_.c_str(), |
| StringPrintf(kNoScriptRedirectFormatter, redirect_url.c_str(), |
| redirect_url.c_str()).c_str(), rewritten_js_url_3_.c_str()); |
| |
| ResponseHeaders headers; |
| headers.Add(HttpAttributes::kContentType, kContentTypeHtml.mime_type()); |
| headers.SetStatusAndReason(HttpStatus::kOK); |
| mock_url_fetcher_.SetResponse(kTestDomain, headers, |
| kFlushEarlyMoreResourcesInputHtml); |
| |
| // Enable FlushSubresourcesFilter filter. |
| RewriteOptions* rewrite_options = server_context()->global_options(); |
| rewrite_options->ClearSignatureForTesting(); |
| rewrite_options->EnableFilter(RewriteOptions::kFlushSubresources); |
| |
| rewrite_options->set_flush_more_resources_early_if_time_permits(true); |
| rewrite_options->EnableExtendCacheFilters(); |
| // Disabling the inline filters so that the resources get flushed early |
| // else our dummy resources are too small and always get inlined. |
| rewrite_options->DisableFilter(RewriteOptions::kInlineCss); |
| rewrite_options->DisableFilter(RewriteOptions::kInlineJavascript); |
| rewrite_options->DisableFilter(RewriteOptions::kInlineImages); |
| rewrite_options->ComputeSignature(); |
| |
| SetResponseWithDefaultHeaders(StrCat(kTestDomain, "1.jpg"), kContentTypeJpeg, |
| "image", kHtmlCacheTimeSec * 2); |
| SetResponseWithDefaultHeaders(StrCat(kTestDomain, "1.css"), kContentTypeCss, |
| kCssContent, kHtmlCacheTimeSec * 2); |
| SetResponseWithDefaultHeaders(StrCat(kTestDomain, "1.js"), |
| kContentTypeJavascript, "javascript", |
| kHtmlCacheTimeSec * 2); |
| GoogleString text; |
| RequestHeaders request_headers; |
| request_headers.Replace(HttpAttributes::kUserAgent, "prefetch_image_tag"); |
| |
| FetchFromProxy(kTestDomain, request_headers, true, &text, &headers); |
| |
| // Fetch the url again. This time FlushEarlyFlow should be triggered but |
| // all resources may not be flushed. |
| FetchFromProxy(kTestDomain, request_headers, true, &text, &headers); |
| |
| // Fetch the url again. This time all resources based on time will be flushed. |
| FetchFromProxy(kTestDomain, request_headers, true, &text, &headers); |
| |
| EXPECT_STREQ(kOutputHtml, text); |
| } |
| |
| TEST_F(FlushEarlyFlowTest, InsertLazyloadJsOnlyIfResourceHtmlNotEmpty) { |
| const char kInputHtml[] = |
| "<!doctype html PUBLIC \"HTML 4.0.1 Strict>" |
| "<html>" |
| "<head>" |
| "<title>Flush Subresources Early example</title>" |
| "</head>" |
| "<body>" |
| "<img src=http://test.com/1.jpg />" |
| "Hello, mod_pagespeed!" |
| "</body>" |
| "</html>"; |
| |
| GoogleString redirect_url = StrCat(kTestDomain, "?PageSpeed=noscript"); |
| GoogleString kOutputHtml = StrCat( |
| "<!doctype html PUBLIC \"HTML 4.0.1 Strict>" |
| "<html>" |
| "<head>" |
| "<title>Flush Subresources Early example</title>", |
| GetLazyloadScriptHtml(), |
| "</head>" |
| "<body>", |
| StringPrintf(kNoScriptRedirectFormatter, redirect_url.c_str(), |
| redirect_url.c_str()), |
| StringPrintf( |
| "<img data-pagespeed-lazy-src=http://test.com/1.jpg.pagespeed.ce.%s.jpg" |
| " src=\"/psajs/1.0.gif\"" |
| " onload=\"%s\" onerror=\"this.onerror=null;%s\"/>" |
| "Hello, mod_pagespeed!" |
| "<script type=\"text/javascript\" data-pagespeed-no-defer>" |
| "pagespeed.lazyLoadImages.overrideAttributeFunctions();</script>" |
| "</body></html>", kMockHashValue, |
| LazyloadImagesFilter::kImageOnloadCode, |
| LazyloadImagesFilter::kImageOnloadCode)); |
| |
| ResponseHeaders headers; |
| headers.Add(HttpAttributes::kContentType, kContentTypeHtml.mime_type()); |
| headers.SetStatusAndReason(HttpStatus::kOK); |
| mock_url_fetcher_.SetResponse(kTestDomain, headers, kInputHtml); |
| |
| // Enable FlushSubresourcesFilter filter. |
| RewriteOptions* rewrite_options = server_context()->global_options(); |
| rewrite_options->ClearSignatureForTesting(); |
| rewrite_options->EnableFilter(RewriteOptions::kFlushSubresources); |
| rewrite_options->EnableExtendCacheFilters(); |
| // Disabling the inline filters so that the resources get flushed early |
| // else our dummy resources are too small and always get inlined. |
| rewrite_options->DisableFilter(RewriteOptions::kInlineCss); |
| rewrite_options->DisableFilter(RewriteOptions::kInlineJavascript); |
| rewrite_options->ComputeSignature(); |
| |
| SetResponseWithDefaultHeaders(StrCat(kTestDomain, "1.jpg"), kContentTypeJpeg, |
| "image", kHtmlCacheTimeSec * 2); |
| |
| scoped_ptr<RewriteOptions> custom_options( |
| server_context()->global_options()->Clone()); |
| custom_options->EnableFilter(RewriteOptions::kLazyloadImages); |
| SetRewriteOptions(custom_options.get()); |
| |
| GoogleString text; |
| RequestHeaders request_headers; |
| // Useragent is set to Firefox/ 9.0 because all flush early flow, defer |
| // javascript and lazyload filter is enabled for this user agent. |
| request_headers.Replace(HttpAttributes::kUserAgent, |
| "Firefox/ 9.0"); |
| |
| FetchFromProxy(kTestDomain, request_headers, true, &text, &headers); |
| |
| // Fetch the url again. This time FlushEarlyFlow should be triggered but no |
| // lazyload js will be flushed early as no resource is present in the html. |
| FetchFromProxy(kTestDomain, request_headers, true, &text, &headers); |
| EXPECT_STREQ(kOutputHtml, text); |
| } |
| |
| TEST_F(FlushEarlyFlowTest, DontInsertLazyloadJsIfMobile) { |
| TestFlushLazyLoadJsEarly(true); |
| } |
| |
| TEST_F(FlushEarlyFlowTest, InsertLazyloadJsIfNotMobile) { |
| TestFlushLazyLoadJsEarly(false); |
| } |
| |
| TEST_F(FlushEarlyFlowTest, PreconnectTest) { |
| TestFlushPreconnects(false); |
| } |
| |
| TEST_F(FlushEarlyFlowTest, NoPreconnectForMobile) { |
| TestFlushPreconnects(true); |
| } |
| |
| TEST_F(FlushEarlyFlowTest, FlushEarlyFlowTestWithLocalStorageDoesNotCrash) { |
| SetupForFlushEarlyFlow(); |
| GoogleString text; |
| RequestHeaders request_headers; |
| request_headers.Replace(HttpAttributes::kUserAgent, |
| "prefetch_link_rel_subresource"); |
| |
| RewriteOptions* rewrite_options = server_context()->global_options(); |
| rewrite_options->ClearSignatureForTesting(); |
| rewrite_options->EnableFilter(RewriteOptions::kLocalStorageCache); |
| rewrite_options->ForceEnableFilter(RewriteOptions::kInlineImages); |
| rewrite_options->ForceEnableFilter(RewriteOptions::kInlineCss); |
| rewrite_options->ComputeSignature(); |
| |
| // This sequence of requests used to cause a crash earlier. Here, we just test |
| // that this server doesn't crash and don't check the output. |
| ResponseHeaders headers; |
| FetchFromProxy(kTestDomain, request_headers, true, &text, &headers); |
| FetchFromProxy(kTestDomain, request_headers, true, &text, &headers); |
| } |
| |
| TEST_F(FlushEarlyFlowTest, FlushEarlyFlowWithIEAddUACompatibilityHeader) { |
| SetupForFlushEarlyFlow(); |
| RequestHeaders request_headers; |
| // Useragent is set to "MSIE 10." because we need to check if appropriate |
| // HttpAttributes::kXUACompatible header is added, which happens only with |
| // MSIE 9 and above, but IE9 is now blacklisted from defer_js. |
| request_headers.Replace(HttpAttributes::kUserAgent, " MSIE 10."); |
| scoped_ptr<RewriteOptions> custom_options( |
| server_context()->global_options()->Clone()); |
| custom_options->EnableFilter(RewriteOptions::kDeferJavascript); |
| custom_options->set_max_prefetch_js_elements(0); |
| SetRewriteOptions(custom_options.get()); |
| |
| GoogleString text; |
| ResponseHeaders headers; |
| |
| FetchFromProxy(kTestDomain, request_headers, true, &text, &headers); |
| // Fetch the url again. This time FlushEarlyFlow should be triggered. |
| FetchFromProxy(kTestDomain, request_headers, true, &text, &headers); |
| |
| EXPECT_STREQ(FlushEarlyRewrittenHtml(UserAgentMatcher::kPrefetchLinkScriptTag, |
| true, false, false, false, |
| false, false, true), text); |
| ConstStringStarVector values; |
| EXPECT_TRUE(headers.Lookup(HttpAttributes::kXUACompatible, &values)); |
| EXPECT_STREQ("IE=edge", *(values[0])); |
| } |
| |
| TEST_F(FlushEarlyFlowTest, FlushEarlyFlowWithDeferJsAndSplitEnabled) { |
| // The default finder class used by split_html is |
| // BeaconCriticalLineInfoFinder, which requires the pcache to be setup, so do |
| // that setup here. |
| PropertyCache* pcache = server_context_->page_property_cache(); |
| const PropertyCache::Cohort* beacon_cohort = |
| SetupCohort(pcache, RewriteDriver::kBeaconCohort); |
| server_context()->set_beacon_cohort(beacon_cohort); |
| server_context()->set_critical_line_info_finder( |
| new BeaconCriticalLineInfoFinder(server_context()->beacon_cohort(), |
| factory()->nonce_generator())); |
| |
| SetupForFlushEarlyFlow(); |
| RequestHeaders request_headers; |
| request_headers.Replace(HttpAttributes::kUserAgent, |
| " MSIE 10."); |
| scoped_ptr<RewriteOptions> custom_options( |
| server_context()->global_options()->Clone()); |
| custom_options->EnableFilter(RewriteOptions::kDeferJavascript); |
| custom_options->EnableFilter(RewriteOptions::kSplitHtml); |
| custom_options->set_max_prefetch_js_elements(0); |
| SetRewriteOptions(custom_options.get()); |
| |
| GoogleString text; |
| ResponseHeaders headers; |
| |
| FetchFromProxy(kTestDomain, request_headers, true, &text, &headers); |
| // Fetch the url again. This time FlushEarlyFlow should be triggered. |
| FetchFromProxy(kTestDomain, request_headers, true, &text, &headers); |
| |
| EXPECT_STREQ(FlushEarlyRewrittenHtml(UserAgentMatcher::kPrefetchLinkScriptTag, |
| true, false, false, false, |
| true, false, true), text); |
| } |
| |
| class FlushEarlyPrioritizeCriticalCssTest : public FlushEarlyFlowTest { |
| public: |
| virtual void SetUp() { |
| FlushEarlyFlowTest::SetUp(); |
| SetOptions(); |
| InitializeResponses(); |
| } |
| |
| void SetOptions() { |
| // Enable FlushSubresourcesFilter filter. |
| RewriteOptions* rewrite_options = server_context()->global_options(); |
| rewrite_options->ClearSignatureForTesting(); |
| rewrite_options->EnableFilter(RewriteOptions::kFlushSubresources); |
| rewrite_options->EnableFilter(RewriteOptions::kPrioritizeCriticalCss); |
| |
| // Disabling the inline filters so that the resources get flushed early |
| // else our dummy resources are too small and always get inlined. |
| rewrite_options->DisableFilter(RewriteOptions::kInlineCss); |
| rewrite_options->DisableFilter(RewriteOptions::kRewriteJavascriptExternal); |
| rewrite_options->DisableFilter(RewriteOptions::kRewriteJavascriptInline); |
| |
| rewrite_options->set_enable_flush_early_critical_css(true); |
| rewrite_options->set_use_selectors_for_critical_css(false); |
| rewrite_options->ComputeSignature(); |
| } |
| |
| void InitializeResponses() { |
| // Some weird but valid CSS. |
| SetResponseWithDefaultHeaders("a.css", kContentTypeCss, |
| "div,span,*::first-letter { display: block; }" |
| "p { display: inline; }", |
| kHtmlCacheTimeSec * 2); |
| SetResponseWithDefaultHeaders("b.css?x=1&y=2", kContentTypeCss, |
| "@media screen,print { * { margin: 0px; } }", |
| kHtmlCacheTimeSec * 2); |
| } |
| |
| GoogleString InputHtml() { |
| return GoogleString( |
| "<!doctype html PUBLIC \"HTML 4.0.1 Strict>" |
| "<html><head>" |
| "<title>Flush Subresources Early example</title>" |
| "<link rel=\"stylesheet\" type=\"text/css\" href=\"a.css\">" |
| "<link rel=\"stylesheet\" type=\"text/css\" href=\"b.css?x=1&y=2\">" |
| "</head>" |
| "<body>" |
| "Hello, mod_pagespeed!" |
| "</body></html>"); |
| } |
| |
| GoogleString ExpectedHtml(GoogleString full_styles_html) { |
| GoogleString expected_html = StrCat( |
| "<!doctype html PUBLIC \"HTML 4.0.1 Strict>" |
| "<html><head>", |
| FlushedCss("div,*::first-letter{display:block}"), // a.css |
| FlushedCss("@media screen{*{margin:0}}"), // b.css |
| StringPrintf(FlushEarlyContentWriterFilter::kPrefetchStartTimeScript, |
| 2 /* num_resources_flushed */), |
| "<title>Flush Subresources Early example</title>"); |
| StrAppend(&expected_html, |
| ApplyFlushEarlyScript(), |
| InvokeFlushEarlyScript(), // invoke a.css |
| InvokeFlushEarlyScript(), // invoke b.css |
| "</head><body>", |
| NoScriptRedirectHtml(), |
| "Hello, mod_pagespeed!", |
| full_styles_html, |
| "</body></html>"); |
| return expected_html; |
| } |
| |
| GoogleString FlushedCss(GoogleString critical_css) { |
| return StrCat( |
| "<script type=\"text/psa_flush_style\" id=\"", kMockHashValue, "\">", |
| critical_css, "</script>"); |
| } |
| |
| GoogleString ApplyFlushEarlyScript() { |
| return StrCat( |
| "<script id=\"psa_flush_style_early\"" |
| " data-pagespeed-no-defer type=\"text/javascript\">", |
| CriticalSelectorFilter::kApplyFlushEarlyCss, "</script>"); |
| } |
| |
| GoogleString InvokeFlushEarlyScript() { |
| // The test uses a fixed hash for all URLs. |
| return StrCat( |
| "<script data-pagespeed-no-defer type=\"text/javascript\">" |
| "applyFlushedCriticalCss(\"", kMockHashValue, "\", \"\");" |
| "</script>"); |
| } |
| |
| GoogleString CssLinkEncodedHref(GoogleString url) { |
| return StrCat( |
| "<link rel=\"stylesheet\" type=\"text/css\" href=\"", |
| Encode("", "cf", kMockHashValue, url, "css"), |
| "\">"); |
| } |
| |
| GoogleString url() { return kTestDomain; } |
| |
| void ValidateFlushEarly(const StringPiece& case_id, |
| const GoogleString& input_html, |
| const GoogleString& expected_html) { |
| RequestHeaders request_headers; |
| request_headers.Replace(HttpAttributes::kUserAgent, |
| UserAgentMatcherTestBase::kChrome18UserAgent); |
| |
| ResponseHeaders headers; |
| headers.Add(HttpAttributes::kContentType, kContentTypeHtml.mime_type()); |
| headers.SetStatusAndReason(HttpStatus::kOK); |
| mock_url_fetcher_.SetResponse(url(), headers, input_html); |
| |
| GoogleString actual_html; |
| FetchFromProxy(url(), request_headers, true, &actual_html, &headers); |
| |
| // Fetch the url again. This time FlushEarlyFlow should be triggered. |
| FetchFromProxy(url(), request_headers, true, &actual_html, &headers); |
| EXPECT_EQ(expected_html, actual_html) << "Test id:" << case_id; |
| } |
| }; |
| |
| TEST_F(FlushEarlyPrioritizeCriticalCssTest, |
| FlushEarlyFlowWithCriticalCssEnabled) { |
| // Add critical css rules. |
| MockCriticalCssFinder* critical_css_finder = |
| new MockCriticalCssFinder(rewrite_driver(), statistics()); |
| server_context()->set_critical_css_finder(critical_css_finder); |
| critical_css_finder->AddCriticalCss( |
| "http://test.com/a.css", "div,*::first-letter{display:block}", 100); |
| critical_css_finder->AddCriticalCss( |
| "http://test.com/b.css?x=1&y=2", "@media screen{*{margin:0}}", 100); |
| |
| GoogleString full_styles_html = StrCat( |
| "<noscript class=\"psa_add_styles\">", |
| CssLinkEncodedHref("a.css"), |
| CssLinkEncodedHref("b.css?x=1&y=2"), |
| "</noscript>" |
| "<script data-pagespeed-no-defer type=\"text/javascript\">", |
| CriticalCssFilter::kAddStylesScript, |
| "window['pagespeed'] = window['pagespeed'] || {};" |
| "window['pagespeed']['criticalCss'] = {" |
| " 'total_critical_inlined_size': 60," |
| " 'total_original_external_size': 200," |
| " 'total_overhead_size': 60," |
| " 'num_replaced_links': 2," |
| " 'num_unreplaced_links': 0};" |
| "</script>"); |
| ValidateFlushEarly( |
| "critical_css", InputHtml(), ExpectedHtml(full_styles_html)); |
| } |
| |
| class TestCriticalSelectorFinder : public CriticalSelectorFinder { |
| public: |
| TestCriticalSelectorFinder(const PropertyCache::Cohort* cohort, |
| Statistics* stats) |
| : CriticalSelectorFinder(cohort, NULL /* nonce_generator */, stats) {} |
| |
| virtual ~TestCriticalSelectorFinder() {} |
| |
| virtual int SupportInterval() const { return 1; } |
| |
| protected: |
| virtual bool ShouldReplacePriorResult() const { return true; } |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(TestCriticalSelectorFinder); |
| }; |
| |
| TEST_F(FlushEarlyPrioritizeCriticalCssTest, |
| FlushEarlyFlowWithCriticalSelectorFilterEnabled) { |
| PropertyCache* pcache = server_context_->page_property_cache(); |
| const PropertyCache::Cohort* beacon_cohort = |
| SetupCohort(pcache, RewriteDriver::kBeaconCohort); |
| server_context()->set_beacon_cohort(beacon_cohort); |
| |
| rewrite_driver()->Clear(); |
| rewrite_driver()->set_request_context( |
| RequestContext::NewTestRequestContext(factory()->thread_system())); |
| |
| MockPropertyPage* page = NewMockPage( |
| url(), |
| server_context()->GetRewriteOptionsSignatureHash( |
| server_context()->global_options()), |
| UserAgentMatcher::kDesktop); |
| rewrite_driver()->set_property_page(page); |
| pcache->Read(page); |
| |
| RewriteOptions* rewrite_options = server_context()->global_options(); |
| rewrite_options->ClearSignatureForTesting(); |
| rewrite_options->set_use_selectors_for_critical_css(true); |
| rewrite_options->ComputeSignature(); |
| |
| server_context()->set_critical_selector_finder(new TestCriticalSelectorFinder( |
| server_context()->beacon_cohort(), statistics())); |
| |
| // Write critical selectors to property cache |
| StringSet selectors; |
| selectors.insert("div"); |
| selectors.insert("*"); |
| CriticalSelectorFinder* finder = server_context()->critical_selector_finder(); |
| finder->WriteCriticalSelectorsToPropertyCache( |
| selectors, "" /* last_nonce */, rewrite_driver()); |
| rewrite_driver()->property_page()-> |
| WriteCohort(server_context()->beacon_cohort()); |
| |
| EXPECT_TRUE(finder->IsCriticalSelector(rewrite_driver(), "div")); |
| EXPECT_TRUE(finder->IsCriticalSelector(rewrite_driver(), "*")); |
| |
| GoogleString full_styles_html = StrCat( |
| "<noscript class=\"psa_add_styles\">", CssLinkEncodedHref("a.css"), |
| CssLinkEncodedHref("b.css?x=1&y=2"), |
| "</noscript>" |
| "<script data-pagespeed-no-defer type=\"text/javascript\">", |
| rewrite_driver()->server_context()->static_asset_manager()->GetAsset( |
| StaticAssetEnum::CRITICAL_CSS_LOADER_JS, |
| rewrite_driver()->options()), |
| "pagespeed.CriticalCssLoader.Run();</script>"); |
| ValidateFlushEarly( |
| "critical_selector", InputHtml(), ExpectedHtml(full_styles_html)); |
| } |
| |
| } // namespace net_instaweb |