blob: 4f29f68336c924ac5c4862a4835d1be6fe314b75 [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/collect_dependencies_filter.h"
#include "net/instaweb/http/public/http_cache.h"
#include "net/instaweb/http/public/http_cache_failure.h"
#include "net/instaweb/rewriter/dependencies.pb.h"
#include "net/instaweb/rewriter/public/dependency_tracker.h"
#include "net/instaweb/rewriter/public/rewrite_driver.h"
#include "net/instaweb/rewriter/public/rewrite_options.h"
#include "net/instaweb/rewriter/public/rewrite_test_base.h"
#include "net/instaweb/rewriter/public/server_context.h"
#include "net/instaweb/rewriter/public/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/gmock.h"
#include "pagespeed/kernel/base/gtest.h"
#include "pagespeed/kernel/base/mock_message_handler.h"
#include "pagespeed/kernel/base/proto_matcher.h"
#include "pagespeed/kernel/base/string.h"
#include "pagespeed/kernel/base/string_util.h"
#include "pagespeed/kernel/html/html_parse_test_base.h"
#include "pagespeed/kernel/http/content_type.h"
#include "pagespeed/opt/http/request_context.h"
namespace net_instaweb {
namespace {
const char kRequestUrl[] = "http://www.example.com/";
class CollectDependenciesFilterTest : public RewriteTestBase {
protected:
void SetUp() override {
RewriteTestBase::SetUp();
options()->EnableFilter(RewriteOptions::kExperimentHttp2);
// 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();
// TODO(morlovich): Once indirect dependency collection is in, make sure to
// test that it interacts with this correctly.
SetResponseWithDefaultHeaders("a.css", kContentTypeCss,
" * { display: block }", 100);
SetResponseWithDefaultHeaders("c.css", kContentTypeCss,
" * { display: list-item }", 150);
SetResponseWithDefaultHeaders("b.js", kContentTypeJavascript,
" var b = 42", 200);
SetResponseWithDefaultHeaders("d.js", kContentTypeJavascript,
" var d = 32", 250);
start_time_ms_ = timer()->NowMs();
}
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[ ]]>
}
GoogleString FormatRelTimeSec(int delta_sec) {
return Integer64ToString(start_time_ms_ + delta_sec * Timer::kSecondMs);
}
PropertyCache* pcache_;
PropertyPage* page_;
int64 start_time_ms_;
const int64 kYearSec = Timer::kYearMs / Timer::kSecondMs;
};
TEST_F(CollectDependenciesFilterTest, BasicOperation) {
options()->EnableFilter(RewriteOptions::kRewriteCss);
options()->EnableFilter(RewriteOptions::kRewriteJavascriptExternal);
rewrite_driver()->AddFilters();
const char kInput[] = "<link rel=stylesheet href=a.css>"
"<script src=b.js></script>";
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);
// Read stuff back in from pcache.
ResetDriver();
DependencyTracker* tracker = rewrite_driver()->dependency_tracker();
rewrite_driver()->StartParse(kTestDomain);
ASSERT_TRUE(tracker->read_in_info() != nullptr);
EXPECT_THAT(*tracker->read_in_info(), EqualsProto(StrCat(
"dependency {"
"url: 'http://test.com/A.a.css.pagespeed.cf.0.css'"
"content_type: DEP_CSS "
"validity_info {"
"type: CACHED "
"expiration_time_ms: ", FormatRelTimeSec(100), " "
"date_ms: ", FormatRelTimeSec(0),
"}"
"validity_info {"
"type: CACHED "
"last_modified_time_ms: ", FormatRelTimeSec(0), " ",
"expiration_time_ms: ", FormatRelTimeSec(kYearSec), " "
"date_ms: ", FormatRelTimeSec(0),
"}"
"order_key: 0"
"}"
"dependency {"
"url: 'http://test.com/b.js.pagespeed.jm.0.js'"
"content_type: DEP_JAVASCRIPT "
"validity_info {"
"type: CACHED "
"expiration_time_ms: ", FormatRelTimeSec(200), " "
"date_ms: ", FormatRelTimeSec(0),
"}"
"validity_info {"
"type: CACHED "
"last_modified_time_ms: ", FormatRelTimeSec(0), " ",
"expiration_time_ms: ", FormatRelTimeSec(kYearSec), " "
"date_ms: ", FormatRelTimeSec(0),
"}"
"order_key: 1"
"}")));
rewrite_driver()->FinishParse();
}
TEST_F(CollectDependenciesFilterTest, MediaTopLevel) {
SetResponseWithDefaultHeaders("e.css", kContentTypeCss,
" * { display: inline-block }", 400);
rewrite_driver()->AddFilters();
const char kInput[] =
"<link rel=stylesheet href=a.css media=\"screen,print\">"
"<link rel=stylesheet href=c.css media=print>"
"<link rel=stylesheet href=e.css media=all>";
ValidateNoChanges("media_top_level", kInput);
// Read stuff back in from pcache.
ResetDriver();
DependencyTracker* tracker = rewrite_driver()->dependency_tracker();
rewrite_driver()->StartParse(kTestDomain);
ASSERT_TRUE(tracker->read_in_info() != nullptr);
EXPECT_THAT(*tracker->read_in_info(), EqualsProto(StrCat(
"dependency {"
"url: 'http://test.com/a.css'"
"content_type: DEP_CSS "
"validity_info {"
"type: CACHED "
"expiration_time_ms: ", FormatRelTimeSec(100), " "
"date_ms: ", FormatRelTimeSec(0),
"}"
"order_key: 0"
"}"
"dependency {"
"url: 'http://test.com/e.css'"
"content_type: DEP_CSS "
"validity_info {"
"type: CACHED "
"expiration_time_ms: ", FormatRelTimeSec(400), " "
"date_ms: ", FormatRelTimeSec(0),
"}"
"order_key: 1"
"}")));
rewrite_driver()->FinishParse();
}
TEST_F(CollectDependenciesFilterTest, HandleEmptyResources) {
// We expect failures to still be hinted (since the browser will try to fetch
// them), but we need to be careful not to crash. (We used to).
// Using RememberFailure seems like the easiest way of getting empty
// Resource objects.
http_cache()->RememberFailure(
StrCat(kTestDomain, "e.css"), rewrite_driver()->CacheFragment(),
kFetchStatusOtherError, message_handler());
http_cache()->RememberFailure(
StrCat(kTestDomain, "f.js"), rewrite_driver()->CacheFragment(),
kFetchStatusOtherError, message_handler());
const char kInput[] = "<link rel=stylesheet href=e.css>"
"<script src=f.js></script>";
ValidateNoChanges("unoptimized", kInput);
// Read stuff back in from pcache.
ResetDriver();
DependencyTracker* tracker = rewrite_driver()->dependency_tracker();
rewrite_driver()->StartParse(kTestDomain);
ASSERT_TRUE(tracker->read_in_info() != nullptr);
EXPECT_THAT(*tracker->read_in_info(), EqualsProto(
"dependency {"
"url: 'http://test.com/e.css'"
"content_type: DEP_CSS "
"order_key: 0"
"}"
"dependency {"
"url: 'http://test.com/f.js'"
"content_type: DEP_JAVASCRIPT "
"order_key: 1"
"}"));
rewrite_driver()->FinishParse();
}
TEST_F(CollectDependenciesFilterTest, Unoptimized) {
const char kInput[] = "<link rel=stylesheet href=a.css>"
"<script src=b.js></script>";
ValidateNoChanges("unoptimized", kInput);
// Read stuff back in from pcache.
ResetDriver();
DependencyTracker* tracker = rewrite_driver()->dependency_tracker();
rewrite_driver()->StartParse(kTestDomain);
ASSERT_TRUE(tracker->read_in_info() != nullptr);
EXPECT_THAT(*tracker->read_in_info(), EqualsProto(StrCat(
"dependency {"
"url: 'http://test.com/a.css'"
"content_type: DEP_CSS "
"validity_info {"
"type: CACHED "
"expiration_time_ms: ", FormatRelTimeSec(100), " "
"date_ms: ", FormatRelTimeSec(0),
"}"
"order_key: 0"
"}"
"dependency {"
"url: 'http://test.com/b.js'"
"content_type: DEP_JAVASCRIPT "
"validity_info {"
"type: CACHED "
"expiration_time_ms: ", FormatRelTimeSec(200), " "
"date_ms: ", FormatRelTimeSec(0),
"}"
"order_key: 1"
"}")));
rewrite_driver()->FinishParse();
}
TEST_F(CollectDependenciesFilterTest, Inliners) {
// Currently we don't collect info on inline resources --- the filters
// themsleves are expected to help --- but we should at least behave
// sanely on them.
options()->EnableFilter(RewriteOptions::kInlineCss);
options()->EnableFilter(RewriteOptions::kInlineJavascript);
options()->EnableFilter(RewriteOptions::kRewriteCss);
options()->EnableFilter(RewriteOptions::kRewriteJavascriptInline);
options()->EnableFilter(RewriteOptions::kRewriteJavascriptExternal);
rewrite_driver()->AddFilters();
const char kInput[] = "<link rel=stylesheet href=a.css>"
"<script src=b.js></script>";
const char kOutput[] =
"<style>*{display:block}</style>"
"<script>var b=42</script>";
ValidateExpected("inliners", kInput, kOutput);
// Read stuff back in from pcache.
ResetDriver();
DependencyTracker* tracker = rewrite_driver()->dependency_tracker();
rewrite_driver()->StartParse(kTestDomain);
EXPECT_EQ(tracker->read_in_info(), nullptr);
rewrite_driver()->FinishParse();
}
TEST_F(CollectDependenciesFilterTest, Combiners) {
// Because of implementation details of each, we notice output of CombineCss
// but not of CombineJavascript (as it create a new <script> we don't see).
// This is not thought to be a problem since we'll near certainly be turning
// off Combine for H2 anyway.
options()->EnableFilter(RewriteOptions::kCombineCss);
options()->EnableFilter(RewriteOptions::kCombineJavascript);
rewrite_driver()->AddFilters();
const char kInput[] = "<link rel=stylesheet href=a.css>"
"<link rel=stylesheet href=c.css>"
"<script src=b.js></script>"
"<script src=d.js></script>";
const char kOutput[] =
"<link rel=stylesheet href=a.css+c.css.pagespeed.cc.0.css>"
"<script src=\"b.js+d.js.pagespeed.jc.0.js\"></script>"
"<script>eval(mod_pagespeed_0);</script>"
"<script>eval(mod_pagespeed_0);</script>";
ValidateExpected("combiners", kInput, kOutput);
// Read stuff back in from pcache.
ResetDriver();
DependencyTracker* tracker = rewrite_driver()->dependency_tracker();
rewrite_driver()->StartParse(kTestDomain);
ASSERT_TRUE(tracker->read_in_info() != nullptr);
EXPECT_THAT(*tracker->read_in_info(), EqualsProto(StrCat(
"dependency {"
"url: 'http://test.com/a.css+c.css.pagespeed.cc.0.css'"
"content_type: DEP_CSS "
"validity_info {"
"type: CACHED "
"expiration_time_ms: ", FormatRelTimeSec(100), " " // a.css
"date_ms: ", FormatRelTimeSec(0),
"}"
"validity_info {"
"type: CACHED "
"expiration_time_ms: ", FormatRelTimeSec(150), " " // c.css
"date_ms: ", FormatRelTimeSec(0),
"}"
"validity_info {"
"type: CACHED "
"last_modified_time_ms: ", FormatRelTimeSec(0), " ", // a + c
"expiration_time_ms: ", FormatRelTimeSec(kYearSec), " "
"date_ms: ", FormatRelTimeSec(0),
"}"
"order_key: 0"
"}")));
rewrite_driver()->FinishParse();
}
TEST_F(CollectDependenciesFilterTest, Chain) {
// With lots of filters involved. (Not turning on combine JS here since
// it would make us lose track of the JS. Ditto for inliners).
options()->EnableFilter(RewriteOptions::kRewriteCss);
options()->EnableFilter(RewriteOptions::kRewriteJavascriptExternal);
options()->EnableFilter(RewriteOptions::kCombineCss);
options()->EnableFilter(RewriteOptions::kExtendCacheCss);
options()->EnableFilter(RewriteOptions::kExtendCacheScripts);
rewrite_driver()->AddFilters();
const char kInput[] = "<link rel=stylesheet href=a.css>"
"<link rel=stylesheet href=c.css>"
"<script src=b.js></script>"
"<script src=d.js></script>";
const char kOutput[] =
"<link rel=stylesheet href=A.a.css+c.css,Mcc.0.css.pagespeed.cf.0.css>"
"<script src=b.js.pagespeed.jm.0.js></script>"
"<script src=d.js.pagespeed.jm.0.js></script>";
ValidateExpected("combiners", kInput, kOutput);
// Read stuff back in from pcache.
ResetDriver();
DependencyTracker* tracker = rewrite_driver()->dependency_tracker();
rewrite_driver()->StartParse(kTestDomain);
ASSERT_TRUE(tracker->read_in_info() != nullptr);
EXPECT_THAT(*tracker->read_in_info(), EqualsProto(StrCat(StrCat(
"dependency {"
"url: 'http://test.com/A.a.css+c.css,Mcc.0.css.pagespeed.cf.0.css'"
"content_type: DEP_CSS "
"validity_info {"
"type: CACHED "
"expiration_time_ms: ", FormatRelTimeSec(100), " " // a.css
"date_ms: ", FormatRelTimeSec(0),
"}"
"validity_info {"
"type: CACHED "
"expiration_time_ms: ", FormatRelTimeSec(150), " " // c.css
"date_ms: ", FormatRelTimeSec(0),
"}"
"validity_info {"
"type: CACHED "
"last_modified_time_ms: ", FormatRelTimeSec(0), " ", // a + c
"expiration_time_ms: ", FormatRelTimeSec(kYearSec), " "
"date_ms: ", FormatRelTimeSec(0),
"}"
"validity_info {"
"type: CACHED "
"last_modified_time_ms: ", FormatRelTimeSec(0), " ", // (a + c).cf
"expiration_time_ms: ", FormatRelTimeSec(kYearSec), " "
"date_ms: ", FormatRelTimeSec(0),
"}"
"order_key: 0"
"}"),
"dependency {"
"url: 'http://test.com/b.js.pagespeed.jm.0.js'"
"content_type: DEP_JAVASCRIPT "
"validity_info {"
"type: CACHED "
"expiration_time_ms: ", FormatRelTimeSec(200), " " // b.js
"date_ms: ", FormatRelTimeSec(0),
"}"
"validity_info {"
"type: CACHED "
"last_modified_time_ms: ", FormatRelTimeSec(0), " ", // b.js.jm
"expiration_time_ms: ", FormatRelTimeSec(kYearSec), " "
"date_ms: ", FormatRelTimeSec(0),
"}"
"order_key: 2"
"}"
"dependency {"
"url: 'http://test.com/d.js.pagespeed.jm.0.js'"
"content_type: DEP_JAVASCRIPT "
"validity_info {"
"type: CACHED "
"expiration_time_ms: ", FormatRelTimeSec(250), " " // d.js
"date_ms: ", FormatRelTimeSec(0),
"}"
"validity_info {"
"type: CACHED "
"last_modified_time_ms: ", FormatRelTimeSec(0), " ", // d.js.jm
"expiration_time_ms: ", FormatRelTimeSec(kYearSec), " "
"date_ms: ", FormatRelTimeSec(0),
"}"
"order_key: 3"
"}")));
rewrite_driver()->FinishParse();
}
TEST_F(CollectDependenciesFilterTest, Indirect) {
// Test that we collect indirect dependencies.
SetResponseWithDefaultHeaders("d.css", kContentTypeCss,
"@import \"i1.css\" all;\n"
"@import \"i2.css\" print, screen;\n"
"@import \"i3.css\" print; ", 300);
options()->EnableFilter(RewriteOptions::kRewriteCss);
rewrite_driver()->AddFilters();
const char kInput[] = "<link rel=stylesheet href=d.css>";
const char kOutput[] =
"<link rel=stylesheet href=A.d.css.pagespeed.cf.0.css>";
ValidateExpected("basic_res", kInput, kOutput);
ResetDriver();
DependencyTracker* tracker = rewrite_driver()->dependency_tracker();
rewrite_driver()->StartParse(kTestDomain);
ASSERT_TRUE(tracker->read_in_info() != nullptr);
EXPECT_THAT(*tracker->read_in_info(), EqualsProto(StrCat(StrCat(
"dependency {"
"url: 'http://test.com/A.d.css.pagespeed.cf.0.css'"
"content_type: DEP_CSS "
"validity_info {"
"type: CACHED "
"expiration_time_ms: ", FormatRelTimeSec(300), " " // d.css
"date_ms: ", FormatRelTimeSec(0),
"}"
"validity_info {"
"type: CACHED "
"last_modified_time_ms: ", FormatRelTimeSec(0), " ", // d.cf
"expiration_time_ms: ", FormatRelTimeSec(kYearSec), " "
"date_ms: ", FormatRelTimeSec(0),
"}"
"order_key: 0"
"}"),
"dependency {"
"url: 'http://test.com/i1.css'"
"content_type: DEP_CSS "
"validity_info {"
"type: CACHED "
"expiration_time_ms: ", FormatRelTimeSec(300), " " // d.css
"date_ms: ", FormatRelTimeSec(0),
"}"
"validity_info {"
"type: CACHED "
"last_modified_time_ms: ", FormatRelTimeSec(0), " ", // d.cf
"expiration_time_ms: ", FormatRelTimeSec(kYearSec), " "
"date_ms: ", FormatRelTimeSec(0),
"}"
"order_key: 0 "
"order_key: 1"
"}"
"dependency {"
"url: 'http://test.com/i2.css'"
"content_type: DEP_CSS "
"validity_info {"
"type: CACHED "
"expiration_time_ms: ", FormatRelTimeSec(300), " " // d.css
"date_ms: ", FormatRelTimeSec(0),
"}"
"validity_info {"
"type: CACHED "
"last_modified_time_ms: ", FormatRelTimeSec(0), " ", // d.cf
"expiration_time_ms: ", FormatRelTimeSec(kYearSec), " "
"date_ms: ", FormatRelTimeSec(0),
"}"
"order_key: 0 "
"order_key: 2"
"}")));
rewrite_driver()->FinishParse();
}
} // namespace
} // namespace net_instaweb