blob: 07b8a1c4f0b026e347579d1c8b8cf653b46b9de9 [file] [log] [blame]
/*
* Copyright 2012 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: guptaa@google.com (Ashish Gupta)
#include "net/instaweb/rewriter/public/static_asset_manager.h"
#include "net/instaweb/rewriter/public/common_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 "pagespeed/kernel/base/gtest.h"
#include "pagespeed/kernel/base/scoped_ptr.h"
#include "pagespeed/kernel/base/string_util.h"
#include "pagespeed/kernel/html/html_element.h"
#include "pagespeed/kernel/html/html_name.h"
#include "pagespeed/kernel/html/html_parse_test_base.h"
#include "pagespeed/kernel/http/content_type.h"
namespace net_instaweb {
namespace {
const char kHtml[] = "<body><br></body>";
const char kScript[] = "alert('foo');";
class StaticAssetManagerTest : public RewriteTestBase {
protected:
StaticAssetManagerTest() {
manager_.reset(new StaticAssetManager("http://proxy-domain",
server_context()->thread_system(),
server_context()->hasher(),
server_context()->message_handler()));
}
virtual void SetUp() {
RewriteTestBase::SetUp();
}
// Helper filters to help test inserting of static JS.
class AddStaticJsBeforeBr: public CommonFilter {
public:
explicit AddStaticJsBeforeBr(RewriteDriver* driver)
: CommonFilter(driver) { }
virtual void StartDocumentImpl() { }
virtual void StartElementImpl(HtmlElement* element) { }
virtual void EndElementImpl(HtmlElement* element) {
if (element->keyword() == HtmlName::kBr) {
HtmlElement* script = driver()->NewElement(element->parent(),
HtmlName::kScript);
driver()->InsertNodeBeforeNode(element, script);
AddJsToElement(kScript, script);
}
}
virtual const char* Name() const { return "AddStaticJsBeforeBr"; }
private:
DISALLOW_COPY_AND_ASSIGN(AddStaticJsBeforeBr);
};
// Extracts the first comment sequences in a script that doesn't
// contain any whitelisted substrings.
//
// TODO(jmarantz): Note that this is not a proper lexer and will be
// fooled by comment sequences in strings, thus this might require
// refactoring to use our js tokenizer in the future, should this
// yield false positives.
StringPiece ExtractCommentSkippingWhitelist(StringPiece script) {
StringPieceVector v;
stringpiece_ssize_type pos = 0;
while (true) {
pos = script.find("/*", pos);
if (pos == StringPiece::npos) {
return "";
}
stringpiece_ssize_type endpos = 0;
endpos = script.find("*/", pos + 2);
if (endpos == StringPiece::npos) {
return "";
}
endpos += 2;
StringPiece comment = script.substr(pos, endpos - pos);
if (comment.find("MochiKit") == StringPiece::npos) {
return comment;
}
pos = endpos;
}
}
scoped_ptr<StaticAssetManager> manager_;
};
TEST_F(StaticAssetManagerTest, TestBlinkHandler) {
const char blink_url[] = "http://proxy-domain/psajs/blink.0.js";
EXPECT_STREQ(blink_url, manager_->GetAssetUrl(StaticAssetEnum::BLINK_JS,
options_));
}
TEST_F(StaticAssetManagerTest, TestBlinkGstatic) {
manager_->set_static_asset_base("http://proxy-domain");
manager_->ServeAssetsFromGStatic(StaticAssetManager::kGStaticBase);
manager_->SetGStaticHashForTest(
StaticAssetEnum::BLINK_JS, "1");
const char blink_url[] =
"//www.gstatic.com/psa/static/1-blink.js";
EXPECT_STREQ(blink_url, manager_->GetAssetUrl(StaticAssetEnum::BLINK_JS,
options_));
}
TEST_F(StaticAssetManagerTest, TestBlinkDebug) {
manager_->ServeAssetsFromGStatic(StaticAssetManager::kGStaticBase);
manager_->SetGStaticHashForTest(StaticAssetEnum::BLINK_JS, "1");
options_->EnableFilter(RewriteOptions::kDebug);
const char blink_url[] = "//www.gstatic.com/psa/static/1-blink.js";
EXPECT_STREQ(blink_url, manager_->GetAssetUrl(StaticAssetEnum::BLINK_JS,
options_));
}
TEST_F(StaticAssetManagerTest, TestDeferJsGstatic) {
manager_->ServeAssetsFromGStatic(StaticAssetManager::kGStaticBase);
manager_->SetGStaticHashForTest(StaticAssetEnum::DEFER_JS, "1");
const char defer_js_url[] =
"//www.gstatic.com/psa/static/1-js_defer.js";
EXPECT_STREQ(defer_js_url, manager_->GetAssetUrl(
StaticAssetEnum::DEFER_JS, options_));
}
TEST_F(StaticAssetManagerTest, TestDeferJsDebug) {
manager_->ServeAssetsFromGStatic(StaticAssetManager::kGStaticBase);
manager_->SetGStaticHashForTest(StaticAssetEnum::DEFER_JS, "1");
options_->EnableFilter(RewriteOptions::kDebug);
const char defer_js_debug_url[] =
"//www.gstatic.com/psa/static/1-js_defer.js";
EXPECT_STREQ(defer_js_debug_url, manager_->GetAssetUrl(
StaticAssetEnum::DEFER_JS, options_));
}
TEST_F(StaticAssetManagerTest, TestDeferJsNonGStatic) {
const char defer_js_url[] =
"http://proxy-domain/psajs/js_defer.0.js";
EXPECT_STREQ(defer_js_url, manager_->GetAssetUrl(
StaticAssetEnum::DEFER_JS, options_));
}
TEST_F(StaticAssetManagerTest, TestDeferJsNonGStaticDebug) {
const char defer_js_url[] =
"http://proxy-domain/psajs/js_defer_debug.0.js";
options_->EnableFilter(RewriteOptions::kDebug);
EXPECT_STREQ(defer_js_url, manager_->GetAssetUrl(
StaticAssetEnum::DEFER_JS, options_));
}
TEST_F(StaticAssetManagerTest, TestJsDebug) {
options_->EnableFilter(RewriteOptions::kDebug);
for (int i = 0; i < StaticAssetEnum::StaticAsset_ARRAYSIZE; ++i) {
StaticAssetEnum::StaticAsset module =
static_cast<StaticAssetEnum::StaticAsset>(i);
// TODO(sligocki): This should generalize to all resources which don't have
// kContentTypeJs. But no interface provides content types currently :/
if (module != StaticAssetEnum::BLANK_GIF &&
module != StaticAssetEnum::MOBILIZE_CSS &&
module != StaticAssetEnum::MOBILIZE_LAYOUT_CSS) {
GoogleString script(manager_->GetAsset(module, options_));
// Debug code is also put through the closure compiler to resolve any uses
// of goog.require. As part of this, comments also get stripped out.
EXPECT_STREQ("", ExtractCommentSkippingWhitelist(script))
<< "Comment found in debug version of asset " << module;
}
}
}
TEST_F(StaticAssetManagerTest, TestJsOpt) {
for (int i = 0; i < StaticAssetEnum::StaticAsset_ARRAYSIZE; ++i) {
StaticAssetEnum::StaticAsset module =
static_cast<StaticAssetEnum::StaticAsset>(i);
// TODO(sligocki): This should generalize to all resources which don't have
// kContentTypeJs. But no interface provides content types currently :/
if (module != StaticAssetEnum::BLANK_GIF &&
module != StaticAssetEnum::MOBILIZE_CSS &&
module != StaticAssetEnum::MOBILIZE_LAYOUT_CSS) {
GoogleString script(manager_->GetAsset(module, options_));
EXPECT_STREQ("", ExtractCommentSkippingWhitelist(script))
<< "Comment found in debug version of asset " << module;
}
}
}
TEST_F(StaticAssetManagerTest, TestHtmlInsertInlineJs) {
SetHtmlMimetype();
AddStaticJsBeforeBr filter(rewrite_driver());
rewrite_driver()->AddFilter(&filter);
ParseUrl(kTestDomain, kHtml);
EXPECT_EQ("<html>\n<body><script type=\"text/javascript\">alert('foo');"
"</script><br></body></html>", output_buffer_);
}
TEST_F(StaticAssetManagerTest, TestXhtmlInsertInlineJs) {
SetXhtmlMimetype();
AddStaticJsBeforeBr filter(rewrite_driver());
rewrite_driver()->AddFilter(&filter);
ParseUrl(kTestDomain, kHtml);
EXPECT_EQ("<html>\n<body><script type=\"text/javascript\">//<![CDATA[\n"
"alert('foo');\n//]]></script><br></body></html>",
output_buffer_);
}
TEST_F(StaticAssetManagerTest, TestHtml5InsertInlineJs) {
SetHtmlMimetype();
AddStaticJsBeforeBr filter(rewrite_driver());
rewrite_driver()->AddFilter(&filter);
GoogleString html = StrCat("<!DOCTYPE html>", kHtml);
ParseUrl(kTestDomain, html);
EXPECT_EQ("<html>\n<!DOCTYPE html><body><script>alert('foo');"
"</script><br></body></html>", output_buffer_);
}
TEST_F(StaticAssetManagerTest, TestEncodedUrls) {
for (int i = 0; i < StaticAssetEnum::StaticAsset_ARRAYSIZE; ++i) {
StaticAssetEnum::StaticAsset module =
static_cast<StaticAssetEnum::StaticAsset>(i);
GoogleString url = manager_->GetAssetUrl(module, options_);
StringPiece file_name = url;
const StringPiece kDomainAndPath = "http://proxy-domain/psajs/";
ASSERT_TRUE(file_name.starts_with(kDomainAndPath));
file_name.remove_prefix(kDomainAndPath.size());
StringPiece content, cache_header;
ContentType content_type;
EXPECT_TRUE(manager_->GetAsset(file_name, &content, &content_type,
&cache_header));
EXPECT_EQ("max-age=31536000", cache_header);
}
}
TEST_F(StaticAssetManagerTest, FullGStaticConf) {
scoped_ptr<RewriteOptions> debug_options(options_->Clone());
debug_options->EnableFilter(RewriteOptions::kDebug);
manager_->ServeAssetsFromGStatic("http://actually_any_cdn.com/");
// Setup initial batch configuration.
StaticAssetConfig config;
config.set_release_label("100");
StaticAssetConfig::Asset* asset_conf = config.add_asset();
asset_conf->set_role(StaticAssetEnum::ADD_INSTRUMENTATION_JS);
asset_conf->set_name("add_instr.js");
asset_conf->set_debug_hash("dbg1");
asset_conf->set_opt_hash("opt1");
asset_conf = config.add_asset();
asset_conf->set_role(StaticAssetEnum::LAZYLOAD_IMAGES_JS);
asset_conf->set_name("lazy.js");
asset_conf->set_debug_hash("dbg2");
asset_conf->set_opt_hash("opt2");
manager_->ApplyGStaticConfiguration(
config, StaticAssetManager::kInitialConfiguration);
// The configuration is sparse, so things still retain defaults.
EXPECT_EQ("http://proxy-domain/psajs/js_defer.0.js",
manager_->GetAssetUrl(StaticAssetEnum::DEFER_JS, options_));
EXPECT_EQ("http://proxy-domain/psajs/js_defer_debug.0.js",
manager_->GetAssetUrl(StaticAssetEnum::DEFER_JS,
debug_options.get()));
// The configured things do work, however.
EXPECT_EQ("http://actually_any_cdn.com/opt1-add_instr.js",
manager_->GetAssetUrl(StaticAssetEnum::ADD_INSTRUMENTATION_JS,
options_));
EXPECT_EQ("http://actually_any_cdn.com/dbg1-add_instr.js",
manager_->GetAssetUrl(StaticAssetEnum::ADD_INSTRUMENTATION_JS,
debug_options.get()));
EXPECT_EQ("http://actually_any_cdn.com/opt2-lazy.js",
manager_->GetAssetUrl(StaticAssetEnum::LAZYLOAD_IMAGES_JS,
options_));
EXPECT_EQ("http://actually_any_cdn.com/dbg2-lazy.js",
manager_->GetAssetUrl(StaticAssetEnum::LAZYLOAD_IMAGES_JS,
debug_options.get()));
// Now try updating with a config with different version.
StaticAssetConfig config2;
config2.set_release_label("99");
asset_conf = config2.add_asset();
asset_conf->set_role(StaticAssetEnum::ADD_INSTRUMENTATION_JS);
asset_conf->set_name("add_instr.js");
asset_conf->set_debug_hash("dbg0");
asset_conf->set_opt_hash("opt0");
asset_conf = config2.add_asset();
asset_conf->set_role(StaticAssetEnum::LAZYLOAD_IMAGES_JS);
asset_conf->set_name("lazy.js");
asset_conf->set_debug_hash("dbg0");
asset_conf->set_opt_hash("opt0");
asset_conf = config2.add_asset();
asset_conf->set_role(StaticAssetEnum::DEFER_JS);
asset_conf->set_name("defer.js");
asset_conf->set_debug_hash("dbg0");
asset_conf->set_opt_hash("opt0");
manager_->ApplyGStaticConfiguration(
config2,
StaticAssetManager::kUpdateConfiguration);
// Nothing is actually changed.
EXPECT_EQ("http://proxy-domain/psajs/js_defer.0.js",
manager_->GetAssetUrl(StaticAssetEnum::DEFER_JS, options_));
EXPECT_EQ("http://proxy-domain/psajs/js_defer_debug.0.js",
manager_->GetAssetUrl(StaticAssetEnum::DEFER_JS,
debug_options.get()));
EXPECT_EQ("http://actually_any_cdn.com/opt1-add_instr.js",
manager_->GetAssetUrl(StaticAssetEnum::ADD_INSTRUMENTATION_JS,
options_));
EXPECT_EQ("http://actually_any_cdn.com/dbg1-add_instr.js",
manager_->GetAssetUrl(StaticAssetEnum::ADD_INSTRUMENTATION_JS,
debug_options.get()));
EXPECT_EQ("http://actually_any_cdn.com/opt2-lazy.js",
manager_->GetAssetUrl(StaticAssetEnum::LAZYLOAD_IMAGES_JS,
options_));
EXPECT_EQ("http://actually_any_cdn.com/dbg2-lazy.js",
manager_->GetAssetUrl(StaticAssetEnum::LAZYLOAD_IMAGES_JS,
debug_options.get()));
// Now with matching version, things can change. Notice that we still didn't
// update things that were not specified in initial config. This is a design
// tradeoff: it makes it harder to hotfix things, but means we won't get stuck
// with a stale override in CDD.
StaticAssetConfig config3;
config3.set_release_label("100");
asset_conf = config3.add_asset();
asset_conf->set_role(StaticAssetEnum::ADD_INSTRUMENTATION_JS);
asset_conf->set_name("add_instr.js");
asset_conf->set_debug_hash("dbg3");
asset_conf->set_opt_hash("opt3");
asset_conf = config3.add_asset();
asset_conf->set_role(StaticAssetEnum::LAZYLOAD_IMAGES_JS);
asset_conf->set_name("lazy.js");
asset_conf->set_debug_hash("dbg4");
asset_conf->set_opt_hash("opt4");
asset_conf = config3.add_asset();
asset_conf->set_role(StaticAssetEnum::DEFER_JS);
asset_conf->set_name("defer.js");
asset_conf->set_debug_hash("dbg5");
asset_conf->set_opt_hash("opt5");
manager_->ApplyGStaticConfiguration(
config3,
StaticAssetManager::kUpdateConfiguration);
// Everything that was initially configured via this is changed.
EXPECT_EQ("http://proxy-domain/psajs/js_defer.0.js",
manager_->GetAssetUrl(StaticAssetEnum::DEFER_JS, options_));
EXPECT_EQ("http://proxy-domain/psajs/js_defer_debug.0.js",
manager_->GetAssetUrl(StaticAssetEnum::DEFER_JS,
debug_options.get()));
EXPECT_EQ("http://actually_any_cdn.com/opt3-add_instr.js",
manager_->GetAssetUrl(StaticAssetEnum::ADD_INSTRUMENTATION_JS,
options_));
EXPECT_EQ("http://actually_any_cdn.com/dbg3-add_instr.js",
manager_->GetAssetUrl(StaticAssetEnum::ADD_INSTRUMENTATION_JS,
debug_options.get()));
EXPECT_EQ("http://actually_any_cdn.com/opt4-lazy.js",
manager_->GetAssetUrl(StaticAssetEnum::LAZYLOAD_IMAGES_JS,
options_));
EXPECT_EQ("http://actually_any_cdn.com/dbg4-lazy.js",
manager_->GetAssetUrl(StaticAssetEnum::LAZYLOAD_IMAGES_JS,
debug_options.get()));
// Now test that resetting config works.
manager_->ResetGStaticConfiguration();
EXPECT_EQ("http://proxy-domain/psajs/js_defer.0.js",
manager_->GetAssetUrl(StaticAssetEnum::DEFER_JS, options_));
EXPECT_EQ("http://proxy-domain/psajs/js_defer_debug.0.js",
manager_->GetAssetUrl(StaticAssetEnum::DEFER_JS,
debug_options.get()));
// The configured things do work, however.
EXPECT_EQ("http://actually_any_cdn.com/opt1-add_instr.js",
manager_->GetAssetUrl(StaticAssetEnum::ADD_INSTRUMENTATION_JS,
options_));
EXPECT_EQ("http://actually_any_cdn.com/dbg1-add_instr.js",
manager_->GetAssetUrl(StaticAssetEnum::ADD_INSTRUMENTATION_JS,
debug_options.get()));
EXPECT_EQ("http://actually_any_cdn.com/opt2-lazy.js",
manager_->GetAssetUrl(StaticAssetEnum::LAZYLOAD_IMAGES_JS,
options_));
EXPECT_EQ("http://actually_any_cdn.com/dbg2-lazy.js",
manager_->GetAssetUrl(StaticAssetEnum::LAZYLOAD_IMAGES_JS,
debug_options.get()));
}
} // namespace
} // namespace net_instaweb