[Content Extensions] Make blocked async XHR call onerror
[WebKit-https.git] / Source / WebCore / xml / XMLHttpRequest.cpp
1 /*
2  *  Copyright (C) 2004, 2006, 2008 Apple Inc. All rights reserved.
3  *  Copyright (C) 2005-2007 Alexey Proskuryakov <ap@webkit.org>
4  *  Copyright (C) 2007, 2008 Julien Chaffraix <jchaffraix@webkit.org>
5  *  Copyright (C) 2008, 2011 Google Inc. All rights reserved.
6  *  Copyright (C) 2012 Intel Corporation
7  *
8  *  This library is free software; you can redistribute it and/or
9  *  modify it under the terms of the GNU Lesser General Public
10  *  License as published by the Free Software Foundation; either
11  *  version 2 of the License, or (at your option) any later version.
12  *
13  *  This library is distributed in the hope that it will be useful,
14  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
15  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  *  Lesser General Public License for more details.
17  *
18  *  You should have received a copy of the GNU Lesser General Public
19  *  License along with this library; if not, write to the Free Software
20  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
21  */
22
23 #include "config.h"
24 #include "XMLHttpRequest.h"
25
26 #include "Blob.h"
27 #include "CachedResourceRequestInitiators.h"
28 #include "ContentSecurityPolicy.h"
29 #include "CrossOriginAccessControl.h"
30 #include "DOMFormData.h"
31 #include "DOMImplementation.h"
32 #include "Event.h"
33 #include "ExceptionCode.h"
34 #include "File.h"
35 #include "HTMLDocument.h"
36 #include "HTTPHeaderNames.h"
37 #include "HTTPParsers.h"
38 #include "InspectorInstrumentation.h"
39 #include "JSDOMBinding.h"
40 #include "JSDOMWindow.h"
41 #include "MemoryCache.h"
42 #include "ParsedContentType.h"
43 #include "ResourceError.h"
44 #include "ResourceRequest.h"
45 #include "ScriptController.h"
46 #include "SecurityOriginPolicy.h"
47 #include "Settings.h"
48 #include "SharedBuffer.h"
49 #include "TextResourceDecoder.h"
50 #include "ThreadableLoader.h"
51 #include "XMLHttpRequestException.h"
52 #include "XMLHttpRequestProgressEvent.h"
53 #include "XMLHttpRequestUpload.h"
54 #include "markup.h"
55 #include <heap/Strong.h>
56 #include <mutex>
57 #include <runtime/ArrayBuffer.h>
58 #include <runtime/ArrayBufferView.h>
59 #include <runtime/JSCInlines.h>
60 #include <runtime/JSLock.h>
61 #include <wtf/NeverDestroyed.h>
62 #include <wtf/Ref.h>
63 #include <wtf/RefCountedLeakCounter.h>
64 #include <wtf/StdLibExtras.h>
65 #include <wtf/text/CString.h>
66
67 namespace WebCore {
68
69 DEFINE_DEBUG_ONLY_GLOBAL(WTF::RefCountedLeakCounter, xmlHttpRequestCounter, ("XMLHttpRequest"));
70
71 // Histogram enum to see when we can deprecate xhr.send(ArrayBuffer).
72 enum XMLHttpRequestSendArrayBufferOrView {
73     XMLHttpRequestSendArrayBuffer,
74     XMLHttpRequestSendArrayBufferView,
75     XMLHttpRequestSendArrayBufferOrViewMax,
76 };
77
78 static bool isSetCookieHeader(const String& name)
79 {
80     return equalIgnoringCase(name, "set-cookie") || equalIgnoringCase(name, "set-cookie2");
81 }
82
83 static void replaceCharsetInMediaType(String& mediaType, const String& charsetValue)
84 {
85     unsigned int pos = 0, len = 0;
86
87     findCharsetInMediaType(mediaType, pos, len);
88
89     if (!len) {
90         // When no charset found, do nothing.
91         return;
92     }
93
94     // Found at least one existing charset, replace all occurrences with new charset.
95     while (len) {
96         mediaType.replace(pos, len, charsetValue);
97         unsigned int start = pos + charsetValue.length();
98         findCharsetInMediaType(mediaType, pos, len, start);
99     }
100 }
101
102 static void logConsoleError(ScriptExecutionContext* context, const String& message)
103 {
104     if (!context)
105         return;
106     // FIXME: It's not good to report the bad usage without indicating what source line it came from.
107     // We should pass additional parameters so we can tell the console where the mistake occurred.
108     context->addConsoleMessage(MessageSource::JS, MessageLevel::Error, message);
109 }
110
111 Ref<XMLHttpRequest> XMLHttpRequest::create(ScriptExecutionContext& context)
112 {
113     Ref<XMLHttpRequest> xmlHttpRequest = adoptRef(*new XMLHttpRequest(context));
114     xmlHttpRequest->suspendIfNeeded();
115     return xmlHttpRequest;
116 }
117
118 XMLHttpRequest::XMLHttpRequest(ScriptExecutionContext& context)
119     : ActiveDOMObject(&context)
120     , m_async(true)
121     , m_includeCredentials(false)
122     , m_state(UNSENT)
123     , m_createdDocument(false)
124     , m_error(false)
125     , m_uploadEventsAllowed(true)
126     , m_uploadComplete(false)
127     , m_sameOriginRequest(true)
128     , m_receivedLength(0)
129     , m_lastSendLineNumber(0)
130     , m_lastSendColumnNumber(0)
131     , m_exceptionCode(0)
132     , m_progressEventThrottle(this)
133     , m_responseTypeCode(ResponseTypeDefault)
134     , m_responseCacheIsValid(false)
135     , m_resumeTimer(*this, &XMLHttpRequest::resumeTimerFired)
136     , m_dispatchErrorOnResuming(false)
137     , m_networkErrorTimer(*this, &XMLHttpRequest::networkErrorTimerFired)
138     , m_timeoutTimer(*this, &XMLHttpRequest::didReachTimeout)
139 {
140 #ifndef NDEBUG
141     xmlHttpRequestCounter.increment();
142 #endif
143 }
144
145 XMLHttpRequest::~XMLHttpRequest()
146 {
147 #ifndef NDEBUG
148     xmlHttpRequestCounter.decrement();
149 #endif
150 }
151
152 Document* XMLHttpRequest::document() const
153 {
154     ASSERT(scriptExecutionContext());
155     return downcast<Document>(scriptExecutionContext());
156 }
157
158 SecurityOrigin* XMLHttpRequest::securityOrigin() const
159 {
160     return scriptExecutionContext()->securityOrigin();
161 }
162
163 #if ENABLE(DASHBOARD_SUPPORT)
164 bool XMLHttpRequest::usesDashboardBackwardCompatibilityMode() const
165 {
166     if (scriptExecutionContext()->isWorkerGlobalScope())
167         return false;
168     Settings* settings = document()->settings();
169     return settings && settings->usesDashboardBackwardCompatibilityMode();
170 }
171 #endif
172
173 XMLHttpRequest::State XMLHttpRequest::readyState() const
174 {
175     return m_state;
176 }
177
178 String XMLHttpRequest::responseText(ExceptionCode& ec)
179 {
180     if (m_responseTypeCode != ResponseTypeDefault && m_responseTypeCode != ResponseTypeText) {
181         ec = INVALID_STATE_ERR;
182         return "";
183     }
184     return responseTextIgnoringResponseType();
185 }
186
187 void XMLHttpRequest::didCacheResponseJSON()
188 {
189     ASSERT(m_responseTypeCode == ResponseTypeJSON);
190     ASSERT(doneWithoutErrors());
191     m_responseCacheIsValid = true;
192     m_responseBuilder.clear();
193 }
194
195 Document* XMLHttpRequest::responseXML(ExceptionCode& ec)
196 {
197     if (m_responseTypeCode != ResponseTypeDefault && m_responseTypeCode != ResponseTypeDocument) {
198         ec = INVALID_STATE_ERR;
199         return nullptr;
200     }
201
202     if (!doneWithoutErrors())
203         return nullptr;
204
205     if (!m_createdDocument) {
206         String mimeType = responseMIMEType();
207         bool isHTML = equalIgnoringCase(mimeType, "text/html");
208
209         // The W3C spec requires the final MIME type to be some valid XML type, or text/html.
210         // If it is text/html, then the responseType of "document" must have been supplied explicitly.
211         if ((m_response.isHTTP() && !responseIsXML() && !isHTML)
212             || (isHTML && m_responseTypeCode == ResponseTypeDefault)
213             || scriptExecutionContext()->isWorkerGlobalScope()) {
214             m_responseDocument = nullptr;
215         } else {
216             if (isHTML)
217                 m_responseDocument = HTMLDocument::create(0, m_url);
218             else
219                 m_responseDocument = Document::create(0, m_url);
220             // FIXME: Set Last-Modified.
221             m_responseDocument->setContent(m_responseBuilder.toStringPreserveCapacity());
222             m_responseDocument->setSecurityOriginPolicy(scriptExecutionContext()->securityOriginPolicy());
223             m_responseDocument->overrideMIMEType(mimeType);
224
225             if (!m_responseDocument->wellFormed())
226                 m_responseDocument = nullptr;
227         }
228         m_createdDocument = true;
229     }
230
231     return m_responseDocument.get();
232 }
233
234 Blob* XMLHttpRequest::responseBlob()
235 {
236     ASSERT(m_responseTypeCode == ResponseTypeBlob);
237     ASSERT(doneWithoutErrors());
238
239     if (!m_responseBlob) {
240         if (m_binaryResponseBuilder) {
241             // FIXME: We just received the data from NetworkProcess, and are sending it back. This is inefficient.
242             Vector<char> data;
243             data.append(m_binaryResponseBuilder->data(), m_binaryResponseBuilder->size());
244             String normalizedContentType = Blob::normalizedContentType(responseMIMEType()); // responseMIMEType defaults to text/xml which may be incorrect.
245             m_responseBlob = Blob::create(WTF::move(data), normalizedContentType);
246             m_binaryResponseBuilder = nullptr;
247         } else {
248             // If we errored out or got no data, we still return a blob, just an empty one.
249             m_responseBlob = Blob::create();
250         }
251     }
252
253     return m_responseBlob.get();
254 }
255
256 ArrayBuffer* XMLHttpRequest::responseArrayBuffer()
257 {
258     ASSERT(m_responseTypeCode == ResponseTypeArrayBuffer);
259     ASSERT(doneWithoutErrors());
260
261     if (!m_responseArrayBuffer) {
262         if (m_binaryResponseBuilder)
263             m_responseArrayBuffer = m_binaryResponseBuilder->createArrayBuffer();
264         else
265             m_responseArrayBuffer = ArrayBuffer::create(nullptr, 0);
266         m_binaryResponseBuilder = nullptr;
267     }
268
269     return m_responseArrayBuffer.get();
270 }
271
272 void XMLHttpRequest::setTimeout(unsigned timeout, ExceptionCode& ec)
273 {
274     if (scriptExecutionContext()->isDocument() && !m_async) {
275         logConsoleError(scriptExecutionContext(), "XMLHttpRequest.timeout cannot be set for synchronous HTTP(S) requests made from the window context.");
276         ec = INVALID_ACCESS_ERR;
277         return;
278     }
279     m_timeoutMilliseconds = timeout;
280     if (!m_timeoutTimer.isActive())
281         return;
282     if (!m_timeoutMilliseconds) {
283         m_timeoutTimer.stop();
284         return;
285     }
286     std::chrono::duration<double> interval = std::chrono::milliseconds { m_timeoutMilliseconds } - (std::chrono::steady_clock::now() - m_sendingTime);
287     m_timeoutTimer.startOneShot(std::max(0.0, interval.count()));
288 }
289
290 void XMLHttpRequest::setResponseType(const String& responseType, ExceptionCode& ec)
291 {
292     if (m_state >= LOADING) {
293         ec = INVALID_STATE_ERR;
294         return;
295     }
296
297     // Newer functionality is not available to synchronous requests in window contexts, as a spec-mandated
298     // attempt to discourage synchronous XHR use. responseType is one such piece of functionality.
299     // We'll only disable this functionality for HTTP(S) requests since sync requests for local protocols
300     // such as file: and data: still make sense to allow.
301     if (!m_async && scriptExecutionContext()->isDocument() && m_url.protocolIsInHTTPFamily()) {
302         logConsoleError(scriptExecutionContext(), "XMLHttpRequest.responseType cannot be changed for synchronous HTTP(S) requests made from the window context.");
303         ec = INVALID_ACCESS_ERR;
304         return;
305     }
306
307     if (responseType == "")
308         m_responseTypeCode = ResponseTypeDefault;
309     else if (responseType == "text")
310         m_responseTypeCode = ResponseTypeText;
311     else if (responseType == "json")
312         m_responseTypeCode = ResponseTypeJSON;
313     else if (responseType == "document")
314         m_responseTypeCode = ResponseTypeDocument;
315     else if (responseType == "blob")
316         m_responseTypeCode = ResponseTypeBlob;
317     else if (responseType == "arraybuffer")
318         m_responseTypeCode = ResponseTypeArrayBuffer;
319     else
320         ASSERT_NOT_REACHED();
321 }
322
323 String XMLHttpRequest::responseType()
324 {
325     switch (m_responseTypeCode) {
326     case ResponseTypeDefault:
327         return "";
328     case ResponseTypeText:
329         return "text";
330     case ResponseTypeJSON:
331         return "json";
332     case ResponseTypeDocument:
333         return "document";
334     case ResponseTypeBlob:
335         return "blob";
336     case ResponseTypeArrayBuffer:
337         return "arraybuffer";
338     }
339     return "";
340 }
341
342 String XMLHttpRequest::responseURL() const
343 {
344     URL responseURL(m_response.url());
345     responseURL.removeFragmentIdentifier();
346
347     return responseURL.string();
348 }
349
350 void XMLHttpRequest::setLastSendLineAndColumnNumber(unsigned lineNumber, unsigned columnNumber)
351 {
352     m_lastSendLineNumber = lineNumber;
353     m_lastSendColumnNumber = columnNumber;
354 }
355
356 XMLHttpRequestUpload* XMLHttpRequest::upload()
357 {
358     if (!m_upload)
359         m_upload = std::make_unique<XMLHttpRequestUpload>(this);
360     return m_upload.get();
361 }
362
363 void XMLHttpRequest::changeState(State newState)
364 {
365     if (m_state != newState) {
366         m_state = newState;
367         callReadyStateChangeListener();
368     }
369 }
370
371 void XMLHttpRequest::callReadyStateChangeListener()
372 {
373     if (!scriptExecutionContext())
374         return;
375
376     InspectorInstrumentationCookie cookie = InspectorInstrumentation::willDispatchXHRReadyStateChangeEvent(scriptExecutionContext(), *this);
377
378     if (m_async || (m_state <= OPENED || m_state == DONE))
379         m_progressEventThrottle.dispatchReadyStateChangeEvent(Event::create(eventNames().readystatechangeEvent, false, false), m_state == DONE ? FlushProgressEvent : DoNotFlushProgressEvent);
380
381     InspectorInstrumentation::didDispatchXHRReadyStateChangeEvent(cookie);
382     if (m_state == DONE && !m_error) {
383         InspectorInstrumentationCookie cookie = InspectorInstrumentation::willDispatchXHRLoadEvent(scriptExecutionContext(), *this);
384         m_progressEventThrottle.dispatchProgressEvent(eventNames().loadEvent);
385         InspectorInstrumentation::didDispatchXHRLoadEvent(cookie);
386         m_progressEventThrottle.dispatchProgressEvent(eventNames().loadendEvent);
387     }
388 }
389
390 void XMLHttpRequest::setWithCredentials(bool value, ExceptionCode& ec)
391 {
392     if (m_state > OPENED || m_loader) {
393         ec = INVALID_STATE_ERR;
394         return;
395     }
396
397     m_includeCredentials = value;
398 }
399
400 bool XMLHttpRequest::isAllowedHTTPMethod(const String& method)
401 {
402     return !equalIgnoringCase(method, "TRACE")
403         && !equalIgnoringCase(method, "TRACK")
404         && !equalIgnoringCase(method, "CONNECT");
405 }
406
407 String XMLHttpRequest::uppercaseKnownHTTPMethod(const String& method)
408 {
409     const char* const methods[] = { "DELETE", "GET", "HEAD", "OPTIONS", "POST", "PUT" };
410     for (auto* value : methods) {
411         if (equalIgnoringCase(method, value)) {
412             // Don't bother allocating a new string if it's already all uppercase.
413             if (method == value)
414                 break;
415             return ASCIILiteral(value);
416         }
417     }
418     return method;
419 }
420
421 static bool isForbiddenRequestHeader(const String& name)
422 {
423     HTTPHeaderName headerName;
424     if (!findHTTPHeaderName(name, headerName))
425         return false;
426
427     switch (headerName) {
428     case HTTPHeaderName::AcceptCharset:
429     case HTTPHeaderName::AcceptEncoding:
430     case HTTPHeaderName::AccessControlRequestHeaders:
431     case HTTPHeaderName::AccessControlRequestMethod:
432     case HTTPHeaderName::Connection:
433     case HTTPHeaderName::ContentLength:
434     case HTTPHeaderName::ContentTransferEncoding:
435     case HTTPHeaderName::Cookie:
436     case HTTPHeaderName::Cookie2:
437     case HTTPHeaderName::Date:
438     case HTTPHeaderName::DNT:
439     case HTTPHeaderName::Expect:
440     case HTTPHeaderName::Host:
441     case HTTPHeaderName::KeepAlive:
442     case HTTPHeaderName::Origin:
443     case HTTPHeaderName::Referer:
444     case HTTPHeaderName::TE:
445     case HTTPHeaderName::Trailer:
446     case HTTPHeaderName::TransferEncoding:
447     case HTTPHeaderName::Upgrade:
448     case HTTPHeaderName::UserAgent:
449     case HTTPHeaderName::Via:
450         return true;
451
452     default:
453         return false;
454     }
455 }
456
457 bool XMLHttpRequest::isAllowedHTTPHeader(const String& name)
458 {
459     if (isForbiddenRequestHeader(name))
460         return false;
461
462     if (name.startsWith("proxy-", false))
463         return false;
464
465     if (name.startsWith("sec-", false))
466         return false;
467
468     return true;
469 }
470
471 void XMLHttpRequest::open(const String& method, const URL& url, ExceptionCode& ec)
472 {
473     open(method, url, true, ec);
474 }
475
476 void XMLHttpRequest::open(const String& method, const URL& url, bool async, ExceptionCode& ec)
477 {
478     if (!internalAbort())
479         return;
480
481     State previousState = m_state;
482     m_state = UNSENT;
483     m_error = false;
484     m_uploadComplete = false;
485
486     // clear stuff from possible previous load
487     clearResponse();
488     clearRequest();
489
490     ASSERT(m_state == UNSENT);
491
492     if (!isValidHTTPToken(method)) {
493         ec = SYNTAX_ERR;
494         return;
495     }
496
497     if (!isAllowedHTTPMethod(method)) {
498         ec = SECURITY_ERR;
499         return;
500     }
501
502     // FIXME: Convert this to check the isolated world's Content Security Policy once webkit.org/b/104520 is solved.
503     bool shouldBypassMainWorldContentSecurityPolicy = ContentSecurityPolicy::shouldBypassMainWorldContentSecurityPolicy(*scriptExecutionContext());
504     if (!scriptExecutionContext()->contentSecurityPolicy()->allowConnectToSource(url, shouldBypassMainWorldContentSecurityPolicy)) {
505         // FIXME: Should this be throwing an exception?
506         ec = SECURITY_ERR;
507         return;
508     }
509
510     if (!async && scriptExecutionContext()->isDocument()) {
511         if (document()->settings() && !document()->settings()->syncXHRInDocumentsEnabled()) {
512             logConsoleError(scriptExecutionContext(), "Synchronous XMLHttpRequests are disabled for this page.");
513             ec = INVALID_ACCESS_ERR;
514             return;
515         }
516
517         // Newer functionality is not available to synchronous requests in window contexts, as a spec-mandated
518         // attempt to discourage synchronous XHR use. responseType is one such piece of functionality.
519         // We'll only disable this functionality for HTTP(S) requests since sync requests for local protocols
520         // such as file: and data: still make sense to allow.
521         if (url.protocolIsInHTTPFamily() && m_responseTypeCode != ResponseTypeDefault) {
522             logConsoleError(scriptExecutionContext(), "Synchronous HTTP(S) requests made from the window context cannot have XMLHttpRequest.responseType set.");
523             ec = INVALID_ACCESS_ERR;
524             return;
525         }
526
527         // Similarly, timeouts are disabled for synchronous requests as well.
528         if (m_timeoutMilliseconds > 0) {
529             logConsoleError(scriptExecutionContext(), "Synchronous XMLHttpRequests must not have a timeout value set.");
530             ec = INVALID_ACCESS_ERR;
531             return;
532         }
533     }
534
535     m_method = uppercaseKnownHTTPMethod(method);
536
537     m_url = url;
538
539     m_async = async;
540
541     ASSERT(!m_loader);
542
543     // Check previous state to avoid dispatching readyState event
544     // when calling open several times in a row.
545     if (previousState != OPENED)
546         changeState(OPENED);
547     else
548         m_state = OPENED;
549 }
550
551 void XMLHttpRequest::open(const String& method, const URL& url, bool async, const String& user, ExceptionCode& ec)
552 {
553     URL urlWithCredentials(url);
554     urlWithCredentials.setUser(user);
555
556     open(method, urlWithCredentials, async, ec);
557 }
558
559 void XMLHttpRequest::open(const String& method, const URL& url, bool async, const String& user, const String& password, ExceptionCode& ec)
560 {
561     URL urlWithCredentials(url);
562     urlWithCredentials.setUser(user);
563     urlWithCredentials.setPass(password);
564
565     open(method, urlWithCredentials, async, ec);
566 }
567
568 bool XMLHttpRequest::initSend(ExceptionCode& ec)
569 {
570     if (!scriptExecutionContext())
571         return false;
572
573     if (m_state != OPENED || m_loader) {
574         ec = INVALID_STATE_ERR;
575         return false;
576     }
577
578     m_error = false;
579     return true;
580 }
581
582 void XMLHttpRequest::send(ExceptionCode& ec)
583 {
584     send(String(), ec);
585 }
586
587 void XMLHttpRequest::send(Document* document, ExceptionCode& ec)
588 {
589     ASSERT(document);
590
591     if (!initSend(ec))
592         return;
593
594     if (m_method != "GET" && m_method != "HEAD" && m_url.protocolIsInHTTPFamily()) {
595         String contentType = getRequestHeader("Content-Type");
596         if (contentType.isEmpty()) {
597 #if ENABLE(DASHBOARD_SUPPORT)
598             if (usesDashboardBackwardCompatibilityMode())
599                 setRequestHeaderInternal("Content-Type", "application/x-www-form-urlencoded");
600             else
601 #endif
602                 // FIXME: this should include the charset used for encoding.
603                 setRequestHeaderInternal("Content-Type", document->isHTMLDocument() ? "text/html;charset=UTF-8":"application/xml;charset=UTF-8");
604         }
605
606         // FIXME: According to XMLHttpRequest Level 2, this should use the Document.innerHTML algorithm
607         // from the HTML5 specification to serialize the document.
608         String body = createMarkup(*document);
609
610         m_requestEntityBody = FormData::create(UTF8Encoding().encode(body, EntitiesForUnencodables));
611         if (m_upload)
612             m_requestEntityBody->setAlwaysStream(true);
613     }
614
615     createRequest(ec);
616 }
617
618 void XMLHttpRequest::send(const String& body, ExceptionCode& ec)
619 {
620     if (!initSend(ec))
621         return;
622
623     if (!body.isNull() && m_method != "GET" && m_method != "HEAD" && m_url.protocolIsInHTTPFamily()) {
624         String contentType = getRequestHeader("Content-Type");
625         if (contentType.isEmpty()) {
626 #if ENABLE(DASHBOARD_SUPPORT)
627             if (usesDashboardBackwardCompatibilityMode())
628                 setRequestHeaderInternal("Content-Type", "application/x-www-form-urlencoded");
629             else
630 #endif
631                 setRequestHeaderInternal("Content-Type", "text/plain;charset=UTF-8");
632         } else {
633             replaceCharsetInMediaType(contentType, "UTF-8");
634             m_requestHeaders.set(HTTPHeaderName::ContentType, contentType);
635         }
636
637         m_requestEntityBody = FormData::create(UTF8Encoding().encode(body, EntitiesForUnencodables));
638         if (m_upload)
639             m_requestEntityBody->setAlwaysStream(true);
640     }
641
642     createRequest(ec);
643 }
644
645 void XMLHttpRequest::send(Blob* body, ExceptionCode& ec)
646 {
647     if (!initSend(ec))
648         return;
649
650     if (m_method != "GET" && m_method != "HEAD" && m_url.protocolIsInHTTPFamily()) {
651         const String& contentType = getRequestHeader("Content-Type");
652         if (contentType.isEmpty()) {
653             const String& blobType = body->type();
654             if (!blobType.isEmpty() && isValidContentType(blobType))
655                 setRequestHeaderInternal("Content-Type", blobType);
656             else {
657                 // From FileAPI spec, whenever media type cannot be determined, empty string must be returned.
658                 setRequestHeaderInternal("Content-Type", "");
659             }
660         }
661
662         m_requestEntityBody = FormData::create();
663         m_requestEntityBody->appendBlob(body->url());
664     }
665
666     createRequest(ec);
667 }
668
669 void XMLHttpRequest::send(DOMFormData* body, ExceptionCode& ec)
670 {
671     if (!initSend(ec))
672         return;
673
674     if (m_method != "GET" && m_method != "HEAD" && m_url.protocolIsInHTTPFamily()) {
675         m_requestEntityBody = FormData::createMultiPart(*(static_cast<FormDataList*>(body)), body->encoding(), document());
676
677         m_requestEntityBody->generateFiles(document());
678
679         String contentType = getRequestHeader("Content-Type");
680         if (contentType.isEmpty()) {
681             contentType = makeString("multipart/form-data; boundary=", m_requestEntityBody->boundary().data());
682             setRequestHeaderInternal("Content-Type", contentType);
683         }
684     }
685
686     createRequest(ec);
687 }
688
689 void XMLHttpRequest::send(ArrayBuffer* body, ExceptionCode& ec)
690 {
691     String consoleMessage("ArrayBuffer is deprecated in XMLHttpRequest.send(). Use ArrayBufferView instead.");
692     scriptExecutionContext()->addConsoleMessage(MessageSource::JS, MessageLevel::Warning, consoleMessage);
693
694     sendBytesData(body->data(), body->byteLength(), ec);
695 }
696
697 void XMLHttpRequest::send(ArrayBufferView* body, ExceptionCode& ec)
698 {
699     sendBytesData(body->baseAddress(), body->byteLength(), ec);
700 }
701
702 void XMLHttpRequest::sendBytesData(const void* data, size_t length, ExceptionCode& ec)
703 {
704     if (!initSend(ec))
705         return;
706
707     if (m_method != "GET" && m_method != "HEAD" && m_url.protocolIsInHTTPFamily()) {
708         m_requestEntityBody = FormData::create(data, length);
709         if (m_upload)
710             m_requestEntityBody->setAlwaysStream(true);
711     }
712
713     createRequest(ec);
714 }
715
716 void XMLHttpRequest::createRequest(ExceptionCode& ec)
717 {
718     // Only GET request is supported for blob URL.
719     if (m_url.protocolIs("blob") && m_method != "GET") {
720         ec = XMLHttpRequestException::NETWORK_ERR;
721         return;
722     }
723
724     // The presence of upload event listeners forces us to use preflighting because POSTing to an URL that does not
725     // permit cross origin requests should look exactly like POSTing to an URL that does not respond at all.
726     // Also, only async requests support upload progress events.
727     bool uploadEvents = false;
728     if (m_async) {
729         m_progressEventThrottle.dispatchProgressEvent(eventNames().loadstartEvent);
730         if (m_requestEntityBody && m_upload) {
731             uploadEvents = m_upload->hasEventListeners();
732             m_upload->dispatchProgressEvent(eventNames().loadstartEvent);
733         }
734     }
735
736     m_sameOriginRequest = securityOrigin()->canRequest(m_url);
737
738     // We also remember whether upload events should be allowed for this request in case the upload listeners are
739     // added after the request is started.
740     m_uploadEventsAllowed = m_sameOriginRequest || uploadEvents || !isSimpleCrossOriginAccessRequest(m_method, m_requestHeaders);
741
742     ResourceRequest request(m_url);
743     request.setRequester(ResourceRequest::Requester::XHR);
744     request.setHTTPMethod(m_method);
745
746     if (m_requestEntityBody) {
747         ASSERT(m_method != "GET");
748         ASSERT(m_method != "HEAD");
749         request.setHTTPBody(m_requestEntityBody.release());
750     }
751
752     if (!m_requestHeaders.isEmpty())
753         request.setHTTPHeaderFields(m_requestHeaders);
754
755     ThreadableLoaderOptions options;
756     options.setSendLoadCallbacks(SendCallbacks);
757     options.setSniffContent(DoNotSniffContent);
758     options.preflightPolicy = uploadEvents ? ForcePreflight : ConsiderPreflight;
759     options.setAllowCredentials((m_sameOriginRequest || m_includeCredentials) ? AllowStoredCredentials : DoNotAllowStoredCredentials);
760     options.crossOriginRequestPolicy = UseAccessControl;
761     options.securityOrigin = securityOrigin();
762     options.initiator = cachedResourceRequestInitiators().xmlhttprequest;
763
764     if (m_timeoutMilliseconds) {
765         if (!m_async)
766             request.setTimeoutInterval(m_timeoutMilliseconds / 1000.0);
767         else {
768             m_sendingTime = std::chrono::steady_clock::now();
769             m_timeoutTimer.startOneShot(std::chrono::milliseconds { m_timeoutMilliseconds });
770         }
771     }
772
773     m_exceptionCode = 0;
774     m_error = false;
775
776     if (m_async) {
777         if (m_upload)
778             request.setReportUploadProgress(true);
779
780         // ThreadableLoader::create can return null here, for example if we're no longer attached to a page or if a content blocker blocks the load.
781         // This is true while running onunload handlers.
782         // FIXME: Maybe we need to be able to send XMLHttpRequests from onunload, <http://bugs.webkit.org/show_bug.cgi?id=10904>.
783         m_loader = ThreadableLoader::create(scriptExecutionContext(), this, request, options);
784
785         // Neither this object nor the JavaScript wrapper should be deleted while
786         // a request is in progress because we need to keep the listeners alive,
787         // and they are referenced by the JavaScript wrapper.
788         setPendingActivity(this);
789         if (!m_loader)
790             m_networkErrorTimer.startOneShot(0);
791     } else {
792         InspectorInstrumentation::willLoadXHRSynchronously(scriptExecutionContext());
793         ThreadableLoader::loadResourceSynchronously(scriptExecutionContext(), request, *this, options);
794         InspectorInstrumentation::didLoadXHRSynchronously(scriptExecutionContext());
795     }
796
797     if (!m_exceptionCode && m_error)
798         m_exceptionCode = XMLHttpRequestException::NETWORK_ERR;
799     ec = m_exceptionCode;
800 }
801
802 void XMLHttpRequest::abort()
803 {
804     // internalAbort() calls dropProtection(), which may release the last reference.
805     Ref<XMLHttpRequest> protect(*this);
806
807     bool sendFlag = m_loader;
808
809     if (!internalAbort())
810         return;
811
812     clearResponseBuffers();
813
814     // Clear headers as required by the spec
815     m_requestHeaders.clear();
816
817     if ((m_state <= OPENED && !sendFlag) || m_state == DONE)
818         m_state = UNSENT;
819     else {
820         ASSERT(!m_loader);
821         changeState(DONE);
822         m_state = UNSENT;
823     }
824
825     dispatchErrorEvents(eventNames().abortEvent);
826 }
827
828 bool XMLHttpRequest::internalAbort()
829 {
830     m_error = true;
831
832     // FIXME: when we add the support for multi-part XHR, we will have to think be careful with this initialization.
833     m_receivedLength = 0;
834
835     m_decoder = nullptr;
836
837     if (m_timeoutTimer.isActive())
838         m_timeoutTimer.stop();
839
840     if (!m_loader)
841         return true;
842
843     // Cancelling m_loader may trigger a window.onload callback which can call open() on the same xhr.
844     // This would create internalAbort reentrant call.
845     // m_loader is set to null before being cancelled to exit early in any reentrant internalAbort() call.
846     RefPtr<ThreadableLoader> loader = m_loader.release();
847     loader->cancel();
848
849     // If window.onload callback calls open() and send() on the same xhr, m_loader is now set to a new value.
850     // The function calling internalAbort() should abort to let the open() and send() calls continue properly.
851     // We ask the function calling internalAbort() to exit by returning false.
852     // Save this information to a local variable since we are going to drop protection.
853     bool newLoadStarted = m_loader;
854
855     dropProtection();
856
857     return !newLoadStarted;
858 }
859
860 void XMLHttpRequest::clearResponse()
861 {
862     m_response = ResourceResponse();
863     clearResponseBuffers();
864 }
865
866 void XMLHttpRequest::clearResponseBuffers()
867 {
868     m_responseBuilder.clear();
869     m_responseEncoding = String();
870     m_createdDocument = false;
871     m_responseDocument = nullptr;
872     m_responseBlob = nullptr;
873     m_binaryResponseBuilder = nullptr;
874     m_responseArrayBuffer = nullptr;
875     m_responseCacheIsValid = false;
876 }
877
878 void XMLHttpRequest::clearRequest()
879 {
880     m_requestHeaders.clear();
881     m_requestEntityBody = nullptr;
882 }
883
884 void XMLHttpRequest::genericError()
885 {
886     clearResponse();
887     clearRequest();
888     m_error = true;
889
890     changeState(DONE);
891 }
892
893 void XMLHttpRequest::networkError()
894 {
895     genericError();
896     dispatchErrorEvents(eventNames().errorEvent);
897     internalAbort();
898 }
899
900 void XMLHttpRequest::networkErrorTimerFired()
901 {
902     networkError();
903     dropProtection();
904 }
905     
906 void XMLHttpRequest::abortError()
907 {
908     genericError();
909     dispatchErrorEvents(eventNames().abortEvent);
910 }
911
912 void XMLHttpRequest::dropProtection()
913 {
914     // The XHR object itself holds on to the responseText, and
915     // thus has extra cost even independent of any
916     // responseText or responseXML objects it has handed
917     // out. But it is protected from GC while loading, so this
918     // can't be recouped until the load is done, so only
919     // report the extra cost at that point.
920     JSC::VM& vm = scriptExecutionContext()->vm();
921     JSC::JSLockHolder lock(vm);
922     // FIXME: Adopt reportExtraMemoryVisited, and switch to reportExtraMemoryAllocated.
923     // https://bugs.webkit.org/show_bug.cgi?id=142595
924     vm.heap.deprecatedReportExtraMemory(m_responseBuilder.length() * 2);
925
926     unsetPendingActivity(this);
927 }
928
929 void XMLHttpRequest::overrideMimeType(const String& override, ExceptionCode& ec)
930 {
931     if (m_state == LOADING || m_state == DONE) {
932         ec = INVALID_STATE_ERR;
933         return;
934     }
935
936     m_mimeTypeOverride = override;
937 }
938
939 void XMLHttpRequest::setRequestHeader(const String& name, const String& value, ExceptionCode& ec)
940 {
941     if (m_state != OPENED || m_loader) {
942 #if ENABLE(DASHBOARD_SUPPORT)
943         if (usesDashboardBackwardCompatibilityMode())
944             return;
945 #endif
946
947         ec = INVALID_STATE_ERR;
948         return;
949     }
950
951     String normalizedValue = stripLeadingAndTrailingHTTPSpaces(value);
952     if (!isValidHTTPToken(name) || !isValidHTTPHeaderValue(normalizedValue)) {
953         ec = SYNTAX_ERR;
954         return;
955     }
956
957     // A privileged script (e.g. a Dashboard widget) can set any headers.
958     if (!securityOrigin()->canLoadLocalResources() && !isAllowedHTTPHeader(name)) {
959         logConsoleError(scriptExecutionContext(), "Refused to set unsafe header \"" + name + "\"");
960         return;
961     }
962
963     setRequestHeaderInternal(name, normalizedValue);
964 }
965
966 void XMLHttpRequest::setRequestHeaderInternal(const String& name, const String& value)
967 {
968     m_requestHeaders.add(name, value);
969 }
970
971 String XMLHttpRequest::getRequestHeader(const String& name) const
972 {
973     return m_requestHeaders.get(name);
974 }
975
976 String XMLHttpRequest::getAllResponseHeaders() const
977 {
978     if (m_state < HEADERS_RECEIVED || m_error)
979         return "";
980
981     StringBuilder stringBuilder;
982
983     HTTPHeaderSet accessControlExposeHeaderSet;
984     parseAccessControlExposeHeadersAllowList(m_response.httpHeaderField(HTTPHeaderName::AccessControlExposeHeaders), accessControlExposeHeaderSet);
985     
986     for (const auto& header : m_response.httpHeaderFields()) {
987         // Hide Set-Cookie header fields from the XMLHttpRequest client for these reasons:
988         //     1) If the client did have access to the fields, then it could read HTTP-only
989         //        cookies; those cookies are supposed to be hidden from scripts.
990         //     2) There's no known harm in hiding Set-Cookie header fields entirely; we don't
991         //        know any widely used technique that requires access to them.
992         //     3) Firefox has implemented this policy.
993         if (isSetCookieHeader(header.key) && !securityOrigin()->canLoadLocalResources())
994             continue;
995
996         if (!m_sameOriginRequest && !isOnAccessControlResponseHeaderWhitelist(header.key) && !accessControlExposeHeaderSet.contains(header.key))
997             continue;
998
999         stringBuilder.append(header.key);
1000         stringBuilder.append(':');
1001         stringBuilder.append(' ');
1002         stringBuilder.append(header.value);
1003         stringBuilder.append('\r');
1004         stringBuilder.append('\n');
1005     }
1006
1007     return stringBuilder.toString();
1008 }
1009
1010 String XMLHttpRequest::getResponseHeader(const String& name) const
1011 {
1012     if (m_state < HEADERS_RECEIVED || m_error)
1013         return String();
1014
1015     // See comment in getAllResponseHeaders above.
1016     if (isSetCookieHeader(name) && !securityOrigin()->canLoadLocalResources()) {
1017         logConsoleError(scriptExecutionContext(), "Refused to get unsafe header \"" + name + "\"");
1018         return String();
1019     }
1020
1021     HTTPHeaderSet accessControlExposeHeaderSet;
1022     parseAccessControlExposeHeadersAllowList(m_response.httpHeaderField(HTTPHeaderName::AccessControlExposeHeaders), accessControlExposeHeaderSet);
1023
1024     if (!m_sameOriginRequest && !isOnAccessControlResponseHeaderWhitelist(name) && !accessControlExposeHeaderSet.contains(name)) {
1025         logConsoleError(scriptExecutionContext(), "Refused to get unsafe header \"" + name + "\"");
1026         return String();
1027     }
1028     return m_response.httpHeaderField(name);
1029 }
1030
1031 String XMLHttpRequest::responseMIMEType() const
1032 {
1033     String mimeType = extractMIMETypeFromMediaType(m_mimeTypeOverride);
1034     if (mimeType.isEmpty()) {
1035         if (m_response.isHTTP())
1036             mimeType = extractMIMETypeFromMediaType(m_response.httpHeaderField(HTTPHeaderName::ContentType));
1037         else
1038             mimeType = m_response.mimeType();
1039     }
1040     if (mimeType.isEmpty())
1041         mimeType = "text/xml";
1042
1043     return mimeType;
1044 }
1045
1046 bool XMLHttpRequest::responseIsXML() const
1047 {
1048     // FIXME: Remove the lower() call when DOMImplementation.isXMLMIMEType() is modified
1049     //        to do case insensitive MIME type matching.
1050     return DOMImplementation::isXMLMIMEType(responseMIMEType().lower());
1051 }
1052
1053 int XMLHttpRequest::status() const
1054 {
1055     if (m_state == UNSENT || m_state == OPENED || m_error)
1056         return 0;
1057
1058     if (m_response.httpStatusCode())
1059         return m_response.httpStatusCode();
1060
1061     return 0;
1062 }
1063
1064 String XMLHttpRequest::statusText() const
1065 {
1066     if (m_state == UNSENT || m_state == OPENED || m_error)
1067         return String();
1068
1069     if (!m_response.httpStatusText().isNull())
1070         return m_response.httpStatusText();
1071
1072     return String();
1073 }
1074
1075 void XMLHttpRequest::didFail(const ResourceError& error)
1076 {
1077
1078     // If we are already in an error state, for instance we called abort(), bail out early.
1079     if (m_error)
1080         return;
1081
1082     if (error.isCancellation()) {
1083         m_exceptionCode = XMLHttpRequestException::ABORT_ERR;
1084         abortError();
1085         return;
1086     }
1087
1088     // In case of worker sync timeouts.
1089     if (error.isTimeout()) {
1090         didReachTimeout();
1091         return;
1092     }
1093
1094     // Network failures are already reported to Web Inspector by ResourceLoader.
1095     if (error.domain() == errorDomainWebKitInternal)
1096         logConsoleError(scriptExecutionContext(), "XMLHttpRequest cannot load " + error.failingURL() + ". " + error.localizedDescription());
1097
1098     m_exceptionCode = XMLHttpRequestException::NETWORK_ERR;
1099     networkError();
1100 }
1101
1102 void XMLHttpRequest::didFailRedirectCheck()
1103 {
1104     networkError();
1105 }
1106
1107 void XMLHttpRequest::didFinishLoading(unsigned long identifier, double)
1108 {
1109     if (m_error)
1110         return;
1111
1112     if (m_state < HEADERS_RECEIVED)
1113         changeState(HEADERS_RECEIVED);
1114
1115     if (m_decoder)
1116         m_responseBuilder.append(m_decoder->flush());
1117
1118     m_responseBuilder.shrinkToFit();
1119
1120     InspectorInstrumentation::didFinishXHRLoading(scriptExecutionContext(), this, identifier, m_responseBuilder.toStringPreserveCapacity(), m_url, m_lastSendURL, m_lastSendLineNumber, m_lastSendColumnNumber);
1121
1122     bool hadLoader = m_loader;
1123     m_loader = nullptr;
1124
1125     changeState(DONE);
1126     m_responseEncoding = String();
1127     m_decoder = nullptr;
1128
1129     if (m_timeoutTimer.isActive())
1130         m_timeoutTimer.stop();
1131
1132     if (hadLoader)
1133         dropProtection();
1134 }
1135
1136 void XMLHttpRequest::didSendData(unsigned long long bytesSent, unsigned long long totalBytesToBeSent)
1137 {
1138     if (!m_upload)
1139         return;
1140
1141     if (m_uploadEventsAllowed)
1142         m_upload->dispatchThrottledProgressEvent(true, bytesSent, totalBytesToBeSent);
1143     if (bytesSent == totalBytesToBeSent && !m_uploadComplete) {
1144         m_uploadComplete = true;
1145         if (m_uploadEventsAllowed) {
1146             m_upload->dispatchProgressEvent(eventNames().loadEvent);
1147             m_upload->dispatchProgressEvent(eventNames().loadendEvent);
1148         }
1149     }
1150 }
1151
1152 void XMLHttpRequest::didReceiveResponse(unsigned long identifier, const ResourceResponse& response)
1153 {
1154     InspectorInstrumentation::didReceiveXHRResponse(scriptExecutionContext(), identifier);
1155
1156     m_response = response;
1157     if (!m_mimeTypeOverride.isEmpty())
1158         m_response.setHTTPHeaderField(HTTPHeaderName::ContentType, m_mimeTypeOverride);
1159 }
1160
1161 void XMLHttpRequest::didReceiveData(const char* data, int len)
1162 {
1163     if (m_error)
1164         return;
1165
1166     if (m_state < HEADERS_RECEIVED)
1167         changeState(HEADERS_RECEIVED);
1168
1169     // FIXME: Should we update "Content-Type" header field with m_mimeTypeOverride value in case it has changed since didReceiveResponse?
1170     if (!m_mimeTypeOverride.isEmpty())
1171         m_responseEncoding = extractCharsetFromMediaType(m_mimeTypeOverride);
1172     if (m_responseEncoding.isEmpty())
1173         m_responseEncoding = m_response.textEncodingName();
1174
1175     bool useDecoder = shouldDecodeResponse();
1176
1177     if (useDecoder && !m_decoder) {
1178         if (!m_responseEncoding.isEmpty())
1179             m_decoder = TextResourceDecoder::create("text/plain", m_responseEncoding);
1180         // allow TextResourceDecoder to look inside the m_response if it's XML or HTML
1181         else if (responseIsXML()) {
1182             m_decoder = TextResourceDecoder::create("application/xml");
1183             // Don't stop on encoding errors, unlike it is done for other kinds of XML resources. This matches the behavior of previous WebKit versions, Firefox and Opera.
1184             m_decoder->useLenientXMLDecoding();
1185         } else if (equalIgnoringCase(responseMIMEType(), "text/html"))
1186             m_decoder = TextResourceDecoder::create("text/html", "UTF-8");
1187         else
1188             m_decoder = TextResourceDecoder::create("text/plain", "UTF-8");
1189     }
1190
1191     if (!len)
1192         return;
1193
1194     if (len == -1)
1195         len = strlen(data);
1196
1197     if (useDecoder)
1198         m_responseBuilder.append(m_decoder->decode(data, len));
1199     else if (m_responseTypeCode == ResponseTypeArrayBuffer || m_responseTypeCode == ResponseTypeBlob) {
1200         // Buffer binary data.
1201         if (!m_binaryResponseBuilder)
1202             m_binaryResponseBuilder = SharedBuffer::create();
1203         m_binaryResponseBuilder->append(data, len);
1204     }
1205
1206     if (!m_error) {
1207         m_receivedLength += len;
1208
1209         if (m_async) {
1210             long long expectedLength = m_response.expectedContentLength();
1211             bool lengthComputable = expectedLength > 0 && m_receivedLength <= expectedLength;
1212             unsigned long long total = lengthComputable ? expectedLength : 0;
1213             m_progressEventThrottle.dispatchThrottledProgressEvent(lengthComputable, m_receivedLength, total);
1214         }
1215
1216         if (m_state != LOADING)
1217             changeState(LOADING);
1218         else
1219             // Firefox calls readyStateChanged every time it receives data, 4449442
1220             callReadyStateChangeListener();
1221     }
1222 }
1223
1224 void XMLHttpRequest::dispatchErrorEvents(const AtomicString& type)
1225 {
1226     if (!m_uploadComplete) {
1227         m_uploadComplete = true;
1228         if (m_upload && m_uploadEventsAllowed) {
1229             m_upload->dispatchProgressEvent(eventNames().progressEvent);
1230             m_upload->dispatchProgressEvent(type);
1231             m_upload->dispatchProgressEvent(eventNames().loadendEvent);
1232         }
1233     }
1234     m_progressEventThrottle.dispatchProgressEvent(eventNames().progressEvent);
1235     m_progressEventThrottle.dispatchProgressEvent(type);
1236     m_progressEventThrottle.dispatchProgressEvent(eventNames().loadendEvent);
1237 }
1238
1239 void XMLHttpRequest::didReachTimeout()
1240 {
1241     // internalAbort() calls dropProtection(), which may release the last reference.
1242     Ref<XMLHttpRequest> protect(*this);
1243     if (!internalAbort())
1244         return;
1245
1246     clearResponse();
1247     clearRequest();
1248
1249     m_error = true;
1250     m_exceptionCode = XMLHttpRequestException::TIMEOUT_ERR;
1251
1252     if (!m_async) {
1253         m_state = DONE;
1254         m_exceptionCode = TIMEOUT_ERR;
1255         return;
1256     }
1257
1258     changeState(DONE);
1259
1260     dispatchErrorEvents(eventNames().timeoutEvent);
1261 }
1262
1263 bool XMLHttpRequest::canSuspendForPageCache() const
1264 {
1265     // If the load event has not fired yet, cancelling the load in suspend() may cause
1266     // the load event to be fired and arbitrary JS execution, which would be unsafe.
1267     // Therefore, we prevent suspending in this case.
1268     return document()->loadEventFinished();
1269 }
1270
1271 const char* XMLHttpRequest::activeDOMObjectName() const
1272 {
1273     return "XMLHttpRequest";
1274 }
1275
1276 void XMLHttpRequest::suspend(ReasonForSuspension reason)
1277 {
1278     m_progressEventThrottle.suspend();
1279
1280     if (m_resumeTimer.isActive()) {
1281         m_resumeTimer.stop();
1282         m_dispatchErrorOnResuming = true;
1283     }
1284
1285     if (reason == ActiveDOMObject::PageCache && m_loader) {
1286         // Going into PageCache, abort the request and dispatch a network error on resuming.
1287         genericError();
1288         m_dispatchErrorOnResuming = true;
1289         bool aborted = internalAbort();
1290         // It should not be possible to restart the load when aborting in suspend() because
1291         // we are not allowed to execute in JS in suspend().
1292         ASSERT_UNUSED(aborted, aborted);
1293     }
1294 }
1295
1296 void XMLHttpRequest::resume()
1297 {
1298     m_progressEventThrottle.resume();
1299
1300     // We are not allowed to execute arbitrary JS in resume() so dispatch
1301     // the error event in a timer.
1302     if (m_dispatchErrorOnResuming && !m_resumeTimer.isActive())
1303         m_resumeTimer.startOneShot(0);
1304 }
1305
1306 void XMLHttpRequest::resumeTimerFired()
1307 {
1308     ASSERT(m_dispatchErrorOnResuming);
1309     m_dispatchErrorOnResuming = false;
1310     dispatchErrorEvents(eventNames().errorEvent);
1311 }
1312
1313 void XMLHttpRequest::stop()
1314 {
1315     internalAbort();
1316 }
1317
1318 void XMLHttpRequest::contextDestroyed()
1319 {
1320     ASSERT(!m_loader);
1321     ActiveDOMObject::contextDestroyed();
1322 }
1323
1324 } // namespace WebCore