Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Integrate setTimeout and setInterval with HTML5 event loop #16550

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 0 additions & 1 deletion Source/WebCore/Headers.cmake
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
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
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
@@ -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
@@ -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
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
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
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
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