blob: 2d44771879cc05c13a135123072bda63562c4200 [file] [log] [blame]
/*
* Copyright (C) 2020 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#include "WebWheelEventCoalescer.h"
#include "Logging.h"
#include "NativeWebWheelEvent.h"
#include "WebEventConversion.h"
#include <WebCore/AnimationFrameRate.h>
#include <wtf/text/TextStream.h>
namespace WebKit {
// Represents the number of wheel events we can hold in the queue before we start pushing them preemptively.
constexpr unsigned wheelEventQueueSizeThreshold = 10;
#if !LOG_DISABLED
static WTF::TextStream& operator<<(WTF::TextStream& ts, const WebWheelEvent& wheelEvent)
{
ts << platform(wheelEvent);
return ts;
}
#endif
bool WebWheelEventCoalescer::canCoalesce(const WebWheelEvent& a, const WebWheelEvent& b)
{
if (a.position() != b.position())
return false;
if (a.globalPosition() != b.globalPosition())
return false;
if (a.modifiers() != b.modifiers())
return false;
if (a.granularity() != b.granularity())
return false;
#if PLATFORM(COCOA)
if (a.phase() != b.phase())
return false;
if (a.momentumPhase() != b.momentumPhase())
return false;
#endif
#if PLATFORM(COCOA) || PLATFORM(GTK) || USE(LIBWPE)
if (a.hasPreciseScrollingDeltas() != b.hasPreciseScrollingDeltas())
return false;
#endif
return true;
}
WebWheelEvent WebWheelEventCoalescer::coalesce(const WebWheelEvent& a, const WebWheelEvent& b)
{
ASSERT(canCoalesce(a, b));
auto mergedDelta = a.delta() + b.delta();
auto mergedWheelTicks = a.wheelTicks() + b.wheelTicks();
#if PLATFORM(COCOA)
auto mergedUnacceleratedScrollingDelta = a.unacceleratedScrollingDelta() + b.unacceleratedScrollingDelta();
return WebWheelEvent(WebEvent::Wheel, b.position(), b.globalPosition(), mergedDelta, mergedWheelTicks, b.granularity(), b.directionInvertedFromDevice(), b.phase(), b.momentumPhase(), b.hasPreciseScrollingDeltas(), b.scrollCount(), mergedUnacceleratedScrollingDelta, b.modifiers(), b.timestamp());
#elif PLATFORM(GTK) || USE(LIBWPE)
return WebWheelEvent(WebEvent::Wheel, b.position(), b.globalPosition(), mergedDelta, mergedWheelTicks, b.phase(), b.momentumPhase(), b.granularity(), b.hasPreciseScrollingDeltas(), b.modifiers(), b.timestamp());
#else
return WebWheelEvent(WebEvent::Wheel, b.position(), b.globalPosition(), mergedDelta, mergedWheelTicks, b.granularity(), b.modifiers(), b.timestamp());
#endif
}
bool WebWheelEventCoalescer::isMomentumPhaseEvent(const WebWheelEvent& event)
{
return event.phase() == WebWheelEvent::Phase::PhaseNone && event.momentumPhase() == WebWheelEvent::Phase::PhaseChanged;
}
bool WebWheelEventCoalescer::shouldDispatchEventNow(const WebWheelEvent& event) const
{
#if PLATFORM(GTK)
// Don't queue events representing a non-trivial scrolling phase to
// avoid having them trapped in the queue, potentially preventing a
// scrolling session to beginning or end correctly.
// This is only needed by platforms whose WebWheelEvent has this phase
// information (Cocoa and GTK+) but Cocoa was fine without it.
if (event.phase() == WebWheelEvent::Phase::PhaseNone
|| event.phase() == WebWheelEvent::Phase::PhaseChanged
|| event.momentumPhase() == WebWheelEvent::Phase::PhaseNone
|| event.momentumPhase() == WebWheelEvent::Phase::PhaseChanged)
return true;
#else
UNUSED_PARAM(event);
#endif
return m_wheelEventQueue.size() >= wheelEventQueueSizeThreshold;
}
std::optional<WebWheelEvent> WebWheelEventCoalescer::nextEventToDispatch()
{
if (m_wheelEventQueue.isEmpty())
return std::nullopt;
auto coalescedEvent = m_wheelEventQueue.takeFirst();
auto coalescedSequence = makeUnique<CoalescedEventSequence>();
coalescedSequence->append(coalescedEvent);
WebWheelEvent coalescedWebEvent = coalescedEvent;
while (!m_wheelEventQueue.isEmpty() && canCoalesce(coalescedWebEvent, m_wheelEventQueue.first())) {
auto firstEvent = m_wheelEventQueue.takeFirst();
coalescedSequence->append(firstEvent);
coalescedWebEvent = coalesce(coalescedWebEvent, firstEvent);
}
#if !LOG_DISABLED
if (coalescedSequence->size() > 1)
LOG_WITH_STREAM(WheelEvents, stream << "WebWheelEventCoalescer::wheelEventWithCoalescing coalsesced " << *coalescedSequence << " into " << coalescedWebEvent);
#endif
m_eventsBeingProcessed.append(WTFMove(coalescedSequence));
return coalescedWebEvent;
}
bool WebWheelEventCoalescer::shouldDispatchEvent(const NativeWebWheelEvent& event)
{
LOG_WITH_STREAM(WheelEvents, stream << "WebWheelEventCoalescer::shouldDispatchEvent " << event << " (" << m_wheelEventQueue.size() << " events in the queue, " << m_eventsBeingProcessed.size() << " event sequences being processed, coalesce during decleration " << m_shouldCoalesceEventsDuringDeceleration << ")");
m_wheelEventQueue.append(event);
auto lastEventInterval = event.timestamp() - m_lastEventTime;
m_lastEventTime = event.timestamp();
if (isMomentumPhaseEvent(event) && shouldCoalesceEventsDuringDeceleration() && lastEventInterval) {
constexpr double momentumVelocityEventFrequencyReductionThreashold = 320.0; // Points per second.
auto instantaneousVelocity = std::max(std::abs(event.delta().width()), std::abs(event.delta().height())) / lastEventInterval.seconds();
constexpr auto maxCoalescingInterval = WebCore::FullSpeedAnimationInterval;
auto lastDispatchedEventInterval = event.timestamp() - m_lastDispatchedEventTime;
if (instantaneousVelocity < momentumVelocityEventFrequencyReductionThreashold && lastDispatchedEventInterval < maxCoalescingInterval) {
LOG_WITH_STREAM(WheelEvents, stream << " coalesced event that came within " << lastDispatchedEventInterval.milliseconds() << " of previous dispatch");
return false;
}
}
if (!m_eventsBeingProcessed.isEmpty()) {
if (!shouldDispatchEventNow(m_wheelEventQueue.last())) {
LOG_WITH_STREAM(WheelEvents, stream << "WebWheelEventCoalescer::shouldDispatchEvent - " << m_wheelEventQueue.size() << " events queued; not dispatching");
return false;
}
// The queue has too many wheel events, so push a new event.
// FIXME: This logic is confusing, and possibly not necessary.
}
m_lastDispatchedEventTime = event.timestamp();
return true;
}
NativeWebWheelEvent WebWheelEventCoalescer::takeOldestEventBeingProcessed()
{
ASSERT(hasEventsBeingProcessed());
auto oldestSequence = m_eventsBeingProcessed.takeFirst();
return oldestSequence->last();
}
void WebWheelEventCoalescer::clear()
{
m_wheelEventQueue.clear();
m_eventsBeingProcessed.clear();
}
} // namespace WebKit