/* * Copyright (C) 2010 Julien Chaffraix 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 "EventTarget.h" #include "XMLHttpRequestProgressEvent.h" namespace WebCore { const double XMLHttpRequestProgressEventThrottle::minimumProgressEventDispatchingIntervalInSeconds = .05; // 50 ms per specification. XMLHttpRequestProgressEventThrottle::XMLHttpRequestProgressEventThrottle(EventTarget* target) : m_target(target) , m_hasThrottledProgressEvent(false) , m_lengthComputable(false) , m_loaded(0) , m_total(0) , m_deferEvents(false) , m_dispatchDeferredEventsTimer(*this, &XMLHttpRequestProgressEventThrottle::dispatchDeferredEvents) { ASSERT(target); } XMLHttpRequestProgressEventThrottle::~XMLHttpRequestProgressEventThrottle() { } 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_deferEvents) { // Only store the latest progress event while suspended. m_deferredProgressEvent = XMLHttpRequestProgressEvent::create(eventNames().progressEvent, lengthComputable, loaded, total); return; } if (!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_hasThrottledProgressEvent); dispatchEvent(XMLHttpRequestProgressEvent::create(eventNames().progressEvent, lengthComputable, loaded, total)); startRepeating(minimumProgressEventDispatchingIntervalInSeconds); m_hasThrottledProgressEvent = false; return; } // The timer is already active so minimumProgressEventDispatchingIntervalInSeconds is the least frequent event. m_hasThrottledProgressEvent = true; } void XMLHttpRequestProgressEventThrottle::dispatchReadyStateChangeEvent(Event& event, ProgressEventAction progressEventAction) { if (progressEventAction == FlushProgressEvent) flushProgressEvent(); dispatchEvent(event); } void XMLHttpRequestProgressEventThrottle::dispatchEvent(Event& event) { if (m_deferEvents) { if (m_deferredEvents.size() > 1 && event.type() == eventNames().readystatechangeEvent && event.type() == m_deferredEvents.last()->type()) { // Readystatechange events are state-less so avoid repeating two identical events in a row on resume. return; } m_deferredEvents.append(event); } else m_target->dispatchEvent(event); } void XMLHttpRequestProgressEventThrottle::dispatchProgressEvent(const AtomicString& 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)) dispatchEvent(XMLHttpRequestProgressEvent::create(type, m_lengthComputable, m_loaded, m_total)); } void XMLHttpRequestProgressEventThrottle::flushProgressEvent() { if (m_deferEvents && m_deferredProgressEvent) { // Move the progress event to the queue, to get it in the right order on resume. m_deferredEvents.append(m_deferredProgressEvent.releaseNonNull()); return; } if (!hasEventToDispatch()) return; Ref event = XMLHttpRequestProgressEvent::create(eventNames().progressEvent, m_lengthComputable, m_loaded, m_total); m_hasThrottledProgressEvent = false; // We stop the timer as this is called when no more events are supposed to occur. stop(); dispatchEvent(WTFMove(event)); } void XMLHttpRequestProgressEventThrottle::dispatchDeferredEvents() { ASSERT(m_deferEvents); m_deferEvents = false; // Take over the deferred events before dispatching them which can potentially add more. auto deferredEvents = WTFMove(m_deferredEvents); RefPtr deferredProgressEvent = WTFMove(m_deferredProgressEvent); for (auto& deferredEvent : deferredEvents) dispatchEvent(deferredEvent); // The progress event will be in the m_deferredEvents vector if the load was finished while suspended. // If not, just send the most up-to-date progress on resume. if (deferredProgressEvent) dispatchEvent(*deferredProgressEvent); } void XMLHttpRequestProgressEventThrottle::fired() { ASSERT(isActive()); if (!hasEventToDispatch()) { // No progress event was queued since the previous dispatch, we can safely stop the timer. stop(); return; } dispatchEvent(XMLHttpRequestProgressEvent::create(eventNames().progressEvent, m_lengthComputable, m_loaded, m_total)); m_hasThrottledProgressEvent = false; } bool XMLHttpRequestProgressEventThrottle::hasEventToDispatch() const { return m_hasThrottledProgressEvent && isActive(); } void XMLHttpRequestProgressEventThrottle::suspend() { // If re-suspended before deferred events have been dispatched, just stop the dispatch // and continue the last suspend. if (m_dispatchDeferredEventsTimer.isActive()) { ASSERT(m_deferEvents); m_dispatchDeferredEventsTimer.stop(); return; } ASSERT(!m_deferredProgressEvent); ASSERT(m_deferredEvents.isEmpty()); ASSERT(!m_deferEvents); m_deferEvents = true; // If we have a progress event waiting to be dispatched, // just defer it. if (hasEventToDispatch()) { m_deferredProgressEvent = XMLHttpRequestProgressEvent::create(eventNames().progressEvent, m_lengthComputable, m_loaded, m_total); m_hasThrottledProgressEvent = false; } stop(); } void XMLHttpRequestProgressEventThrottle::resume() { ASSERT(!m_hasThrottledProgressEvent); if (m_deferredEvents.isEmpty() && !m_deferredProgressEvent) { m_deferEvents = false; return; } // Do not dispatch events inline here, since ScriptExecutionContext is iterating over // the list of active DOM objects to resume them, and any activated JS event-handler // could insert new active DOM objects to the list. // m_deferEvents is kept true until all deferred events have been dispatched. m_dispatchDeferredEventsTimer.startOneShot(0); } } // namespace WebCore