blob: b585e6dc5d6bdc2d0ef61557a81bb9b7eb476abc [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: slamm@google.com (Stephen Lamm)
#include "net/instaweb/rewriter/public/google_analytics_filter.h"
#include "net/instaweb/rewriter/google_analytics_snippet.h"
#include "net/instaweb/rewriter/public/rewrite_driver.h"
#include "net/instaweb/rewriter/public/rewrite_test_base.h"
#include "pagespeed/kernel/base/gtest.h"
#include "pagespeed/kernel/base/string.h"
#include "pagespeed/kernel/base/string_util.h"
namespace net_instaweb {
namespace {
// Expected async glue methods as written to the html output.
// The test sets this list in SetUp().
const char kAsyncGlueMethods[] =
" '_trackPageview',\n"
" '_setCookiePath'\n";
// Typical initialization code for the synchronous snippet.
const char kSyncInit[] =
"<script type=\"text/javascript\">\n"
"try {\n"
"var pageTracker = _gat._getTracker(\"UA-XXXXX-X\");\n"
"pageTracker._trackPageview();"
"} catch(err) {}"
"</script>";
// The initialization code with _getTracker replaced.
const char kAsyncGlueInit[] =
"<script type=\"text/javascript\">\n"
"try {\n"
"var pageTracker = _modpagespeed_getRewriteTracker(\"UA-XXXXX-X\");\n"
"pageTracker._trackPageview();"
"} catch(err) {}"
"</script>";
class GoogleAnalyticsFilterTest : public RewriteTestBase {
protected:
virtual void SetUp() {
// Setup the statistics.
RewriteTestBase::SetUp();
GoogleAnalyticsFilter::InitStats(statistics());
GoogleAnalyticsFilter::MethodVector* glue_methods =
new GoogleAnalyticsFilter::MethodVector;
GoogleAnalyticsFilter::MethodVector* unhandled_methods =
new GoogleAnalyticsFilter::MethodVector;
glue_methods->push_back("_trackPageview");
glue_methods->push_back("_setCookiePath");
unhandled_methods->push_back("_get");
unhandled_methods->push_back("_getLinkerUrl");
RewriteDriver* driver = rewrite_driver();
driver->AppendOwnedPreRenderFilter(new GoogleAnalyticsFilter(
driver, statistics(), glue_methods, unhandled_methods));
}
// Create the expected html.
GoogleString GetAsyncLoadAndInit(
const StringPiece& load_prefix, const StringPiece& load_suffix) const {
return StrCat(
load_prefix,
kGaSnippetPrefix, // from google_analytics_snippet.h
kAsyncGlueMethods,
kGaSnippetSuffix, // from google_analytics_snippet.h
load_suffix,
kAsyncGlueInit);
}
GoogleAnalyticsFilter* google_analytics_filter_;
};
TEST_F(GoogleAnalyticsFilterTest, SyncScriptSrcMadeAsync) {
GoogleString html_input = StrCat(
"<script src=\"http://www.google-analytics.com/ga.js\""
" type=\"text/javascript\">\n"
"</script>\n",
kSyncInit);
GoogleString expected_html = GetAsyncLoadAndInit(
"<script type=\"text/javascript\">",
"\n</script>\n"); // starting newline leftover from empty script
ValidateExpected("sync_script_src_made_async",
html_input, expected_html);
}
// Test the boundary case of load script without a characters node
TEST_F(GoogleAnalyticsFilterTest, SyncScriptSrcNoCharactersNodeMadeAsync) {
GoogleString html_input = StrCat(
"<script src=\"http://www.google-analytics.com/ga.js\""
" type=\"text/javascript\"></script>\n",
kSyncInit);
GoogleString expected_html = GetAsyncLoadAndInit(
"<script type=\"text/javascript\">",
"</script>\n"); // no newline between script tags
ValidateExpected("sync_script_src_no_characters_node_made_async",
html_input, expected_html);
}
TEST_F(GoogleAnalyticsFilterTest, SyncDocumentWriteMadeAsync) {
GoogleString html_input = StrCat(
"<script type='text/javascript'>\n"
"var gaJsHost = ((\"https:\" == document.location.protocol) ? "
"\"https://ssl.\" : \"http://www.\");\n"
"document.write(unescape(\"%3Cscript src=\" + gaJsHost + \"google-"
"analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E\"));\n"
"</script>\n",
kSyncInit);
GoogleString expected_html = GetAsyncLoadAndInit(
"<script type='text/javascript'>\n"
"var gaJsHost = ((\"https:\" == document.location.protocol) ? "
"\"https://ssl.\" : \"http://www.\");\n",
"\n</script>\n");
ValidateExpected("sync_document_write_made_async",
html_input, expected_html);
}
// Test the boundary case of "document.write" at the first position
// of the characters node.
TEST_F(GoogleAnalyticsFilterTest, SyncDocumentWriteAtPositionZero) {
GoogleString html_input = StrCat(
"<script type='text/javascript'>document.write("
"unescape(\"%3Cscript src=\"http://www.google-analytics.com/ga.js' "
"type='text/javascript'%3E%3C/script%3E\"));\n"
"</script>\n",
kSyncInit);
GoogleString expected_html = GetAsyncLoadAndInit(
"<script type='text/javascript'>",
"\n</script>\n");
ValidateExpected("sync_document_write_made_async",
html_input, expected_html);
}
TEST_F(GoogleAnalyticsFilterTest, MultipleLoadReducedToSingle) {
GoogleString html_input = StrCat(
"<script type='text/javascript'>document.write("
"unescape(\"%3Cscript src=\"http://www.google-analytics.com/ga.js' "
"type='text/javascript'%3E%3C/script%3E\"));\n"
"</script>\n",
kSyncInit,
"<script type='text/javascript'>\n"
"var gaJsHost = ((\"https:\" == document.location.protocol) ? "
"\"https://ssl.\" : \"http://www.\");\n"
"document.write(unescape(\"%3Cscript src=\" + gaJsHost + \"google-"
"analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E\"));\n"
"</script>\n",
kSyncInit,
"<script src=\"http://www.google-analytics.com/ga.js\""
" type=\"text/javascript\"></script>",
kSyncInit);
GoogleString expected_html = StrCat(
GetAsyncLoadAndInit(
"<script type='text/javascript'>",
"\n</script>\n"),
"<script type='text/javascript'>\n"
"var gaJsHost = ((\"https:\" == document.location.protocol) ?"
" \"https://ssl.\" : \"http://www.\");\n"
// The second load (document.write) is removed completely.
"\n"
"</script>\n",
kAsyncGlueInit,
// The third load (script src) is removed completely.
kAsyncGlueInit);
ValidateExpected("multiple_load_reduced to_single",
html_input, expected_html);
}
TEST_F(GoogleAnalyticsFilterTest, AsyncGiveNoChanges) {
GoogleString html_input =
"<script type=\"text/javascript\">\n"
"var _gaq = _gaq || [];\n"
"_gaq.push(['_setAccount', 'UA-XXXXX-X']);\n"
"_gaq.push(['_trackPageview']);\n"
"(function() {\n"
" var ga = document.createElement('script');\n"
" ga.type = 'text/javascript'; ga.async = true;\n"
" ga.src = ('https:' == document.location.protocol ? 'https://ssl' : "
"'http://www') +\n"
" '.google-analytics.com/ga.js';\n"
" var s = document.getElementsByTagName('script')[0]\n"
" s.parentNode.insertBefore(ga, s);\n"
"})();\n"
"</script>\n";
ValidateNoChanges("async_unchanged", html_input);
}
TEST_F(GoogleAnalyticsFilterTest, NonstandardInitGiveNoChanges) {
GoogleString html_input =
"<script type=\"text/javascript\">\n"
"googleAnalytics = new TFHtmlUtilsGoogleAnalytics({\n"
" 'trackingcode': \"UA-XXXXX-X\"\n"
"});\n"
"googleAnalytics.trackPageview();\n"
"} catch(err) {}\n"
"</script>\n";
ValidateNoChanges("nonstandard_init_unchanged", html_input);
}
TEST_F(GoogleAnalyticsFilterTest, UnhandledCallCausesSkippedRewrite) {
GoogleString html_input = StrCat(
"<script src=\"http://www.google-analytics.com/ga.js\""
" type=\"text/javascript\"></script>\n",
kSyncInit,
// _getLinkerUrl is listed as an unhandled call.
"<script type=\"text/javascript\">pageTracker._getLinkerUrl ();\n"
"</script>\n");
ValidateNoChanges("unhandled_call_causes_skipped_rewrite", html_input);
}
TEST_F(GoogleAnalyticsFilterTest, UnknownCallStillAllowsRewrite) {
GoogleString html_input = StrCat(
"<script src=\"http://www.google-analytics.com/ga.js\""
" type=\"text/javascript\"></script>\n",
kSyncInit,
// _getFoo is not explicitly listed as an unhandled call.
"<script type=\"text/javascript\">pageTracker._getFoo();</script>\n");
GoogleString expected_html = StrCat(
GetAsyncLoadAndInit(
"<script type=\"text/javascript\">",
"</script>\n"),
"<script type=\"text/javascript\">pageTracker._getFoo();</script>\n");
ValidateExpected("unknown_call_still_allows_rewrite",
html_input, expected_html);
}
} // namespace
} // namespace net_instaweb