blob: de31c75f3acb34fc67fa276ecdca6e0c234e48da [file] [log] [blame]
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code if governed by a BSD-style license that can be
// found in LICENSE file.
#include "base/numerics/safe_conversions.h"
#include "base/test/scoped_feature_list.h"
#include "base/time/time.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/platform/platform.h"
#include "third_party/blink/public/platform/scheduler/web_thread_scheduler.h"
#include "third_party/blink/renderer/core/testing/sim/sim_request.h"
#include "third_party/blink/renderer/core/testing/sim/sim_test.h"
#include "third_party/blink/renderer/platform/scheduler/public/thread.h"
#include "third_party/blink/renderer/platform/scheduler/public/thread_scheduler.h"
#include "third_party/blink/renderer/platform/testing/runtime_enabled_features_test_helpers.h"
#include "third_party/blink/renderer/platform/testing/testing_platform_support_with_mock_scheduler.h"
#include "third_party/blink/renderer/platform/testing/unit_test_helpers.h"
using testing::AnyOf;
using testing::ElementsAre;
namespace blink {
// When a page is backgrounded this is the absolute smallest amount of time
// that can elapse between timer wake-ups.
constexpr auto kDefaultThrottledWakeUpInterval =
base::TimeDelta::FromSeconds(1);
// This test suite relies on messages being posted to the console. In order to
// be resilient against messages not posted by this specific test suite, a small
// prefix is used to allowed filtering.
constexpr char kTestConsoleMessagePrefix[] = "[ThrottlingTest]";
// A SimTest with mock time.
class ThrottlingTestBase : public SimTest {
public:
ThrottlingTestBase() {
platform_->SetAutoAdvanceNowToPendingTasks(false);
// Align the time on a 1-minute interval, to simplify expectations.
platform_->AdvanceClock(
platform_->NowTicks().SnappedToNextTick(
base::TimeTicks(), base::TimeDelta::FromMinutes(1)) -
platform_->NowTicks());
}
String BuildTimerConsoleMessage(String suffix = String()) {
String message(kTestConsoleMessagePrefix);
message = message + " Timer called";
if (!suffix.IsNull())
message + " " + suffix;
return message;
}
// Returns a filtered copy of console messages where items not prefixed with
// |kTestConsoleMessagePrefix| are removed.
Vector<String> FilteredConsoleMessages() {
Vector<String> result = ConsoleMessages();
result.erase(
std::remove_if(result.begin(), result.end(),
[](const String& element) {
return !element.StartsWith(kTestConsoleMessagePrefix);
}),
result.end());
return result;
}
ScopedTestingPlatformSupport<TestingPlatformSupportWithMockScheduler>
platform_;
};
class DisableBackgroundThrottlingIsRespectedTest
: public ThrottlingTestBase,
private ScopedTimerThrottlingForBackgroundTabsForTest {
public:
DisableBackgroundThrottlingIsRespectedTest()
: ScopedTimerThrottlingForBackgroundTabsForTest(false) {}
};
TEST_F(DisableBackgroundThrottlingIsRespectedTest,
DisableBackgroundThrottlingIsRespected) {
SimRequest main_resource("https://example.com/", "text/html");
LoadURL("https://example.com/");
const String console_message = BuildTimerConsoleMessage();
main_resource.Complete(
String::Format("(<script>"
" function f(repetitions) {"
" if (repetitions == 0) return;"
" console.log('%s');"
" setTimeout(f, 10, repetitions - 1);"
" }"
" f(5);"
"</script>)",
console_message.Utf8().c_str()));
GetDocument().GetPage()->GetPageScheduler()->SetPageVisible(false);
// Run delayed tasks for 1 second. All tasks should be completed
// with throttling disabled.
platform_->RunForPeriod(base::TimeDelta::FromSeconds(1));
EXPECT_THAT(FilteredConsoleMessages(),
ElementsAre(console_message, console_message, console_message,
console_message, console_message));
}
class BackgroundPageThrottlingTest : public ThrottlingTestBase {};
TEST_F(BackgroundPageThrottlingTest, TimersThrottledInBackgroundPage) {
SimRequest main_resource("https://example.com/", "text/html");
LoadURL("https://example.com/");
const String console_message = BuildTimerConsoleMessage();
main_resource.Complete(
String::Format("(<script>"
" function f(repetitions) {"
" if (repetitions == 0) return;"
" console.log('%s');"
" setTimeout(f, 10, repetitions - 1);"
" }"
" setTimeout(f, 10, 50);"
"</script>)",
console_message.Utf8().c_str()));
GetDocument().GetPage()->GetPageScheduler()->SetPageVisible(false);
// Make sure that we run no more than one task a second.
platform_->RunForPeriod(base::TimeDelta::FromSeconds(3));
EXPECT_THAT(FilteredConsoleMessages(),
ElementsAre(console_message, console_message, console_message));
}
// Verify the execution time of non-nested timers on a hidden page.
// - setTimeout(..., 0) and setTimeout(..., -1) schedule their callback after
// 1ms. The 1 ms delay exists for historical reasons crbug.com/402694.
// - setTimeout(..., 5) schedules its callback at the next aligned time
TEST_F(BackgroundPageThrottlingTest, WithoutNesting) {
SimRequest main_resource("https://example.com/", "text/html");
LoadURL("https://example.com/");
String timeout_0_message = BuildTimerConsoleMessage("0");
String timeout_minus_1_message = BuildTimerConsoleMessage("-1");
String timeout_5_message = BuildTimerConsoleMessage("5");
main_resource.Complete(String::Format(
"<script>"
" setTimeout(function() {"
" setTimeout(function() { console.log('%s'); }, 0);"
" setTimeout(function() { console.log('%s'); }, -1);"
" setTimeout(function() { console.log('%s'); }, 5);"
" }, 1000);"
"</script>",
timeout_0_message.Utf8().c_str(), timeout_minus_1_message.Utf8().c_str(),
timeout_5_message.Utf8().c_str()));
GetDocument().GetPage()->GetPageScheduler()->SetPageVisible(false);
platform_->RunForPeriod(base::TimeDelta::FromMilliseconds(1001));
EXPECT_THAT(FilteredConsoleMessages(),
ElementsAre(timeout_0_message, timeout_minus_1_message));
platform_->RunForPeriod(base::TimeDelta::FromMilliseconds(998));
EXPECT_THAT(FilteredConsoleMessages(),
ElementsAre(timeout_0_message, timeout_minus_1_message));
platform_->RunForPeriod(base::TimeDelta::FromMilliseconds(1));
EXPECT_THAT(FilteredConsoleMessages(),
ElementsAre(timeout_0_message, timeout_minus_1_message,
timeout_5_message));
}
// Verify that on a hidden page, a timer created with setTimeout(..., 0) is
// throttled after 5 nesting levels.
TEST_F(BackgroundPageThrottlingTest, NestedSetTimeoutZero) {
SimRequest main_resource("https://example.com/", "text/html");
LoadURL("https://example.com/");
const String console_message = BuildTimerConsoleMessage();
main_resource.Complete(
String::Format("<script>"
" function f(repetitions) {"
" if (repetitions == 0) return;"
" console.log('%s');"
" setTimeout(f, 0, repetitions - 1);"
" }"
" setTimeout(f, 0, 50);"
"</script>",
console_message.Utf8().c_str()));
GetDocument().GetPage()->GetPageScheduler()->SetPageVisible(false);
platform_->RunForPeriod(base::TimeDelta::FromMilliseconds(1));
EXPECT_THAT(FilteredConsoleMessages(), Vector<String>(1, console_message));
platform_->RunForPeriod(base::TimeDelta::FromMilliseconds(1));
EXPECT_THAT(FilteredConsoleMessages(), Vector<String>(2, console_message));
platform_->RunForPeriod(base::TimeDelta::FromMilliseconds(1));
EXPECT_THAT(FilteredConsoleMessages(), Vector<String>(3, console_message));
platform_->RunForPeriod(base::TimeDelta::FromMilliseconds(1));
EXPECT_THAT(FilteredConsoleMessages(), Vector<String>(4, console_message));
platform_->RunForPeriod(base::TimeDelta::FromMilliseconds(995));
EXPECT_THAT(FilteredConsoleMessages(), Vector<String>(4, console_message));
platform_->RunForPeriod(base::TimeDelta::FromMilliseconds(1));
EXPECT_THAT(FilteredConsoleMessages(), Vector<String>(5, console_message));
}
// Verify that in a hidden page, a timer created with setInterval(..., 0) is
// throttled after 5 nesting levels.
TEST_F(BackgroundPageThrottlingTest, NestedSetIntervalZero) {
SimRequest main_resource("https://example.com/", "text/html");
LoadURL("https://example.com/");
const String console_message = BuildTimerConsoleMessage();
main_resource.Complete(
String::Format("<script>"
" function f() {"
" if (repetitions == 0) clearInterval(interval_id);"
" console.log('%s');"
" repetitions = repetitions - 1;"
" }"
" var repetitions = 50;"
" var interval_id = setInterval(f, 0);"
"</script>",
console_message.Utf8().c_str()));
GetDocument().GetPage()->GetPageScheduler()->SetPageVisible(false);
platform_->RunForPeriod(base::TimeDelta::FromMilliseconds(1));
EXPECT_THAT(FilteredConsoleMessages(), Vector<String>(1, console_message));
platform_->RunForPeriod(base::TimeDelta::FromMilliseconds(1));
EXPECT_THAT(FilteredConsoleMessages(), Vector<String>(2, console_message));
platform_->RunForPeriod(base::TimeDelta::FromMilliseconds(1));
EXPECT_THAT(FilteredConsoleMessages(), Vector<String>(3, console_message));
platform_->RunForPeriod(base::TimeDelta::FromMilliseconds(1));
EXPECT_THAT(FilteredConsoleMessages(), Vector<String>(4, console_message));
platform_->RunForPeriod(base::TimeDelta::FromMilliseconds(995));
EXPECT_THAT(FilteredConsoleMessages(), Vector<String>(4, console_message));
platform_->RunForPeriod(base::TimeDelta::FromMilliseconds(1));
EXPECT_THAT(FilteredConsoleMessages(), Vector<String>(5, console_message));
}
namespace {
class IntensiveWakeUpThrottlingTest : public ThrottlingTestBase {
public:
IntensiveWakeUpThrottlingTest() {
scoped_feature_list_.InitWithFeatures(
{features::kIntensiveWakeUpThrottling},
// Disable freezing because it hides the effect of intensive throttling.
{features::kStopInBackground});
}
// Expect a console message every second, for |num_1hz_messages| seconds.
// Then, expect a console messages every minute.
void ExpectRepeatingTimerConsoleMessages(int num_1hz_messages) {
for (int i = 0; i < num_1hz_messages; ++i) {
ConsoleMessages().clear();
platform_->RunForPeriod(base::TimeDelta::FromSeconds(1));
EXPECT_EQ(FilteredConsoleMessages().size(), 1U);
}
constexpr int kNumIterations = 3;
for (int i = 0; i < kNumIterations; ++i) {
ConsoleMessages().clear();
platform_->RunForPeriod(base::TimeDelta::FromSeconds(30));
// Task shouldn't execute earlier than expected.
EXPECT_EQ(FilteredConsoleMessages().size(), 0U);
platform_->RunForPeriod(base::TimeDelta::FromSeconds(30));
EXPECT_EQ(FilteredConsoleMessages().size(), 1U);
}
}
void TestNoIntensiveThrottlingOnTitleOrFaviconUpdate(
const String& console_message) {
// The page does not attempt to run onTimer in the first 5 minutes.
platform_->RunForPeriod(base::TimeDelta::FromMinutes(5));
EXPECT_THAT(FilteredConsoleMessages(), ElementsAre());
// onTimer() communicates in background and re-posts itself. The background
// communication inhibits intensive wake up throttling for 3 seconds, which
// allows the re-posted task to run after |kDefaultThrottledWakeUpInterval|.
constexpr int kNumIterations = 3;
for (int i = 0; i < kNumIterations; ++i) {
platform_->RunForPeriod(kDefaultThrottledWakeUpInterval);
EXPECT_THAT(FilteredConsoleMessages(), ElementsAre(console_message));
ConsoleMessages().clear();
}
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
};
// Use to install a function that does not actually communicate with the user.
constexpr char kCommunicationNop[] =
"<script>"
" function maybeCommunicateInBackground() {"
" return;"
" }"
"</script>";
// Use to install a function that will communicate with the user via title
// update.
constexpr char kCommunicateThroughTitleScript[] =
"<script>"
" function maybeCommunicateInBackground() {"
" document.title += \"A\";"
" }"
"</script>";
// Use to install a function that will communicate with the user via favicon
// update.
constexpr char kCommunicateThroughFavisonScript[] =
"<script>"
" function maybeCommunicateInBackground() {"
" document.querySelector(\"link[rel*='icon']\").href = \"favicon.ico\";"
" }"
"</script>";
// A script that schedules a timer task which logs to the console. The timer
// task has a high nesting level and its timeout is not aligned on the intensive
// wake up throttling interval.
constexpr char kLongUnalignedTimerScriptTemplate[] =
"<script>"
" function onTimerWithHighNestingLevel() {"
" console.log('%s');"
" }"
" function onTimerWithLowNestingLevel(nesting_level) {"
" if (nesting_level == 4) {"
" setTimeout(onTimerWithHighNestingLevel, 338 * 1000);"
" } else {"
" setTimeout(onTimerWithLowNestingLevel, 1000, nesting_level + 1);"
" }"
" }"
" setTimeout(onTimerWithLowNestingLevel, 1000, 1);"
"</script>";
// A time delta that matches the delay in the above script.
constexpr base::TimeDelta kLongUnalignedTimerDelay =
base::TimeDelta::FromSeconds(342);
// Builds a page that waits 5 minutes and then creates a timer that reschedules
// itself 50 times with 10 ms delay. The timer task logs |console_message| to
// the console and invokes maybeCommunicateInBackground(). The caller must
// provide the definition of maybeCommunicateInBackground() via
// |communicate_script|.
String BuildRepeatingTimerPage(const char* console_message,
const char* communicate_script) {
constexpr char kRepeatingTimerPageTemplate[] =
"<html>"
"<head>"
" <link rel='icon' href='http://www.foobar.com/favicon.ico'>"
"</head>"
"<body>"
"<script>"
" function onTimer(repetitions) {"
" if (repetitions == 0) return;"
" console.log('%s');"
" maybeCommunicateInBackground();"
" setTimeout(onTimer, 10, repetitions - 1);"
" }"
" function afterFiveMinutes() {"
" setTimeout(onTimer, 10, 50);"
" }"
" setTimeout(afterFiveMinutes, 5 * 60 * 1000);"
"</script>"
"%s" // |communicate_script| inserted here
"</body>"
"</html>";
return String::Format(kRepeatingTimerPageTemplate, console_message,
communicate_script);
}
} // namespace
// Verify that a main frame timer that reposts itself with a 10 ms timeout runs
// once every minute.
TEST_F(IntensiveWakeUpThrottlingTest, MainFrameTimer_ShortTimeout) {
SimRequest main_resource("https://example.com/", "text/html");
LoadURL("https://example.com/");
// Page does not communicate with the user. Normal intensive throttling
// applies.
main_resource.Complete(BuildRepeatingTimerPage(
BuildTimerConsoleMessage().Utf8().c_str(), kCommunicationNop));
GetDocument().GetPage()->GetPageScheduler()->SetPageVisible(false);
// No timer is scheduled in the 5 first minutes.
platform_->RunForPeriod(base::TimeDelta::FromMinutes(5));
EXPECT_THAT(FilteredConsoleMessages(), ElementsAre());
// Expected execution:
//
// t = 5min 0s : afterFiveMinutes nesting=1 (low)
// t = 5min 1s : onTimer nesting=2 (low) <
// t = 5min 2s : onTimer nesting=3 (low) < 4 seconds at 1 Hz
// t = 5min 3s : onTimer nesting=4 (low) <
// t = 5min 4s : onTimer nesting=5 (high) ** <
// t = 6min : onTimer nesting=6 (high)
// t = 7min : onTimer nesting=7 (high)
// ...
//
// ** In a main frame, a task with high nesting level is 1-second aligned
// when no task with high nesting level ran in the last minute.
ExpectRepeatingTimerConsoleMessages(4);
}
// Verify that a main frame timer that reposts itself with a 10 ms timeout runs
// once every |kDefaultThrottledWakeUpInterval| after the first confirmed page
// communication through title update.
TEST_F(IntensiveWakeUpThrottlingTest, MainFrameTimer_ShortTimeout_TitleUpdate) {
SimRequest main_resource("https://example.com/", "text/html");
LoadURL("https://example.com/");
const String console_message = BuildTimerConsoleMessage();
main_resource.Complete(BuildRepeatingTimerPage(
console_message.Utf8().c_str(), kCommunicateThroughTitleScript));
GetDocument().GetPage()->GetPageScheduler()->SetPageVisible(false);
TestNoIntensiveThrottlingOnTitleOrFaviconUpdate(console_message);
}
// Verify that a main frame timer that reposts itself with a 10 ms timeout runs
// once every |kDefaultThrottledWakeUpInterval| after the first confirmed page
// communication through favicon update.
TEST_F(IntensiveWakeUpThrottlingTest,
MainFrameTimer_ShortTimeout_FaviconUpdate) {
SimRequest main_resource("https://example.com/", "text/html");
LoadURL("https://example.com/");
const String console_message = BuildTimerConsoleMessage();
main_resource.Complete(BuildRepeatingTimerPage(
console_message.Utf8().c_str(), kCommunicateThroughFavisonScript));
GetDocument().GetPage()->GetPageScheduler()->SetPageVisible(false);
TestNoIntensiveThrottlingOnTitleOrFaviconUpdate(console_message);
}
// Verify that a same-origin subframe timer that reposts itself with a 10 ms
// timeout runs once every minute.
TEST_F(IntensiveWakeUpThrottlingTest, SameOriginSubFrameTimer_ShortTimeout) {
SimRequest main_resource("https://example.com/", "text/html");
SimRequest subframe_resource("https://example.com/iframe.html", "text/html");
LoadURL("https://example.com/");
main_resource.Complete(R"(<iframe src="https://example.com/iframe.html" />)");
// Run tasks to let the main frame request the iframe resource. It is not
// possible to complete the iframe resource request before that.
platform_->RunUntilIdle();
subframe_resource.Complete(BuildRepeatingTimerPage(
BuildTimerConsoleMessage().Utf8().c_str(), kCommunicationNop));
GetDocument().GetPage()->GetPageScheduler()->SetPageVisible(false);
// No timer is scheduled in the 5 first minutes.
platform_->RunForPeriod(base::TimeDelta::FromMinutes(5));
EXPECT_THAT(FilteredConsoleMessages(), ElementsAre());
// Expected execution:
//
// t = 5min 0s : afterFiveMinutes nesting=1 (low)
// t = 5min 1s : onTimer nesting=2 (low) <
// t = 5min 2s : onTimer nesting=3 (low) < 4 seconds at 1 Hz
// t = 5min 3s : onTimer nesting=4 (low) <
// t = 5min 4s : onTimer nesting=5 (high) ** <
// t = 6min : onTimer nesting=6 (high)
// t = 7min : onTimer nesting=7 (high)
// ...
//
// ** In a same-origin frame, a task with high nesting level is 1-second
// aligned when no task with high nesting level ran in the last minute.
ExpectRepeatingTimerConsoleMessages(4);
}
// Verify that a cross-origin subframe timer that reposts itself with a 10 ms
// timeout runs once every minute.
TEST_F(IntensiveWakeUpThrottlingTest, CrossOriginSubFrameTimer_ShortTimeout) {
SimRequest main_resource("https://example.com/", "text/html");
SimRequest subframe_resource("https://cross-origin.example.com/iframe.html",
"text/html");
LoadURL("https://example.com/");
main_resource.Complete(
R"(<iframe src="https://cross-origin.example.com/iframe.html" />)");
// Run tasks to let the main frame request the iframe resource. It is not
// possible to complete the iframe resource request before that.
platform_->RunUntilIdle();
subframe_resource.Complete(BuildRepeatingTimerPage(
BuildTimerConsoleMessage().Utf8().c_str(), kCommunicationNop));
GetDocument().GetPage()->GetPageScheduler()->SetPageVisible(false);
// No timer is scheduled in the 5 first minutes.
platform_->RunForPeriod(base::TimeDelta::FromMinutes(5));
EXPECT_THAT(FilteredConsoleMessages(), ElementsAre());
// Expected execution:
//
// t = 5min 0s : afterFiveMinutes nesting=1 (low)
// t = 5min 1s : onTimer nesting=2 (low) <
// t = 5min 2s : onTimer nesting=3 (low) < 3 seconds at 1 Hz
// t = 5min 3s : onTimer nesting=4 (low) <
// t = 6min : onTimer nesting=5 (high)
// t = 7min : onTimer nesting=6 (high)
// t = 8min : onTimer nesting=7 (high)
// ...
ExpectRepeatingTimerConsoleMessages(3);
}
// Verify that a main frame timer with a long timeout runs at the desired run
// time when there is no other recent timer wake up.
TEST_F(IntensiveWakeUpThrottlingTest, MainFrameTimer_LongUnalignedTimeout) {
SimRequest main_resource("https://example.com/", "text/html");
LoadURL("https://example.com/");
const String console_message = BuildTimerConsoleMessage();
main_resource.Complete(String::Format(kLongUnalignedTimerScriptTemplate,
console_message.Utf8().c_str()));
GetDocument().GetPage()->GetPageScheduler()->SetPageVisible(false);
platform_->RunForPeriod(kLongUnalignedTimerDelay -
base::TimeDelta::FromSeconds(1));
EXPECT_THAT(FilteredConsoleMessages(), ElementsAre());
platform_->RunForPeriod(base::TimeDelta::FromSeconds(1));
EXPECT_THAT(FilteredConsoleMessages(), ElementsAre(console_message));
}
// Verify that a same-origin subframe timer with a long timeout runs at the
// desired run time when there is no other recent timer wake up.
TEST_F(IntensiveWakeUpThrottlingTest,
SameOriginSubFrameTimer_LongUnalignedTimeout) {
SimRequest main_resource("https://example.com/", "text/html");
SimRequest subframe_resource("https://example.com/iframe.html", "text/html");
LoadURL("https://example.com/");
main_resource.Complete(R"(<iframe src="https://example.com/iframe.html" />)");
// Run tasks to let the main frame request the iframe resource. It is not
// possible to complete the iframe resource request before that.
platform_->RunUntilIdle();
const String console_message = BuildTimerConsoleMessage();
subframe_resource.Complete(String::Format(kLongUnalignedTimerScriptTemplate,
console_message.Utf8().c_str()));
GetDocument().GetPage()->GetPageScheduler()->SetPageVisible(false);
platform_->RunForPeriod(kLongUnalignedTimerDelay -
base::TimeDelta::FromSeconds(1));
EXPECT_THAT(FilteredConsoleMessages(), ElementsAre());
platform_->RunForPeriod(base::TimeDelta::FromSeconds(1));
EXPECT_THAT(FilteredConsoleMessages(), ElementsAre(console_message));
}
// Verify that a cross-origin subframe timer with a long timeout runs at an
// aligned time, even when there is no other recent timer wake up (in a
// same-origin frame, it would have run at the desired time).
TEST_F(IntensiveWakeUpThrottlingTest,
CrossOriginSubFrameTimer_LongUnalignedTimeout) {
SimRequest main_resource("https://example.com/", "text/html");
SimRequest subframe_resource("https://cross-origin.example.com/iframe.html",
"text/html");
LoadURL("https://example.com/");
main_resource.Complete(
R"(<iframe src="https://cross-origin.example.com/iframe.html" />)");
// Run tasks to let the main frame request the iframe resource. It is not
// possible to complete the iframe resource request before that.
platform_->RunUntilIdle();
const String console_message = BuildTimerConsoleMessage();
subframe_resource.Complete(String::Format(kLongUnalignedTimerScriptTemplate,
console_message.Utf8().c_str()));
GetDocument().GetPage()->GetPageScheduler()->SetPageVisible(false);
platform_->RunForPeriod(base::TimeDelta::FromSeconds(342));
EXPECT_THAT(FilteredConsoleMessages(), ElementsAre());
// Fast-forward to the next aligned time.
platform_->RunForPeriod(base::TimeDelta::FromSeconds(18));
EXPECT_THAT(FilteredConsoleMessages(), ElementsAre(console_message));
}
// Verify that if both the main frame and a cross-origin frame schedule a timer
// with a long unaligned delay, the main frame timer runs at the desired time
// (because there was no recent same-origin wake up) while the cross-origin
// timer runs at an aligned time.
TEST_F(IntensiveWakeUpThrottlingTest,
MainFrameAndCrossOriginSubFrameTimer_LongUnalignedTimeout) {
SimRequest main_resource("https://example.com/", "text/html");
SimRequest subframe_resource("https://cross-origin.example.com/iframe.html",
"text/html");
LoadURL("https://example.com/");
const String console_message = BuildTimerConsoleMessage();
const String script = String::Format(kLongUnalignedTimerScriptTemplate,
console_message.Utf8().c_str());
main_resource.Complete(
script +
"<iframe src=\"https://cross-origin.example.com/iframe.html\" />");
// Run tasks to let the main frame request the iframe resource. It is not
// possible to complete the iframe resource request before that.
platform_->RunUntilIdle();
subframe_resource.Complete(script);
GetDocument().GetPage()->GetPageScheduler()->SetPageVisible(false);
platform_->RunForPeriod(base::TimeDelta::FromSeconds(342));
EXPECT_THAT(FilteredConsoleMessages(), ElementsAre(console_message));
// Fast-forward to the next aligned time.
platform_->RunForPeriod(base::TimeDelta::FromSeconds(18));
EXPECT_THAT(FilteredConsoleMessages(),
ElementsAre(console_message, console_message));
}
} // namespace blink