Skip to content

Commit

Permalink
Integrate setTimeout and setInterval with HTML5 event loop
Browse files Browse the repository at this point in the history
https://bugs.webkit.org/show_bug.cgi?id=203137

Reviewed by Chris Dumez.

This PR integrates DOM timers with HTML5 event loop. Specifically, DOMTimer now uses
EventLoopTaskGroup::scheduleTask and EventLoopTaskGroup::scheduleRepeatingTask instead of
inheriting from SuspendableTimer. This PR also deletes SuspendableTimer class now that
we've migrated all uses of it to EventLoop's equivalents.

* Source/WebCore/Headers.cmake:
* Source/WebCore/Sources.txt:
* Source/WebCore/WebCore.xcodeproj/project.pbxproj:
* Source/WebCore/dom/EventLoop.cpp:
(WebCore::EventLoop::scheduleTask): Fixed a bug that the newly created timer won't be
suspended when the task group had been suspended.
(WebCore::EventLoop::scheduleRepeatingTask): Ditto.
(WebCore::EventLoopTaskGroup::setTimerAlignment):
(WebCore::EventLoopTaskGroup::setTimerHasReachedMaxNestingLevel):
(WebCore::EventLoopTaskGroup::adjustTimerNextTimeout):
(WebCore::EventLoopTaskGroup::adjustTimerRepeatInterval):
(WebCore::EventLoopTaskGroup::didChangeTimerAlignmentInterval):
* Source/WebCore/dom/EventLoop.h:
* Source/WebCore/dom/ScriptExecutionContext.cpp:
(WebCore::ScriptExecutionContext::didChangeTimerAlignmentInterval):
* Source/WebCore/dom/ScriptExecutionContext.h:
* Source/WebCore/dom/TaskSource.h:
* Source/WebCore/page/DOMTimer.cpp:
(WebCore::DOMTimer::DOMTimer):
(WebCore::DOMTimer::removeById):
(WebCore::DOMTimer::fired):
(WebCore::DOMTimer::stop):
(WebCore::DOMTimer::updateTimerIntervalIfNecessary):
(WebCore::DOMTimer::didStop): Deleted.
* Source/WebCore/page/DOMTimer.h:
* Source/WebCore/page/SuspendableTimer.cpp: Removed.
* Source/WebCore/page/SuspendableTimer.h: Removed.
* Source/WebCore/testing/Internals.cpp:
(WebCore::Internals::isTimerThrottled):

Canonical link: https://commits.webkit.org/266828@main
  • Loading branch information
rniwa committed Aug 11, 2023
1 parent 6198453 commit 43d0b6d
Show file tree
Hide file tree
Showing 13 changed files with 136 additions and 280 deletions.
1 change: 0 additions & 1 deletion Source/WebCore/Headers.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -1530,7 +1530,6 @@ set(WebCore_PRIVATE_FRAMEWORK_HEADERS
page/SpeechSynthesisClient.h
page/StorageBlockingPolicy.h
page/StructuredSerializeOptions.h
page/SuspendableTimer.h
page/TextDirectionSubmenuInclusionBehavior.h
page/TextIndicator.h
page/TranslationContextMenuInfo.h
Expand Down
1 change: 0 additions & 1 deletion Source/WebCore/Sources.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1959,7 +1959,6 @@ page/SettingsBase.cpp
page/ShadowRealmGlobalScope.cpp
page/ShareDataReader.cpp
page/SpatialNavigation.cpp
page/SuspendableTimer.cpp
page/TextIndicator.cpp
page/UndoItem.cpp
page/UndoManager.cpp
Expand Down
5 changes: 0 additions & 5 deletions Source/WebCore/WebCore.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -2439,7 +2439,6 @@
626CDE0F1140424C001E5A68 /* SpatialNavigation.h in Headers */ = {isa = PBXBuildFile; fileRef = 626CDE0D1140424C001E5A68 /* SpatialNavigation.h */; settings = {ATTRIBUTES = (Private, ); }; };
628D214C12131ED10055DCFC /* NetworkingContext.h in Headers */ = {isa = PBXBuildFile; fileRef = 628D214B12131ED10055DCFC /* NetworkingContext.h */; settings = {ATTRIBUTES = (Private, ); }; };
628D214E12131EF40055DCFC /* FrameNetworkingContext.h in Headers */ = {isa = PBXBuildFile; fileRef = 628D214D12131EF40055DCFC /* FrameNetworkingContext.h */; settings = {ATTRIBUTES = (Private, ); }; };
62C1217D11AB9E77003C462C /* SuspendableTimer.h in Headers */ = {isa = PBXBuildFile; fileRef = 62C1217B11AB9E77003C462C /* SuspendableTimer.h */; settings = {ATTRIBUTES = (Private, ); }; };
62CD325A1157E57C0063B0A7 /* CustomEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = 62CD32571157E57C0063B0A7 /* CustomEvent.h */; };
63152D191F9531EE007A5E4B /* ApplicationManifestLoader.h in Headers */ = {isa = PBXBuildFile; fileRef = 63152D171F9531EE007A5E4B /* ApplicationManifestLoader.h */; };
63189AE30E83A33300012E41 /* NodeRareData.h in Headers */ = {isa = PBXBuildFile; fileRef = 63189AE20E83A33300012E41 /* NodeRareData.h */; settings = {ATTRIBUTES = (); }; };
Expand Down Expand Up @@ -12033,7 +12032,6 @@
628D214B12131ED10055DCFC /* NetworkingContext.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NetworkingContext.h; sourceTree = "<group>"; };
628D214D12131EF40055DCFC /* FrameNetworkingContext.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FrameNetworkingContext.h; sourceTree = "<group>"; };
62C1217A11AB9E76003C462C /* SuspendableTimer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SuspendableTimer.cpp; sourceTree = "<group>"; };
62C1217B11AB9E77003C462C /* SuspendableTimer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SuspendableTimer.h; sourceTree = "<group>"; };
62CD32561157E57C0063B0A7 /* CustomEvent.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CustomEvent.cpp; sourceTree = "<group>"; };
62CD32571157E57C0063B0A7 /* CustomEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CustomEvent.h; sourceTree = "<group>"; };
62CD32581157E57C0063B0A7 /* CustomEvent.idl */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = CustomEvent.idl; sourceTree = "<group>"; };
Expand Down Expand Up @@ -26912,8 +26910,6 @@
BC4A533625605AE10028C592 /* StorageBlockingPolicy.h */,
46FEFBC626DD678200B0C488 /* StructuredSerializeOptions.h */,
46FEFBC726DD678300B0C488 /* StructuredSerializeOptions.idl */,
62C1217A11AB9E76003C462C /* SuspendableTimer.cpp */,
62C1217B11AB9E77003C462C /* SuspendableTimer.h */,
BC4A5324256055590028C592 /* TextDirectionSubmenuInclusionBehavior.h */,
2D4F96F11A1ECC240098BF88 /* TextIndicator.cpp */,
2D4F96F21A1ECC240098BF88 /* TextIndicator.h */,
Expand Down Expand Up @@ -41062,7 +41058,6 @@
93B2D8160F9920D2006AE6B2 /* SuddenTermination.h in Headers */,
97C078501165D5BE003A32EF /* SuffixTree.h in Headers */,
97627B9814FB5424002CDCA1 /* Supplementable.h in Headers */,
62C1217D11AB9E77003C462C /* SuspendableTimer.h in Headers */,
B22279740D00BF220071B782 /* SVGAElement.h in Headers */,
24D912B113CA9A1F00D21915 /* SVGAltGlyphDefElement.h in Headers */,
65653F2E0D9727D200CA9723 /* SVGAltGlyphElement.h in Headers */,
Expand Down
79 changes: 75 additions & 4 deletions Source/WebCore/dom/EventLoop.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2019 Apple Inc. All rights reserved.
* Copyright (C) 2008-2023 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
Expand Down Expand Up @@ -28,7 +28,6 @@

#include "Microtasks.h"
#include "ScriptExecutionContext.h"
#include "SuspendableTimer.h"

namespace WebCore {

Expand Down Expand Up @@ -94,6 +93,33 @@ class EventLoopTimer final : public RefCounted<EventLoopTimer>, public TimerBase
}
}

void adjustNextFireTime(Seconds delta)
{
if (!m_suspended)
TimerBase::augmentFireInterval(delta);
else if (m_savedIsActive)
m_savedNextFireInterval += delta;
else {
m_savedIsActive = true;
m_savedNextFireInterval = delta;
m_savedRepeatInterval = 0_s;
}
}

void adjustRepeatInterval(Seconds delta)
{
if (!m_suspended)
TimerBase::augmentRepeatInterval(delta);
else if (m_savedIsActive) {
m_savedNextFireInterval += delta;
m_savedRepeatInterval += delta;
} else {
m_savedIsActive = true;
m_savedNextFireInterval = delta;
m_savedRepeatInterval = delta;
}
}

private:
EventLoopTimer(Type type, std::unique_ptr<EventLoopTask>&& task)
: m_task(WTFMove(task))
Expand All @@ -106,9 +132,10 @@ class EventLoopTimer final : public RefCounted<EventLoopTimer>, public TimerBase
Ref protectedThis { *this };
if (!m_task)
return;
WeakPtr group = m_task->group();
m_task->execute();
if (m_type == Type::OneShot)
m_task->group()->removeScheduledTimer(*this);
if (group && m_type == Type::OneShot)
group->removeScheduledTimer(*this);
}

std::unique_ptr<EventLoopTask> m_task;
Expand Down Expand Up @@ -164,6 +191,8 @@ EventLoopTimerHandle EventLoop::scheduleTask(Seconds timeout, std::unique_ptr<Ev
{
auto timer = EventLoopTimer::create(EventLoopTimer::Type::OneShot, WTFMove(action));
timer->startOneShot(timeout);
if (timer->group()->isSuspended())
timer->suspend();

ASSERT(timer->group());
timer->group()->didAddTimer(timer);
Expand All @@ -183,6 +212,8 @@ EventLoopTimerHandle EventLoop::scheduleRepeatingTask(Seconds nextTimeout, Secon
{
auto timer = EventLoopTimer::create(EventLoopTimer::Type::Repeating, WTFMove(action));
timer->startRepeating(nextTimeout, interval);
if (timer->group()->isSuspended())
timer->suspend();

ASSERT(timer->group());
timer->group()->didAddTimer(timer);
Expand Down Expand Up @@ -420,6 +451,46 @@ void EventLoopTaskGroup::removeRepeatingTimer(EventLoopTimer& timer)
m_timers.remove(timer);
}

void EventLoopTaskGroup::setTimerAlignment(EventLoopTimerHandle handle, TimerAlignment& timerAlignment)
{
if (!handle.m_timer)
return;
ASSERT(m_timers.contains(*handle.m_timer));
handle.m_timer->setTimerAlignment(timerAlignment);
}

void EventLoopTaskGroup::didChangeTimerAlignmentInterval(EventLoopTimerHandle handle)
{
if (!handle.m_timer)
return;
ASSERT(m_timers.contains(*handle.m_timer));
handle.m_timer->didChangeAlignmentInterval();
}

void EventLoopTaskGroup::setTimerHasReachedMaxNestingLevel(EventLoopTimerHandle handle, bool value)
{
if (!handle.m_timer)
return;
ASSERT(m_timers.contains(*handle.m_timer));
handle.m_timer->setHasReachedMaxNestingLevel(value);
}

void EventLoopTaskGroup::adjustTimerNextFireTime(EventLoopTimerHandle handle, Seconds delta)
{
if (!handle.m_timer)
return;
ASSERT(m_timers.contains(*handle.m_timer));
handle.m_timer->adjustNextFireTime(delta);
}

void EventLoopTaskGroup::adjustTimerRepeatInterval(EventLoopTimerHandle handle, Seconds delta)
{
if (!handle.m_timer)
return;
ASSERT(m_timers.contains(*handle.m_timer));
handle.m_timer->adjustRepeatInterval(delta);
}

void EventLoopTaskGroup::didAddTimer(EventLoopTimer& timer)
{
auto result = m_timers.add(timer);
Expand Down
10 changes: 9 additions & 1 deletion Source/WebCore/dom/EventLoop.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2019 Apple Inc. All rights reserved.
* Copyright (C) 2008-2023 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
Expand Down Expand Up @@ -40,6 +40,7 @@ class EventLoopTimer;
class EventTarget;
class MicrotaskQueue;
class ScriptExecutionContext;
class TimerAlignment;

class EventLoopTask {
WTF_MAKE_NONCOPYABLE(EventLoopTask);
Expand Down Expand Up @@ -79,6 +80,7 @@ class EventLoopTimerHandle {

private:
friend class EventLoop;
friend class EventLoopTaskGroup;

void unspecifiedBoolTypeInstance() const { }

Expand Down Expand Up @@ -208,6 +210,12 @@ class EventLoopTaskGroup : public CanMakeWeakPtr<EventLoopTaskGroup> {
EventLoopTimerHandle scheduleRepeatingTask(Seconds nextTimeout, Seconds interval, TaskSource, EventLoop::TaskFunction&&);
void removeRepeatingTimer(EventLoopTimer&);

void setTimerAlignment(EventLoopTimerHandle, TimerAlignment&);
void didChangeTimerAlignmentInterval(EventLoopTimerHandle);
void setTimerHasReachedMaxNestingLevel(EventLoopTimerHandle, bool);
void adjustTimerNextFireTime(EventLoopTimerHandle, Seconds delta);
void adjustTimerRepeatInterval(EventLoopTimerHandle, Seconds delta);

void didAddTimer(EventLoopTimer&);
void didRemoveTimer(EventLoopTimer&);

Expand Down
3 changes: 2 additions & 1 deletion Source/WebCore/dom/ScriptExecutionContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -571,8 +571,9 @@ Seconds ScriptExecutionContext::minimumDOMTimerInterval() const

void ScriptExecutionContext::didChangeTimerAlignmentInterval()
{
auto& eventLoop = this->eventLoop();
for (auto& timer : m_timeouts.values())
timer->didChangeAlignmentInterval();
eventLoop.didChangeTimerAlignmentInterval(timer->timer());
}

Seconds ScriptExecutionContext::domTimerAlignmentInterval(bool) const
Expand Down
2 changes: 1 addition & 1 deletion Source/WebCore/dom/ScriptExecutionContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ class ScriptExecutionContext : public SecurityContext, public CanMakeCheckedPtr,
virtual Seconds domTimerAlignmentInterval(bool hasReachedMaxNestingLevel) const;

// TimerAlignment
std::optional<MonotonicTime> alignedFireTime(bool hasReachedMaxNestingLevel, MonotonicTime fireTime) const final;
WEBCORE_EXPORT std::optional<MonotonicTime> alignedFireTime(bool hasReachedMaxNestingLevel, MonotonicTime fireTime) const final;

virtual EventTarget* errorEventTarget() = 0;

Expand Down
1 change: 1 addition & 0 deletions Source/WebCore/dom/TaskSource.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ enum class TaskSource : uint8_t {
Reporting,
ScreenWakelock,
Speech,
Timer,
UserInteraction,
WebGL,
WebXR,
Expand Down
58 changes: 36 additions & 22 deletions Source/WebCore/page/DOMTimer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ struct NestedTimersMap {
bool NestedTimersMap::isTrackingNestedTimers = false;

DOMTimer::DOMTimer(ScriptExecutionContext& context, Function<void(ScriptExecutionContext&)>&& action, Seconds interval, Type type)
: SuspendableTimerBase(&context)
: ActiveDOMObject(&context)
, m_nestingLevel(context.timerNestingLevel())
, m_action(WTFMove(action))
, m_originalInterval(interval)
Expand All @@ -169,12 +169,21 @@ DOMTimer::DOMTimer(ScriptExecutionContext& context, Function<void(ScriptExecutio
, m_currentTimerInterval(intervalClampedToMinimum())
, m_userGestureTokenToForward(UserGestureIndicator::currentUserGesture())
{
setTimerAlignment(context);
setHasReachedMaxNestingLevel(m_nestingLevel >= (m_oneShot ? maxTimerNestingLevelForOneShotTimers : maxTimerNestingLevelForRepeatingTimers));
if (m_oneShot)
startOneShot(m_currentTimerInterval);
else
startRepeating(m_originalInterval, m_currentTimerInterval);
auto& eventLoop = context.eventLoop();
if (m_oneShot) {
m_timer = eventLoop.scheduleTask(m_currentTimerInterval, TaskSource::Timer, [weakThis = WeakPtr { *this }] {
if (RefPtr strongThis = weakThis.get())
strongThis->fired();
});
} else {
m_timer = eventLoop.scheduleRepeatingTask(m_originalInterval, m_currentTimerInterval, TaskSource::Timer, [weakThis = WeakPtr { *this }] {
if (RefPtr strongThis = weakThis.get())
strongThis->fired();
});
}
eventLoop.setTimerAlignment(m_timer, context);
m_hasReachedMaxNestingLevel = m_nestingLevel >= (m_oneShot ? maxTimerNestingLevelForOneShotTimers : maxTimerNestingLevelForRepeatingTimers);
eventLoop.setTimerHasReachedMaxNestingLevel(m_timer, m_hasReachedMaxNestingLevel);
}

DOMTimer::~DOMTimer() = default;
Expand Down Expand Up @@ -237,7 +246,8 @@ void DOMTimer::removeById(ScriptExecutionContext& context, int timeoutId)

InspectorInstrumentation::didRemoveTimer(context, timeoutId);

context.takeTimeout(timeoutId);
if (auto timer = context.takeTimeout(timeoutId))
timer->m_timer = nullptr;
}

inline bool DOMTimer::isDOMTimersThrottlingEnabled(const Document& document) const
Expand Down Expand Up @@ -297,11 +307,12 @@ void DOMTimer::fired()
ScriptExecutionContext& context = *scriptExecutionContext();

#if PLATFORM(IOS_FAMILY)
if (is<Document>(context)) {
auto& document = downcast<Document>(context);
if (auto* holdingTank = document.domTimerHoldingTankIfExists(); holdingTank && holdingTank->contains(*this)) {
if (m_oneShot)
startOneShot(0_s);
if (RefPtr document = dynamicDowncast<Document>(context); document && m_oneShot) {
if (auto* holdingTank = document->domTimerHoldingTankIfExists(); holdingTank && holdingTank->contains(*this)) {
m_timer = document->eventLoop().scheduleTask(0_s, TaskSource::Timer, [weakThis = WeakPtr { *this }] {
if (RefPtr strongThis = weakThis.get())
strongThis->fired();
});
return;
}
}
Expand All @@ -312,7 +323,6 @@ void DOMTimer::fired()
if (m_userGestureTokenToForward && m_userGestureTokenToForward->hasExpired(UserGestureToken::maximumIntervalForUserGestureForwarding))
m_userGestureTokenToForward = nullptr;

ASSERT(!isSuspended());
ASSERT(!context.activeDOMObjectsAreSuspended());
UserGestureIndicator gestureIndicator(m_userGestureTokenToForward);
// Only the first execution of a multi-shot timer should get an affirmative user gesture indicator.
Expand All @@ -321,11 +331,11 @@ void DOMTimer::fired()
InspectorInstrumentation::willFireTimer(context, m_timeoutId, m_oneShot);

// Simple case for non-one-shot timers.
if (isActive()) {
ASSERT(!m_oneShot);
if (!m_oneShot) {
if (m_nestingLevel < maxTimerNestingLevel) {
m_nestingLevel++;
setHasReachedMaxNestingLevel(m_nestingLevel >= maxTimerNestingLevelForRepeatingTimers);
m_hasReachedMaxNestingLevel = m_nestingLevel >= maxTimerNestingLevelForRepeatingTimers;
context.eventLoop().setTimerHasReachedMaxNestingLevel(m_timer, m_hasReachedMaxNestingLevel);
updateTimerIntervalIfNecessary();
}

Expand Down Expand Up @@ -356,37 +366,41 @@ void DOMTimer::fired()
if (nestedTimers) {
for (auto& idAndTimer : *nestedTimers) {
auto& timer = idAndTimer.value;
if (timer->isActive() && timer->m_oneShot)
if (timer->m_oneShot)
timer->updateThrottlingStateIfNecessary(fireState);
}
nestedTimers->stopTracking();
}
}

void DOMTimer::didStop()
void DOMTimer::stop()
{
// Need to release JS objects potentially protected by ScheduledAction
// because they can form circular references back to the ScriptExecutionContext
// which will cause a memory leak.
m_timer = nullptr;
m_action = nullptr;
}

void DOMTimer::updateTimerIntervalIfNecessary()
{
ASSERT(m_nestingLevel <= maxTimerNestingLevel);

if (!scriptExecutionContext())
return;

auto previousInterval = m_currentTimerInterval;
m_currentTimerInterval = intervalClampedToMinimum();
if (previousInterval == m_currentTimerInterval)
return;

ScriptExecutionContext& context = *scriptExecutionContext();
if (m_oneShot) {
LOG(DOMTimers, "%p - Updating DOMTimer's fire interval from %.2f ms to %.2f ms due to throttling.", this, previousInterval.milliseconds(), m_currentTimerInterval.milliseconds());
augmentFireInterval(m_currentTimerInterval - previousInterval);
context.eventLoop().adjustTimerNextFireTime(m_timer, m_currentTimerInterval - previousInterval);
} else {
ASSERT(repeatInterval() == previousInterval);
LOG(DOMTimers, "%p - Updating DOMTimer's repeat interval from %.2f ms to %.2f ms due to throttling.", this, previousInterval.milliseconds(), m_currentTimerInterval.milliseconds());
augmentRepeatInterval(m_currentTimerInterval - previousInterval);
context.eventLoop().adjustTimerRepeatInterval(m_timer, m_currentTimerInterval - previousInterval);
}
}

Expand Down
Loading

0 comments on commit 43d0b6d

Please sign in to comment.