blob: 78c3765eda53ffd7c8e3c78952e0f711c28ca1ba [file] [log] [blame]
/*
* Copyright 2016 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: morlovich@google.com (Maksim Orlovich)
#include "net/instaweb/rewriter/public/rewrite_driver.h"
#include "net/instaweb/rewriter/public/rewrite_options.h"
#include "net/instaweb/rewriter/public/rewrite_test_base.h"
#include "net/instaweb/rewriter/public/server_context.h"
#include "net/instaweb/rewriter/public/test_rewrite_driver_factory.h"
#include "net/instaweb/util/public/mock_property_page.h"
#include "net/instaweb/util/public/property_cache.h"
#include "pagespeed/kernel/base/gtest.h"
#include "pagespeed/kernel/base/string.h"
#include "pagespeed/kernel/base/string_util.h"
#include "pagespeed/kernel/http/content_type.h"
#include "pagespeed/kernel/http/http_names.h"
#include "pagespeed/kernel/http/response_headers.h"
#include "pagespeed/kernel/html/html_parse_test_base.h"
#include "pagespeed/opt/http/request_context.h"
namespace net_instaweb {
namespace {
const char kRequestUrl[] = "http://www.example.com/";
class PushPreloadFilterTest : public RewriteTestBase {
protected:
void SetUp() override {
RewriteTestBase::SetUp();
options()->EnableFilter(RewriteOptions::kHintPreloadSubresources);
// Setup pcache.
pcache_ = rewrite_driver()->server_context()->page_property_cache();
const PropertyCache::Cohort* deps_cohort =
SetupCohort(pcache_, RewriteDriver::kDependenciesCohort);
server_context()->set_dependencies_cohort(deps_cohort);
ResetDriver();
SetResponseWithDefaultHeaders("a.css", kContentTypeCss,
" * { display: block }", 100);
SetResponseWithDefaultHeaders("b.js", kContentTypeJavascript,
" var b = 42", 200);
}
void ResetDriver() {
rewrite_driver()->Clear();
rewrite_driver()->set_request_context(
RequestContext::NewTestRequestContext(factory()->thread_system()));
page_ = NewMockPage(kRequestUrl);
rewrite_driver()->set_property_page(page_);
pcache_->Read(page_);
rewrite_driver()->PropertyCacheSetupDone();
SetHtmlMimetype(); // Don't wrap scripts in <![CDATA[ ]]>
}
PropertyCache* pcache_;
PropertyPage* page_;
};
TEST_F(PushPreloadFilterTest, WeirdTiming) {
// Event buffering causes us to clear mutable_response_headers()
// at first flush window even if we haven't yet even delivered StartDocument.
// At the very least, that shouldn't cause us to crash.
// TODO(morlovich): Discuss what the API should be with Josh.
rewrite_driver()->AddFilters();
rewrite_driver()->StartParse(kTestDomain);
rewrite_driver()->Flush();
rewrite_driver()->FinishParse();
}
TEST_F(PushPreloadFilterTest, BasicOperation) {
options()->EnableFilter(RewriteOptions::kRewriteCss);
options()->EnableFilter(RewriteOptions::kRewriteJavascriptExternal);
rewrite_driver()->AddFilters();
static const char kInput[] = "<link rel=stylesheet href=a.css>"
"<script src=b.js></script>";
static const char kOutput[] =
"<link rel=stylesheet href=A.a.css.pagespeed.cf.0.css>"
"<script src=b.js.pagespeed.jm.0.js></script>";
ValidateExpected("basic_res", kInput, kOutput);
// Now that we've collected stuff, see if it produces proper headers.
ResetDriver();
rewrite_driver()->StartParse(kTestDomain);
rewrite_driver()->ParseText("<!doctype html><html>");
rewrite_driver()->Flush(); // Run filters
ConstStringStarVector links;
EXPECT_TRUE(rewrite_driver()->response_headers()->Lookup(
HttpAttributes::kLink, &links));
rewrite_driver()->FinishParse();
ASSERT_EQ(2, links.size());
EXPECT_STREQ(
"</A.a.css.pagespeed.cf.0.css>; rel=preload; as=style; nopush",
*links[0]);
EXPECT_STREQ(
"</b.js.pagespeed.jm.0.js>; rel=preload; as=script; nopush",
*links[1]);
}
TEST_F(PushPreloadFilterTest, Invalidation) {
// Test for keeping track of us remembering when things expire.
options()->EnableFilter(RewriteOptions::kRewriteCss);
options()->EnableFilter(RewriteOptions::kRewriteJavascriptExternal);
rewrite_driver()->AddFilters();
// b.js expires in 200, a.css expires in 100.
static const char kInput[] = "<script src=b.js></script>"
"<link rel=stylesheet href=a.css>";
static const char kOutput[] =
"<script src=b.js.pagespeed.jm.0.js></script>"
"<link rel=stylesheet href=A.a.css.pagespeed.cf.0.css>";
ValidateExpected("invalidation", kInput, kOutput);
// Enough to invalidate a, but not b.
AdvanceTimeMs(150 * 1000);
ResetDriver();
ValidateExpected("invalidation2", kInput, kOutput);
ConstStringStarVector links;
EXPECT_TRUE(rewrite_driver()->response_headers()->Lookup(
HttpAttributes::kLink, &links));
// Only b.js should be pushed --- or rather the .pagespeed version.
ASSERT_EQ(1, links.size());
EXPECT_STREQ("</b.js.pagespeed.jm.0.js>; rel=preload; as=script; nopush",
*links[0]);
}
TEST_F(PushPreloadFilterTest, Invalidation2) {
// Test for keeping track of us remembering when things expire.
options()->EnableFilter(RewriteOptions::kRewriteCss);
options()->EnableFilter(RewriteOptions::kRewriteJavascriptExternal);
rewrite_driver()->AddFilters();
// b.js expires in 200, a.css expires in 100.
static const char kInput[] = "<script src=b.js></script>"
"<link rel=stylesheet href=a.css>";
static const char kOutput[] =
"<script src=b.js.pagespeed.jm.0.js></script>"
"<link rel=stylesheet href=A.a.css.pagespeed.cf.0.css>";
ValidateExpected("invalidation", kInput, kOutput);
// Enough to invalidate both a and b.
AdvanceTimeMs(250 * 1000);
ResetDriver();
ValidateExpected("invalidation2", kInput, kOutput);
ConstStringStarVector links;
EXPECT_FALSE(rewrite_driver()->response_headers()->Lookup(
HttpAttributes::kLink, &links));
}
TEST_F(PushPreloadFilterTest, InvalidationOrder) {
// Test for keeping track of us remembering when things expire.
options()->EnableFilter(RewriteOptions::kRewriteCss);
options()->EnableFilter(RewriteOptions::kRewriteJavascriptExternal);
rewrite_driver()->AddFilters();
// b.js expires in 200, a.css expires in 100.
static const char kInput[] = "<link rel=stylesheet href=a.css>"
"<script src=b.js></script>";
static const char kOutput[] =
"<link rel=stylesheet href=A.a.css.pagespeed.cf.0.css>"
"<script src=b.js.pagespeed.jm.0.js></script>";
ValidateExpected("invalidation", kInput, kOutput);
// Enough to invalidate a, but not b. However, since a is before b, nothing
// will be hinted.
AdvanceTimeMs(150 * 1000);
ResetDriver();
ValidateExpected("invalidation2", kInput, kOutput);
ConstStringStarVector links;
EXPECT_FALSE(rewrite_driver()->response_headers()->Lookup(
HttpAttributes::kLink, &links));
}
TEST_F(PushPreloadFilterTest, IndirectCollected) {
SetResponseWithDefaultHeaders("c.css", kContentTypeCss,
"@import \"i1.css\" all;\n"
"@import \"i2.css\" print, screen;\n"
"@import \"i3.css\" print; ", 100);
SetResponseWithDefaultHeaders("d.css", kContentTypeCss,
"@import \"i1.css\" all; \n"
"@import \"i4.css\"; ", 100);
options()->EnableFilter(RewriteOptions::kRewriteCss);
rewrite_driver()->AddFilters();
const char kInput[] = "<link rel=stylesheet href=c.css>"
"<link rel=stylesheet href=d.css>";
const char kOutput[] =
"<link rel=stylesheet href=A.c.css.pagespeed.cf.0.css>"
"<link rel=stylesheet href=A.d.css.pagespeed.cf.0.css>";
ValidateExpected("basic_res", kInput, kOutput);
// Now that we've collected stuff, see if it produces proper headers.
ResetDriver();
rewrite_driver()->StartParse(kTestDomain);
rewrite_driver()->ParseText("<!doctype html><html>");
rewrite_driver()->Flush(); // Run filters
ConstStringStarVector links;
EXPECT_TRUE(rewrite_driver()->response_headers()->Lookup(
HttpAttributes::kLink, &links));
rewrite_driver()->FinishParse();
ASSERT_EQ(5, links.size());
// These should be in preorder wrt to the dependencies between
// CSS and things in it
EXPECT_STREQ(
"</A.c.css.pagespeed.cf.0.css>; rel=preload; as=style; nopush",
*links[0]);
EXPECT_STREQ(
"</i1.css>; rel=preload; as=style; nopush",
*links[1]);
EXPECT_STREQ(
"</i2.css>; rel=preload; as=style; nopush",
*links[2]);
EXPECT_STREQ(
"</A.d.css.pagespeed.cf.0.css>; rel=preload; as=style; nopush",
*links[3]);
// not i3, since it's print only.
// i1 already hinted.
// i4 isn't, though.
EXPECT_STREQ("</i4.css>; rel=preload; as=style; nopush", *links[4]);
}
} // namespace
} // namespace net_instaweb