1/*
2 * Copyright (C) 2010 Julien Chaffraix <jchaffraix@webkit.org>  All right reserved.
3 * Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies)
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
15 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
16 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
17 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
18 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
19 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
20 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
21 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
22 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27#include "config.h"
28#include "XMLHttpRequestProgressEventThrottle.h"
29
30#include "EventTarget.h"
31#include "XMLHttpRequestProgressEvent.h"
32
33namespace WebCore {
34
35const double XMLHttpRequestProgressEventThrottle::minimumProgressEventDispatchingIntervalInSeconds = .05; // 50 ms per specification.
36
37XMLHttpRequestProgressEventThrottle::XMLHttpRequestProgressEventThrottle(EventTarget* target)
38    : m_target(target)
39    , m_loaded(0)
40    , m_total(0)
41    , m_deferEvents(false)
42    , m_dispatchDeferredEventsTimer(this, &XMLHttpRequestProgressEventThrottle::dispatchDeferredEvents)
43{
44    ASSERT(target);
45}
46
47XMLHttpRequestProgressEventThrottle::~XMLHttpRequestProgressEventThrottle()
48{
49}
50
51void XMLHttpRequestProgressEventThrottle::dispatchProgressEvent(bool lengthComputable, unsigned long long loaded, unsigned long long total)
52{
53    if (m_deferEvents) {
54        // Only store the latest progress event while suspended.
55        m_deferredProgressEvent = XMLHttpRequestProgressEvent::create(eventNames().progressEvent, lengthComputable, loaded, total);
56        return;
57    }
58
59    if (!isActive()) {
60        // The timer is not active so the least frequent event for now is every byte.
61        // Just go ahead and dispatch the event.
62
63        // We should not have any pending loaded & total information from a previous run.
64        ASSERT(!m_loaded);
65        ASSERT(!m_total);
66
67        dispatchEvent(XMLHttpRequestProgressEvent::create(eventNames().progressEvent, lengthComputable, loaded, total));
68        startRepeating(minimumProgressEventDispatchingIntervalInSeconds);
69        return;
70    }
71
72    // The timer is already active so minimumProgressEventDispatchingIntervalInSeconds is the least frequent event.
73    m_lengthComputable = lengthComputable;
74    m_loaded = loaded;
75    m_total = total;
76}
77
78void XMLHttpRequestProgressEventThrottle::dispatchReadyStateChangeEvent(PassRefPtr<Event> event, ProgressEventAction progressEventAction)
79{
80    if (progressEventAction == FlushProgressEvent)
81        flushProgressEvent();
82
83    dispatchEvent(event);
84}
85
86void XMLHttpRequestProgressEventThrottle::dispatchEvent(PassRefPtr<Event> event)
87{
88    ASSERT(event);
89    if (m_deferEvents) {
90        if (m_deferredEvents.size() > 1 && event->type() == eventNames().readystatechangeEvent && event->type() == m_deferredEvents.last()->type()) {
91            // Readystatechange events are state-less so avoid repeating two identical events in a row on resume.
92            return;
93        }
94        m_deferredEvents.append(event);
95    } else
96        m_target->dispatchEvent(event);
97}
98
99void XMLHttpRequestProgressEventThrottle::dispatchEventAndLoadEnd(PassRefPtr<Event> event)
100{
101    ASSERT(event->type() == eventNames().loadEvent || event->type() == eventNames().abortEvent || event->type() == eventNames().errorEvent || event->type() == eventNames().timeoutEvent);
102
103    dispatchEvent(event);
104    dispatchEvent(XMLHttpRequestProgressEvent::create(eventNames().loadendEvent));
105}
106
107void XMLHttpRequestProgressEventThrottle::flushProgressEvent()
108{
109    if (m_deferEvents && m_deferredProgressEvent) {
110        // Move the progress event to the queue, to get it in the right order on resume.
111        m_deferredEvents.append(m_deferredProgressEvent);
112        m_deferredProgressEvent = 0;
113        return;
114    }
115
116    if (!hasEventToDispatch())
117        return;
118
119    PassRefPtr<Event> event = XMLHttpRequestProgressEvent::create(eventNames().progressEvent, m_lengthComputable, m_loaded, m_total);
120    m_loaded = 0;
121    m_total = 0;
122
123    // We stop the timer as this is called when no more events are supposed to occur.
124    stop();
125
126    dispatchEvent(event);
127}
128
129void XMLHttpRequestProgressEventThrottle::dispatchDeferredEvents(Timer<XMLHttpRequestProgressEventThrottle>* timer)
130{
131    ASSERT_UNUSED(timer, timer == &m_dispatchDeferredEventsTimer);
132    ASSERT(m_deferEvents);
133    m_deferEvents = false;
134
135    // Take over the deferred events before dispatching them which can potentially add more.
136    Vector<RefPtr<Event> > deferredEvents;
137    m_deferredEvents.swap(deferredEvents);
138
139    RefPtr<Event> deferredProgressEvent = m_deferredProgressEvent;
140    m_deferredProgressEvent = 0;
141
142    Vector<RefPtr<Event> >::const_iterator it = deferredEvents.begin();
143    const Vector<RefPtr<Event> >::const_iterator end = deferredEvents.end();
144    for (; it != end; ++it)
145        dispatchEvent(*it);
146
147    // The progress event will be in the m_deferredEvents vector if the load was finished while suspended.
148    // If not, just send the most up-to-date progress on resume.
149    if (deferredProgressEvent)
150        dispatchEvent(deferredProgressEvent);
151}
152
153void XMLHttpRequestProgressEventThrottle::fired()
154{
155    ASSERT(isActive());
156    if (!hasEventToDispatch()) {
157        // No progress event was queued since the previous dispatch, we can safely stop the timer.
158        stop();
159        return;
160    }
161
162    dispatchEvent(XMLHttpRequestProgressEvent::create(eventNames().progressEvent, m_lengthComputable, m_loaded, m_total));
163    m_total = 0;
164    m_loaded = 0;
165}
166
167bool XMLHttpRequestProgressEventThrottle::hasEventToDispatch() const
168{
169    return (m_total || m_loaded) && isActive();
170}
171
172void XMLHttpRequestProgressEventThrottle::suspend()
173{
174    // If re-suspended before deferred events have been dispatched, just stop the dispatch
175    // and continue the last suspend.
176    if (m_dispatchDeferredEventsTimer.isActive()) {
177        ASSERT(m_deferEvents);
178        m_dispatchDeferredEventsTimer.stop();
179        return;
180    }
181    ASSERT(!m_deferredProgressEvent);
182    ASSERT(m_deferredEvents.isEmpty());
183    ASSERT(!m_deferEvents);
184
185    m_deferEvents = true;
186    // If we have a progress event waiting to be dispatched,
187    // just defer it.
188    if (hasEventToDispatch()) {
189        m_deferredProgressEvent = XMLHttpRequestProgressEvent::create(eventNames().progressEvent, m_lengthComputable, m_loaded, m_total);
190        m_total = 0;
191        m_loaded = 0;
192    }
193    stop();
194}
195
196void XMLHttpRequestProgressEventThrottle::resume()
197{
198    ASSERT(!m_loaded);
199    ASSERT(!m_total);
200
201    if (m_deferredEvents.isEmpty() && !m_deferredProgressEvent) {
202        m_deferEvents = false;
203        return;
204    }
205
206    // Do not dispatch events inline here, since ScriptExecutionContext is iterating over
207    // the list of active DOM objects to resume them, and any activated JS event-handler
208    // could insert new active DOM objects to the list.
209    // m_deferEvents is kept true until all deferred events have been dispatched.
210    m_dispatchDeferredEventsTimer.startOneShot(0);
211}
212
213} // namespace WebCore
214