blob: 1ba9801c6d834cf62a23b66396cb82226801834b [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: morlovich@google.com (Maksim Orlovich)
//
// Unit tests for JsCombineFilter.
#include "net/instaweb/rewriter/public/js_combine_filter.h"
#include <vector>
#include "net/instaweb/http/public/async_fetch.h"
#include "net/instaweb/rewriter/public/cache_extender.h"
#include "net/instaweb/rewriter/public/domain_lawyer.h"
#include "net/instaweb/rewriter/public/resource.h"
#include "net/instaweb/rewriter/public/resource_namer.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 "pagespeed/kernel/base/basictypes.h"
#include "pagespeed/kernel/base/charset_util.h"
#include "pagespeed/kernel/base/gtest.h"
#include "pagespeed/kernel/base/mock_message_handler.h"
#include "pagespeed/kernel/base/statistics.h"
#include "pagespeed/kernel/base/string.h"
#include "pagespeed/kernel/cache/lru_cache.h"
#include "pagespeed/kernel/html/empty_html_filter.h"
#include "pagespeed/kernel/html/html_element.h"
#include "pagespeed/kernel/html/html_name.h"
#include "pagespeed/kernel/html/html_node.h"
#include "pagespeed/kernel/html/html_parse_test_base.h"
#include "pagespeed/kernel/http/content_type.h"
#include "pagespeed/kernel/http/google_url.h"
#include "pagespeed/kernel/http/response_headers.h"
#include "pagespeed/kernel/http/semantic_type.h"
#include "pagespeed/kernel/thread/queued_worker_pool.h"
#include "pagespeed/kernel/thread/worker_test_base.h"
namespace net_instaweb {
namespace {
const char kJsUrl1[] = "a.js";
const char kJsUrl2[] = "b.js";
const char kJsUrl3[] = "c.js";
const char kJsUrl4[] = "d.js";
const char kStrictUrl1[] = "strict1.js";
const char kStrictUrl2[] = "strict2.js";
const char kPseudoStrictUrl1[] = "pseudo_strict1.js";
const char kPseudoStrictUrl2[] = "pseudo_strict2.js";
const char kIntrospectiveUrl1[] = "introspective1.js";
const char kIntrospectiveUrl2[] = "introspective2.js";
const char kJsText1[] = "// script1\nvar a=\"hello\\nsecond line\"";
const char kMinifiedJs1[] = "var a=\"hello\\nsecond line\"";
const char kJsText2[] = "// script2\r\nvar b=42;\n";
const char kMinifiedJs2[] = "var b=42;";
const char kJsText3[] = "var x = 42;\nvar y = 31459;\n";
const char kJsText4[] = "var m = 'abcd';\n";
const char kStrictText1[] = "'use strict'; var x = 32;";
const char kStrictText2[] = "\"use strict\"; var x = 42;";
const char kPseudoStrictText1[] = "function(){'use strict'; var x = 32;}";
const char kPseudoStrictText2[] = "function(){\"use strict\"; var x = 42;}";
const char kIntrospectiveText1[] = "var x = 7; $('script') ; var y = 42;";
const char kIntrospectiveText2[] = "document.getElementsByTagName('script');";
const char kEscapedJs1[] =
"\"// script1\\nvar a=\\\"hello\\\\nsecond line\\\"\"";
const char kEscapedJs2[] = "\"// script2\\r\\nvar b=42;\\n\"";
const char kMinifiedEscapedJs1[] = "\"var a=\\\"hello\\\\nsecond line\\\"\"";
const char kMinifiedEscapedJs2[] = "\"var b=42;\"";
const char kAlternateDomain[] = "http://alternate.com/";
} // namespace
// Test fixture for JsCombineFilter unit tests.
class JsCombineFilterTest : public RewriteTestBase {
public:
struct ScriptInfo {
HtmlElement* element;
GoogleString url; // if empty, the <script> didn't have a src
GoogleString text_content;
};
typedef std::vector<ScriptInfo> ScriptInfoVector;
// Helper class that collects all the script elements in html and
// their sources and bodies.
//
// It also verifies that is will be no nesting of things inside scripts.
class ScriptCollector : public EmptyHtmlFilter {
public:
explicit ScriptCollector(ScriptInfoVector* output)
: output_(output), active_script_(NULL) {
}
virtual void StartElement(HtmlElement* element) {
EXPECT_EQ(NULL, active_script_);
if (element->keyword() == HtmlName::kScript) {
active_script_ = element;
script_content_.clear();
}
}
virtual void Characters(HtmlCharactersNode* characters) {
script_content_ += characters->contents();
}
virtual void EndElement(HtmlElement* element) {
if (element->keyword() == HtmlName::kScript) {
ScriptInfo info;
info.element = element;
const char* url_cstr = element->AttributeValue(HtmlName::kSrc);
if (url_cstr != NULL) {
info.url = GoogleString(url_cstr);
}
info.text_content = script_content_;
output_->push_back(info);
active_script_ = NULL;
}
}
virtual const char* Name() const { return "ScriptCollector"; }
private:
ScriptInfoVector* output_;
GoogleString script_content_; // contents of any script tag, if any.
HtmlElement* active_script_; // any script we're in.
DISALLOW_COPY_AND_ASSIGN(ScriptCollector);
};
virtual void SetUp() {
RewriteTestBase::SetUp();
UseMd5Hasher();
SetDefaultLongCacheHeaders(&kContentTypeJavascript, &default_js_header_);
SimulateJsResource(kJsUrl1, kJsText1);
SimulateJsResource(kJsUrl2, kJsText2);
SimulateJsResourceOnDomain(kAlternateDomain, kJsUrl2, kJsText2);
SimulateJsResource(kJsUrl3, kJsText3);
SimulateJsResource(kJsUrl4, kJsText4);
SimulateJsResource(kStrictUrl1, kStrictText1);
SimulateJsResource(kStrictUrl2, kStrictText2);
SimulateJsResource(kPseudoStrictUrl1, kPseudoStrictText1);
SimulateJsResource(kPseudoStrictUrl2, kPseudoStrictText2);
SimulateJsResource(kIntrospectiveUrl1, kIntrospectiveText1);
SimulateJsResource(kIntrospectiveUrl2, kIntrospectiveText2);
options()->SoftEnableFilterForTesting(RewriteOptions::kCombineJavascript);
SetUpExtraFilters();
rewrite_driver()->AddFilters();
// Some tests need an another domain, with (different)source files on it as
// well.
GoogleString test_domain(kTestDomain);
if (EndsInSlash(test_domain)) {
test_domain.resize(test_domain.length() - 1);
}
other_domain_ = StrCat(test_domain, ".us/");
SimulateJsResourceOnDomain(other_domain_, kJsUrl1, "othera");
SimulateJsResourceOnDomain(other_domain_, kJsUrl2, "otherb");
}
virtual void SetUpExtraFilters() {
}
void SimulateJsResource(const StringPiece& url, const StringPiece& text) {
SimulateJsResourceOnDomain(kTestDomain, url, text);
}
void SimulateJsResourceOnDomain(const StringPiece& domain,
const StringPiece& url,
const StringPiece& text) {
SetFetchResponse(StrCat(domain, url), default_js_header_, text);
}
void PrepareToCollectScriptsInto(ScriptInfoVector* output) {
rewrite_driver()->AddOwnedPostRenderFilter(new ScriptCollector(output));
}
// Makes sure that the script looks like a combination.
void VerifyCombinedOnDomain(const StringPiece& base_url,
const StringPiece& domain,
const ScriptInfo& info,
const StringVector& name_vector) {
EXPECT_FALSE(info.url.empty());
// We need to check against the encoded form of the given domain.
GoogleUrl encoded(EncodeWithBase(base_url, domain, "x", "0", "x", "x"));
// The combination url should incorporate both names...
GoogleUrl base_gurl(base_url);
GoogleUrl combination_url(base_gurl, info.url);
ASSERT_TRUE(encoded.IsAnyValid()) << encoded.UncheckedSpec();
ASSERT_TRUE(combination_url.IsAnyValid()) << info.url;
EXPECT_STREQ(encoded.AllExceptLeaf(), combination_url.AllExceptLeaf());
ResourceNamer namer;
EXPECT_TRUE(
rewrite_driver()->Decode(combination_url.LeafWithQuery(), &namer));
EXPECT_STREQ(RewriteOptions::kJavascriptCombinerId, namer.id());
GoogleString encoding;
for (int i = 0, n = name_vector.size(); i < n; ++i) {
StrAppend(&encoding, (i == 0) ? "" : "+", name_vector[i]);
}
EXPECT_STREQ(encoding, namer.name());
EXPECT_STREQ("js", namer.ext());
}
void VerifyCombined(const ScriptInfo& info, const StringVector& name) {
VerifyCombinedOnDomain(kTestDomain, kTestDomain, info, name);
}
// Make sure the script looks like it was rewritten for a use of given URL
void VerifyUseOnDomain(const StringPiece& domain, const ScriptInfo& info,
const StringPiece& rel_url) {
GoogleString abs_url = StrCat(domain, rel_url);
EXPECT_TRUE(info.url.empty());
EXPECT_EQ(
StrCat("eval(",
JsCombineFilter::VarName(rewrite_driver(), abs_url),
");"),
info.text_content);
}
void VerifyUse(const ScriptInfo& info, const StringPiece& rel_url) {
VerifyUseOnDomain(kTestDomain, info, rel_url);
}
GoogleString TestHtml() {
return StrCat("<script src=", kJsUrl1, "></script>",
"<script src=", kJsUrl2, "></script>");
}
// Test basic combining of multiple JS files. The resultant names and
// hashes may differ depending on whether we are working with rewritten
// or sharded domains, and whether we minify the js files before combining
// them. Thus we must pass in the hashes for the various components.
//
// Note that we must use the MD5 hasher for this test because the
// combiner generates local javascript variable names using the
// content-hasher.
void TestCombineJs(const StringVector& combined_name,
const StringPiece& combined_hash,
const StringPiece& hash1,
const StringPiece& hash2,
bool minified,
const StringPiece& domain) {
ScriptInfoVector scripts;
PrepareToCollectScriptsInto(&scripts);
GoogleUrl html_url(kTestDomain);
ParseUrl(html_url.Spec(), TestHtml());
// This should produce 3 script elements, with the first one referring to
// the combination, and the second and third using eval.
ASSERT_EQ(3, scripts.size());
VerifyCombinedOnDomain(domain, domain, scripts[0], combined_name);
VerifyUse(scripts[1], kJsUrl1);
VerifyUse(scripts[2], kJsUrl2);
// Now check the actual contents. These might change slightly
// during implementation changes, requiring update of the test;
// but this is also not dependent on VarName working right.
EXPECT_STREQ(AddHtmlBody(
StrCat("<script src=\"", scripts[0].url, "\"></script>"
"<script>eval(mod_pagespeed_", hash1, ");</script>"
"<script>eval(mod_pagespeed_", hash2, ");</script>")),
output_buffer_);
// Check that the combined URL is what we'd expect.
GoogleString combined_path =
Encode("", "jc", combined_hash, combined_name, "js");
GoogleUrl encoded_domain(Encode(domain, "x", "0", "x", "x"));
// We can be be given URLs with ',M' in them, which are URL escaped to have
// two commas, which is not what we want, so reverse that. We can be given
// such URLs because it's too hard to do the encoding programatically.
GlobalReplaceSubstring(",,M", ",M", &combined_path);
GoogleUrl output_url(html_url, scripts[0].url);
EXPECT_EQ(StrCat(encoded_domain.AllExceptLeaf(), combined_path),
output_url.Spec());
// Now fetch the combined URL.
GoogleString combination_src;
ASSERT_TRUE(FetchResourceUrl(output_url.Spec(), &combination_src));
EXPECT_STREQ(StrCat(
StrCat("var mod_pagespeed_", hash1, " = ",
(minified ? kMinifiedEscapedJs1 : kEscapedJs1), ";\n"),
StrCat("var mod_pagespeed_", hash2, " = ",
(minified ? kMinifiedEscapedJs2 : kEscapedJs2), ";\n")),
combination_src);
ServeResourceFromManyContexts(output_url.Spec().as_string(),
combination_src);
}
protected:
ResponseHeaders default_js_header_;
GoogleString other_domain_;
};
class JsFilterAndCombineFilterTest : public JsCombineFilterTest {
virtual void SetUpExtraFilters() {
options()->SoftEnableFilterForTesting(
RewriteOptions::kRewriteJavascriptExternal);
}
};
// Test for basic operation, including escaping and fetch reconstruction.
TEST_F(JsCombineFilterTest, CombineJs) {
TestCombineJs(MultiUrl(kJsUrl1, kJsUrl2), "g2Xe9o4bQ2", "KecOGCIjKt",
"dzsx6RqvJJ", false, kTestDomain);
}
class JsCombineFilterCustomOptions : public JsCombineFilterTest {
protected:
// Derived classes should set their options and then call
// JsCombineFilterTest::SetUp().
virtual void SetUp() {}
};
TEST_F(JsCombineFilterCustomOptions, CombineJsPreserveURLsOn) {
options()->set_js_preserve_urls(true);
JsCombineFilterTest::SetUp();
ValidateNoChanges("combine_js_preserve_urls_on",
StrCat("<script src=", kJsUrl1, "></script>",
"<script src=", kJsUrl2, "></script>"));
}
// When cache is unhealthy, don't rewrite URLs in HTML.
TEST_F(JsCombineFilterTest, CombineJsUnhealthy) {
lru_cache()->set_is_healthy(false);
ValidateNoChanges("unhealthy", TestHtml());
}
// But do serve correctly rewritten resources when
// .pagespeed. resources are requested even if cache is unhealthy.
TEST_F(JsCombineFilterTest, ServeFilesUnhealthy) {
lru_cache()->set_is_healthy(false);
SetResponseWithDefaultHeaders(kJsUrl1, kContentTypeJavascript, "var a;", 100);
SetResponseWithDefaultHeaders(kJsUrl2, kContentTypeJavascript, "var b;", 100);
GoogleString content;
const GoogleString combined_url = Encode(
kTestDomain, "jc", "0", MultiUrl(kJsUrl1, kJsUrl2), "js");
ASSERT_TRUE(FetchResourceUrl(combined_url, &content));
const char kCombinedContent[] =
"var mod_pagespeed_KecOGCIjKt = \"var a;\";\n"
"var mod_pagespeed_dzsx6RqvJJ = \"var b;\";\n";
EXPECT_EQ(kCombinedContent, content);
}
class JsCombineAndCacheExtendFilterTest : public JsCombineFilterTest {
virtual void SetUpExtraFilters() {
options()->SoftEnableFilterForTesting(RewriteOptions::kExtendCacheScripts);
}
};
TEST_F(JsCombineAndCacheExtendFilterTest, CombineJsNoExtraCacheExtension) {
// Make sure we don't end up trying to cache extend things
// the combiner removed. We need to custom-set resources here to give them
// shorter TTL than the fixture would.
SetResponseWithDefaultHeaders(kJsUrl1, kContentTypeJavascript, kJsText1, 100);
SetResponseWithDefaultHeaders(kJsUrl2, kContentTypeJavascript, kJsText2, 100);
TestCombineJs(MultiUrl(kJsUrl1, kJsUrl2), "g2Xe9o4bQ2", "KecOGCIjKt",
"dzsx6RqvJJ", false, kTestDomain);
EXPECT_EQ(0,
rewrite_driver()->statistics()->GetVariable(
CacheExtender::kCacheExtensions)->Get());
}
// Turning on AvoidRewritingIntrospectiveJavascript should not affect normal
// rewriting.
TEST_F(JsCombineFilterTest, CombineJsAvoidRewritingIntrospectiveJavascripOn) {
options()->ClearSignatureForTesting();
options()->set_avoid_renaming_introspective_javascript(true);
server_context()->ComputeSignature(options());
TestCombineJs(MultiUrl(kJsUrl1, kJsUrl2), "g2Xe9o4bQ2", "KecOGCIjKt",
"dzsx6RqvJJ", false, kTestDomain);
}
TEST_F(JsFilterAndCombineFilterTest, ReconstructNoTimeout) {
// Nested fetch should not timeout on reconstruction. Note that we still
// need this to work even though we no longer create nesting; for migration
// reasons.
GoogleString rel_url =
Encode("", "jc", "FA3Pqioukh",
MultiUrl("a.js.pagespeed.jm.FUEwDOA7jh.js",
"b.js.pagespeed.jm.Y1kknPfzVs.js"), "js");
GoogleString url = StrCat(kTestDomain, rel_url);
const char kLegacyVar1[] = "mod_pagespeed_S$0tgbTH0O";
const char kLegacyVar2[] = "mod_pagespeed_ose8Vzgyj9";
// First rewrite the page, to see what the evals look like.
// These should actually just look like a.js + b.js these days.
GoogleString simple_rel_url =
Encode("", "jc", "HrCUtQsDp_", MultiUrl("a.js", "b.js"), "js");
const char kVar1[] = "mod_pagespeed_KecOGCIjKt";
const char kVar2[] = "mod_pagespeed_dzsx6RqvJJ";
ValidateExpected("no_timeout",
StrCat("<script src=", kJsUrl1, "></script>",
"<script src=", kJsUrl2, "></script>"),
StrCat("<script src=\"", simple_rel_url, "\"></script>",
"<script>eval(", kVar1, ");</script>",
"<script>eval(", kVar2, ");</script>"));
// Clear cache..
lru_cache()->Clear();
server_context()->global_options()->ClearSignatureForTesting();
server_context()->global_options()
->set_test_instant_fetch_rewrite_deadline(true);
server_context()->ComputeSignature(server_context()->global_options());
StringAsyncFetch async_fetch(rewrite_driver_->request_context());
// Note that here we specifically use a pool'ed rewrite driver, since
// the bug we were testing for only occurred with them.
RewriteDriver* driver =
server_context()->NewRewriteDriver(CreateRequestContext());
WorkerTestBase::SyncPoint unblock_rewrite(server_context()->thread_system());
// Wedge the actual rewrite queue to force the timeout to trigger.
driver->low_priority_rewrite_worker()->Add(
new WorkerTestBase::WaitRunFunction(&unblock_rewrite));
driver->FetchResource(url, &async_fetch);
unblock_rewrite.Notify();
AdvanceTimeMs(50);
driver->WaitForShutDown();
driver->Cleanup();
// Make sure we have the right hashes. Note that we fetched an old style
// URL, that had both .js and .jm in it, so the variable names are the old
// ones, not new ones.
EXPECT_NE(GoogleString::npos,
async_fetch.buffer().find(kLegacyVar1));
EXPECT_NE(GoogleString::npos,
async_fetch.buffer().find(kLegacyVar2));
}
TEST_F(JsFilterAndCombineFilterTest, MinifyCombineJs) {
TestCombineJs(MultiUrl("a.js", "b.js"),
"HrCUtQsDp_", // combined hash
"KecOGCIjKt", // var name for a.js (same as in CombineJs)
"dzsx6RqvJJ", // var name for b.js (same as in CombineJs)
true, kTestDomain);
}
// Even with inline_unauthorized_resources set to true, we should not combine
// unauthorized and authorized resources. Also, we should not allow fetching
// of component minified unauthorized resources even if they were created.
TEST_F(JsFilterAndCombineFilterTest, TestCrossDomainRejectUnauthEnabled) {
options()->ClearSignatureForTesting();
options()->SoftEnableFilterForTesting(RewriteOptions::kDebug);
options()->AddInlineUnauthorizedResourceType(semantic_type::kScript);
server_context()->ComputeSignature(options());
GoogleUrl gurl(StrCat(other_domain_, kJsUrl1));
GoogleString debug_message = StrCat(
"<!--", RewriteDriver::GenerateUnauthorizedDomainDebugComment(gurl),
"-->");
// Note that we get the debug message twice, once for the combining attempt
// and once for the rewriting attempt, both of which fail due to the domain
// being unauthorized. This is unfortunate but not worth fixing right now.
ValidateExpected("xd",
StrCat("<script src=", gurl.Spec(), "></script>",
"<script src=", kJsUrl2, "></script>"),
StrCat("<script src=", gurl.Spec(), "></script>",
debug_message, debug_message,
"<script src=",
Encode("", "jm", "Y1kknPfzVs", kJsUrl2, "js"),
">",
"</script>"));
GoogleString contents;
ASSERT_FALSE(FetchResourceUrl(StrCat(other_domain_, kJsUrl1), &contents));
}
// Issue 308: ModPagespeedShardDomain disables combine_js. Actually
// the code (in url_partnership.cc) was already doing the right thing,
// but was not previously confirmed in a unit-test.
TEST_F(JsFilterAndCombineFilterTest, MinifyShardCombineJs) {
ASSERT_TRUE(AddShard(kTestDomain, "a.com,b.com"));
// Make sure the shards have the resources, too.
SimulateJsResourceOnDomain("http://a.com/", kJsUrl1, kJsText1);
SimulateJsResourceOnDomain("http://a.com/", kJsUrl2, kJsText2);
SimulateJsResourceOnDomain("http://b.com/", kJsUrl1, kJsText1);
SimulateJsResourceOnDomain("http://b.com/", kJsUrl2, kJsText2);
TestCombineJs(MultiUrl("a.js", "b.js"),
"HrCUtQsDp_", "KecOGCIjKt", "dzsx6RqvJJ", true,
"http://b.com/");
}
TEST_F(JsFilterAndCombineFilterTest, MinifyCombineAcrossHosts) {
ScriptInfoVector scripts;
PrepareToCollectScriptsInto(&scripts);
GoogleString js_url_2(StrCat(kAlternateDomain, kJsUrl2));
AddDomain(kAlternateDomain);
ParseUrl(kTestDomain, StrCat("<script src=", kJsUrl1, "></script>",
"<script src=", js_url_2, "></script>"));
ASSERT_EQ(2, scripts.size());
GoogleUrl base_url(kTestDomain);
GoogleUrl url0(base_url, scripts[0].url);
ServeResourceFromManyContexts(url0.spec_c_str(), kMinifiedJs1);
GoogleUrl url1(base_url, scripts[1].url);
ServeResourceFromManyContexts(url1.spec_c_str(), kMinifiedJs2);
}
class JsFilterAndCombineProxyTest : public JsFilterAndCombineFilterTest {
public:
JsFilterAndCombineProxyTest() {
SetUseTestUrlNamer(true);
}
};
TEST_F(JsFilterAndCombineProxyTest, MinifyCombineSameHostProxy) {
// TODO(jmarantz): This more intrusive test-helper fails. I'd like
// to look at it with Matt in the context of the new TestUrlNamer
// infrastructure. However that should not block the point of this
// test which is that the combination should be made if the
// hosts do match, unlike MinifyCombineAcrossHostsProxy below.
//
// Specifically, VerifyCombinedOnDomain appears not to know about
// TestUrlNamer.
//
// bool test_url_namer = factory()->use_test_url_namer();
// DCHECK(test_url_namer);
// TestCombineJs("a.js,Mjm.FUEwDOA7jh.js+b.js,Mjm.Y1kknPfzVs.js",
// test_url_namer ? "8erozavBF5" : "FA3Pqioukh",
// test_url_namer ? "JO0ZTfFSfI" : "S$0tgbTH0O",
// test_url_namer ? "8QmSuIkgv_" : "ose8Vzgyj9",
// true, kTestDomain);
ScriptInfoVector scripts;
PrepareToCollectScriptsInto(&scripts);
ParseUrl(kTestDomain, StrCat("<script src=", kJsUrl1, "></script>",
"<script src=", kJsUrl2, "></script>"));
ASSERT_EQ(3, scripts.size()) << "successful combination yields 3 scripts";
}
TEST_F(JsFilterAndCombineProxyTest, MinifyCombineAcrossHostsProxy) {
ScriptInfoVector scripts;
PrepareToCollectScriptsInto(&scripts);
GoogleString js_url_2(StrCat(kAlternateDomain, kJsUrl2));
AddDomain(kAlternateDomain);
ParseUrl(kTestDomain, StrCat("<script src=", kJsUrl1, "></script>",
"<script src=", js_url_2, "></script>"));
ASSERT_EQ(2, scripts.size()) << "If combination fails, we get 2 scripts";
// Note: This absolutifies path because it uses TestUrlNamer which moves
// it to a different domain.
EXPECT_EQ(Encode(kTestDomain, "jm", "FUEwDOA7jh", kJsUrl1, "js"),
scripts[0].url);
ServeResourceFromManyContexts(scripts[0].url, kMinifiedJs1);
EXPECT_EQ(Encode(kAlternateDomain, "jm", "Y1kknPfzVs", kJsUrl2, "js"),
scripts[1].url);
ServeResourceFromManyContexts(scripts[1].url, kMinifiedJs2);
}
TEST_F(JsCombineFilterTest, NotReallyStrict) {
// https://code.google.com/p/modpagespeed/issues/detail?id=909
GoogleString simple_rel_url =
Encode("", "jc", "DuUKa0RTAg",
MultiUrl(kPseudoStrictUrl1, kPseudoStrictUrl2), "js");
const char kVar1[] = "mod_pagespeed_s8uoLecjQN";
const char kVar2[] = "mod_pagespeed_oHli2SAs3Q";
ValidateExpected("not_strict",
StrCat("<script src=", kPseudoStrictUrl1, "></script>",
"<script src=", kPseudoStrictUrl2, "></script>"),
StrCat("<script src=\"", simple_rel_url, "\"></script>",
"<script>eval(", kVar1, ");</script>",
"<script>eval(", kVar2, ");</script>"));
}
// Various things that prevent combining
TEST_F(JsCombineFilterTest, TestBarriers) {
ValidateNoChanges("noscript",
StrCat("<noscript><script src=", kJsUrl1, "></script>",
"</noscript><script src=", kJsUrl2, "></script>"));
// inline scripts or scripts with random stuff inside
ValidateNoChanges("non-inline",
StrCat("<script src=", kJsUrl1, "></script>",
"<script>code</script>"));
ValidateNoChanges("content",
StrCat("<script src=", kJsUrl1, "></script>",
"<script src=", kJsUrl2, ">code</script>"));
// Languages
ValidateNoChanges("tcl",
StrCat("<script language=tcl src=", kJsUrl1, "></script>"
"<script src=", kJsUrl2, "></script>"));
ValidateNoChanges("tcl2",
StrCat("<script language=tcl src=", kJsUrl1, "></script>"
"<script language=tcl src=", kJsUrl2, "></script>"));
ValidateNoChanges("tcl3",
StrCat("<script src=", kJsUrl1, "></script>"
"<script language=tcl src=", kJsUrl2, "></script>"));
// Execution model
ValidateNoChanges("exec",
StrCat("<script src=", kJsUrl1, "></script>"
"<script defer src=", kJsUrl2, "></script>"));
// IE conditional comments
ValidateNoChanges("iec",
StrCat("<script src=", kJsUrl1, "></script>",
"<!--[if IE]><![endif]-->",
"<script src=", kJsUrl2, "></script>"));
// Strict mode, with 2 different quote styles
ValidateNoChanges("strict1",
StrCat("<script src=", kJsUrl1, "></script>",
"<script src=", kStrictUrl1, "></script>"));
ValidateNoChanges("strict2",
StrCat("<script src=", kJsUrl1, "></script>",
"<script src=", kStrictUrl2, "></script>"));
ValidateNoChanges("strict3",
StrCat("<script src=", kStrictUrl1, "></script>",
"<script src=", kJsUrl1, "></script>"));
ValidateNoChanges("strict4",
StrCat("<script src=", kStrictUrl2, "></script>",
"<script src=", kJsUrl1, "></script>"));
// UnsafeToRename, with plain and jquery syntax
options()->ClearSignatureForTesting();
options()->set_avoid_renaming_introspective_javascript(true);
server_context()->ComputeSignature(options());
ValidateNoChanges("introspective1",
StrCat("<script src=", kJsUrl1, "></script>",
"<script src=", kIntrospectiveUrl1, "></script>"));
ValidateNoChanges("introspective2",
StrCat("<script src=", kJsUrl1, "></script>",
"<script src=", kIntrospectiveUrl2, "></script>"));
}
// Make sure that rolling back a <script> that has both a source and inline data
// out of the combination works even when we have more than one filter involved.
// This used to crash under async flow.
TEST_F(JsFilterAndCombineFilterTest, TestScriptInlineTextRollback) {
ValidateExpected("rollback1",
StrCat("<script src=", kJsUrl1, "></script>",
"<script src=", kJsUrl2, ">TEXT HERE</script>"),
StrCat("<script src=",
Encode("", "jm", "FUEwDOA7jh", kJsUrl1, "js"),
">",
"</script>",
"<script src=",
Encode("", "jm", "Y1kknPfzVs", kJsUrl2, "js"),
">",
"TEXT HERE</script>"));
}
// Things between scripts that should not prevent combination
TEST_F(JsCombineFilterTest, TestNonBarriers) {
StringVector combined_url = MultiUrl(kJsUrl1, kJsUrl2);
// Intervening text
ScriptInfoVector scripts;
PrepareToCollectScriptsInto(&scripts);
ParseUrl(kTestDomain, StrCat("<script src=", kJsUrl1, "></script>",
"some text",
"<script src=", kJsUrl2, "></script>"));
// This should produce 3 script elements, with the first one referring to
// the combination, and the second and third using eval.
ASSERT_EQ(3, scripts.size());
VerifyCombined(scripts[0], combined_url);
VerifyUse(scripts[1], kJsUrl1);
VerifyUse(scripts[2], kJsUrl2);
// Same thing with other tags, even nested.
scripts.clear();
ParseUrl(kTestDomain, StrCat("<s><script src=", kJsUrl1, "></script></s>",
"<div>block</div><!-- comment -->",
"<b><script src=", kJsUrl2, "></script></b>"));
ASSERT_EQ(3, scripts.size());
VerifyCombined(scripts[0], combined_url);
VerifyUse(scripts[1], kJsUrl1);
VerifyUse(scripts[2], kJsUrl2);
// Whitespace inside scripts is OK.
scripts.clear();
ParseUrl(kTestDomain, StrCat("<script src=", kJsUrl1, "> </script>",
"<div>block</div>",
"<b><script src=", kJsUrl2, ">\t</script></b>"));
ASSERT_EQ(3, scripts.size());
VerifyCombined(scripts[0], combined_url);
VerifyUse(scripts[1], kJsUrl1);
VerifyUse(scripts[2], kJsUrl2);
}
// Flush in the middle of first one will not prevent us from combining it.
TEST_F(JsCombineFilterTest, TestFlushMiddle1) {
ScriptInfoVector scripts;
PrepareToCollectScriptsInto(&scripts);
SetupWriter();
html_parse()->StartParse(kTestDomain);
html_parse()->ParseText(StrCat("<script src=", kJsUrl1, ">"));
html_parse()->Flush();
html_parse()->ParseText("</script>");
html_parse()->ParseText(StrCat("<script src=", kJsUrl2, "></script>"));
html_parse()->ParseText(StrCat("<script src=", kJsUrl3, "></script>"));
html_parse()->FinishParse();
ASSERT_EQ(4, scripts.size());
VerifyCombined(scripts[0], MultiUrl(kJsUrl1, kJsUrl2, kJsUrl3));
VerifyUse(scripts[1], kJsUrl1);
VerifyUse(scripts[2], kJsUrl2);
VerifyUse(scripts[3], kJsUrl3);
}
// Flush in the middle of a second tag - the flush will just spit out the
// first script tag, and we'll hold back the second one till after we
// see the "</script>", which will then be combined with the third.
TEST_F(JsCombineFilterTest, TestFlushMiddle2) {
ScriptInfoVector scripts;
PrepareToCollectScriptsInto(&scripts);
SetupWriter();
html_parse()->StartParse(kTestDomain);
html_parse()->ParseText(StrCat("<script src=", kJsUrl1, "></script>"));
html_parse()->ParseText(StrCat("<script src=", kJsUrl2, ">"));
html_parse()->Flush();
html_parse()->ParseText("</script>");
html_parse()->ParseText(StrCat("<script src=", kJsUrl3, "></script>"));
html_parse()->FinishParse();
ASSERT_EQ(4, scripts.size());
EXPECT_STREQ(kJsUrl1, scripts[0].url);
VerifyCombined(scripts[1], MultiUrl(kJsUrl2, kJsUrl3));
VerifyUse(scripts[2], kJsUrl2);
VerifyUse(scripts[3], kJsUrl3);
}
// Flush in the middle of a third tag -- first two should be combined.
TEST_F(JsCombineFilterTest, TestFlushMiddle3) {
ScriptInfoVector scripts;
PrepareToCollectScriptsInto(&scripts);
SetupWriter();
html_parse()->StartParse(kTestDomain);
html_parse()->ParseText(StrCat("<script src=", kJsUrl1, "></script>"));
html_parse()->ParseText(StrCat("<script src=", kJsUrl2, "></script>"));
html_parse()->Flush();
html_parse()->ParseText(StrCat("<script src=", kJsUrl3, "></script>"));
html_parse()->FinishParse();
ASSERT_EQ(4, scripts.size());
VerifyCombined(scripts[0], MultiUrl(kJsUrl1, kJsUrl2));
VerifyUse(scripts[1], kJsUrl1);
VerifyUse(scripts[2], kJsUrl2);
EXPECT_EQ(kJsUrl3, scripts[3].url);
}
// Make sure we honor <base> properly.
// Note: this test relies on <base> tag implicitly authorizing things,
// which we may wish to change in the future.
TEST_F(JsCombineFilterTest, TestBase) {
ScriptInfoVector scripts;
PrepareToCollectScriptsInto(&scripts);
ParseUrl(kTestDomain, StrCat("<base href=", other_domain_, ">",
"<script src=", kJsUrl1, "></script>"
"<script src=", kJsUrl2, "></script>"));
ASSERT_EQ(3, scripts.size());
VerifyCombinedOnDomain(other_domain_, other_domain_, scripts[0],
MultiUrl(kJsUrl1, kJsUrl2));
VerifyUseOnDomain(other_domain_, scripts[1], kJsUrl1);
VerifyUseOnDomain(other_domain_, scripts[2], kJsUrl2);
}
// Make sure we check for cross-domain rejections.
TEST_F(JsCombineFilterTest, TestCrossDomainReject) {
ValidateNoChanges("xd",
StrCat("<script src=", other_domain_, kJsUrl1, "></script>",
"<script src=", kJsUrl2, "></script>"));
ValidateNoChanges(
"xd.2", StrCat("<script src=", other_domain_, kJsUrl1, "></script>",
"<script src=", other_domain_, kJsUrl2, "></script>"));
ValidateNoChanges(
"xd.3", StrCat("<script src=", kJsUrl1, "></script>",
"<script src=", other_domain_, kJsUrl2, "></script>"));
}
// Make sure we check for cross-domain rejections even when
// inline_unauthorized_resources is set to true.
TEST_F(JsCombineFilterTest, TestCrossDomainRejectUnauthEnabled) {
options()->ClearSignatureForTesting();
options()->AddInlineUnauthorizedResourceType(semantic_type::kScript);
server_context()->ComputeSignature(options());
ValidateNoChanges("xd",
StrCat("<script src=", other_domain_, kJsUrl1, "></script>",
"<script src=", kJsUrl2, "></script>"));
ValidateNoChanges(
"xd.2", StrCat("<script src=", other_domain_, kJsUrl1, "></script>",
"<script src=", other_domain_, kJsUrl2, "></script>"));
ValidateNoChanges(
"xd.3", StrCat("<script src=", kJsUrl1, "></script>",
"<script src=", other_domain_, kJsUrl2, "></script>"));
}
// Validate that we can recover a combination after a cross-domain rejection
TEST_F(JsCombineFilterTest, TestCrossDomainRecover) {
ASSERT_TRUE(AddDomain(other_domain_));
ScriptInfoVector scripts;
PrepareToCollectScriptsInto(&scripts);
SetupWriter();
html_parse()->StartParse(kTestDomain);
// 2 scripts on main domain --- should be combined with each other
html_parse()->ParseText(StrCat("<script src=", kJsUrl1, "></script>"));
html_parse()->ParseText(StrCat("<script src=", kJsUrl2, "></script>"));
// 2 scripts on other domain --- should be combined with each other
html_parse()->ParseText(
StrCat("<script src=", other_domain_, kJsUrl1, "></script>"));
html_parse()->ParseText(
StrCat("<script src=", other_domain_, kJsUrl2, "></script>"));
html_parse()->FinishParse();
ASSERT_EQ(6, scripts.size());
VerifyCombined(scripts[0], MultiUrl(kJsUrl1, kJsUrl2));
VerifyUse(scripts[1], kJsUrl1);
VerifyUse(scripts[2], kJsUrl2);
VerifyCombinedOnDomain(kTestDomain, other_domain_, scripts[3],
MultiUrl(kJsUrl1, kJsUrl2));
VerifyUseOnDomain(other_domain_, scripts[4], kJsUrl1);
VerifyUseOnDomain(other_domain_, scripts[5], kJsUrl2);
}
TEST_F(JsCombineFilterTest, TestCombineStats) {
Variable* num_reduced =
statistics()->GetVariable(JsCombineFilter::kJsFileCountReduction);
EXPECT_EQ(0, num_reduced->Get());
// Now combine 3 files into one.
ParseUrl(kTestDomain, StrCat("<script src=", kJsUrl1, "></script>",
"<script src=", kJsUrl2, "></script>",
"<script src=", kJsUrl3, "></script>"));
EXPECT_STREQ("jc", AppliedRewriterStringFromLog());
EXPECT_EQ(2, num_reduced->Get());
}
TEST_F(JsCombineFilterTest, TestCombineShard) {
// Make sure we produce consistent output when sharding/serving off a
// different host.
GoogleString path =
Encode("", "jc", "0", MultiUrl(kJsUrl1, kJsUrl2), "js");
GoogleString src1;
EXPECT_TRUE(FetchResourceUrl(StrCat(kTestDomain, path), &src1));
const char kOtherDomain[] = "http://cdn.example.com/";
SimulateJsResourceOnDomain(kOtherDomain, kJsUrl1, kJsText1);
SimulateJsResourceOnDomain(kOtherDomain, kJsUrl2, kJsText2);
GoogleString src2;
EXPECT_TRUE(FetchResourceUrl(StrCat(kOtherDomain, path), &src2));
EXPECT_EQ(src1, src2);
}
TEST_F(JsCombineFilterTest, PartlyInvalidFetchCache) {
// Regression test where a combination involving a 404 gets fetched,
// and then rewritten --- incorrectly.
// Note: arguably this shouldn't get cached at all; but it certainly
// should not result in an inappropriate result.
SetFetchResponse404("404.js");
SetResponseWithDefaultHeaders(kJsUrl1, kContentTypeJavascript, "var a;", 100);
SetResponseWithDefaultHeaders(kJsUrl2, kContentTypeJavascript, "var b;", 100);
EXPECT_FALSE(
TryFetchResource(
Encode(kTestDomain, "jc", "0", MultiUrl(kJsUrl1, kJsUrl2, "404.js"),
"js")));
ValidateNoChanges("partly_invalid",
StrCat("<script src=a.js></script>",
"<script src=b.js></script>"
"<script src=404.js></script>"));
}
TEST_F(JsCombineFilterTest, CharsetDetermination) {
GoogleString x_js_url = "x.js";
GoogleString y_js_url = "y.js";
GoogleString z_js_url = "z.js";
const char x_js_body[] = "var x;";
const char y_js_body[] = "var y;";
const char z_js_body[] = "var z;";
GoogleString bom_body = StrCat(kUtf8Bom, y_js_body);
// x.js has no charset header nor a BOM.
// y.js has no charset header but has a BOM.
// z.js has a charset header but no BOM.
ResponseHeaders default_header;
SetDefaultLongCacheHeaders(&kContentTypeJavascript, &default_header);
SetFetchResponse(StrCat(kTestDomain, x_js_url), default_header, x_js_body);
SetFetchResponse(StrCat(kTestDomain, y_js_url), default_header, bom_body);
default_header.MergeContentType("text/javascript; charset=iso-8859-1");
SetFetchResponse(StrCat(kTestDomain, z_js_url), default_header, z_js_body);
ResourcePtr x_js_resource(CreateResource(kTestDomain, x_js_url));
ResourcePtr y_js_resource(CreateResource(kTestDomain, y_js_url));
ResourcePtr z_js_resource(CreateResource(kTestDomain, z_js_url));
EXPECT_TRUE(ReadIfCached(x_js_resource));
EXPECT_TRUE(ReadIfCached(y_js_resource));
EXPECT_TRUE(ReadIfCached(z_js_resource));
StringPiece result;
const StringPiece kUsAsciiCharset("us-ascii");
// Nothing set: charset should be empty.
result = RewriteFilter::GetCharsetForScript(x_js_resource.get(), "", "");
EXPECT_TRUE(result.empty());
// Only the containing charset is set.
result = RewriteFilter::GetCharsetForScript(x_js_resource.get(),
"", kUsAsciiCharset);
EXPECT_STREQ(result, kUsAsciiCharset);
// The containing charset is trumped by the resource's BOM.
result = RewriteFilter::GetCharsetForScript(y_js_resource.get(),
"", kUsAsciiCharset);
EXPECT_STREQ("utf-8", result);
// The resource's BOM is trumped by the element's charset attribute.
result = RewriteFilter::GetCharsetForScript(y_js_resource.get(),
"gb", kUsAsciiCharset);
EXPECT_STREQ("gb", result);
// The element's charset attribute is trumped by the resource's header.
result = RewriteFilter::GetCharsetForScript(z_js_resource.get(),
"gb", kUsAsciiCharset);
EXPECT_STREQ("iso-8859-1", result);
}
TEST_F(JsCombineFilterTest, AllDifferentCharsets) {
GoogleString html_url = StrCat(kTestDomain, "bom.html");
GoogleString a_js_url = kJsUrl1;
GoogleString b_js_url = kJsUrl2;
GoogleString c_js_url = kJsUrl3;
GoogleString d_js_url = kJsUrl4;
const char a_js_body[] = "var a;";
const char b_js_body[] = "var b;";
const char c_js_body[] = "var c;";
const char d_js_body[] = "var d;";
GoogleString bom_body = StrCat(kUtf8Bom, c_js_body);
// a.js has no charset header nor BOM nor an attribute: use the page.
// b.js has no charset header nor a BOM but has an attribute: use the attr.
// c.js has no charset header nor attribute but has a BOM: use the BOM.
// d.js has a charset header but no BOM nor attribute: use the charset.
ResponseHeaders default_header;
SetDefaultLongCacheHeaders(&kContentTypeJavascript, &default_header);
SetFetchResponse(StrCat(kTestDomain, a_js_url), default_header, a_js_body);
SetFetchResponse(StrCat(kTestDomain, b_js_url), default_header, b_js_body);
SetFetchResponse(StrCat(kTestDomain, c_js_url), default_header, bom_body);
default_header.MergeContentType("text/javascript; charset=iso-8859-1");
SetFetchResponse(StrCat(kTestDomain, d_js_url), default_header, d_js_body);
ScriptInfoVector scripts;
PrepareToCollectScriptsInto(&scripts);
GoogleString input_buffer =
"<head>\n"
" <meta charset=\"gb\">\n"
" <script src=a.js></script>"
" <script src=b.js charset=us-ascii></script>"
" <script src=c.js></script>"
" <script src=d.js></script>"
"</head>\n";
ParseUrl(html_url, input_buffer);
// This should leave the same 4 original scripts.
EXPECT_EQ(4, scripts.size());
EXPECT_EQ(kJsUrl1, scripts[0].url);
EXPECT_EQ(kJsUrl2, scripts[1].url);
EXPECT_EQ(kJsUrl3, scripts[2].url);
EXPECT_EQ(kJsUrl4, scripts[3].url);
}
TEST_F(JsCombineFilterTest, BomMismatch) {
GoogleString html_url = StrCat(kTestDomain, "bom.html");
GoogleString x_js_url = "x.js";
GoogleString y_js_url = "y.js";
// BOM documentation: http://www.unicode.org/faq/utf_bom.html
const char x_js_body[] = "var x;";
const char y_js_body[] = "var y;";
GoogleString bom_body = StrCat(kUtf8Bom, y_js_body);
ResponseHeaders default_header;
SetDefaultLongCacheHeaders(&kContentTypeJavascript, &default_header);
SetFetchResponse(StrCat(kTestDomain, x_js_url), default_header, x_js_body);
SetFetchResponse(StrCat(kTestDomain, y_js_url), default_header, bom_body);
ScriptInfoVector scripts;
PrepareToCollectScriptsInto(&scripts);
// x.js will have an indeterminate charset: it's not in the resource headers,
// nor the element's attribute, there's no BOM, and the HTML doesn't set it.
GoogleString input_buffer(StrCat(
"<head>\n"
" <script src=x.js></script>\n",
" <script src=y.js></script>\n",
"</head>\n"));
ParseUrl(html_url, input_buffer);
ASSERT_EQ(2, scripts.size());
GoogleString input_buffer_reversed =
"<head>\n"
" <script src=y.js></script>\n"
" <script src=x.js></script>\n"
"</head>\n";
scripts.clear();
ParseUrl(html_url, input_buffer_reversed);
ASSERT_EQ(2, scripts.size());
}
TEST_F(JsCombineFilterTest, EmbeddedBom) {
// Test that we can combine 2 JS, one with a BOM and one without, and that
// the BOM is retained in the combination.
GoogleUrl html_url(StrCat(kTestDomain, "bom.html"));
GoogleString x_js_url = "x.js";
GoogleString y_js_url = "y.js";
// BOM documentation: http://www.unicode.org/faq/utf_bom.html
const char x_js_body[] = "var x;";
const char y_js_body[] = "var y;";
GoogleString bom_body = StrCat(kUtf8Bom, y_js_body);
ResponseHeaders default_header;
SetDefaultLongCacheHeaders(&kContentTypeJavascript, &default_header);
SetFetchResponse(StrCat(kTestDomain, x_js_url), default_header, x_js_body);
SetFetchResponse(StrCat(kTestDomain, y_js_url), default_header, bom_body);
ScriptInfoVector scripts;
PrepareToCollectScriptsInto(&scripts);
// x.js now has a charset of utf-8 thanks to the meta tag.
GoogleString input_buffer =
"<head>\n"
" <meta charset=\"UTF-8\">\n"
" <script src=x.js></script>\n"
" <script src=y.js></script>\n"
"</head>\n";
ParseUrl(html_url.Spec(), input_buffer);
ASSERT_EQ(3, scripts.size());
VerifyCombined(scripts[0], MultiUrl(x_js_url, y_js_url));
VerifyUse(scripts[1], x_js_url);
VerifyUse(scripts[2], y_js_url);
GoogleString actual_combination;
GoogleUrl output_url(html_url, scripts[0].url);
EXPECT_TRUE(FetchResourceUrl(output_url.Spec(), &actual_combination));
int bom_pos = actual_combination.find(kUtf8Bom);
EXPECT_EQ(73, bom_pos); // WARNING: MAGIC VALUE!
GoogleString input_buffer_reversed =
"<head>\n"
" <meta charset=\"UTF-8\">\n"
" <script src=y.js></script>\n"
" <script src=x.js></script>\n"
"</head>\n";
scripts.clear();
ParseUrl(html_url.Spec(), input_buffer_reversed);
actual_combination.clear();
ASSERT_EQ(3UL, scripts.size());
VerifyCombined(scripts[0], MultiUrl(y_js_url, x_js_url));
VerifyUse(scripts[1], y_js_url);
VerifyUse(scripts[2], x_js_url);
output_url.Reset(html_url, scripts[0].url);
EXPECT_TRUE(FetchResourceUrl(output_url.Spec(), &actual_combination));
bom_pos = actual_combination.find(kUtf8Bom);
EXPECT_EQ(32, bom_pos); // WARNING: MAGIC VALUE!
}
TEST_F(JsCombineFilterTest, EmbeddedBomReconstruct) {
// Make sure we that BOMs are retained when reconstructing.
const char kJsX[] = "x.js";
const char kJsY[] = "y.js";
const GoogleString kJsText = StrCat(kUtf8Bom, "var z;");
SetResponseWithDefaultHeaders(kJsX, kContentTypeJavascript, kJsText, 300);
SetResponseWithDefaultHeaders(kJsY, kContentTypeJavascript, kJsText, 300);
GoogleString js_url =
Encode(kTestDomain, "jc", "0", MultiUrl(kJsX, kJsY), "js");
GoogleString js_min =
StrCat("var mod_pagespeed_CpWSqUZO1U = \"", kJsText, "\";\n"
"var mod_pagespeed_YdaXhTyTOx = \"", kJsText, "\";\n");
GoogleString js_out;
EXPECT_TRUE(FetchResourceUrl(js_url, &js_out));
EXPECT_EQ(js_min, js_out);
}
TEST_F(JsCombineFilterTest, TestMaxCombinedJsSize) {
// Make sure we don't produce combined js resource bigger than the
// max_combined_js_bytes().
options()->ClearSignatureForTesting();
options()->set_max_combined_js_bytes(
STATIC_STRLEN(kJsText1) + STATIC_STRLEN(kJsText2));
server_context()->ComputeSignature(options());
ScriptInfoVector scripts;
PrepareToCollectScriptsInto(&scripts);
SetupWriter();
html_parse()->StartParse(kTestDomain);
html_parse()->ParseText(StrCat("<script src=", kJsUrl1, "></script>"));
html_parse()->ParseText(StrCat("<script src=", kJsUrl2, "></script>"));
html_parse()->ParseText(StrCat("<script src=", kJsUrl3, "></script>"));
html_parse()->ParseText(StrCat("<script src=", kJsUrl4, "></script>"));
html_parse()->FinishParse();
ASSERT_EQ(6, scripts.size());
VerifyCombined(scripts[0], MultiUrl(kJsUrl1, kJsUrl2));
VerifyUse(scripts[1], kJsUrl1);
VerifyUse(scripts[2], kJsUrl2);
VerifyCombined(scripts[3], MultiUrl(kJsUrl3, kJsUrl4));
VerifyUse(scripts[4], kJsUrl3);
VerifyUse(scripts[5], kJsUrl4);
}
TEST_F(JsCombineFilterTest, NoCombineNoDeferAttribute) {
ValidateNoChanges(
"pagespeed_no_defer",
StrCat("<script src=", kJsUrl1, " pagespeed_no_defer></script>",
"<script src=", kJsUrl2, "></script>"));
}
TEST_F(JsCombineFilterTest, NoCombineDataNoDeferAttribute) {
ValidateNoChanges(
"data-pagespeed-no-defer",
StrCat("<script src=", kJsUrl1, " data-pagespeed-no-defer></script>",
"<script src=", kJsUrl2, "></script>"));
}
TEST_F(JsCombineFilterTest, PreserveUrlRelativity) {
options()->ClearSignatureForTesting();
options()->set_preserve_url_relativity(true);
server_context()->ComputeSignature(options());
ScriptInfoVector scripts;
PrepareToCollectScriptsInto(&scripts);
Parse("preserve_url_relativity",
StrCat("<script src=", kJsUrl1, "></script>"
"<script src=", kJsUrl2, "></script>"));
ASSERT_EQ(3, scripts.size()); // Combine URL script + 2 eval scripts.
StringPiece combine_url(scripts[0].url);
EXPECT_TRUE(combine_url.starts_with("a.js+b.js.pagespeed.jc")) << combine_url;
}
TEST_F(JsCombineFilterTest, NoPreserveUrlRelativity) {
options()->ClearSignatureForTesting();
options()->set_preserve_url_relativity(false);
server_context()->ComputeSignature(options());
ScriptInfoVector scripts;
PrepareToCollectScriptsInto(&scripts);
Parse("preserve_url_relativity",
StrCat("<script src=", kJsUrl1, "></script>"
"<script src=", kJsUrl2, "></script>"));
ASSERT_EQ(3, scripts.size()); // Combine URL script + 2 eval scripts.
StringPiece combine_url(scripts[0].url);
EXPECT_TRUE(combine_url.starts_with("http://test.com/a.js+b.js.pagespeed.jc"))
<< combine_url;
}
TEST_F(JsCombineFilterTest, LoadShedPartition) {
// We want both primary and secondary contexts to use the same cache, as we'll
// need to use secondary to look at results of primary.
SetupSharedCache();
// Arrange for partition to get canceled, by outright shutting down the
// thread where it's supposed to run.
server_context()->low_priority_rewrite_workers()->ShutDown();
// That obviously results in no rewrites.
ValidateNoChanges(
"pagespeed_load_shed",
StrCat("<script src=", kJsUrl1, "></script>",
"<script src=", kJsUrl2, "></script>"));
// Flip over to the alternate server, since we broke the primary one's
// threads.
SetActiveServer(kSecondary);
// Need to re-enable stuff since the fixture only turned it on on primary.
AddFilter(RewriteOptions::kCombineJavascript);
ScriptInfoVector scripts;
PrepareToCollectScriptsInto(&scripts);
Parse("pagespeed_try_again",
StrCat("<script src=", kJsUrl1, "></script>"
"<script src=", kJsUrl2, "></script>"));
ASSERT_EQ(3, scripts.size()); // Combine URL script + 2 eval scripts.
StringPiece combine_url(scripts[0].url);
EXPECT_TRUE(combine_url.starts_with("a.js+b.js.pagespeed.jc"))
<< combine_url;
}
TEST_F(JsCombineFilterTest, IsLikelyStrictMode) {
EXPECT_TRUE(
JsCombineFilter::IsLikelyStrictMode(
server_context()->js_tokenizer_patterns(),
"'some string';\n \"use strict\""));
EXPECT_TRUE(
JsCombineFilter::IsLikelyStrictMode(
server_context()->js_tokenizer_patterns(),
"'use strict'; function x() {}"));
EXPECT_FALSE(
JsCombineFilter::IsLikelyStrictMode(
server_context()->js_tokenizer_patterns(),
"// 'use strict';"));
EXPECT_TRUE(
JsCombineFilter::IsLikelyStrictMode(
server_context()->js_tokenizer_patterns(),
"// Comment \n'use strict';"));
// Function is strict, but whole script is not.
EXPECT_FALSE(
JsCombineFilter::IsLikelyStrictMode(
server_context()->js_tokenizer_patterns(),
"function x() {'use strict'; }"));
// Shouldn't permit just random operators.
EXPECT_FALSE(
JsCombineFilter::IsLikelyStrictMode(
server_context()->js_tokenizer_patterns(),
"+ * ! 'use strict';"));
}
TEST_F(JsCombineFilterTest, MapRewriteDomainAccrossDirs) {
// Setup a MapRewriteDomain "CDN" with a subdir.
const char kSubDir[] = "subdir/";
options()->ClearSignatureForTesting();
options()->WriteableDomainLawyer()->AddRewriteDomainMapping(
StrCat(other_domain_, kSubDir),
kTestDomain,
message_handler());
server_context()->ComputeSignature(options());
SimulateJsResourceOnDomain(other_domain_, StrCat(kSubDir, kJsUrl1), kJsText1);
SimulateJsResourceOnDomain(other_domain_, StrCat(kSubDir, kJsUrl2), kJsText2);
const char kDataHash1[] = "K4w22M2i$3";
const char kDataHash2[] = "4SWUiisZ$T";
GoogleString combined_url =
StrCat(other_domain_, kSubDir, "a.js+b.js.pagespeed.jc.8HvRqZnJ8O.js");
ValidateExpected("rewrite_subdir", TestHtml(),
StrCat(
StrCat("<script src=\"", combined_url, "\"></script>"),
StrCat("<script>eval(mod_pagespeed_", kDataHash1,
");</script>"),
StrCat("<script>eval(mod_pagespeed_", kDataHash2,
");</script>")));
// Now make sure hashes on other domain match, on reconstruction.
EXPECT_EQ(0, lru_cache()->num_deletes());
lru_cache()->Delete(HttpCacheKey(combined_url));
EXPECT_EQ(1, lru_cache()->num_deletes()); // deleted OK.
lru_cache()->ClearStats();
GoogleString combination_src;
ASSERT_TRUE(FetchResourceUrl(combined_url, &combination_src));
EXPECT_TRUE(combination_src.find(kDataHash1) != GoogleString::npos)
<< combination_src;
EXPECT_TRUE(combination_src.find(kDataHash2) != GoogleString::npos)
<< combination_src;
EXPECT_GT(lru_cache()->num_inserts(), 0); // Actually did work.
}
} // namespace net_instaweb