blob: 85b712a62bdd0f4e29f83d7896469ac7d15cd798 [file] [log] [blame]
/*
* Copyright 2010 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: jmarantz@google.com (Joshua Marantz)
// Unit-test the javascript filter
#include "net/instaweb/rewriter/public/javascript_filter.h"
#include "net/instaweb/http/public/http_cache.h"
#include "net/instaweb/http/public/log_record.h"
#include "net/instaweb/http/public/logging_proto.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/rewriter/public/debug_filter.h"
#include "net/instaweb/rewriter/public/javascript_code_block.h"
#include "net/instaweb/rewriter/public/javascript_library_identification.h"
#include "net/instaweb/rewriter/public/js_outline_filter.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/support_noscript_filter.h"
#include "pagespeed/kernel/base/basictypes.h"
#include "pagespeed/kernel/base/gmock.h"
#include "pagespeed/kernel/base/gtest.h"
#include "pagespeed/kernel/base/hasher.h"
#include "pagespeed/kernel/base/md5_hasher.h"
#include "pagespeed/kernel/base/statistics.h"
#include "pagespeed/kernel/base/string.h"
#include "pagespeed/kernel/base/string_util.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/google_url.h"
#include "pagespeed/kernel/http/http_names.h"
#include "pagespeed/kernel/http/response_headers.h"
#include "pagespeed/kernel/http/semantic_type.h"
namespace {
const char kHtmlFormat[] =
"<script type='text/javascript' src='%s'></script>\n";
const char kInlineScriptFormat[] =
"<script type='text/javascript'>"
"%s"
"</script>";
const char kEndInlineScript[] = "<script type='text/javascript'>";
const char kCdataWrapper[] = "//<![CDATA[\n%s\n//]]>";
const char kCdataAltWrapper[] = "//<![CDATA[\r%s\r//]]>";
const char kInlineJs[] =
"<script type='text/javascript'>%s</script>\n";
const char kJsData[] =
"alert ( 'hello, world!' ) "
" /* removed */ <!-- removed --> "
" // single-line-comment";
const char kJsMinData[] = "alert('hello, world!')";
const char kFilterId[] = "jm";
const char kOrigJsName[] = "hello.js";
const char kOrigJsNameRegexp[] = "*hello.js*";
const char kUnauthorizedJs[] = "http://other.domain.com/hello.js";
const char kRewrittenJsName[] = "hello.js";
const char kLibraryUrl[] = "https://www.example.com/hello/1.0/hello.js";
const char kIntrospectiveJS[] =
"<script type='text/javascript' src='introspective.js'></script>";
const char kJsonData[] = " { 'foo' : [ 'bar' , 'baz' ] } ";
const char kJsonMinData[] = "{'foo':['bar','baz']}";
const char kOrigJsonName[] = "hello.json";
const char kRewrittenJsonName[] = "hello.json";
} // namespace
namespace net_instaweb {
class JavascriptFilterTest : public RewriteTestBase,
public ::testing::WithParamInterface<bool> {
protected:
virtual void SetUp() {
RewriteTestBase::SetUp();
options()->set_use_experimental_js_minifier(GetParam());
expected_rewritten_path_ = Encode("", kFilterId, "0",
kRewrittenJsName, "js");
blocks_minified_ = statistics()->GetVariable(
JavascriptRewriteConfig::kBlocksMinified);
libraries_identified_ = statistics()->GetVariable(
JavascriptRewriteConfig::kLibrariesIdentified);
minification_failures_ = statistics()->GetVariable(
JavascriptRewriteConfig::kMinificationFailures);
total_bytes_saved_ = statistics()->GetVariable(
JavascriptRewriteConfig::kTotalBytesSaved);
total_original_bytes_ = statistics()->GetVariable(
JavascriptRewriteConfig::kTotalOriginalBytes);
num_uses_ = statistics()->GetVariable(
JavascriptRewriteConfig::kMinifyUses);
did_not_shrink_ = statistics()->GetVariable(
JavascriptRewriteConfig::kJSDidNotShrink);
}
void InitFilters() {
options()->EnableFilter(RewriteOptions::kRewriteJavascriptExternal);
options()->EnableFilter(RewriteOptions::kRewriteJavascriptInline);
options()->EnableFilter(RewriteOptions::kCanonicalizeJavascriptLibraries);
rewrite_driver_->AddFilters();
}
void InitTest(int64 ttl) {
SetResponseWithDefaultHeaders(
kOrigJsName, kContentTypeJavascript, kJsData, ttl);
SetResponseWithDefaultHeaders(
kUnauthorizedJs, kContentTypeJavascript, kJsData, ttl);
}
void InitFiltersAndTest(int64 ttl) {
InitFilters();
InitTest(ttl);
}
void RegisterLibrary() {
MD5Hasher hasher(JavascriptLibraryIdentification::kNumHashChars);
GoogleString hash = hasher.Hash(kJsMinData);
EXPECT_TRUE(
options()->RegisterLibrary(
STATIC_STRLEN(kJsMinData), hash, kLibraryUrl));
EXPECT_EQ(JavascriptLibraryIdentification::kNumHashChars, hash.size());
}
// Generate HTML loading a single script with the specified URL.
GoogleString GenerateHtml(const char* a) {
return StringPrintf(kHtmlFormat, a);
}
// Generate HTML loading a single script twice from the specified URL.
GoogleString GenerateTwoHtml(const char* a) {
GoogleString once = GenerateHtml(a);
return StrCat(once, once);
}
void TestCorruptUrl(const char* new_suffix) {
// Do a normal rewrite test
InitFiltersAndTest(100);
ValidateExpected("no_ext_corruption",
GenerateHtml(kOrigJsName),
GenerateHtml(expected_rewritten_path_.c_str()));
// Fetch messed up URL.
ASSERT_TRUE(StringCaseEndsWith(expected_rewritten_path_, ".js"));
GoogleString munged_url =
ChangeSuffix(expected_rewritten_path_, false /* replace */,
".js", new_suffix);
GoogleString out;
EXPECT_TRUE(FetchResourceUrl(StrCat(kTestDomain, munged_url), &out));
// Rewrite again; should still get normal URL
ValidateExpected("no_ext_corruption",
GenerateHtml(kOrigJsName),
GenerateHtml(expected_rewritten_path_.c_str()));
}
void SourceMapTest(StringPiece input_js, StringPiece expected_output_js,
StringPiece expected_mapping_vlq);
GoogleString expected_rewritten_path_;
// Stats
Variable* blocks_minified_;
Variable* libraries_identified_;
Variable* minification_failures_;
Variable* did_not_shrink_;
Variable* total_bytes_saved_;
Variable* total_original_bytes_;
Variable* num_uses_;
};
TEST_P(JavascriptFilterTest, DoRewrite) {
InitFiltersAndTest(100);
AbstractLogRecord* log_record =
rewrite_driver_->request_context()->log_record();
log_record->SetAllowLoggingUrls(true);
ValidateExpected("do_rewrite",
GenerateHtml(kOrigJsName),
GenerateHtml(expected_rewritten_path_.c_str()));
EXPECT_EQ(1, blocks_minified_->Get());
EXPECT_EQ(0, minification_failures_->Get());
EXPECT_EQ(STATIC_STRLEN(kJsData) - STATIC_STRLEN(kJsMinData),
total_bytes_saved_->Get());
EXPECT_EQ(STATIC_STRLEN(kJsData), total_original_bytes_->Get());
EXPECT_EQ(1, num_uses_->Get());
EXPECT_STREQ("jm", AppliedRewriterStringFromLog());
VerifyRewriterInfoEntry(log_record, "jm", 0, 0, 1, 1,
"http://test.com/hello.js");
}
TEST_P(JavascriptFilterTest, DontRewriteUnauthorizedDomain) {
InitFiltersAndTest(100);
ValidateNoChanges("dont_rewrite", GenerateHtml(kUnauthorizedJs));
}
TEST_P(JavascriptFilterTest, DebugForUnauthorizedDomain) {
StringVector expected_disabled_filters;
SupportNoscriptFilter tmp(rewrite_driver());
expected_disabled_filters.push_back(tmp.Name());
const char kCaseId[] = "debug_unauthorized_domain";
const GoogleString html_input = GenerateHtml(kUnauthorizedJs);
// Remove the trailing newline as it's in the way :-(
GoogleString html_output = html_input.substr(0, html_input.length() - 1);
GoogleUrl gurl(kUnauthorizedJs);
StrAppend(&html_output,
"<!--",
RewriteDriver::GenerateUnauthorizedDomainDebugComment(gurl),
"-->"
"\n");
html_output = AddHtmlBody(html_output);
GoogleString end_document_message = DebugFilter::FormatEndDocumentMessage(
0, 0, 0, 0, 0, false, StringSet(), expected_disabled_filters);
options()->EnableFilter(RewriteOptions::kDebug);
InitFiltersAndTest(100);
Parse(kCaseId, html_input);
EXPECT_HAS_SUBSTR(html_output, output_buffer_) << "Test id:" << kCaseId;
EXPECT_HAS_SUBSTR(end_document_message, output_buffer_)
<< "Test id:" << kCaseId;
}
TEST_P(JavascriptFilterTest, DontRewriteUnauthorizedDomainWithUnauthOptionSet) {
InitFiltersAndTest(100);
options()->ClearSignatureForTesting();
options()->AddInlineUnauthorizedResourceType(semantic_type::kScript);
server_context()->ComputeSignature(options());
ValidateNoChanges("dont_rewrite", GenerateHtml(kUnauthorizedJs));
}
TEST_P(JavascriptFilterTest, DontRewriteDisallowedScripts) {
SetResponseWithDefaultHeaders(
"a.js", kContentTypeJavascript, "document.write('a');", 100);
options()->Disallow("*a.js*");
options()->EnableFilter(RewriteOptions::kInlineJavascript);
SetHtmlMimetype();
InitFiltersAndTest(100);
ValidateExpected("inline javascript disallowed",
StringPrintf(kHtmlFormat, "a.js"),
StringPrintf(kHtmlFormat, "a.js"));
}
TEST_P(JavascriptFilterTest, DoInlineAllowedForInliningScripts) {
SetResponseWithDefaultHeaders(
"a.js", kContentTypeJavascript, "document.write('a');", 100);
options()->AllowOnlyWhenInlining("*a.js*");
options()->EnableFilter(RewriteOptions::kInlineJavascript);
SetHtmlMimetype();
InitFiltersAndTest(100);
ValidateExpected("inline javascript allowed for inlining",
StringPrintf(kHtmlFormat, "a.js"),
StringPrintf(kInlineJs, "document.write('a');"));
}
TEST_P(JavascriptFilterTest, RewriteButExceedLogThreshold) {
InitFiltersAndTest(100);
rewrite_driver_->log_record()->SetRewriterInfoMaxSize(0);
ValidateExpected("do_rewrite",
GenerateHtml(kOrigJsName),
GenerateHtml(expected_rewritten_path_.c_str()));
EXPECT_STREQ("", AppliedRewriterStringFromLog());
}
TEST_P(JavascriptFilterTest, DoRewriteUnhealthy) {
lru_cache()->set_is_healthy(false);
InitFiltersAndTest(100);
ValidateNoChanges("do_rewrite", GenerateHtml(kOrigJsName));
EXPECT_STREQ("", AppliedRewriterStringFromLog());
}
TEST_P(JavascriptFilterTest, RewriteAlreadyCachedProperly) {
InitFiltersAndTest(100000000); // cached for a long time to begin with
// But we will rewrite because we can make the data smaller.
ValidateExpected("rewrite_despite_being_cached_properly",
GenerateHtml(kOrigJsName),
GenerateHtml(expected_rewritten_path_.c_str()));
}
TEST_P(JavascriptFilterTest, NoRewriteOriginUncacheable) {
InitFiltersAndTest(0); // origin not cacheable
ValidateExpected("no_extend_origin_not_cacheable",
GenerateHtml(kOrigJsName),
GenerateHtml(kOrigJsName));
EXPECT_EQ(0, blocks_minified_->Get());
EXPECT_EQ(0, minification_failures_->Get());
EXPECT_EQ(0, total_bytes_saved_->Get());
EXPECT_EQ(0, total_original_bytes_->Get());
EXPECT_EQ(0, num_uses_->Get());
}
TEST_P(JavascriptFilterTest, IdentifyLibrary) {
RegisterLibrary();
InitFiltersAndTest(100);
ValidateExpected("identify_library",
GenerateHtml(kOrigJsName),
GenerateHtml(kLibraryUrl));
EXPECT_EQ(1, libraries_identified_->Get());
EXPECT_EQ(1, blocks_minified_->Get());
EXPECT_EQ(0, minification_failures_->Get());
}
TEST_P(JavascriptFilterTest, IdentifyLibraryTwice) {
// Make sure cached recognition is handled properly.
RegisterLibrary();
InitFiltersAndTest(100);
ValidateExpected("identify_library_twice",
GenerateTwoHtml(kOrigJsName),
GenerateTwoHtml(kLibraryUrl));
// The second rewrite uses cached data from the first rewrite.
EXPECT_EQ(1, libraries_identified_->Get());
EXPECT_EQ(1, blocks_minified_->Get());
EXPECT_EQ(0, minification_failures_->Get());
}
TEST_P(JavascriptFilterTest, JsPreserveURLsOnTest) {
// Make sure that when in conservative mode the URL stays the same.
RegisterLibrary();
options()->SoftEnableFilterForTesting(
RewriteOptions::kRewriteJavascriptExternal);
options()->SoftEnableFilterForTesting(
RewriteOptions::kCanonicalizeJavascriptLibraries);
options()->set_js_preserve_urls(true);
rewrite_driver()->AddFilters();
EXPECT_TRUE(options()->Enabled(RewriteOptions::kRewriteJavascriptExternal));
// Verify that preserve had a chance to forbid some filters.
EXPECT_FALSE(options()->Enabled(
RewriteOptions::kCanonicalizeJavascriptLibraries));
InitTest(100);
// Make sure the URL doesn't change.
ValidateExpected("js_urls_preserved",
GenerateHtml(kOrigJsName),
GenerateHtml(kOrigJsName));
// We should have optimized the JS even though we didn't render the URL.
ClearStats();
GoogleString out_js_url = Encode(kTestDomain, "jm", "0", kRewrittenJsName,
"js");
GoogleString out_js;
EXPECT_TRUE(FetchResourceUrl(out_js_url, &out_js));
EXPECT_EQ(1, http_cache()->cache_hits()->Get());
EXPECT_EQ(0, http_cache()->cache_misses()->Get());
EXPECT_EQ(0, http_cache()->cache_inserts()->Get());
EXPECT_EQ(1, static_cast<int>(lru_cache()->num_hits()));
EXPECT_EQ(0, static_cast<int>(lru_cache()->num_misses()));
EXPECT_EQ(0, static_cast<int>(lru_cache()->num_inserts()));
// Was the JS minified?
EXPECT_EQ(kJsMinData, out_js);
}
TEST_P(JavascriptFilterTest, JsPreserveOverridingExtend) {
// Make sure that when in conservative mode the URL stays the same.
RegisterLibrary();
scoped_ptr<RewriteOptions> global_options(options()->NewOptions());
global_options->EnableFilter(RewriteOptions::kExtendCacheCss);
scoped_ptr<RewriteOptions> vhost_options(options()->NewOptions());
vhost_options->SoftEnableFilterForTesting(
RewriteOptions::kRewriteJavascriptExternal);
vhost_options->SoftEnableFilterForTesting(
RewriteOptions::kCanonicalizeJavascriptLibraries);
vhost_options->set_js_preserve_urls(true);
options()->Merge(*global_options);
options()->Merge(*vhost_options);
rewrite_driver()->AddFilters();
EXPECT_TRUE(options()->Enabled(RewriteOptions::kRewriteJavascriptExternal));
InitTest(100);
// Make sure the URL doesn't change.
ValidateExpected("js_urls_preserved",
GenerateHtml(kOrigJsName),
GenerateHtml(kOrigJsName));
// We should have preemptively optimized the JS even though we didn't render
// the URL.
ClearStats();
GoogleString out_js_url = Encode(kTestDomain, "jm", "0", kOrigJsName, "js");
GoogleString out_js;
EXPECT_TRUE(FetchResourceUrl(out_js_url, &out_js));
EXPECT_EQ(1, http_cache()->cache_hits()->Get());
EXPECT_EQ(0, http_cache()->cache_misses()->Get());
EXPECT_EQ(0, http_cache()->cache_inserts()->Get());
EXPECT_EQ(1, static_cast<int>(lru_cache()->num_hits()));
EXPECT_EQ(0, static_cast<int>(lru_cache()->num_misses()));
EXPECT_EQ(0, static_cast<int>(lru_cache()->num_inserts()));
// Was the JS minified?
EXPECT_EQ(kJsMinData, out_js);
}
TEST_P(JavascriptFilterTest, JsExtendOverridingPreserve) {
// Make sure that when in conservative mode the URL stays the same.
RegisterLibrary();
scoped_ptr<RewriteOptions> global_options(options()->NewOptions());
global_options->set_js_preserve_urls(true);
global_options->EnableFilter(RewriteOptions::kRewriteJavascriptExternal);
scoped_ptr<RewriteOptions> vhost_options(options()->NewOptions());
vhost_options->EnableFilter(RewriteOptions::kExtendCacheScripts);
options()->Merge(*global_options);
options()->Merge(*vhost_options);
rewrite_driver()->AddFilters();
EXPECT_TRUE(options()->Enabled(RewriteOptions::kRewriteJavascriptExternal));
InitTest(100);
// Make sure the URL is updated.
ValidateExpected("js_extend_overrides_preserve",
GenerateHtml(kOrigJsName),
GenerateHtml(
Encode("", "jm", "0", kRewrittenJsName, "js").c_str()));
ClearStats();
GoogleString out_js;
GoogleString out_js_url = Encode(kTestDomain, "jm", "0", kRewrittenJsName,
"js");
EXPECT_TRUE(FetchResourceUrl(out_js_url, &out_js));
EXPECT_EQ(1, http_cache()->cache_hits()->Get());
EXPECT_EQ(0, http_cache()->cache_misses()->Get());
EXPECT_EQ(0, http_cache()->cache_inserts()->Get());
EXPECT_EQ(1, static_cast<int>(lru_cache()->num_hits()));
EXPECT_EQ(0, static_cast<int>(lru_cache()->num_misses()));
EXPECT_EQ(0, static_cast<int>(lru_cache()->num_inserts()));
// Was the JS minified?
EXPECT_EQ(kJsMinData, out_js);
}
TEST_P(JavascriptFilterTest, JsPreserveURLsNoPreemptiveRewriteTest) {
// Make sure that when in conservative mode the URL stays the same.
RegisterLibrary();
options()->SoftEnableFilterForTesting(
RewriteOptions::kRewriteJavascriptExternal);
options()->set_js_preserve_urls(true);
options()->set_in_place_preemptive_rewrite_javascript(false);
rewrite_driver()->AddFilters();
EXPECT_TRUE(options()->Enabled(
RewriteOptions::kRewriteJavascriptExternal));
InitTest(100);
// Make sure the URL doesn't change.
ValidateExpected("js_urls_preserved_no_preemptive",
GenerateHtml(kOrigJsName),
GenerateHtml(kOrigJsName));
// We should not have attempted any rewriting.
EXPECT_EQ(0, http_cache()->cache_hits()->Get());
EXPECT_EQ(0, http_cache()->cache_misses()->Get());
EXPECT_EQ(0, http_cache()->cache_inserts()->Get());
EXPECT_EQ(0, static_cast<int>(lru_cache()->num_hits()));
EXPECT_EQ(0, static_cast<int>(lru_cache()->num_misses()));
EXPECT_EQ(0, static_cast<int>(lru_cache()->num_inserts()));
// But, if we fetch the JS directly, we should receive the optimized version.
ClearStats();
GoogleString out_js_url = Encode(kTestDomain, "jm", "0", kRewrittenJsName,
"js");
GoogleString out_js;
EXPECT_TRUE(FetchResourceUrl(out_js_url, &out_js));
EXPECT_EQ(kJsMinData, out_js);
}
TEST_P(JavascriptFilterTest, IdentifyLibraryNoMinification) {
// Don't enable kRewriteJavascript. This should still identify the library.
RegisterLibrary();
options()->EnableFilter(RewriteOptions::kCanonicalizeJavascriptLibraries);
rewrite_driver_->AddFilters();
InitTest(100);
ValidateExpected("identify_library_no_minification",
GenerateHtml(kOrigJsName),
GenerateHtml(kLibraryUrl));
EXPECT_EQ(1, libraries_identified_->Get());
EXPECT_EQ(1, blocks_minified_->Get());
EXPECT_EQ(0, minification_failures_->Get());
EXPECT_EQ(0, total_bytes_saved_->Get());
}
TEST_P(JavascriptFilterTest, DisallowedUrlsNotCheckedForCanonicalization) {
RegisterLibrary();
options()->EnableFilter(RewriteOptions::kCanonicalizeJavascriptLibraries);
options()->Disallow(kOrigJsNameRegexp);
rewrite_driver_->AddFilters();
InitTest(100);
ValidateExpected("no_library_identification",
GenerateHtml(kOrigJsName),
GenerateHtml(kOrigJsName));
EXPECT_EQ(0, libraries_identified_->Get());
EXPECT_EQ(0, blocks_minified_->Get());
EXPECT_EQ(0, minification_failures_->Get());
EXPECT_EQ(0, total_bytes_saved_->Get());
}
TEST_P(JavascriptFilterTest, AllowWhenInliningUrlsStillNotChecked) {
RegisterLibrary();
options()->EnableFilter(RewriteOptions::kCanonicalizeJavascriptLibraries);
options()->AllowOnlyWhenInlining(kOrigJsNameRegexp);
rewrite_driver_->AddFilters();
InitTest(100);
ValidateExpected("no_library_identification",
GenerateHtml(kOrigJsName),
GenerateHtml(kOrigJsName));
EXPECT_EQ(0, libraries_identified_->Get());
EXPECT_EQ(0, blocks_minified_->Get());
EXPECT_EQ(0, minification_failures_->Get());
EXPECT_EQ(0, total_bytes_saved_->Get());
}
TEST_P(JavascriptFilterTest, IdentifyFailureNoMinification) {
// Don't enable kRewriteJavascript. We should attempt library identification,
// fail, and not modify the code even though it can be minified.
options()->EnableFilter(RewriteOptions::kCanonicalizeJavascriptLibraries);
rewrite_driver_->AddFilters();
InitTest(100);
// We didn't register any libraries, so we should see that minification
// happened but that nothing changed on the page.
ValidateExpected("identify_failure_no_minification",
GenerateHtml(kOrigJsName),
GenerateHtml(kOrigJsName));
EXPECT_EQ(0, libraries_identified_->Get());
EXPECT_EQ(1, blocks_minified_->Get());
EXPECT_EQ(0, minification_failures_->Get());
}
TEST_P(JavascriptFilterTest, IgnoreLibraryNoIdentification) {
RegisterLibrary();
// We register the library but don't enable library redirection.
options()->EnableFilter(RewriteOptions::kRewriteJavascriptExternal);
rewrite_driver_->AddFilters();
InitTest(100);
ValidateExpected("ignore_library",
GenerateHtml(kOrigJsName),
GenerateHtml(expected_rewritten_path_.c_str()));
EXPECT_EQ(0, libraries_identified_->Get());
EXPECT_EQ(1, blocks_minified_->Get());
EXPECT_EQ(0, minification_failures_->Get());
}
TEST_P(JavascriptFilterTest, DontCombineIdentified) {
// Don't combine a 3rd-party library with other scripts if we'd otherwise
// redirect that library to its canonical url. Doing so will cause us to
// download content that we think has a fair probability of being cached in
// the browser already. If we're better off combining, we shouldn't be
// considering the library as a candidate for library identification in the
// first place.
RegisterLibrary();
options()->EnableFilter(RewriteOptions::kCombineJavascript);
InitFiltersAndTest(100);
ValidateExpected("DontCombineIdentified",
GenerateTwoHtml(kOrigJsName),
GenerateTwoHtml(kLibraryUrl));
}
TEST_P(JavascriptFilterTest, DontInlineIdentified) {
// Don't inline a one-line library that was rewritten to a canonical url.
RegisterLibrary();
options()->EnableFilter(RewriteOptions::kInlineJavascript);
InitFiltersAndTest(100);
ValidateExpected("DontInlineIdentified",
GenerateHtml(kOrigJsName),
GenerateHtml(kLibraryUrl));
}
TEST_P(JavascriptFilterTest, ServeFiles) {
InitFilters();
TestServeFiles(&kContentTypeJavascript, kFilterId, "js",
kOrigJsName, kJsData,
kRewrittenJsName, kJsMinData);
EXPECT_EQ(1, blocks_minified_->Get());
EXPECT_EQ(0, minification_failures_->Get());
EXPECT_EQ(STATIC_STRLEN(kJsData) - STATIC_STRLEN(kJsMinData),
total_bytes_saved_->Get());
EXPECT_EQ(STATIC_STRLEN(kJsData), total_original_bytes_->Get());
// Note: We do not count any uses, because we did not write the URL into
// an HTML file, just served it on request.
EXPECT_EQ(0, num_uses_->Get());
// Finally, serve from a completely separate server.
ServeResourceFromManyContexts(StrCat(kTestDomain, expected_rewritten_path_),
kJsMinData);
}
TEST_P(JavascriptFilterTest, ServeFilesUnhealthy) {
lru_cache()->set_is_healthy(false);
InitFilters();
InitTest(100);
TestServeFiles(&kContentTypeJavascript, kFilterId, "js",
kOrigJsName, kJsData,
kRewrittenJsName, kJsMinData);
}
TEST_P(JavascriptFilterTest, ServeRewrittenLibrary) {
// If a request comes in for the rewritten version of a JS library
// that we have identified as matching a canonical library, we should
// still serve some useful content. It won't be minified because we
// don't want to update metadata cache entries on the fly.
RegisterLibrary();
InitFiltersAndTest(100);
GoogleString content;
EXPECT_TRUE(
FetchResource(kTestDomain, "jm", kRewrittenJsName, "js", &content));
EXPECT_EQ(kJsData, content);
// And having done so, we should still identify the library in subsequent html
// (ie the cache should not be corrupted to prevent library identification).
ValidateExpected("identify_library",
GenerateHtml(kOrigJsName),
GenerateHtml(kLibraryUrl));
}
TEST_P(JavascriptFilterTest, ServeJsonFile) {
InitFilters();
// Set content type extension to "js" because filter is caching it with js
// extension, but the in-place lookup will still cache and serve with original
// content type. Tests for this in in_place_rewrite_context_test.
TestServeFiles(&kContentTypeJson, kFilterId, "js",
kOrigJsonName, kJsonData,
kRewrittenJsonName, kJsonMinData);
EXPECT_EQ(1, blocks_minified_->Get());
EXPECT_EQ(0, minification_failures_->Get());
EXPECT_EQ(STATIC_STRLEN(kJsonData) - STATIC_STRLEN(kJsonMinData),
total_bytes_saved_->Get());
EXPECT_EQ(STATIC_STRLEN(kJsonData), total_original_bytes_->Get());
// Note: We do not count any uses, because we did not write the URL into
// an HTML file, just served it on request.
EXPECT_EQ(0, num_uses_->Get());
}
TEST_P(JavascriptFilterTest, IdentifyAjaxLibrary) {
// If ajax rewriting is enabled, we won't minify a library when it is fetched,
// but it will still be replaced on the containing page.
RegisterLibrary();
options()->set_in_place_rewriting_enabled(true);
options()->EnableFilter(RewriteOptions::kCanonicalizeJavascriptLibraries);
rewrite_driver_->AddFilters();
InitTest(100);
GoogleString url = StrCat(kTestDomain, kOrigJsName);
// Do resource fetch for js; this will cause it to be filtered in the
// background.
GoogleString content;
EXPECT_TRUE(FetchResourceUrl(url, &content));
EXPECT_EQ(kJsData, content);
// A second resource fetch for the js will still obtain the unminified
// content, since we don't save the minified version for identified libraries.
content.clear();
EXPECT_TRUE(FetchResourceUrl(url, &content));
EXPECT_EQ(kJsData, content);
// But rewriting the page will see the js url.
ValidateExpected("IdentifyAjaxLibrary",
GenerateHtml(kOrigJsName),
GenerateHtml(kLibraryUrl));
}
TEST_P(JavascriptFilterTest, InvalidInputMimetype) {
InitFilters();
// Make sure we can rewrite properly even when input has corrupt mimetype.
ContentType not_java_script = kContentTypeJavascript;
not_java_script.mime_type_ = "text/semicolon-inserted";
const char* kNotJsFile = "script.notjs";
SetResponseWithDefaultHeaders(kNotJsFile, not_java_script, kJsData, 100);
ValidateExpected("wrong_mime",
GenerateHtml(kNotJsFile),
GenerateHtml(Encode("", "jm", "0",
kNotJsFile, "js").c_str()));
}
TEST_P(JavascriptFilterTest, RewriteJs404) {
InitFilters();
// Test to make sure that a missing input is handled well.
SetFetchResponse404("404.js");
ValidateNoChanges("404", "<script src='404.js'></script>");
EXPECT_EQ(0, blocks_minified_->Get());
EXPECT_EQ(0, minification_failures_->Get());
EXPECT_EQ(0, num_uses_->Get());
// Second time, to make sure caching doesn't break it.
ValidateNoChanges("404", "<script src='404.js'></script>");
EXPECT_EQ(0, blocks_minified_->Get());
EXPECT_EQ(0, minification_failures_->Get());
EXPECT_EQ(0, num_uses_->Get());
}
// Make sure bad requests do not corrupt our extension.
TEST_P(JavascriptFilterTest, NoExtensionCorruption) {
TestCorruptUrl(".js%22");
}
TEST_P(JavascriptFilterTest, NoQueryCorruption) {
TestCorruptUrl(".js?query");
}
TEST_P(JavascriptFilterTest, NoWrongExtCorruption) {
TestCorruptUrl(".html");
}
TEST_P(JavascriptFilterTest, InlineJavascript) {
// Test minification of a simple inline script
InitFiltersAndTest(100);
ValidateExpected("inline javascript",
StringPrintf(kInlineJs, kJsData),
StringPrintf(kInlineJs, kJsMinData));
EXPECT_EQ(1, blocks_minified_->Get());
EXPECT_EQ(0, minification_failures_->Get());
EXPECT_EQ(STATIC_STRLEN(kJsData) - STATIC_STRLEN(kJsMinData),
total_bytes_saved_->Get());
EXPECT_EQ(STATIC_STRLEN(kJsData), total_original_bytes_->Get());
EXPECT_EQ(1, num_uses_->Get());
}
TEST_P(JavascriptFilterTest, NoMinificationInlineJS) {
// Test no minification of a simple inline script.
InitFiltersAndTest(100);
const char kSmallJS[] = "alert('hello');";
ValidateExpected("inline javascript",
StringPrintf(kInlineJs, kSmallJS),
StringPrintf(kInlineJs, kSmallJS));
// There was no minification error, so 1 here.
EXPECT_EQ(1, blocks_minified_->Get());
EXPECT_EQ(1, did_not_shrink_->Get());
// No failures though.
EXPECT_EQ(0, minification_failures_->Get());
}
TEST_P(JavascriptFilterTest, StripInlineWhitespace) {
// Make sure we strip inline whitespace when minifying external scripts.
InitFiltersAndTest(100);
ValidateExpected(
"StripInlineWhitespace",
StrCat("<script src='", kOrigJsName, "'> \t\n </script>"),
StrCat("<script src='",
Encode("", "jm", "0", kOrigJsName, "js"),
"'></script>"));
}
TEST_P(JavascriptFilterTest, RetainInlineData) {
// Test to make sure we keep inline data when minifying external scripts.
InitFiltersAndTest(100);
ValidateExpected("StripInlineWhitespace",
StrCat("<script src='", kOrigJsName, "'> data </script>"),
StrCat("<script src='",
Encode("", "jm", "0", kOrigJsName, "js"),
"'> data </script>"));
}
// Test minification of a simple inline script in markup with no
// mimetype, where the script is wrapped in a commented-out CDATA.
//
// Note that javascript_filter never adds CDATA. It only removes it
// if it's sure the mimetype is HTML.
TEST_P(JavascriptFilterTest, CdataJavascriptNoMimetype) {
InitFiltersAndTest(100);
ValidateExpected(
"cdata javascript no mimetype",
StringPrintf(kInlineJs, StringPrintf(kCdataWrapper, kJsData).c_str()),
StringPrintf(kInlineJs, StringPrintf(kCdataWrapper, kJsMinData).c_str()));
ValidateExpected(
"cdata javascript no mimetype with \\r",
StringPrintf(kInlineJs, StringPrintf(kCdataAltWrapper, kJsData).c_str()),
StringPrintf(kInlineJs, StringPrintf(kCdataWrapper, kJsMinData).c_str()));
}
// Same as CdataJavascriptNoMimetype, but with explicit HTML mimetype.
TEST_P(JavascriptFilterTest, CdataJavascriptHtmlMimetype) {
SetHtmlMimetype();
InitFiltersAndTest(100);
ValidateExpected(
"cdata javascript with explicit HTML mimetype",
StringPrintf(kInlineJs, StringPrintf(kCdataWrapper, kJsData).c_str()),
StringPrintf(kInlineJs, kJsMinData));
ValidateExpected(
"cdata javascript with explicit HTML mimetype and \\r",
StringPrintf(kInlineJs, StringPrintf(kCdataAltWrapper, kJsData).c_str()),
StringPrintf(kInlineJs, kJsMinData));
}
// Same as CdataJavascriptNoMimetype, but with explicit XHTML mimetype.
TEST_P(JavascriptFilterTest, CdataJavascriptXhtmlMimetype) {
SetXhtmlMimetype();
InitFiltersAndTest(100);
ValidateExpected(
"cdata javascript with explicit XHTML mimetype",
StringPrintf(kInlineJs, StringPrintf(kCdataWrapper, kJsData).c_str()),
StringPrintf(kInlineJs, StringPrintf(kCdataWrapper, kJsMinData).c_str()));
ValidateExpected(
"cdata javascript with explicit XHTML mimetype and \\r",
StringPrintf(kInlineJs, StringPrintf(kCdataAltWrapper, kJsData).c_str()),
StringPrintf(kInlineJs, StringPrintf(kCdataWrapper, kJsMinData).c_str()));
}
TEST_P(JavascriptFilterTest, XHtmlInlineJavascript) {
// Test minification of a simple inline script in xhtml
// where it must be wrapped in CDATA.
InitFiltersAndTest(100);
const GoogleString xhtml_script_format =
StrCat(kXhtmlDtd, StringPrintf(kInlineJs, kCdataWrapper));
ValidateExpected("xhtml inline javascript",
StringPrintf(xhtml_script_format.c_str(), kJsData),
StringPrintf(xhtml_script_format.c_str(), kJsMinData));
const GoogleString xhtml_script_alt_format =
StrCat(kXhtmlDtd, StringPrintf(kInlineJs, kCdataAltWrapper));
ValidateExpected("xhtml inline javascript",
StringPrintf(xhtml_script_alt_format.c_str(), kJsData),
StringPrintf(xhtml_script_format.c_str(), kJsMinData));
}
// http://code.google.com/p/modpagespeed/issues/detail?id=324
TEST_P(JavascriptFilterTest, RetainExtraHeaders) {
InitFilters();
GoogleString url = StrCat(kTestDomain, kOrigJsName);
SetResponseWithDefaultHeaders(url, kContentTypeJavascript, kJsData, 300);
TestRetainExtraHeaders(kOrigJsName, "jm", "js");
}
// http://code.google.com/p/modpagespeed/issues/detail?id=327 -- we were
// previously busting regexps with backslashes in them.
TEST_P(JavascriptFilterTest, BackslashInRegexp) {
InitFilters();
GoogleString input = StringPrintf(kInlineJs, "/http:\\/\\/[^/]+\\//");
ValidateNoChanges("backslash_in_regexp", input);
}
TEST_P(JavascriptFilterTest, WeirdSrcCrash) {
InitFilters();
// These used to crash due to bugs in the lexer breaking invariants some
// filters relied on.
//
// Note that the attribute-value "foo<bar" gets converted into "foo%3Cbar"
// by this line:
// const GoogleUrl resource_url(base_url(), input_url);
// in CommonFilter::CreateInputResource. Following that, resource_url.Spec()
// has the %3C in it. I guess that's probably the right thing to do, but
// I was a little surprised.
static const char kUrl[] = "foo%3Cbar";
SetResponseWithDefaultHeaders(kUrl, kContentTypeJavascript, kJsData, 300);
ValidateExpected("weird_attr", "<script src=foo<bar>Content",
StrCat("<script src=",
Encode("", "jm", "0", kUrl, "js"),
">Content"));
ValidateNoChanges("weird_tag", "<script<foo>");
}
TEST_P(JavascriptFilterTest, MinificationFailure) {
InitFilters();
SetResponseWithDefaultHeaders("foo.js", kContentTypeJavascript,
"/* truncated comment", 100);
ValidateNoChanges("fail", "<script src=foo.js></script>");
EXPECT_EQ(0, blocks_minified_->Get());
EXPECT_EQ(1, minification_failures_->Get());
EXPECT_EQ(0, num_uses_->Get());
EXPECT_EQ(1, did_not_shrink_->Get());
}
TEST_P(JavascriptFilterTest, ReuseRewrite) {
InitFiltersAndTest(100);
ValidateExpected("reuse_rewrite1",
GenerateHtml(kOrigJsName),
GenerateHtml(expected_rewritten_path_.c_str()));
// First time: We minify JS and use the minified version.
EXPECT_EQ(1, blocks_minified_->Get());
EXPECT_EQ(1, num_uses_->Get());
ClearStats();
ValidateExpected("reuse_rewrite2",
GenerateHtml(kOrigJsName),
GenerateHtml(expected_rewritten_path_.c_str()));
// Second time: We reuse the original rewrite.
EXPECT_EQ(0, blocks_minified_->Get());
EXPECT_EQ(1, num_uses_->Get());
}
TEST_P(JavascriptFilterTest, NoReuseInline) {
InitFiltersAndTest(100);
ValidateExpected("reuse_inline1",
StringPrintf(kInlineJs, kJsData),
StringPrintf(kInlineJs, kJsMinData));
// First time: We minify JS and use the minified version.
EXPECT_EQ(1, blocks_minified_->Get());
EXPECT_EQ(1, num_uses_->Get());
ClearStats();
ValidateExpected("reuse_inline2",
StringPrintf(kInlineJs, kJsData),
StringPrintf(kInlineJs, kJsMinData));
// Second time: Apparently we minify it again.
// NOTE: This test is here to document current behavior. It should be fine
// to change this behavior so that the rewrite is cached (although it may
// not be worth it).
EXPECT_EQ(1, blocks_minified_->Get());
EXPECT_EQ(1, num_uses_->Get());
}
// See http://code.google.com/p/modpagespeed/issues/detail?id=542
TEST_P(JavascriptFilterTest, ExtraCdataOnMalformedInput) {
InitFiltersAndTest(100);
// This is an entirely bogus thing to have in a script tag, but that was
// what was reported by a user. We were wrapping this an an extra CDATA
// tag, so this test proves we are no longer doing that.
static const char kIssue542LinkInScript[] =
"<![CDATA[<link href='http://fonts.googleapis.com/css'>]]>";
const GoogleString kHtmlInput = StringPrintf(
kInlineScriptFormat,
StrCat("\n", kIssue542LinkInScript, "\n").c_str());
const GoogleString kHtmlOutput = StringPrintf(
kInlineScriptFormat,
kIssue542LinkInScript);
ValidateExpected("broken_cdata", kHtmlInput, kHtmlOutput);
}
TEST_P(JavascriptFilterTest, ValidCdata) {
InitFiltersAndTest(100);
const GoogleString kHtmlInput = StringPrintf(
kInlineScriptFormat,
StringPrintf(kCdataWrapper, "alert ( 'foo' ) ; \n").c_str());
const GoogleString kHtmlOutput = StringPrintf(
kInlineScriptFormat,
StringPrintf(kCdataWrapper, "alert('foo');").c_str());
ValidateExpected("valid_cdata", kHtmlInput, kHtmlOutput);
}
TEST_P(JavascriptFilterTest, FlushInInlineJS) {
InitFilters();
SetupWriter();
rewrite_driver()->StartParse(kTestDomain);
rewrite_driver()->ParseText("<html><body><script> alert ( 'Hel");
// Flush in middle of inline JS.
rewrite_driver()->Flush();
rewrite_driver()->ParseText("lo, World!' ) </script></body></html>");
rewrite_driver()->FinishParse();
// Expect text to be rewritten because it is coalesced.
// HtmlParse will send events like this to filter:
// StartElement script
// Flush
// Characters ...
// EndElement script
EXPECT_EQ("<html><body><script>alert('Hello, World!')</script></body></html>",
output_buffer_);
}
TEST_P(JavascriptFilterTest, FlushInEndTag) {
InitFilters();
SetupWriter();
rewrite_driver()->StartParse(kTestDomain);
rewrite_driver()->ParseText(
"<html><body><script> alert ( 'Hello, World!' ) </scr");
// Flush in middle of closing </script> tag.
rewrite_driver()->Flush();
rewrite_driver()->ParseText("ipt></body></html>");
rewrite_driver()->FinishParse();
// Expect text to be rewritten because it is coalesced.
// HtmlParse will send events like this to filter:
// StartElement script
// Characters ...
// Flush
// EndElement script
EXPECT_EQ("<html><body><script>alert('Hello, World!')</script></body></html>",
output_buffer_);
}
TEST_P(JavascriptFilterTest, FlushAfterBeginScript) {
InitFilters();
SetupWriter();
rewrite_driver()->StartParse(kTestDomain);
rewrite_driver()->ParseText(
"<html><body><script>");
rewrite_driver()->Flush();
rewrite_driver()->ParseText("alert ( 'Hello, World!' ) </script>"
"<script></script></body></html>");
rewrite_driver()->FinishParse();
// Expect text to be rewritten because it is coalesced.
// HtmlParse will send events like this to filter:
// StartElement script
// Flush
// Characters ...
// EndElement script
// StartElement script
// EndElement script
EXPECT_EQ("<html><body><script>alert('Hello, World!')</script>"
"<script></script></body></html>",
output_buffer_);
}
TEST_P(JavascriptFilterTest, StripInlineWhitespaceFlush) {
// Make sure we strip inline whitespace when minifying external scripts even
// if there's a flush between open and close.
InitFiltersAndTest(100);
SetupWriter();
const GoogleString kScriptTag =
StrCat("<script type='text/javascript' src='", kOrigJsName, "'>");
rewrite_driver()->StartParse(kTestDomain);
rewrite_driver()->ParseText(kScriptTag);
rewrite_driver()->ParseText(" \t\n");
rewrite_driver()->Flush();
rewrite_driver()->ParseText(" </script>\n");
rewrite_driver()->FinishParse();
const GoogleString expected =
StringPrintf(kHtmlFormat, expected_rewritten_path_.c_str());
EXPECT_EQ(expected, output_buffer_);
}
TEST_P(JavascriptFilterTest, Aris) {
options()->EnableFilter(RewriteOptions::kDebug);
InitFilters();
const char introspective_js[] =
"var script_tags = document.getElementsByTagName('script');";
SetResponseWithDefaultHeaders("introspective.js", kContentTypeJavascript,
introspective_js, 100);
Parse("introspective", GenerateHtml("introspective.js"));
const GoogleString kInsertComment =
StrCat(kIntrospectiveJS, "<!--",
JavascriptCodeBlock::kIntrospectionComment, "-->");
EXPECT_THAT(output_buffer_, ::testing::HasSubstr(kInsertComment));
}
TEST_P(JavascriptFilterTest, ArisSourceMaps) {
options()->EnableFilter(RewriteOptions::kIncludeJsSourceMaps);
options()->EnableFilter(RewriteOptions::kDebug);
InitFilters();
const char introspective_js[] =
"var script_tags = document.getElementsByTagName('script');";
SetResponseWithDefaultHeaders("introspective.js", kContentTypeJavascript,
introspective_js, 100);
Parse("introspective", GenerateHtml("introspective.js"));
const GoogleString kInsertComment =
StrCat(kIntrospectiveJS, "<!--",
JavascriptCodeBlock::kIntrospectionComment, "-->");
EXPECT_THAT(output_buffer_, ::testing::HasSubstr(kInsertComment));
}
TEST_P(JavascriptFilterTest, ArisCombineJs) {
options()->EnableFilter(RewriteOptions::kCombineJavascript);
options()->EnableFilter(RewriteOptions::kDebug);
InitFilters();
const char introspective_js[] =
"var script_tags = document.getElementsByTagName('script');";
SetResponseWithDefaultHeaders("introspective.js", kContentTypeJavascript,
introspective_js, 100);
SetResponseWithDefaultHeaders("a.js", kContentTypeJavascript,
kJsData, 100);
SetResponseWithDefaultHeaders("b.js", kContentTypeJavascript,
kJsData, 100);
static const char kHtmlBefore[] =
"<script type='text/javascript' src='introspective.js'></script>\n"
"<script type='text/javascript' src='a.js'></script>\n"
"<script type='text/javascript' src='b.js'></script>\n";
const GoogleString kHtmlAfter = StrCat(
kIntrospectiveJS, "<!--", JavascriptCodeBlock::kIntrospectionComment,
"-->\n", "<script src=\"a.js+b.js.pagespeed.jc.0.js\"></script>",
"<script>eval(mod_pagespeed_0);</script>\n",
"<script>eval(mod_pagespeed_0);</script>\n");
Parse("introspective", kHtmlBefore);
EXPECT_THAT(output_buffer_, ::testing::HasSubstr(kHtmlAfter));
}
void JavascriptFilterTest::SourceMapTest(StringPiece input_js,
StringPiece expected_output_js,
StringPiece expected_mapping_vlq) {
UseMd5Hasher();
options()->EnableFilter(RewriteOptions::kIncludeJsSourceMaps);
InitFilters();
SetResponseWithDefaultHeaders("input.js", kContentTypeJavascript,
input_js, 100);
GoogleString expected_map = StrCat(
")]}'\n{\"mappings\":\"", expected_mapping_vlq, "\",\"names\":[],"
"\"sources\":[\"http://test.com/input.js?PageSpeed=off\"],"
"\"version\":3}\n");
GoogleString source_map_url =
Encode(kTestDomain, RewriteOptions::kJavascriptMinSourceMapId,
hasher()->Hash(expected_map), "input.js", "map");
GoogleString expected_output = expected_output_js.as_string();
if (options()->use_experimental_js_minifier()) {
StrAppend(&expected_output, "\n"
"//# sourceMappingURL=", source_map_url, "\n");
}
const GoogleString rewritten_js_name =
Encode("", RewriteOptions::kJavascriptMinId,
hasher()->Hash(expected_output), "input.js", "js");
ValidateExpected("source_maps",
GenerateHtml("input.js"),
GenerateHtml(rewritten_js_name.c_str()));
GoogleString output_js;
EXPECT_TRUE(FetchResourceUrl(StrCat(kTestDomain, rewritten_js_name),
&output_js));
EXPECT_STREQ(expected_output, output_js);
if (options()->use_experimental_js_minifier()) {
GoogleString map;
EXPECT_TRUE(FetchResourceUrl(source_map_url, &map));
EXPECT_STREQ(expected_map, map);
// Test Resource flow without HTML flow.
ServeResourceFromManyContexts(source_map_url, expected_map);
// Test fetching Source Map with wrong/out-of-date hash.
GoogleString different_hash_url =
Encode(kTestDomain, RewriteOptions::kJavascriptMinSourceMapId,
"Different", "input.js", "map");
ResponseHeaders map_headers;
// Test both the uncached and cached case.
for (int i = 0; i < 2; ++i) {
EXPECT_TRUE(FetchResourceUrl(different_hash_url, &map, &map_headers));
EXPECT_EQ(HttpStatus::kNotFound, map_headers.status_code());
EXPECT_STREQ(RewriteContext::kHashMismatchMessage, map);
}
}
}
TEST_P(JavascriptFilterTest, SourceMapsSimple) {
const char input_js[] = " foo bar ";
const char expected_output_js[] = "foo bar";
const char vlq[] =
// Comment format: (gen_line, gen_col, src_file, src_line, src_col) token
"AAAE," // (0, 0, 0, 0, 2) foo [space]
"IAAK"; // (0, +4, +0, +0, +5) bar
SourceMapTest(input_js, expected_output_js, vlq);
}
TEST_P(JavascriptFilterTest, SourceMapsMedium) {
const char input_js[] =
"alert ( 'hello, world!' ) \n"
" /* removed */ <!-- removed --> \n"
" // single-line-comment\n"
"document.write( \"<!-- comment -->\" );";
const char expected_output_js[] =
"alert('hello, world!')\n"
"document.write(\"<!-- comment -->\");";
const char vlq[] =
// Comment format: (gen_line, gen_col, src_file, src_line, src_col) token
"AAAA," // (0, 0, 0, 0, 0) alert
"KAAU," // (0, +5, +0, +0, +10) (
"CAAK," // (0, +1, +0, +0, +5) 'hello, world!'
"eAAmB;" // (0, +15, +0, +0, +19) )
"AAGlC," // (1, 0, +0, +3, -34) document.write(
"eAAgB," // (1, +15, +0, +0, +16) "<!-- comment -->"
"kBAAmB"; // (1, +18, +0, +0, +19) );
SourceMapTest(input_js, expected_output_js, vlq);
}
TEST_P(JavascriptFilterTest, NoSourceMapJsCombine) {
options()->EnableFilter(RewriteOptions::kCombineJavascript);
options()->EnableFilter(RewriteOptions::kIncludeJsSourceMaps);
InitFilters();
SetResponseWithDefaultHeaders("a.js", kContentTypeJavascript,
kJsData, 100);
SetResponseWithDefaultHeaders("b.js", kContentTypeJavascript,
kJsData, 100);
const char combined_name[] = "a.js+b.js.pagespeed.jc.0.js";
static const char kHtmlBefore[] =
"<script type='text/javascript' src='a.js'></script>\n"
"<script type='text/javascript' src='b.js'></script>\n";
GoogleString kHtmlAfter = StrCat(
"<script src=\"", combined_name, "\"></script>"
"<script>eval(mod_pagespeed_0);</script>\n"
"<script>eval(mod_pagespeed_0);</script>\n");
ValidateExpected("introspective", kHtmlBefore, kHtmlAfter);
// Note: There is no //# ScriptSourceMap in combine output.
GoogleString expected_output = StrCat(
"var mod_pagespeed_0 = \"", kJsMinData, "\";\n"
"var mod_pagespeed_0 = \"", kJsMinData, "\";\n");
GoogleString output_js;
EXPECT_TRUE(FetchResourceUrl(StrCat(kTestDomain, combined_name), &output_js));
EXPECT_EQ(expected_output, output_js);
}
TEST_P(JavascriptFilterTest, SourceMapUnsanitaryUrl) {
if (!options()->use_experimental_js_minifier()) return;
options()->EnableFilter(RewriteOptions::kIncludeJsSourceMaps);
InitFilters();
// Most servers will ignore unknown query params.
mock_url_fetcher()->set_strip_query_params(true);
SetResponseWithDefaultHeaders("input.js", kContentTypeJavascript,
kJsData, 100);
GoogleString unsanitary_url =
Encode(kTestDomain, RewriteOptions::kJavascriptMinId,
"0", "input.js?evil=\n", "js");
GoogleString output_js;
EXPECT_TRUE(FetchResourceUrl(unsanitary_url, &output_js));
// Note: The important thing is that there's no newline in the mapping URL.
GoogleString expected_output_js = StrCat(
kJsMinData, "\n//# sourceMappingURL="
"http://test.com/input.js,qevil=.pagespeed.sm.0.map\n");
EXPECT_EQ(expected_output_js, output_js);
}
// For non-pagespeed input JS, we must add ?PageSpeed=off to the source URL
// to avoid IPRO rewriting the source. However, we should not add ?PageSpeed=off
// for .pagespeed. input files, because that doesn't make any sense.
// https://code.google.com/p/modpagespeed/issues/detail?id=1043
TEST_P(JavascriptFilterTest, ProperSourceMapForPagespeedInput) {
if (!options()->use_experimental_js_minifier()) return;
options()->EnableFilter(RewriteOptions::kIncludeJsSourceMaps);
options()->EnableFilter(RewriteOptions::kRewriteJavascriptExternal);
options()->EnableFilter(RewriteOptions::kOutlineJavascript);
options()->set_js_outline_min_bytes(0); // Make sure everything is outlined.
UseMd5Hasher(); // We should use a real hasher for outlining.
rewrite_driver_->AddFilters();
const char input_js[] = "foo = 1;";
GoogleString outlined_js_tail =
Encode("", JsOutlineFilter::kFilterId,
hasher()->Hash(input_js), "_", "js");
const char expected_mapping_vlq[] = "AAAA,GAAI,CAAE";
// Note: No ?PageSpeed=off
GoogleString expected_source_map = StrCat(
")]}'\n{\"mappings\":\"", expected_mapping_vlq, "\",\"names\":[],"
"\"sources\":[\"", kTestDomain, outlined_js_tail, "\"],"
"\"version\":3}\n");
GoogleString source_map_url =
Encode(kTestDomain, RewriteOptions::kJavascriptMinSourceMapId,
hasher()->Hash(expected_source_map), outlined_js_tail, "map");
const char rewritten_js[] = "foo=1;";
GoogleString expected_output_js =
StrCat(rewritten_js, "\n"
"//# sourceMappingURL=", source_map_url, "\n");
GoogleString rewritten_js_url =
Encode(kTestDomain, RewriteOptions::kJavascriptMinId,
hasher()->Hash(expected_output_js), outlined_js_tail, "js");
GoogleString input_html = StrCat("<script>", input_js, "</script>");
GoogleString expected_output_html =
StrCat("<script src=\"", rewritten_js_url, "\"></script>");
ValidateExpected("source_map_pagespeed", input_html, expected_output_html);
GoogleString output_js;
EXPECT_TRUE(FetchResourceUrl(rewritten_js_url, &output_js));
EXPECT_EQ(expected_output_js, output_js);
GoogleString source_map;
EXPECT_TRUE(FetchResourceUrl(source_map_url, &source_map));
EXPECT_EQ(expected_source_map, source_map);
}
TEST_P(JavascriptFilterTest, SourceMapOnDemandNotEnabled) {
options()->DisableFilter(RewriteOptions::kIncludeJsSourceMaps);
options()->EnableFilter(RewriteOptions::kRewriteJavascriptExternal);
rewrite_driver_->AddFilters();
// From SourceMapsSimple
const char input_js[] = " foo bar ";
const char expected_vlq[] = "AAAE,IAAK";
GoogleString expected_map = StrCat(
")]}'\n{\"mappings\":\"", expected_vlq, "\",\"names\":[],"
"\"sources\":[\"http://test.com/input.js?PageSpeed=off\"],"
"\"version\":3}\n");
SetResponseWithDefaultHeaders("input.js", kContentTypeJavascript,
input_js, 100);
GoogleString source_map_url =
Encode(kTestDomain, RewriteOptions::kJavascriptMinSourceMapId,
"0", "input.js", "map");
GoogleString source_map;
bool result = FetchResourceUrl(source_map_url, &source_map);
if (options()->use_experimental_js_minifier()) {
EXPECT_TRUE(result);
EXPECT_STREQ(expected_map, source_map);
}
// Note: !options()->use_experimental_js_minifier() also checks the
// code_block.SourceMappings().empty() case.
}
// If JS isn't optimizable, do not fallback to serving js for source_map!
TEST_P(JavascriptFilterTest, SourceMapNoOpt) {
if (!options()->use_experimental_js_minifier()) return;
options()->EnableFilter(RewriteOptions::kIncludeJsSourceMaps);
InitFilters();
// Empty contents (not optimizable).
SetResponseWithDefaultHeaders("input.js", kContentTypeJavascript, "", 100);
GoogleString source_map_url =
Encode(kTestDomain, RewriteOptions::kJavascriptMinSourceMapId,
"0", "input.js", "map");
// Test both the uncached and cached case.
for (int i = 0; i < 2; ++i) {
GoogleString map;
// Note: Right now we are failing the fetch in this case, but 404ing would
// also be reasonable. If we change that, update this test.
EXPECT_FALSE(FetchResourceUrl(source_map_url, &map));
}
}
TEST_P(JavascriptFilterTest, InlineAndNotExternal) {
options()->EnableFilter(RewriteOptions::kRewriteJavascriptInline);
options()->DisableFilter(RewriteOptions::kRewriteJavascriptExternal);
rewrite_driver_->AddFilters();
InitTest(100);
ValidateExpected("inline_not_external",
StrCat(StringPrintf(kInlineJs, kJsData),
StringPrintf(kHtmlFormat, kOrigJsName)),
StrCat(StringPrintf(kInlineJs, kJsMinData),
StringPrintf(kHtmlFormat, kOrigJsName)));
}
TEST_P(JavascriptFilterTest, InlineAndNotExternalPreserve) {
// js_preserve_urls should not affect minification of inline JS.
options()->set_js_preserve_urls(true);
options()->set_in_place_preemptive_rewrite_javascript(false);
options()->EnableFilter(RewriteOptions::kRewriteJavascriptInline);
options()->DisableFilter(RewriteOptions::kRewriteJavascriptExternal);
rewrite_driver_->AddFilters();
InitTest(100);
ValidateExpected("inline_not_external",
StrCat(StringPrintf(kInlineJs, kJsData),
StringPrintf(kHtmlFormat, kOrigJsName)),
StrCat(StringPrintf(kInlineJs, kJsMinData),
StringPrintf(kHtmlFormat, kOrigJsName)));
}
TEST_P(JavascriptFilterTest, InlineAndCanonicalNotExternal) {
options()->EnableFilter(RewriteOptions::kRewriteJavascriptInline);
options()->EnableFilter(RewriteOptions::kCanonicalizeJavascriptLibraries);
options()->DisableFilter(RewriteOptions::kRewriteJavascriptExternal);
rewrite_driver_->AddFilters();
InitTest(100);
ValidateExpected("inline_and_canonical_and_not_external",
StrCat(StringPrintf(kInlineJs, kJsData),
StringPrintf(kHtmlFormat, kOrigJsName)),
StrCat(StringPrintf(kInlineJs, kJsMinData),
StringPrintf(kHtmlFormat, kOrigJsName)));
}
TEST_P(JavascriptFilterTest, ExternalAndNotInline) {
options()->EnableFilter(RewriteOptions::kRewriteJavascriptExternal);
options()->DisableFilter(RewriteOptions::kRewriteJavascriptInline);
rewrite_driver_->AddFilters();
InitTest(100);
ValidateExpected("external_not_inline",
StrCat(StringPrintf(kInlineJs, kJsData),
StringPrintf(kHtmlFormat, kOrigJsName)),
StrCat(StringPrintf(kInlineJs, kJsData),
StringPrintf(kHtmlFormat,
expected_rewritten_path_.c_str())));
}
TEST_P(JavascriptFilterTest, ContentTypeValidation) {
ValidateFallbackHeaderSanitization(kFilterId);
}
// We test with use_experimental_minifier == GetParam() as both true and false.
INSTANTIATE_TEST_CASE_P(JavascriptFilterTestInstance, JavascriptFilterTest,
::testing::Bool());
} // namespace net_instaweb