| /* |
| * Copyright (C) 2010 Julien Chaffraix <jchaffraix@webkit.org> All right reserved. |
| * Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies) |
| * |
| * 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 THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT |
| * OWNER OR 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 "XMLHttpRequestProgressEventThrottle.h" |
| |
| #include "EventNames.h" |
| #include "EventTarget.h" |
| #include "XMLHttpRequestProgressEvent.h" |
| |
| namespace WebCore { |
| |
| const Seconds XMLHttpRequestProgressEventThrottle::minimumProgressEventDispatchingInterval { 50_ms }; // 50 ms per specification. |
| |
| XMLHttpRequestProgressEventThrottle::XMLHttpRequestProgressEventThrottle(EventTarget& target) |
| : m_target(target) |
| , m_dispatchThrottledProgressEventTimer(target.scriptExecutionContext(), *this, &XMLHttpRequestProgressEventThrottle::dispatchThrottledProgressEventTimerFired) |
| , m_dispatchDeferredEventsAfterResumingTimer(target.scriptExecutionContext(), *this, &XMLHttpRequestProgressEventThrottle::dispatchDeferredEventsAfterResuming) |
| { |
| m_dispatchThrottledProgressEventTimer.suspendIfNeeded(); |
| m_dispatchDeferredEventsAfterResumingTimer.suspendIfNeeded(); |
| } |
| |
| XMLHttpRequestProgressEventThrottle::~XMLHttpRequestProgressEventThrottle() = default; |
| |
| void XMLHttpRequestProgressEventThrottle::dispatchThrottledProgressEvent(bool lengthComputable, unsigned long long loaded, unsigned long long total) |
| { |
| m_lengthComputable = lengthComputable; |
| m_loaded = loaded; |
| m_total = total; |
| |
| if (!m_target.hasEventListeners(eventNames().progressEvent)) |
| return; |
| |
| if (!m_shouldDeferEventsDueToSuspension && !m_dispatchThrottledProgressEventTimer.isActive()) { |
| // The timer is not active so the least frequent event for now is every byte. Just dispatch the event. |
| |
| // We should not have any throttled progress event. |
| ASSERT(!m_hasPendingThrottledProgressEvent); |
| |
| dispatchEventWhenPossible(XMLHttpRequestProgressEvent::create(eventNames().progressEvent, lengthComputable, loaded, total)); |
| m_dispatchThrottledProgressEventTimer.startRepeating(minimumProgressEventDispatchingInterval); |
| m_hasPendingThrottledProgressEvent = false; |
| return; |
| } |
| |
| // The timer is already active so minimumProgressEventDispatchingInterval is the least frequent event. |
| m_hasPendingThrottledProgressEvent = true; |
| } |
| |
| void XMLHttpRequestProgressEventThrottle::dispatchReadyStateChangeEvent(Event& event, ProgressEventAction progressEventAction) |
| { |
| if (progressEventAction == FlushProgressEvent) |
| flushProgressEvent(); |
| |
| dispatchEventWhenPossible(event); |
| } |
| |
| void XMLHttpRequestProgressEventThrottle::dispatchEventWhenPossible(Event& event) |
| { |
| if (m_shouldDeferEventsDueToSuspension) { |
| if (m_eventsDeferredDueToSuspension.size() > 1 && event.type() == eventNames().readystatechangeEvent && event.type() == m_eventsDeferredDueToSuspension.last()->type()) { |
| // Readystatechange events are state-less so avoid repeating two identical events in a row on resume. |
| return; |
| } |
| m_eventsDeferredDueToSuspension.append(event); |
| } else |
| m_target.dispatchEvent(event); |
| } |
| |
| void XMLHttpRequestProgressEventThrottle::dispatchProgressEvent(const AtomString& type) |
| { |
| ASSERT(type == eventNames().loadstartEvent || type == eventNames().progressEvent || type == eventNames().loadEvent || type == eventNames().loadendEvent || type == eventNames().abortEvent || type == eventNames().errorEvent || type == eventNames().timeoutEvent); |
| |
| if (type == eventNames().loadstartEvent) { |
| m_lengthComputable = false; |
| m_loaded = 0; |
| m_total = 0; |
| } |
| |
| if (m_target.hasEventListeners(type)) |
| dispatchEventWhenPossible(XMLHttpRequestProgressEvent::create(type, m_lengthComputable, m_loaded, m_total)); |
| } |
| |
| void XMLHttpRequestProgressEventThrottle::flushProgressEvent() |
| { |
| if (!m_hasPendingThrottledProgressEvent) |
| return; |
| |
| m_hasPendingThrottledProgressEvent = false; |
| // We stop the timer as this is called when no more events are supposed to occur. |
| m_dispatchThrottledProgressEventTimer.cancel(); |
| |
| dispatchEventWhenPossible(XMLHttpRequestProgressEvent::create(eventNames().progressEvent, m_lengthComputable, m_loaded, m_total)); |
| } |
| |
| void XMLHttpRequestProgressEventThrottle::dispatchDeferredEventsAfterResuming() |
| { |
| ASSERT(m_shouldDeferEventsDueToSuspension); |
| m_shouldDeferEventsDueToSuspension = false; |
| |
| // Take over the deferred events before dispatching them which can potentially add more. |
| auto eventsDeferredDueToSuspension = WTFMove(m_eventsDeferredDueToSuspension); |
| |
| flushProgressEvent(); |
| |
| for (auto& deferredEvent : eventsDeferredDueToSuspension) |
| dispatchEventWhenPossible(deferredEvent); |
| } |
| |
| void XMLHttpRequestProgressEventThrottle::dispatchThrottledProgressEventTimerFired() |
| { |
| ASSERT(m_dispatchThrottledProgressEventTimer.isActive()); |
| if (!m_hasPendingThrottledProgressEvent) { |
| // No progress event was queued since the previous dispatch, we can safely stop the timer. |
| m_dispatchThrottledProgressEventTimer.cancel(); |
| return; |
| } |
| |
| dispatchEventWhenPossible(XMLHttpRequestProgressEvent::create(eventNames().progressEvent, m_lengthComputable, m_loaded, m_total)); |
| m_hasPendingThrottledProgressEvent = false; |
| } |
| |
| void XMLHttpRequestProgressEventThrottle::suspend() |
| { |
| m_shouldDeferEventsDueToSuspension = true; |
| } |
| |
| void XMLHttpRequestProgressEventThrottle::resume() |
| { |
| if (m_eventsDeferredDueToSuspension.isEmpty() && !m_hasPendingThrottledProgressEvent) { |
| m_shouldDeferEventsDueToSuspension = false; |
| return; |
| } |
| |
| m_dispatchDeferredEventsAfterResumingTimer.startOneShot(0_s); |
| } |
| |
| } // namespace WebCore |