ThreadedScrollingTree.cpp [plain text]
#include "config.h"
#include "ThreadedScrollingTree.h"
#if ENABLE(ASYNC_SCROLLING) && ENABLE(SCROLLING_THREAD)
#include "AsyncScrollingCoordinator.h"
#include "Logging.h"
#include "PlatformWheelEvent.h"
#include "ScrollingThread.h"
#include "ScrollingTreeFrameScrollingNode.h"
#include "ScrollingTreeNode.h"
#include "ScrollingTreeOverflowScrollProxyNode.h"
#include "ScrollingTreeScrollingNode.h"
#include <wtf/RunLoop.h>
#include <wtf/SetForScope.h>
#include <wtf/SystemTracing.h>
#include <wtf/text/TextStream.h>
#include <wtf/threads/BinarySemaphore.h>
namespace WebCore {
ThreadedScrollingTree::ThreadedScrollingTree(AsyncScrollingCoordinator& scrollingCoordinator)
: m_scrollingCoordinator(&scrollingCoordinator)
{
}
ThreadedScrollingTree::~ThreadedScrollingTree()
{
ASSERT(!m_scrollingCoordinator);
}
WheelEventHandlingResult ThreadedScrollingTree::handleWheelEvent(const PlatformWheelEvent& wheelEvent)
{
ASSERT(ScrollingThread::isCurrentThread());
return ScrollingTree::handleWheelEvent(wheelEvent);
}
bool ThreadedScrollingTree::handleWheelEventAfterMainThread(const PlatformWheelEvent& wheelEvent)
{
SetForScope<bool> disallowLatchingScope(m_allowLatching, false);
auto result = handleWheelEvent(wheelEvent);
return result.wasHandled;
}
void ThreadedScrollingTree::invalidate()
{
ASSERT(ScrollingThread::isCurrentThread());
LockHolder treeLocker(m_treeMutex);
removeAllNodes();
m_delayedRenderingUpdateDetectionTimer = nullptr;
RunLoop::main().dispatch([scrollingCoordinator = WTFMove(m_scrollingCoordinator)] {
});
}
void ThreadedScrollingTree::propagateSynchronousScrollingReasons(const HashSet<ScrollingNodeID>& synchronousScrollingNodes)
{
auto propagateStateToAncestors = [&](ScrollingTreeNode& node) {
ASSERT(is<ScrollingTreeScrollingNode>(node) && !downcast<ScrollingTreeScrollingNode>(node).synchronousScrollingReasons().isEmpty());
auto currNode = node.parent();
while (currNode) {
if (is<ScrollingTreeScrollingNode>(currNode))
downcast<ScrollingTreeScrollingNode>(*currNode).addSynchronousScrollingReason(SynchronousScrollingReason::DescendantScrollersHaveSynchronousScrolling);
if (is<ScrollingTreeOverflowScrollProxyNode>(currNode)) {
currNode = nodeForID(downcast<ScrollingTreeOverflowScrollProxyNode>(*currNode).overflowScrollingNodeID());
continue;
}
currNode = currNode->parent();
}
};
for (auto nodeID : synchronousScrollingNodes) {
if (auto node = nodeForID(nodeID))
propagateStateToAncestors(*node);
}
}
void ThreadedScrollingTree::scrollingTreeNodeDidScroll(ScrollingTreeScrollingNode& node, ScrollingLayerPositionAction scrollingLayerPositionAction)
{
if (!m_scrollingCoordinator)
return;
auto scrollPosition = node.currentScrollPosition();
if (node.isRootNode())
setMainFrameScrollPosition(scrollPosition);
if (isHandlingProgrammaticScroll())
return;
Optional<FloatPoint> layoutViewportOrigin;
if (is<ScrollingTreeFrameScrollingNode>(node))
layoutViewportOrigin = downcast<ScrollingTreeFrameScrollingNode>(node).layoutViewport().location();
#if PLATFORM(MAC)
if (isMonitoringWheelEvents())
deferWheelEventTestCompletionForReason(reinterpret_cast<WheelEventTestMonitor::ScrollableAreaIdentifier>(node.scrollingNodeID()), WheelEventTestMonitor::ScrollingThreadSyncNeeded);
#endif
LOG_WITH_STREAM(Scrolling, stream << "ThreadedScrollingTree::scrollingTreeNodeDidScroll " << node.scrollingNodeID() << " to " << scrollPosition << " bouncing to main thread");
if (RunLoop::isMain()) {
m_scrollingCoordinator->updateScrollPositionAfterAsyncScroll(node.scrollingNodeID(), scrollPosition, layoutViewportOrigin, ScrollType::User, scrollingLayerPositionAction);
return;
}
RunLoop::main().dispatch([strongThis = makeRef(*this), nodeID = node.scrollingNodeID(), scrollPosition, layoutViewportOrigin, scrollingLayerPositionAction] {
if (auto* scrollingCoordinator = strongThis->m_scrollingCoordinator.get())
scrollingCoordinator->scheduleUpdateScrollPositionAfterAsyncScroll(nodeID, scrollPosition, layoutViewportOrigin, scrollingLayerPositionAction);
});
}
void ThreadedScrollingTree::reportSynchronousScrollingReasonsChanged(MonotonicTime timestamp, OptionSet<SynchronousScrollingReason> reasons)
{
RunLoop::main().dispatch([scrollingCoordinator = m_scrollingCoordinator, timestamp, reasons] {
scrollingCoordinator->reportSynchronousScrollingReasonsChanged(timestamp, reasons);
});
}
void ThreadedScrollingTree::reportExposedUnfilledArea(MonotonicTime timestamp, unsigned unfilledArea)
{
RunLoop::main().dispatch([scrollingCoordinator = m_scrollingCoordinator, timestamp, unfilledArea] {
scrollingCoordinator->reportExposedUnfilledArea(timestamp, unfilledArea);
});
}
#if PLATFORM(COCOA)
void ThreadedScrollingTree::currentSnapPointIndicesDidChange(ScrollingNodeID nodeID, unsigned horizontal, unsigned vertical)
{
if (!m_scrollingCoordinator)
return;
RunLoop::main().dispatch([scrollingCoordinator = m_scrollingCoordinator, nodeID, horizontal, vertical] {
scrollingCoordinator->setActiveScrollSnapIndices(nodeID, horizontal, vertical);
});
}
#endif
#if PLATFORM(MAC)
void ThreadedScrollingTree::handleWheelEventPhase(ScrollingNodeID nodeID, PlatformWheelEventPhase phase)
{
if (!m_scrollingCoordinator)
return;
RunLoop::main().dispatch([scrollingCoordinator = m_scrollingCoordinator, nodeID, phase] {
scrollingCoordinator->handleWheelEventPhase(nodeID, phase);
});
}
void ThreadedScrollingTree::setActiveScrollSnapIndices(ScrollingNodeID nodeID, unsigned horizontalIndex, unsigned verticalIndex)
{
if (!m_scrollingCoordinator)
return;
RunLoop::main().dispatch([scrollingCoordinator = m_scrollingCoordinator, nodeID, horizontalIndex, verticalIndex] {
scrollingCoordinator->setActiveScrollSnapIndices(nodeID, horizontalIndex, verticalIndex);
});
}
void ThreadedScrollingTree::scrollingTreeNodeRequestsScroll(ScrollingNodeID nodeID, const FloatPoint& , ScrollType, ScrollClamping)
{
removeWheelEventTestCompletionDeferralForReason(reinterpret_cast<WheelEventTestMonitor::ScrollableAreaIdentifier>(nodeID), WheelEventTestMonitor::RequestedScrollPosition);
}
#endif
void ThreadedScrollingTree::willStartRenderingUpdate()
{
ASSERT(isMainThread());
if (!hasProcessedWheelEventsRecently())
return;
tracePoint(ScrollingThreadRenderUpdateSyncStart);
BinarySemaphore semaphore;
ScrollingThread::dispatch([protectedThis = makeRef(*this), &semaphore]() {
LockHolder treeLocker(protectedThis->m_treeMutex);
semaphore.signal();
protectedThis->waitForRenderingUpdateCompletionOrTimeout();
});
semaphore.wait();
m_state = SynchronizationState::InRenderingUpdate;
}
Seconds ThreadedScrollingTree::maxAllowableRenderingUpdateDurationForSynchronization()
{
constexpr double allowableFrameFraction = 0.5;
auto displayFPS = nominalFramesPerSecond().valueOr(60);
Seconds frameDuration = 1_s / (double)displayFPS;
return allowableFrameFraction * frameDuration;
}
void ThreadedScrollingTree::waitForRenderingUpdateCompletionOrTimeout()
{
ASSERT(ScrollingThread::isCurrentThread());
ASSERT(m_treeMutex.isLocked());
if (m_delayedRenderingUpdateDetectionTimer)
m_delayedRenderingUpdateDetectionTimer->stop();
auto startTime = MonotonicTime::now();
auto timeoutTime = startTime + maxAllowableRenderingUpdateDurationForSynchronization();
bool becameIdle = m_stateCondition.waitUntil(m_treeMutex, timeoutTime, [&] {
return m_state == SynchronizationState::Idle;
});
ASSERT(m_treeMutex.isLocked());
if (!becameIdle) {
m_state = SynchronizationState::Desynchronized;
applyLayerPositionsInternal();
tracePoint(ScrollingThreadRenderUpdateSyncEnd, 1);
} else
tracePoint(ScrollingThreadRenderUpdateSyncEnd);
}
void ThreadedScrollingTree::didCompleteRenderingUpdate()
{
ASSERT(isMainThread());
LockHolder treeLocker(m_treeMutex);
if (m_state == SynchronizationState::InRenderingUpdate)
m_stateCondition.notifyOne();
m_state = SynchronizationState::Idle;
}
void ThreadedScrollingTree::scheduleDelayedRenderingUpdateDetectionTimer(Seconds delay)
{
ASSERT(ScrollingThread::isCurrentThread());
ASSERT(m_treeMutex.isLocked());
if (!m_delayedRenderingUpdateDetectionTimer)
m_delayedRenderingUpdateDetectionTimer = makeUnique<RunLoop::Timer<ThreadedScrollingTree>>(RunLoop::current(), this, &ThreadedScrollingTree::delayedRenderingUpdateDetectionTimerFired);
m_delayedRenderingUpdateDetectionTimer->startOneShot(delay);
}
void ThreadedScrollingTree::delayedRenderingUpdateDetectionTimerFired()
{
ASSERT(ScrollingThread::isCurrentThread());
LockHolder treeLocker(m_treeMutex);
applyLayerPositionsInternal();
m_state = SynchronizationState::Desynchronized;
}
void ThreadedScrollingTree::displayDidRefreshOnScrollingThread()
{
TraceScope tracingScope(ScrollingThreadDisplayDidRefreshStart, ScrollingThreadDisplayDidRefreshEnd);
ASSERT(ScrollingThread::isCurrentThread());
LockHolder treeLocker(m_treeMutex);
if (m_state != SynchronizationState::Idle)
applyLayerPositionsInternal();
switch (m_state) {
case SynchronizationState::Idle: {
m_state = SynchronizationState::WaitingForRenderingUpdate;
constexpr auto maxStartRenderingUpdateDelay = 1_ms;
scheduleDelayedRenderingUpdateDetectionTimer(maxStartRenderingUpdateDelay);
break;
}
case SynchronizationState::WaitingForRenderingUpdate:
case SynchronizationState::InRenderingUpdate:
case SynchronizationState::Desynchronized:
break;
}
}
void ThreadedScrollingTree::displayDidRefresh(PlatformDisplayID displayID)
{
if (displayID != this->displayID())
return;
if (!hasProcessedWheelEventsRecently())
return;
ScrollingThread::dispatch([protectedThis = makeRef(*this)]() {
protectedThis->displayDidRefreshOnScrollingThread();
});
}
}
#endif // ENABLE(ASYNC_SCROLLING) && ENABLE(SCROLLING_THREAD)