| /* |
| * 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: satyanarayana@google.com (Satyanarayana Manyam) |
| |
| #include "net/instaweb/rewriter/public/add_instrumentation_filter.h" |
| |
| #include "net/instaweb/http/public/request_context.h" |
| #include "net/instaweb/http/public/request_timing_info.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/escaping.h" |
| #include "pagespeed/kernel/base/gtest.h" |
| #include "pagespeed/kernel/base/null_message_handler.h" |
| #include "pagespeed/kernel/base/ref_counted_ptr.h" |
| #include "pagespeed/kernel/base/statistics.h" |
| #include "pagespeed/kernel/html/html_keywords.h" |
| #include "pagespeed/kernel/html/html_name.h" |
| #include "pagespeed/kernel/html/html_parse_test_base.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/user_agent_matcher_test_base.h" |
| |
| namespace net_instaweb { |
| |
| class AddInstrumentationFilterTest : public RewriteTestBase { |
| protected: |
| AddInstrumentationFilterTest() {} |
| |
| virtual void SetUp() { |
| options()->set_beacon_url("http://example.com/beacon?org=xxx"); |
| AddInstrumentationFilter::InitStats(statistics()); |
| options()->EnableFilter(RewriteOptions::kAddInstrumentation); |
| RewriteTestBase::SetUp(); |
| report_unload_time_ = false; |
| xhtml_mode_ = false; |
| cdata_mode_ = false; |
| https_mode_ = false; |
| } |
| |
| virtual bool AddBody() const { return false; } |
| |
| void AddFilters() { |
| AddFiltersWithUserAgent(UserAgentMatcherTestBase::kChrome18UserAgent); |
| } |
| |
| void AddFiltersWithUserAgent(StringPiece user_agent) { |
| SetCurrentUserAgent(user_agent); |
| SetDriverRequestHeaders(); |
| rewrite_driver()->AddFilters(); |
| } |
| |
| void RunInjection() { |
| options()->set_report_unload_time(report_unload_time_); |
| AddFilters(); |
| ParseUrl(GetTestUrl(), |
| "<head></head><head></head><body></body><body></body>"); |
| EXPECT_EQ(1, statistics()->GetVariable( |
| AddInstrumentationFilter::kInstrumentationScriptAddedCount)->Get()); |
| } |
| |
| void SetMimetypeToXhtml() { |
| SetXhtmlMimetype(); |
| xhtml_mode_ = !cdata_mode_; |
| } |
| |
| void DoNotRelyOnContentType() { |
| cdata_mode_ = true; |
| server_context()->set_response_headers_finalized(false); |
| } |
| |
| void AssumeHttps() { |
| https_mode_ = true; |
| } |
| |
| GoogleString GetTestUrl() { |
| return StrCat((https_mode_ ? "https://example.com/" : kTestDomain), |
| "index.html?a&b"); |
| } |
| |
| GoogleString CreateInitString(StringPiece beacon_url, |
| StringPiece event, |
| StringPiece extra_params) { |
| GoogleString url; |
| EscapeToJsStringLiteral(rewrite_driver()->google_url().Spec(), false, &url); |
| GoogleString str = "pagespeed.addInstrumentationInit("; |
| StrAppend(&str, "'", beacon_url, "', "); |
| StrAppend(&str, "'", event, "', "); |
| StrAppend(&str, "'", extra_params, "', "); |
| StrAppend(&str, "'", url, "');"); |
| return str; |
| } |
| |
| bool report_unload_time_; |
| bool xhtml_mode_; |
| bool cdata_mode_; |
| bool https_mode_; |
| ResponseHeaders response_headers_; |
| }; |
| |
| TEST_F(AddInstrumentationFilterTest, ScriptInjection) { |
| RunInjection(); |
| EXPECT_TRUE(output_buffer_.find( |
| CreateInitString( |
| options()->beacon_url().http, "load", "")) != |
| GoogleString::npos); |
| } |
| |
| TEST_F(AddInstrumentationFilterTest, ScriptInjectionWithNavigation) { |
| report_unload_time_ = true; |
| RunInjection(); |
| EXPECT_TRUE(output_buffer_.find( |
| CreateInitString( |
| options()->beacon_url().http, "beforeunload", "")) != |
| GoogleString::npos); |
| } |
| |
| // Test an https fetch. |
| TEST_F(AddInstrumentationFilterTest, TestScriptInjectionWithHttps) { |
| AssumeHttps(); |
| RunInjection(); |
| EXPECT_TRUE(output_buffer_.find( |
| CreateInitString( |
| options()->beacon_url().https, "load", "")) != |
| GoogleString::npos); |
| } |
| |
| // Test an https fetch, reporting unload and using Xhtml |
| TEST_F(AddInstrumentationFilterTest, |
| TestScriptInjectionWithHttpsUnloadAndXhtml) { |
| SetMimetypeToXhtml(); |
| AssumeHttps(); |
| report_unload_time_ = true; |
| RunInjection(); |
| EXPECT_TRUE(output_buffer_.find( |
| CreateInitString( |
| options()->beacon_url().https, "beforeunload", "")) != |
| GoogleString::npos); |
| } |
| |
| // Test that experiment id reporting is done correctly. |
| TEST_F(AddInstrumentationFilterTest, TestExperimentIdReporting) { |
| NullMessageHandler handler; |
| options()->set_running_experiment(true); |
| options()->AddExperimentSpec("id=2;percent=10;slot=4;", &handler); |
| options()->AddExperimentSpec("id=7;percent=10;level=CoreFilters;slot=4;", |
| &handler); |
| options()->SetExperimentState(2); |
| RunInjection(); |
| EXPECT_TRUE(output_buffer_.find( |
| CreateInitString( |
| options()->beacon_url().http, "load", "&exptid=2")) != |
| GoogleString::npos); |
| } |
| |
| // Test that extended instrumentation is injected properly. |
| TEST_F(AddInstrumentationFilterTest, TestExtendedInstrumentation) { |
| options()->set_enable_extended_instrumentation(true); |
| RunInjection(); |
| EXPECT_TRUE(output_buffer_.find( |
| CreateInitString( |
| options()->beacon_url().http, "load", "")) != |
| GoogleString::npos); |
| EXPECT_TRUE(output_buffer_.find("getResourceTimingData=function()") != |
| GoogleString::npos); |
| } |
| |
| // Test that headers fetch timing reporting is done correctly. |
| TEST_F(AddInstrumentationFilterTest, TestHeadersFetchTimingReporting) { |
| RequestTimingInfo* timing_info = mutable_timing_info(); |
| timing_info->FetchStarted(); |
| AdvanceTimeMs(200); |
| timing_info->FetchHeaderReceived(); |
| AdvanceTimeMs(100); |
| timing_info->FirstByteReturned(); |
| AdvanceTimeMs(200); |
| timing_info->FetchFinished(); |
| RunInjection(); |
| EXPECT_TRUE(output_buffer_.find( |
| CreateInitString( |
| options()->beacon_url().http, "load", "&hft=200&ft=500&s_ttfb=300")) |
| != GoogleString::npos) << output_buffer_; |
| } |
| |
| |
| // Test that head script is inserted after title and meta tags. |
| TEST_F(AddInstrumentationFilterTest, TestScriptAfterTitleAndMeta) { |
| AddFilters(); |
| ParseUrl(GetTestUrl(), |
| "<head><meta name='abc' /><title></title></head><body></body>"); |
| EXPECT_TRUE(output_buffer_.find( |
| "<head><meta name='abc' /><title></title><script")); |
| } |
| |
| TEST_F(AddInstrumentationFilterTest, TestNon200Response) { |
| AddFilters(); |
| response_headers_.set_status_code(HttpStatus::kForbidden); |
| rewrite_driver()->set_response_headers_ptr(&response_headers_); |
| ParseUrl(GetTestUrl(), |
| "<head></head><head></head><body></body><body></body>"); |
| EXPECT_EQ(1, statistics()->GetVariable( |
| AddInstrumentationFilter::kInstrumentationScriptAddedCount)->Get()); |
| EXPECT_TRUE(output_buffer_.find( |
| CreateInitString( |
| options()->beacon_url().http, "load", "&rc=403")) != |
| GoogleString::npos); |
| } |
| |
| TEST_F(AddInstrumentationFilterTest, TestRequestId) { |
| rewrite_driver()->request_context()->set_request_id(1234567890L); |
| RunInjection(); |
| EXPECT_TRUE(output_buffer_.find( |
| CreateInitString(options()->beacon_url().http, "load", |
| "&id=1234567890")) != GoogleString::npos); |
| } |
| |
| TEST_F(AddInstrumentationFilterTest, TestNoDeferInstrumentationScript) { |
| RunInjection(); |
| EXPECT_TRUE(output_buffer_.find( |
| CreateInitString( |
| options()->beacon_url().http, "load", "")) != |
| GoogleString::npos); |
| const StringPiece* nodefer = |
| HtmlKeywords::KeywordToString(HtmlName::kDataPagespeedNoDefer); |
| EXPECT_TRUE(output_buffer_.find(nodefer->as_string()) != GoogleString::npos); |
| } |
| |
| TEST_F(AddInstrumentationFilterTest, TestDeferInstrumentationScript) { |
| rewrite_driver()->set_defer_instrumentation_script(true); |
| RunInjection(); |
| EXPECT_TRUE(output_buffer_.find( |
| CreateInitString( |
| options()->beacon_url().http, "load", "")) != |
| GoogleString::npos); |
| const StringPiece* nodefer = |
| HtmlKeywords::KeywordToString(HtmlName::kDataPagespeedNoDefer); |
| EXPECT_TRUE(output_buffer_.find(nodefer->as_string()) == GoogleString::npos); |
| } |
| |
| TEST_F(AddInstrumentationFilterTest, TestDisableForBots) { |
| AddFiltersWithUserAgent(UserAgentMatcherTestBase::kGooglebotUserAgent); |
| ValidateNoChanges(GetTestUrl(), |
| "<head></head><head></head><body></body><body></body>"); |
| } |
| |
| } // namespace net_instaweb |