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