// Copyright 2017 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include #include "base/strings/string_number_conversions.h" #include "base/strings/stringprintf.h" #include "content/public/test/browser_test.h" #include "headless/public/devtools/domains/emulation.h" #include "headless/public/devtools/domains/page.h" #include "headless/public/devtools/domains/runtime.h" #include "headless/public/headless_devtools_client.h" #include "headless/public/util/testing/test_in_memory_protocol_handler.h" #include "headless/test/headless_browser_test.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" #include "url/gurl.h" using testing::ElementsAre; using testing::Contains; namespace headless { class VirtualTimeBrowserTest : public HeadlessAsyncDevTooledBrowserTest, public emulation::ExperimentalObserver, public runtime::Observer { public: void SetInitialURL(const std::string& initial_url) { initial_url_ = initial_url; } void RunDevTooledTest() override { devtools_client_->GetEmulation()->GetExperimental()->AddObserver(this); devtools_client_->GetRuntime()->AddObserver(this); devtools_client_->GetRuntime()->Enable(base::BindRepeating( &VirtualTimeBrowserTest::RuntimeEnabled, base::Unretained(this))); } void RuntimeEnabled() { runtime_enabled = true; MaybeSetVirtualTimePolicy(); } virtual void MaybeSetVirtualTimePolicy() { if (!runtime_enabled) return; SetVirtualTimePolicy(); } void SetVirtualTimePolicyDone( std::unique_ptr result) { // Virtual time is paused, so start navigating. devtools_client_->GetPage()->Navigate(initial_url_); } virtual void SetVirtualTimePolicy() { devtools_client_->GetEmulation()->GetExperimental()->SetVirtualTimePolicy( emulation::SetVirtualTimePolicyParams::Builder() .SetPolicy( emulation::VirtualTimePolicy::PAUSE_IF_NETWORK_FETCHES_PENDING) .SetBudget(5000) .SetWaitForNavigation(true) .Build(), base::BindRepeating(&VirtualTimeBrowserTest::SetVirtualTimePolicyDone, base::Unretained(this))); } // runtime::Observer implementation: void OnConsoleAPICalled( const runtime::ConsoleAPICalledParams& params) override { // We expect the arguments always to be a single string. const std::vector>& args = *params.GetArgs(); if (args.size() == 1u && args[0]->HasValue()) log_.push_back(args[0]->GetValue()->GetString()); } std::string initial_url_; std::vector log_; bool initial_load_seen_ = false; bool runtime_enabled = false; }; class VirtualTimeObserverTest : public VirtualTimeBrowserTest { public: VirtualTimeObserverTest() { EXPECT_TRUE(embedded_test_server()->Start()); SetInitialURL( embedded_test_server()->GetURL("/virtual_time_test.html").spec()); } // emulation::Observer implementation: void OnVirtualTimeBudgetExpired( const emulation::VirtualTimeBudgetExpiredParams& params) override { std::vector expected_log = {"Paused @ 0ms", "Advanced to 10ms", "step1", "Advanced to 110ms", "step2", "Paused @ 110ms", "Advanced to 210ms", "step3", "Paused @ 210ms", "Advanced to 310ms", "step4", "pass"}; // Note after the PASS step there are a number of virtual time advances, but // this list seems to be non-deterministic because there's all sorts of // timers in the system. We don't really care about that here. ASSERT_GE(log_.size(), expected_log.size()); for (size_t i = 0; i < expected_log.size(); i++) { EXPECT_EQ(expected_log[i], log_[i]) << "At index " << i; } EXPECT_THAT(log_, Contains("Advanced to 5000ms")); EXPECT_THAT(log_, Contains("Paused @ 5000ms")); FinishAsynchronousTest(); } void OnVirtualTimeAdvanced( const emulation::VirtualTimeAdvancedParams& params) override { log_.push_back(base::StringPrintf("Advanced to %.fms", params.GetVirtualTimeElapsed())); } void OnVirtualTimePaused( const emulation::VirtualTimePausedParams& params) override { log_.push_back( base::StringPrintf("Paused @ %.fms", params.GetVirtualTimeElapsed())); } }; HEADLESS_ASYNC_DEVTOOLED_TEST_F(VirtualTimeObserverTest); class VirtualTimeBaseTest : public VirtualTimeBrowserTest { public: VirtualTimeBaseTest() { EXPECT_TRUE(embedded_test_server()->Start()); SetInitialURL(embedded_test_server()->GetURL("/blank.html").spec()); } void SetVirtualTimePolicy() override { // Set to paused initially. devtools_client_->GetEmulation()->GetExperimental()->SetVirtualTimePolicy( emulation::SetVirtualTimePolicyParams::Builder() .SetPolicy(emulation::VirtualTimePolicy::PAUSE) .Build(), base::BindRepeating(&VirtualTimeBaseTest::SetFirstVirtualTimePolicyDone, base::Unretained(this))); } void SetFirstVirtualTimePolicyDone( std::unique_ptr result) { // Should have enabled virtual time, and returned a valid time base. virtual_time_base_ = result->GetVirtualTimeBase(); EXPECT_NE(0, virtual_time_base_); // Set with wait_for_navigation, the returned time base shouldn't change. devtools_client_->GetEmulation()->GetExperimental()->SetVirtualTimePolicy( emulation::SetVirtualTimePolicyParams::Builder() .SetPolicy( emulation::VirtualTimePolicy::PAUSE_IF_NETWORK_FETCHES_PENDING) .SetBudget(1000) .SetWaitForNavigation(true) .Build(), base::BindRepeating( &VirtualTimeBaseTest::SetSecondVirtualTimePolicyDone, base::Unretained(this))); } void SetSecondVirtualTimePolicyDone( std::unique_ptr result) { EXPECT_EQ(virtual_time_base_, result->GetVirtualTimeBase()); SetVirtualTimePolicyDone(std::move(result)); } // emulation::Observer implementation: void OnVirtualTimeBudgetExpired( const emulation::VirtualTimeBudgetExpiredParams& params) override { FinishAsynchronousTest(); } private: double virtual_time_base_ = 0; }; HEADLESS_ASYNC_DEVTOOLED_TEST_F(VirtualTimeBaseTest); class MaxVirtualTimeTaskStarvationCountTest : public VirtualTimeBrowserTest { public: MaxVirtualTimeTaskStarvationCountTest() { EXPECT_TRUE(embedded_test_server()->Start()); SetInitialURL(embedded_test_server() ->GetURL("/virtual_time_starvation_test.html") .spec()); } void SetVirtualTimePolicy() override { devtools_client_->GetEmulation()->GetExperimental()->SetVirtualTimePolicy( emulation::SetVirtualTimePolicyParams::Builder() .SetPolicy( emulation::VirtualTimePolicy::PAUSE_IF_NETWORK_FETCHES_PENDING) .SetBudget(4001) .SetMaxVirtualTimeTaskStarvationCount(100) .SetWaitForNavigation(true) .Build(), base::BindRepeating(&VirtualTimeBrowserTest::SetVirtualTimePolicyDone, base::Unretained(this))); } // emulation::Observer implementation: void OnVirtualTimeBudgetExpired( const emulation::VirtualTimeBudgetExpiredParams& params) override { // If SetMaxVirtualTimeTaskStarvationCount was not set, this callback would // never fire. FinishAsynchronousTest(); } }; HEADLESS_ASYNC_DEVTOOLED_TEST_F(MaxVirtualTimeTaskStarvationCountTest); namespace { static constexpr char kIndexHtml[] = R"( )"; static constexpr char kIFrame[] = R"(

Hello from the iframe!

)"; static constexpr char kCss[] = R"( .test { color: blue; } )"; } // namespace class FrameDetatchWithPendingResourceLoadVirtualTimeTest : public VirtualTimeBrowserTest, public TestInMemoryProtocolHandler::RequestDeferrer { public: FrameDetatchWithPendingResourceLoadVirtualTimeTest() { SetInitialURL("http://test.com/index.html"); } ProtocolHandlerMap GetProtocolHandlers() override { ProtocolHandlerMap protocol_handlers; std::unique_ptr http_handler( new TestInMemoryProtocolHandler(browser()->BrowserIOThread(), this)); http_handler_ = http_handler.get(); http_handler->InsertResponse("http://test.com/index.html", {kIndexHtml, "text/html"}); http_handler->InsertResponse("http://test.com/iframe.html", {kIFrame, "text/html"}); http_handler->InsertResponse("http://test.com/style.css", {kCss, "text/css"}); protocol_handlers[url::kHttpScheme] = std::move(http_handler); return protocol_handlers; } void RunDevTooledTest() override { http_handler_->SetHeadlessBrowserContext(browser_context_); VirtualTimeBrowserTest::RunDevTooledTest(); } void OnRequest(const GURL& url, base::Closure complete_request) override { // Note this is called on the IO thread. if (url.spec() == "http://test.com/style.css") { // Detach the iframe but leave the css resource fetch hanging. browser()->BrowserMainThread()->PostTask( FROM_HERE, base::BindRepeating( &FrameDetatchWithPendingResourceLoadVirtualTimeTest:: DetatchIFrame, base::Unretained(this))); } else { complete_request.Run(); } } void DetatchIFrame() { devtools_client_->GetRuntime()->Evaluate( "let elem = document.getElementById('iframe1');\n" "elem.parentNode.removeChild(elem);"); } // emulation::Observer implementation: void OnVirtualTimeBudgetExpired( const emulation::VirtualTimeBudgetExpiredParams& params) override { EXPECT_THAT( http_handler_->urls_requested(), ElementsAre("http://test.com/index.html", "http://test.com/iframe.html", "http://test.com/style.css")); // Virtual time should still expire, despite the CSS resource load not // finishing. FinishAsynchronousTest(); } TestInMemoryProtocolHandler* http_handler_; // NOT OWNED }; HEADLESS_ASYNC_DEVTOOLED_TEST_F( FrameDetatchWithPendingResourceLoadVirtualTimeTest); class VirtualTimeLocalStorageTest : public VirtualTimeBrowserTest { public: VirtualTimeLocalStorageTest() { EXPECT_TRUE(embedded_test_server()->Start()); SetInitialURL(embedded_test_server() ->GetURL("/virtual_time_local_storage.html") .spec()); } void OnConsoleAPICalled( const runtime::ConsoleAPICalledParams& params) override { EXPECT_EQ(runtime::ConsoleAPICalledType::LOG, params.GetType()); ASSERT_EQ(1u, params.GetArgs()->size()); ASSERT_EQ(runtime::RemoteObjectType::STRING, (*params.GetArgs())[0]->GetType()); std::string count_string = (*params.GetArgs())[0]->GetValue()->GetString(); int count; ASSERT_TRUE(base::StringToInt(count_string, &count)) << count_string; EXPECT_EQ(count, 400); console_log_seen_ = true; } // emulation::Observer implementation: void OnVirtualTimeBudgetExpired( const emulation::VirtualTimeBudgetExpiredParams& params) override { // If SetMaxVirtualTimeTaskStarvationCount was not set, this callback would // never fire. EXPECT_TRUE(console_log_seen_); FinishAsynchronousTest(); } bool console_log_seen_ = false; }; HEADLESS_ASYNC_DEVTOOLED_TEST_F(VirtualTimeLocalStorageTest); class VirtualTimeSessionStorageTest : public VirtualTimeBrowserTest { public: VirtualTimeSessionStorageTest() { EXPECT_TRUE(embedded_test_server()->Start()); SetInitialURL(embedded_test_server() ->GetURL("/virtual_time_session_storage.html") .spec()); } void OnConsoleAPICalled( const runtime::ConsoleAPICalledParams& params) override { EXPECT_EQ(runtime::ConsoleAPICalledType::LOG, params.GetType()); ASSERT_EQ(1u, params.GetArgs()->size()); ASSERT_EQ(runtime::RemoteObjectType::STRING, (*params.GetArgs())[0]->GetType()); std::string count_string = (*params.GetArgs())[0]->GetValue()->GetString(); int count; ASSERT_TRUE(base::StringToInt(count_string, &count)) << count_string; EXPECT_EQ(count, 400); console_log_seen_ = true; } // emulation::Observer implementation: void OnVirtualTimeBudgetExpired( const emulation::VirtualTimeBudgetExpiredParams& params) override { // If SetMaxVirtualTimeTaskStarvationCount was not set, this callback would // never fire. EXPECT_TRUE(console_log_seen_); FinishAsynchronousTest(); } bool console_log_seen_ = false; }; HEADLESS_ASYNC_DEVTOOLED_TEST_F(VirtualTimeSessionStorageTest); class DeferredLoadDoesntBlockVirtualTimeTest : public VirtualTimeBrowserTest { public: DeferredLoadDoesntBlockVirtualTimeTest() { EXPECT_TRUE(embedded_test_server()->Start()); SetInitialURL(embedded_test_server()->GetURL("/video.html").spec()); } // emulation::Observer implementation: void OnVirtualTimeBudgetExpired( const emulation::VirtualTimeBudgetExpiredParams& params) override { // The video should not block virtual time. FinishAsynchronousTest(); } }; HEADLESS_ASYNC_DEVTOOLED_TEST_F(DeferredLoadDoesntBlockVirtualTimeTest); class Http404DoesntBlockVirtualTimeTest : public VirtualTimeBrowserTest { public: Http404DoesntBlockVirtualTimeTest() { EXPECT_TRUE(embedded_test_server()->Start()); SetInitialURL(embedded_test_server()->GetURL("/NoSuchFile.html").spec()); } // emulation::Observer implementation: void OnVirtualTimeBudgetExpired( const emulation::VirtualTimeBudgetExpiredParams& params) override { // The video should not block virtual time. FinishAsynchronousTest(); } }; HEADLESS_ASYNC_DEVTOOLED_TEST_F(Http404DoesntBlockVirtualTimeTest); class RedirectVirtualTimeTest : public VirtualTimeBrowserTest { public: RedirectVirtualTimeTest() { SetInitialURL("http://test.com/index.html"); } ProtocolHandlerMap GetProtocolHandlers() override { ProtocolHandlerMap protocol_handlers; std::unique_ptr http_handler( new TestInMemoryProtocolHandler(browser()->BrowserIOThread(), nullptr)); http_handler_ = http_handler.get(); http_handler->InsertResponse("http://test.com/index.html", {kIndexHtml, "text/html"}); http_handler->InsertResponse( "http://test.com/iframe.html", {"HTTP/1.1 302 Found\r\nLocation: iframe2.html\r\n\r\n"}); http_handler->InsertResponse("http://test.com/iframe2.html", {kIFrame, "text/html"}); http_handler->InsertResponse( "http://test.com/style.css", {"HTTP/1.1 302 Found\r\nLocation: style2.css\r\n\r\n"}); http_handler->InsertResponse("http://test.com/style2.css", {kCss, "text/css"}); protocol_handlers[url::kHttpScheme] = std::move(http_handler); return protocol_handlers; } void RunDevTooledTest() override { http_handler_->SetHeadlessBrowserContext(browser_context_); VirtualTimeBrowserTest::RunDevTooledTest(); } // emulation::Observer implementation: void OnVirtualTimeBudgetExpired( const emulation::VirtualTimeBudgetExpiredParams& params) override { EXPECT_THAT( http_handler_->urls_requested(), ElementsAre("http://test.com/index.html", "http://test.com/iframe.html", "http://test.com/iframe2.html", "http://test.com/style.css", "http://test.com/style2.css")); // Virtual time should still expire, despite the CSS resource load not // finishing. FinishAsynchronousTest(); } TestInMemoryProtocolHandler* http_handler_; // NOT OWNED }; HEADLESS_ASYNC_DEVTOOLED_TEST_F(RedirectVirtualTimeTest); namespace { static constexpr char kFooDotCom[] = R"( \n" )"; static constexpr char kFooDotComSlashA[] = R"( )"; static constexpr char kBarDotCom[] = R"( )"; static constexpr char kBarDotComSlashB[] = R"( )"; static constexpr char kBarDotComSlashC[] = R"( )"; static constexpr char kBarDotComSlashD[] = R"( )"; static constexpr char kBarDotComSlashE[] = R"( )"; static constexpr char kBarDotComSlashF[] = R"( )"; } // namespace class VirtualTimeAndHistoryNavigationTest : public VirtualTimeBrowserTest { public: VirtualTimeAndHistoryNavigationTest() { SetInitialURL("http://foo.com/"); } ProtocolHandlerMap GetProtocolHandlers() override { ProtocolHandlerMap protocol_handlers; std::unique_ptr http_handler( new TestInMemoryProtocolHandler(browser()->BrowserIOThread(), nullptr)); http_handler_ = http_handler.get(); http_handler->InsertResponse("http://foo.com/", {kFooDotCom, "text/html"}); http_handler->InsertResponse("http://foo.com/a/", {kFooDotComSlashA, "text/html"}); http_handler->InsertResponse("http://bar.com/", {kBarDotCom, "text/html"}); http_handler->InsertResponse("http://bar.com/b/", {kBarDotComSlashB, "text/html"}); http_handler->InsertResponse("http://bar.com/c/", {kBarDotComSlashC, "text/html"}); http_handler->InsertResponse("http://bar.com/d/", {kBarDotComSlashD, "text/html"}); http_handler->InsertResponse("http://bar.com/e/", {kBarDotComSlashE, "text/html"}); http_handler->InsertResponse("http://bar.com/f/", {kBarDotComSlashF, "text/html"}); protocol_handlers[url::kHttpScheme] = std::move(http_handler); return protocol_handlers; } void RunDevTooledTest() override { http_handler_->SetHeadlessBrowserContext(browser_context_); VirtualTimeBrowserTest::RunDevTooledTest(); } // emulation::Observer implementation: void OnVirtualTimeBudgetExpired( const emulation::VirtualTimeBudgetExpiredParams& params) override { if (step_ < test_commands_.size()) { devtools_client_->GetRuntime()->Evaluate( test_commands_[step_++], base::BindRepeating( &VirtualTimeAndHistoryNavigationTest::OnEvaluateResult, base::Unretained(this))); } else { FinishAsynchronousTest(); } } void OnEvaluateResult(std::unique_ptr) { devtools_client_->GetEmulation()->GetExperimental()->SetVirtualTimePolicy( emulation::SetVirtualTimePolicyParams::Builder() .SetPolicy( emulation::VirtualTimePolicy::PAUSE_IF_NETWORK_FETCHES_PENDING) .SetBudget(5000) .Build()); } const std::vector test_commands_ = { "document.location.href = 'http://bar.com/'", "document.getElementById('frame_b').src = '/e/'", "history.back()", "history.forward()", "history.go(-1)", }; size_t step_ = 0; TestInMemoryProtocolHandler* http_handler_; // NOT OWNED }; HEADLESS_ASYNC_DEVTOOLED_TEST_F(VirtualTimeAndHistoryNavigationTest); namespace { static constexpr char kIndexHtmlWithScript[] = R"( )"; static constexpr char kLargeDotJS[] = R"( (function() { var setTitle = function(newTitle) { document.title = newTitle; }; %s setTitle('Test PASS'); })(); )"; } // namespace class PendingScriptVirtualTimeTest : public VirtualTimeBrowserTest { public: PendingScriptVirtualTimeTest() { SetInitialURL("http://test.com/index.html"); } ProtocolHandlerMap GetProtocolHandlers() override { ProtocolHandlerMap protocol_handlers; std::unique_ptr http_handler( new TestInMemoryProtocolHandler(browser()->BrowserIOThread(), nullptr)); http_handler_ = http_handler.get(); http_handler_->InsertResponse("http://test.com/index.html", {kIndexHtmlWithScript, "text/html"}); // We want to pad |kLargeDotJS| out with some dummy code which is parsed // asynchronously to make sure the virtual_time_pauser in PendingScript // actually does something. We construct a large number of long unused // function declarations which seems to trigger the desired code path. std::string dummy; dummy.reserve(1024 * 1024 * 4); for (int i = 0; i < 1024; i++) { dummy.append(base::StringPrintf("var i%d=function(){return '", i)); dummy.append(1024, 'A'); dummy.append("';}\n"); } large_js_ = base::StringPrintf(kLargeDotJS, dummy.c_str()); http_handler_->InsertResponse( "http://test.com/large.js", {large_js_.c_str(), "application/javascript"}); protocol_handlers[url::kHttpScheme] = std::move(http_handler); return protocol_handlers; } void RunDevTooledTest() override { http_handler_->SetHeadlessBrowserContext(browser_context_); VirtualTimeBrowserTest::RunDevTooledTest(); } void OnVirtualTimeBudgetExpired( const emulation::VirtualTimeBudgetExpiredParams& params) override { devtools_client_->GetRuntime()->Evaluate( "document.title", base::BindRepeating(&PendingScriptVirtualTimeTest::OnEvaluateResult, base::Unretained(this))); } void OnEvaluateResult(std::unique_ptr result) { EXPECT_EQ("Test PASS", result->GetResult()->GetValue()->GetString()); FinishAsynchronousTest(); } TestInMemoryProtocolHandler* http_handler_; // NOT OWNED std::string large_js_; }; HEADLESS_ASYNC_DEVTOOLED_TEST_F(PendingScriptVirtualTimeTest); namespace { static constexpr char kResourceErrorLoop[] = R"( )"; } class VirtualTimeAndResourceErrorLoopTest : public VirtualTimeBrowserTest { public: VirtualTimeAndResourceErrorLoopTest() { SetInitialURL("http://foo.com/"); } ProtocolHandlerMap GetProtocolHandlers() override { ProtocolHandlerMap protocol_handlers; std::unique_ptr http_handler( new TestInMemoryProtocolHandler(browser()->BrowserIOThread(), nullptr)); http_handler_ = http_handler.get(); http_handler->InsertResponse("http://foo.com/", {kResourceErrorLoop, "text/html"}); protocol_handlers[url::kHttpScheme] = std::move(http_handler); return protocol_handlers; } void RunDevTooledTest() override { http_handler_->SetHeadlessBrowserContext(browser_context_); VirtualTimeBrowserTest::RunDevTooledTest(); } void SetVirtualTimePolicy() override { devtools_client_->GetEmulation()->GetExperimental()->SetVirtualTimePolicy( emulation::SetVirtualTimePolicyParams::Builder() .SetPolicy( emulation::VirtualTimePolicy::PAUSE_IF_NETWORK_FETCHES_PENDING) .SetBudget(5000) .SetMaxVirtualTimeTaskStarvationCount(1000000) // Prevent flakes. .SetWaitForNavigation(true) .Build(), base::BindRepeating(&VirtualTimeBrowserTest::SetVirtualTimePolicyDone, base::Unretained(this))); } // emulation::Observer implementation: void OnVirtualTimeBudgetExpired( const emulation::VirtualTimeBudgetExpiredParams& params) override { // The budget is 5000 virtual ms. The resources are delivered with 10 // virtual ms delay, so we should have 500 urls. EXPECT_EQ(500u, http_handler_->urls_requested().size()); FinishAsynchronousTest(); } TestInMemoryProtocolHandler* http_handler_; // NOT OWNED }; HEADLESS_ASYNC_DEVTOOLED_TEST_F(VirtualTimeAndResourceErrorLoopTest); class DateDotNowInitialOverrideTest : public HeadlessAsyncDevTooledBrowserTest { public: void CustomizeHeadlessBrowserContext( HeadlessBrowserContext::Builder& builder) override { builder.SetInitialVirtualTime(base::Time::FromJsTime(123450.0)); } void RunDevTooledTest() override { devtools_client_->GetRuntime()->Evaluate( "Date.now()", base::BindRepeating(&DateDotNowInitialOverrideTest::OnEvaluateResult, base::Unretained(this))); } void OnEvaluateResult(std::unique_ptr result) { EXPECT_EQ(123450.0, result->GetResult()->GetValue()->GetDouble()); FinishAsynchronousTest(); } }; HEADLESS_ASYNC_DEVTOOLED_TEST_F(DateDotNowInitialOverrideTest); class JavascriptTimeAlwaysAdvancesTest : public VirtualTimeBrowserTest, public page::Observer { public: JavascriptTimeAlwaysAdvancesTest() { EXPECT_TRUE(embedded_test_server()->Start()); SetInitialURL( embedded_test_server()->GetURL("/virtual_time_and_date.html").spec()); } void RunDevTooledTest() override { devtools_client_->GetPage()->AddObserver(this); devtools_client_->GetPage()->Enable(); devtools_client_->GetPage()->AddScriptToEvaluateOnNewDocument( R"( (function() { let realDateNow = Date.now; let prevDate = 0; Date.now = function() { let nextDate = realDateNow(); if (prevDate >= nextDate) { nextDate = prevDate + 1; } prevDate = nextDate; return nextDate; }; })();)", base::BindRepeating(&JavascriptTimeAlwaysAdvancesTest:: AfterAddScriptToEvaluateOnNewDocument, base::Unretained(this))); } void AfterAddScriptToEvaluateOnNewDocument( std::unique_ptr) { VirtualTimeBrowserTest::RunDevTooledTest(); } void CustomizeHeadlessBrowserContext( HeadlessBrowserContext::Builder& builder) override { builder.SetInitialVirtualTime(base::Time::FromJsTime(1000000.0)); } void OnConsoleAPICalled( const runtime::ConsoleAPICalledParams& params) override { EXPECT_EQ(runtime::ConsoleAPICalledType::LOG, params.GetType()); ASSERT_EQ(1u, params.GetArgs()->size()); ASSERT_EQ(runtime::RemoteObjectType::STRING, (*params.GetArgs())[0]->GetType()); std::string result_string = (*params.GetArgs())[0]->GetValue()->GetString(); EXPECT_EQ("pass", result_string); console_log_seen_ = true; } // emulation::Observer implementation: void OnVirtualTimeBudgetExpired( const emulation::VirtualTimeBudgetExpiredParams& params) override { // If SetMaxVirtualTimeTaskStarvationCount was not set, this callback would // never fire. EXPECT_TRUE(console_log_seen_); FinishAsynchronousTest(); } bool console_log_seen_ = false; }; HEADLESS_ASYNC_DEVTOOLED_TEST_F(JavascriptTimeAlwaysAdvancesTest); namespace { static constexpr char kSiteA[] = R"( )"; static constexpr char kSiteB[] = R"( )"; static constexpr char kSiteC[] = R"( )"; static constexpr char kSiteD[] = R"( )"; } // namespace class CrossOriginNavigationVirtualTimeTest : public VirtualTimeBrowserTest, public TestInMemoryProtocolHandler::RequestDeferrer { public: CrossOriginNavigationVirtualTimeTest() { SetInitialURL("http://a.com/"); } ProtocolHandlerMap GetProtocolHandlers() override { ProtocolHandlerMap protocol_handlers; std::unique_ptr http_handler( new TestInMemoryProtocolHandler(browser()->BrowserIOThread(), this)); http_handler_ = http_handler.get(); http_handler->InsertResponse("http://a.com/", {kSiteA, "text/html"}); http_handler->InsertResponse("http://b.com/", {kSiteB, "text/html"}); http_handler->InsertResponse("http://c.com/", {kSiteC, "text/html"}); http_handler->InsertResponse("http://d.com/", {kSiteD, "text/html"}); protocol_handlers[url::kHttpScheme] = std::move(http_handler); return protocol_handlers; } void RunDevTooledTest() override { http_handler_->SetHeadlessBrowserContext(browser_context_); VirtualTimeBrowserTest::RunDevTooledTest(); } void CustomizeHeadlessBrowserContext( HeadlessBrowserContext::Builder& builder) override { // Make sure the navigations spawn new processes. builder.SetSitePerProcess(true); } void SetVirtualTimePolicy() override { devtools_client_->GetEmulation()->GetExperimental()->SetVirtualTimePolicy( emulation::SetVirtualTimePolicyParams::Builder() .SetPolicy( emulation::VirtualTimePolicy::PAUSE_IF_NETWORK_FETCHES_PENDING) .SetBudget(100) .SetWaitForNavigation(true) .Build(), base::BindRepeating(&VirtualTimeBrowserTest::SetVirtualTimePolicyDone, base::Unretained(this))); } void OnRequest(const GURL& url, base::RepeatingClosure complete_request) override { log_.push_back( base::StringPrintf("url: %s @ %f", url.spec().c_str(), virtual_time_)); complete_request.Run(); } void OnVirtualTimeAdvanced( const emulation::VirtualTimeAdvancedParams& params) override { virtual_time_ = params.GetVirtualTimeElapsed(); } void OnVirtualTimePaused( const emulation::VirtualTimePausedParams& params) override { virtual_time_ = params.GetVirtualTimeElapsed(); } // emulation::Observer implementation: void OnVirtualTimeBudgetExpired( const emulation::VirtualTimeBudgetExpiredParams& params) override { // Issue virtual time in chunks of 100 virtual ms. We do this because that's // how virtual time is used in reality, but also because this wouldn't work // otherwise. The state of the Emulation domain (like every domain) is only // serialized in a protocol command response so if we didn't do this, // progress would be lost and the loads would happen at the wrong virtual // time offsets. if (++count_ < 50) { devtools_client_->GetEmulation()->GetExperimental()->SetVirtualTimePolicy( emulation::SetVirtualTimePolicyParams::Builder() .SetPolicy(emulation::VirtualTimePolicy:: PAUSE_IF_NETWORK_FETCHES_PENDING) .SetBudget(100) .Build()); } else { EXPECT_THAT(log_, ElementsAre("url: http://a.com/ @ 0.000000", "url: http://b.com/ @ 1010.000000", "url: http://c.com/ @ 2010.000000", "url: http://d.com/ @ 3010.000000")); FinishAsynchronousTest(); } } TestInMemoryProtocolHandler* http_handler_; // NOT OWNED std::vector log_; double virtual_time_ = 0; int count_ = 0; }; HEADLESS_ASYNC_DEVTOOLED_TEST_F(CrossOriginNavigationVirtualTimeTest); } // namespace headless