blob: d3ba9e02c1b9d8275e21bef14562dbd22741cc64 [file] [log] [blame]
/*
* 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, "&amp;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_, "&amp;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