Permalink
Cannot retrieve contributors at this time
Join GitHub today
GitHub is home to over 31 million developers working together to host and review code, manage projects, and build software together.
Sign up
Fetching contributors…

/* | |
* Copyright (C) 2004, 2006, 2008 Apple Inc. All rights reserved. | |
* Copyright (C) 2005-2007 Alexey Proskuryakov <ap@webkit.org> | |
* Copyright (C) 2007, 2008 Julien Chaffraix <jchaffraix@webkit.org> | |
* Copyright (C) 2008, 2011 Google Inc. All rights reserved. | |
* Copyright (C) 2012 Intel Corporation | |
* | |
* This library is free software; you can redistribute it and/or | |
* modify it under the terms of the GNU Lesser General Public | |
* License as published by the Free Software Foundation; either | |
* version 2 of the License, or (at your option) any later version. | |
* | |
* This library is distributed in the hope that it will be useful, | |
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
* Lesser General Public License for more details. | |
* | |
* You should have received a copy of the GNU Lesser General Public | |
* License along with this library; if not, write to the Free Software | |
* Foundation, Inc., 51 Franklin Street, Fifth Floor, | |
* Boston, MA 02110-1301 USA | |
*/ | |
#include "third_party/blink/renderer/core/xmlhttprequest/xml_http_request.h" | |
#include <memory> | |
#include "base/auto_reset.h" | |
#include "base/feature_list.h" | |
#include "third_party/blink/public/common/blob/blob_utils.h" | |
#include "third_party/blink/public/common/features.h" | |
#include "third_party/blink/public/mojom/feature_policy/feature_policy.mojom-blink.h" | |
#include "third_party/blink/public/platform/web_url_request.h" | |
#include "third_party/blink/renderer/bindings/core/v8/array_buffer_or_array_buffer_view_or_blob_or_document_or_string_or_form_data_or_url_search_params.h" | |
#include "third_party/blink/renderer/bindings/core/v8/array_buffer_or_array_buffer_view_or_blob_or_usv_string.h" | |
#include "third_party/blink/renderer/core/dom/document_init.h" | |
#include "third_party/blink/renderer/core/dom/document_parser.h" | |
#include "third_party/blink/renderer/core/dom/dom_exception.h" | |
#include "third_party/blink/renderer/core/dom/dom_implementation.h" | |
#include "third_party/blink/renderer/core/dom/events/event.h" | |
#include "third_party/blink/renderer/core/dom/xml_document.h" | |
#include "third_party/blink/renderer/core/editing/serializers/serialization.h" | |
#include "third_party/blink/renderer/core/events/progress_event.h" | |
#include "third_party/blink/renderer/core/execution_context/execution_context.h" | |
#include "third_party/blink/renderer/core/fileapi/blob.h" | |
#include "third_party/blink/renderer/core/fileapi/file.h" | |
#include "third_party/blink/renderer/core/fileapi/file_reader_loader.h" | |
#include "third_party/blink/renderer/core/fileapi/file_reader_loader_client.h" | |
#include "third_party/blink/renderer/core/fileapi/public_url_manager.h" | |
#include "third_party/blink/renderer/core/frame/csp/content_security_policy.h" | |
#include "third_party/blink/renderer/core/frame/deprecation.h" | |
#include "third_party/blink/renderer/core/frame/frame.h" | |
#include "third_party/blink/renderer/core/frame/settings.h" | |
#include "third_party/blink/renderer/core/frame/use_counter.h" | |
#include "third_party/blink/renderer/core/html/forms/form_data.h" | |
#include "third_party/blink/renderer/core/html/html_document.h" | |
#include "third_party/blink/renderer/core/html/parser/text_resource_decoder.h" | |
#include "third_party/blink/renderer/core/inspector/console_message.h" | |
#include "third_party/blink/renderer/core/inspector/inspector_trace_events.h" | |
#include "third_party/blink/renderer/core/loader/threadable_loader.h" | |
#include "third_party/blink/renderer/core/page/chrome_client.h" | |
#include "third_party/blink/renderer/core/page/page.h" | |
#include "third_party/blink/renderer/core/probe/core_probes.h" | |
#include "third_party/blink/renderer/core/typed_arrays/dom_array_buffer.h" | |
#include "third_party/blink/renderer/core/typed_arrays/dom_array_buffer_view.h" | |
#include "third_party/blink/renderer/core/typed_arrays/dom_typed_array.h" | |
#include "third_party/blink/renderer/core/url/url_search_params.h" | |
#include "third_party/blink/renderer/core/xmlhttprequest/xml_http_request_upload.h" | |
#include "third_party/blink/renderer/platform/bindings/dom_wrapper_world.h" | |
#include "third_party/blink/renderer/platform/bindings/exception_state.h" | |
#include "third_party/blink/renderer/platform/bindings/script_state.h" | |
#include "third_party/blink/renderer/platform/blob/blob_data.h" | |
#include "third_party/blink/renderer/platform/file_metadata.h" | |
#include "third_party/blink/renderer/platform/histogram.h" | |
#include "third_party/blink/renderer/platform/loader/cors/cors.h" | |
#include "third_party/blink/renderer/platform/loader/fetch/fetch_initiator_type_names.h" | |
#include "third_party/blink/renderer/platform/loader/fetch/fetch_utils.h" | |
#include "third_party/blink/renderer/platform/loader/fetch/resource_error.h" | |
#include "third_party/blink/renderer/platform/loader/fetch/resource_loader_options.h" | |
#include "third_party/blink/renderer/platform/loader/fetch/resource_request.h" | |
#include "third_party/blink/renderer/platform/loader/fetch/text_resource_decoder_options.h" | |
#include "third_party/blink/renderer/platform/network/http_names.h" | |
#include "third_party/blink/renderer/platform/network/http_parsers.h" | |
#include "third_party/blink/renderer/platform/network/network_log.h" | |
#include "third_party/blink/renderer/platform/network/parsed_content_type.h" | |
#include "third_party/blink/renderer/platform/runtime_enabled_features.h" | |
#include "third_party/blink/renderer/platform/shared_buffer.h" | |
#include "third_party/blink/renderer/platform/weborigin/security_origin.h" | |
#include "third_party/blink/renderer/platform/weborigin/security_policy.h" | |
#include "third_party/blink/renderer/platform/wtf/assertions.h" | |
#include "third_party/blink/renderer/platform/wtf/std_lib_extras.h" | |
#include "third_party/blink/renderer/platform/wtf/text/cstring.h" | |
#include "third_party/blink/renderer/platform/wtf/text/string_builder.h" | |
namespace blink { | |
namespace { | |
// This class protects the wrapper of the associated XMLHttpRequest object | |
// via hasPendingActivity method which returns true if | |
// m_eventDispatchRecursionLevel is positive. | |
class ScopedEventDispatchProtect final { | |
public: | |
explicit ScopedEventDispatchProtect(int* level) : level_(level) { ++*level_; } | |
~ScopedEventDispatchProtect() { | |
DCHECK_GT(*level_, 0); | |
--*level_; | |
} | |
private: | |
int* const level_; | |
}; | |
// These methods were placed in HTTPParsers.h. Since these methods don't | |
// perform ABNF validation but loosely look for the part that is likely to be | |
// indicating the charset parameter, new code should use | |
// HttpUtil::ParseContentType() than these. To discourage use of these methods, | |
// moved from HTTPParser.h to the only user XMLHttpRequest.cpp. | |
// | |
// TODO(tyoshino): Switch XHR to use HttpUtil. See crbug.com/743311. | |
void FindCharsetInMediaType(const String& media_type, | |
unsigned& charset_pos, | |
unsigned& charset_len) { | |
charset_len = 0; | |
size_t pos = charset_pos; | |
unsigned length = media_type.length(); | |
while (pos < length) { | |
pos = media_type.FindIgnoringASCIICase("charset", pos); | |
if (pos == kNotFound) | |
return; | |
// Give up if we find "charset" at the head. | |
if (!pos) | |
return; | |
// Now check that "charset" is not a substring of some longer word. | |
if (media_type[pos - 1] > ' ' && media_type[pos - 1] != ';') { | |
pos += 7; | |
continue; | |
} | |
pos += 7; | |
while (pos < length && media_type[pos] <= ' ') | |
++pos; | |
// Treat this as a charset parameter. | |
if (media_type[pos++] == '=') | |
break; | |
} | |
while (pos < length && (media_type[pos] <= ' ' || media_type[pos] == '"' || | |
media_type[pos] == '\'')) | |
++pos; | |
charset_pos = pos; | |
// we don't handle spaces within quoted parameter values, because charset | |
// names cannot have any | |
while (pos < length && media_type[pos] > ' ' && media_type[pos] != '"' && | |
media_type[pos] != '\'' && media_type[pos] != ';') | |
++pos; | |
charset_len = pos - charset_pos; | |
} | |
String ExtractCharsetFromMediaType(const String& media_type) { | |
unsigned pos = 0; | |
unsigned len = 0; | |
FindCharsetInMediaType(media_type, pos, len); | |
return media_type.Substring(pos, len); | |
} | |
void ReplaceCharsetInMediaType(String& media_type, | |
const String& charset_value) { | |
unsigned pos = 0; | |
while (true) { | |
unsigned len = 0; | |
FindCharsetInMediaType(media_type, pos, len); | |
if (!len) | |
return; | |
media_type.replace(pos, len, charset_value); | |
pos += charset_value.length(); | |
} | |
} | |
void LogConsoleError(ExecutionContext* context, const String& message) { | |
if (!context) | |
return; | |
// FIXME: It's not good to report the bad usage without indicating what source | |
// line it came from. We should pass additional parameters so we can tell the | |
// console where the mistake occurred. | |
ConsoleMessage* console_message = | |
ConsoleMessage::Create(kJSMessageSource, kErrorMessageLevel, message); | |
context->AddConsoleMessage(console_message); | |
} | |
bool ValidateOpenArguments(const AtomicString& method, | |
const KURL& url, | |
ExceptionState& exception_state) { | |
if (!IsValidHTTPToken(method)) { | |
exception_state.ThrowDOMException( | |
DOMExceptionCode::kSyntaxError, | |
"'" + method + "' is not a valid HTTP method."); | |
return false; | |
} | |
if (FetchUtils::IsForbiddenMethod(method)) { | |
exception_state.ThrowSecurityError("'" + method + | |
"' HTTP method is unsupported."); | |
return false; | |
} | |
if (!url.IsValid()) { | |
exception_state.ThrowDOMException(DOMExceptionCode::kSyntaxError, | |
"Invalid URL"); | |
return false; | |
} | |
return true; | |
} | |
} // namespace | |
class XMLHttpRequest::BlobLoader final | |
: public GarbageCollectedFinalized<XMLHttpRequest::BlobLoader>, | |
public FileReaderLoaderClient { | |
public: | |
static BlobLoader* Create(XMLHttpRequest* xhr, | |
scoped_refptr<BlobDataHandle> handle) { | |
return MakeGarbageCollected<BlobLoader>(xhr, std::move(handle)); | |
} | |
BlobLoader(XMLHttpRequest* xhr, scoped_refptr<BlobDataHandle> handle) | |
: xhr_(xhr), | |
loader_(std::make_unique<FileReaderLoader>( | |
FileReaderLoader::kReadByClient, | |
this, | |
xhr->GetExecutionContext()->GetTaskRunner( | |
TaskType::kFileReading))) { | |
loader_->Start(std::move(handle)); | |
} | |
// FileReaderLoaderClient functions. | |
void DidStartLoading() override {} | |
void DidReceiveDataForClient(const char* data, unsigned length) override { | |
DCHECK_LE(length, static_cast<unsigned>(INT_MAX)); | |
xhr_->DidReceiveData(data, length); | |
} | |
void DidFinishLoading() override { xhr_->DidFinishLoadingFromBlob(); } | |
void DidFail(FileErrorCode error) override { xhr_->DidFailLoadingFromBlob(); } | |
void Cancel() { loader_->Cancel(); } | |
void Trace(blink::Visitor* visitor) { visitor->Trace(xhr_); } | |
private: | |
Member<XMLHttpRequest> xhr_; | |
std::unique_ptr<FileReaderLoader> loader_; | |
}; | |
XMLHttpRequest* XMLHttpRequest::Create(ScriptState* script_state) { | |
ExecutionContext* context = ExecutionContext::From(script_state); | |
DOMWrapperWorld& world = script_state->World(); | |
v8::Isolate* isolate = script_state->GetIsolate(); | |
return world.IsIsolatedWorld() | |
? MakeGarbageCollected<XMLHttpRequest>( | |
context, isolate, true, world.IsolatedWorldSecurityOrigin()) | |
: MakeGarbageCollected<XMLHttpRequest>(context, isolate, false, | |
nullptr); | |
} | |
XMLHttpRequest* XMLHttpRequest::Create(ExecutionContext* context) { | |
v8::Isolate* isolate = context->GetIsolate(); | |
CHECK(isolate); | |
return MakeGarbageCollected<XMLHttpRequest>(context, isolate, false, nullptr); | |
} | |
XMLHttpRequest::XMLHttpRequest( | |
ExecutionContext* context, | |
v8::Isolate* isolate, | |
bool is_isolated_world, | |
scoped_refptr<SecurityOrigin> isolated_world_security_origin) | |
: ContextLifecycleObserver(context), | |
progress_event_throttle_( | |
XMLHttpRequestProgressEventThrottle::Create(this)), | |
isolate_(isolate), | |
is_isolated_world_(is_isolated_world), | |
isolated_world_security_origin_( | |
std::move(isolated_world_security_origin)) {} | |
XMLHttpRequest::~XMLHttpRequest() { | |
binary_response_builder_ = nullptr; | |
length_downloaded_to_blob_ = 0; | |
ReportMemoryUsageToV8(); | |
} | |
Document* XMLHttpRequest::GetDocument() const { | |
return To<Document>(GetExecutionContext()); | |
} | |
const SecurityOrigin* XMLHttpRequest::GetSecurityOrigin() const { | |
return isolated_world_security_origin_ | |
? isolated_world_security_origin_.get() | |
: GetExecutionContext()->GetSecurityOrigin(); | |
} | |
SecurityOrigin* XMLHttpRequest::GetMutableSecurityOrigin() { | |
return isolated_world_security_origin_ | |
? isolated_world_security_origin_.get() | |
: GetExecutionContext()->GetMutableSecurityOrigin(); | |
} | |
XMLHttpRequest::State XMLHttpRequest::readyState() const { | |
return state_; | |
} | |
v8::Local<v8::String> XMLHttpRequest::responseText( | |
ExceptionState& exception_state) { | |
if (response_type_code_ != kResponseTypeDefault && | |
response_type_code_ != kResponseTypeText) { | |
exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError, | |
"The value is only accessible if the " | |
"object's 'responseType' is '' or 'text' " | |
"(was '" + | |
responseType() + "')."); | |
return v8::Local<v8::String>(); | |
} | |
if (error_ || (state_ != kLoading && state_ != kDone)) | |
return v8::Local<v8::String>(); | |
return response_text_.V8Value(isolate_); | |
} | |
v8::Local<v8::String> XMLHttpRequest::ResponseJSONSource() { | |
DCHECK_EQ(response_type_code_, kResponseTypeJSON); | |
if (error_ || state_ != kDone) | |
return v8::Local<v8::String>(); | |
return response_text_.V8Value(isolate_); | |
} | |
void XMLHttpRequest::InitResponseDocument() { | |
// The W3C spec requires the final MIME type to be some valid XML type, or | |
// text/html. If it is text/html, then the responseType of "document" must | |
// have been supplied explicitly. | |
bool is_html = ResponseIsHTML(); | |
if ((response_.IsHTTP() && !ResponseIsXML() && !is_html) || | |
(is_html && response_type_code_ == kResponseTypeDefault) || | |
!GetExecutionContext() || GetExecutionContext()->IsWorkerGlobalScope()) { | |
response_document_ = nullptr; | |
return; | |
} | |
DocumentInit init = DocumentInit::Create() | |
.WithContextDocument(GetDocument()->ContextDocument()) | |
.WithURL(response_.ResponseUrl()); | |
if (is_html) | |
response_document_ = HTMLDocument::Create(init); | |
else | |
response_document_ = XMLDocument::Create(init); | |
// FIXME: Set Last-Modified. | |
response_document_->SetSecurityOrigin(GetMutableSecurityOrigin()); | |
response_document_->SetContextFeatures(GetDocument()->GetContextFeatures()); | |
response_document_->SetMimeType(FinalResponseMIMETypeWithFallback()); | |
} | |
Document* XMLHttpRequest::responseXML(ExceptionState& exception_state) { | |
if (response_type_code_ != kResponseTypeDefault && | |
response_type_code_ != kResponseTypeDocument) { | |
exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError, | |
"The value is only accessible if the " | |
"object's 'responseType' is '' or " | |
"'document' (was '" + | |
responseType() + "')."); | |
return nullptr; | |
} | |
if (error_ || state_ != kDone) | |
return nullptr; | |
if (!parsed_response_) { | |
InitResponseDocument(); | |
if (!response_document_) | |
return nullptr; | |
response_document_->SetContent(response_text_.Flatten(isolate_)); | |
if (!response_document_->WellFormed()) { | |
response_document_ = nullptr; | |
} else { | |
response_document_->OverrideLastModified( | |
response_.HttpHeaderField(http_names::kLastModified)); | |
} | |
parsed_response_ = true; | |
} | |
return response_document_; | |
} | |
Blob* XMLHttpRequest::ResponseBlob() { | |
DCHECK_EQ(response_type_code_, kResponseTypeBlob); | |
// We always return null before kDone. | |
if (error_ || state_ != kDone) | |
return nullptr; | |
if (!response_blob_) { | |
std::unique_ptr<BlobData> blob_data = BlobData::Create(); | |
blob_data->SetContentType(FinalResponseMIMETypeWithFallback().LowerASCII()); | |
size_t size = 0; | |
if (binary_response_builder_ && binary_response_builder_->size()) { | |
for (const auto& span : *binary_response_builder_) | |
blob_data->AppendBytes(span.data(), span.size()); | |
size = binary_response_builder_->size(); | |
binary_response_builder_ = nullptr; | |
ReportMemoryUsageToV8(); | |
} | |
response_blob_ = | |
Blob::Create(BlobDataHandle::Create(std::move(blob_data), size)); | |
} | |
return response_blob_; | |
} | |
DOMArrayBuffer* XMLHttpRequest::ResponseArrayBuffer() { | |
DCHECK_EQ(response_type_code_, kResponseTypeArrayBuffer); | |
if (error_ || state_ != kDone) | |
return nullptr; | |
if (!response_array_buffer_ && !response_array_buffer_failure_) { | |
if (binary_response_builder_ && binary_response_builder_->size()) { | |
DOMArrayBuffer* buffer = DOMArrayBuffer::CreateUninitializedOrNull( | |
binary_response_builder_->size(), 1); | |
if (buffer) { | |
bool result = binary_response_builder_->GetBytes( | |
buffer->Data(), static_cast<size_t>(buffer->ByteLength())); | |
DCHECK(result); | |
response_array_buffer_ = buffer; | |
} | |
// https://xhr.spec.whatwg.org/#arraybuffer-response allows clearing | |
// of the 'received bytes' payload when the response buffer allocation | |
// fails. | |
binary_response_builder_ = nullptr; | |
ReportMemoryUsageToV8(); | |
// Mark allocation as failed; subsequent calls to the accessor must | |
// continue to report |null|. | |
// | |
response_array_buffer_failure_ = !buffer; | |
} else { | |
response_array_buffer_ = DOMArrayBuffer::Create(nullptr, 0); | |
} | |
} | |
return response_array_buffer_; | |
} | |
void XMLHttpRequest::setTimeout(unsigned timeout, | |
ExceptionState& exception_state) { | |
// FIXME: Need to trigger or update the timeout Timer here, if needed. | |
// http://webkit.org/b/98156 | |
// XHR2 spec, 4.7.3. "This implies that the timeout attribute can be set while | |
// fetching is in progress. If that occurs it will still be measured relative | |
// to the start of fetching." | |
if (GetExecutionContext() && GetExecutionContext()->IsDocument() && !async_) { | |
exception_state.ThrowDOMException(DOMExceptionCode::kInvalidAccessError, | |
"Timeouts cannot be set for synchronous " | |
"requests made from a document."); | |
return; | |
} | |
timeout_ = TimeDelta::FromMilliseconds(timeout); | |
// From http://www.w3.org/TR/XMLHttpRequest/#the-timeout-attribute: | |
// Note: This implies that the timeout attribute can be set while fetching is | |
// in progress. If that occurs it will still be measured relative to the start | |
// of fetching. | |
// | |
// The timeout may be overridden after send. | |
if (loader_) | |
loader_->SetTimeout(timeout_); | |
} | |
void XMLHttpRequest::setResponseType(const String& response_type, | |
ExceptionState& exception_state) { | |
if (state_ >= kLoading) { | |
exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError, | |
"The response type cannot be set if the " | |
"object's state is LOADING or DONE."); | |
return; | |
} | |
// Newer functionality is not available to synchronous requests in window | |
// contexts, as a spec-mandated attempt to discourage synchronous XHR use. | |
// responseType is one such piece of functionality. | |
if (GetExecutionContext() && GetExecutionContext()->IsDocument() && !async_) { | |
exception_state.ThrowDOMException(DOMExceptionCode::kInvalidAccessError, | |
"The response type cannot be changed for " | |
"synchronous requests made from a " | |
"document."); | |
return; | |
} | |
if (response_type == "") { | |
response_type_code_ = kResponseTypeDefault; | |
} else if (response_type == "text") { | |
response_type_code_ = kResponseTypeText; | |
} else if (response_type == "json") { | |
response_type_code_ = kResponseTypeJSON; | |
} else if (response_type == "document") { | |
response_type_code_ = kResponseTypeDocument; | |
} else if (response_type == "blob") { | |
response_type_code_ = kResponseTypeBlob; | |
} else if (response_type == "arraybuffer") { | |
response_type_code_ = kResponseTypeArrayBuffer; | |
} else { | |
NOTREACHED(); | |
} | |
} | |
String XMLHttpRequest::responseType() { | |
switch (response_type_code_) { | |
case kResponseTypeDefault: | |
return ""; | |
case kResponseTypeText: | |
return "text"; | |
case kResponseTypeJSON: | |
return "json"; | |
case kResponseTypeDocument: | |
return "document"; | |
case kResponseTypeBlob: | |
return "blob"; | |
case kResponseTypeArrayBuffer: | |
return "arraybuffer"; | |
} | |
return ""; | |
} | |
String XMLHttpRequest::responseURL() { | |
KURL response_url(response_.ResponseUrl()); | |
if (!response_url.IsNull()) | |
response_url.RemoveFragmentIdentifier(); | |
return response_url.GetString(); | |
} | |
XMLHttpRequestUpload* XMLHttpRequest::upload() { | |
if (!upload_) | |
upload_ = XMLHttpRequestUpload::Create(this); | |
return upload_; | |
} | |
void XMLHttpRequest::TrackProgress(long long length) { | |
received_length_ += length; | |
ChangeState(kLoading); | |
if (async_) { | |
// readyStateChange event is fired as well. | |
DispatchProgressEventFromSnapshot(event_type_names::kProgress); | |
} | |
} | |
void XMLHttpRequest::ChangeState(State new_state) { | |
if (state_ != new_state) { | |
state_ = new_state; | |
DispatchReadyStateChangeEvent(); | |
} | |
} | |
void XMLHttpRequest::DispatchReadyStateChangeEvent() { | |
if (!GetExecutionContext()) | |
return; | |
ScopedEventDispatchProtect protect(&event_dispatch_recursion_level_); | |
if (async_ || (state_ <= kOpened || state_ == kDone)) { | |
TRACE_EVENT1("devtools.timeline", "XHRReadyStateChange", "data", | |
inspector_xhr_ready_state_change_event::Data( | |
GetExecutionContext(), this)); | |
XMLHttpRequestProgressEventThrottle::DeferredEventAction action = | |
XMLHttpRequestProgressEventThrottle::kIgnore; | |
if (state_ == kDone) { | |
if (error_) | |
action = XMLHttpRequestProgressEventThrottle::kClear; | |
else | |
action = XMLHttpRequestProgressEventThrottle::kFlush; | |
} | |
progress_event_throttle_->DispatchReadyStateChangeEvent( | |
Event::Create(event_type_names::kReadystatechange), action); | |
} | |
if (state_ == kDone && !error_) { | |
TRACE_EVENT1("devtools.timeline", "XHRLoad", "data", | |
inspector_xhr_load_event::Data(GetExecutionContext(), this)); | |
DispatchProgressEventFromSnapshot(event_type_names::kLoad); | |
DispatchProgressEventFromSnapshot(event_type_names::kLoadend); | |
} | |
} | |
void XMLHttpRequest::setWithCredentials(bool value, | |
ExceptionState& exception_state) { | |
if (state_ > kOpened || send_flag_) { | |
exception_state.ThrowDOMException( | |
DOMExceptionCode::kInvalidStateError, | |
"The value may only be set if the object's state is UNSENT or OPENED."); | |
return; | |
} | |
with_credentials_ = value; | |
} | |
void XMLHttpRequest::open(const AtomicString& method, | |
const String& url_string, | |
ExceptionState& exception_state) { | |
if (!GetExecutionContext()) | |
return; | |
KURL url(GetExecutionContext()->CompleteURL(url_string)); | |
if (!ValidateOpenArguments(method, url, exception_state)) | |
return; | |
open(method, url, true, exception_state); | |
} | |
void XMLHttpRequest::open(const AtomicString& method, | |
const String& url_string, | |
bool async, | |
const String& username, | |
const String& password, | |
ExceptionState& exception_state) { | |
if (!GetExecutionContext()) | |
return; | |
KURL url(GetExecutionContext()->CompleteURL(url_string)); | |
if (!ValidateOpenArguments(method, url, exception_state)) | |
return; | |
if (!username.IsNull()) | |
url.SetUser(username); | |
if (!password.IsNull()) | |
url.SetPass(password); | |
open(method, url, async, exception_state); | |
} | |
void XMLHttpRequest::open(const AtomicString& method, | |
const KURL& url, | |
bool async, | |
ExceptionState& exception_state) { | |
NETWORK_DVLOG(1) << this << " open(" << method << ", " << url.ElidedString() | |
<< ", " << async << ")"; | |
DCHECK(ValidateOpenArguments(method, url, exception_state)); | |
if (!InternalAbort()) | |
return; | |
State previous_state = state_; | |
state_ = kUnsent; | |
error_ = false; | |
upload_complete_ = false; | |
if (!async && GetExecutionContext()->IsDocument()) { | |
if (GetDocument()->GetSettings() && | |
!GetDocument()->GetSettings()->GetSyncXHRInDocumentsEnabled()) { | |
exception_state.ThrowDOMException( | |
DOMExceptionCode::kInvalidAccessError, | |
"Synchronous requests are disabled for this page."); | |
return; | |
} | |
// Newer functionality is not available to synchronous requests in window | |
// contexts, as a spec-mandated attempt to discourage synchronous XHR use. | |
// responseType is one such piece of functionality. | |
if (response_type_code_ != kResponseTypeDefault) { | |
exception_state.ThrowDOMException( | |
DOMExceptionCode::kInvalidAccessError, | |
"Synchronous requests from a document must not set a response type."); | |
return; | |
} | |
// Similarly, timeouts are disabled for synchronous requests as well. | |
if (!timeout_.is_zero()) { | |
exception_state.ThrowDOMException( | |
DOMExceptionCode::kInvalidAccessError, | |
"Synchronous requests must not set a timeout."); | |
return; | |
} | |
// Here we just warn that firing sync XHR's may affect responsiveness. | |
// Eventually sync xhr will be deprecated and an "InvalidAccessError" | |
// exception thrown. | |
// Refer : https://xhr.spec.whatwg.org/#sync-warning | |
// Use count for XHR synchronous requests on main thread only. | |
if (!GetDocument()->ProcessingBeforeUnload()) { | |
Deprecation::CountDeprecation( | |
GetExecutionContext(), | |
WebFeature::kXMLHttpRequestSynchronousInNonWorkerOutsideBeforeUnload); | |
} | |
} | |
method_ = FetchUtils::NormalizeMethod(method); | |
url_ = url; | |
if (url_.ProtocolIs("blob") && BlobUtils::MojoBlobURLsEnabled()) { | |
GetExecutionContext()->GetPublicURLManager().Resolve( | |
url_, MakeRequest(&blob_url_loader_factory_)); | |
} | |
async_ = async; | |
DCHECK(!loader_); | |
send_flag_ = false; | |
// Check previous state to avoid dispatching readyState event | |
// when calling open several times in a row. | |
if (previous_state != kOpened) | |
ChangeState(kOpened); | |
else | |
state_ = kOpened; | |
} | |
bool XMLHttpRequest::InitSend(ExceptionState& exception_state) { | |
// We need to check ContextDestroyed because it is possible to create a | |
// XMLHttpRequest with already detached document. | |
// TODO(yhirano): Fix this. | |
if (!GetExecutionContext() || GetExecutionContext()->IsContextDestroyed()) { | |
HandleNetworkError(); | |
ThrowForLoadFailureIfNeeded(exception_state, | |
"Document is already detached."); | |
return false; | |
} | |
if (state_ != kOpened || send_flag_) { | |
exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError, | |
"The object's state must be OPENED."); | |
return false; | |
} | |
if (!async_) { | |
if (GetExecutionContext()->IsDocument() && | |
!GetDocument()->IsFeatureEnabled( | |
mojom::FeaturePolicyFeature::kSyncXHR, | |
ReportOptions::kReportOnFailure, | |
"Synchronous requests are disabled by Feature Policy.")) { | |
HandleNetworkError(); | |
ThrowForLoadFailureIfNeeded(exception_state, String()); | |
return false; | |
} | |
v8::Isolate* isolate = v8::Isolate::GetCurrent(); | |
if (isolate && v8::MicrotasksScope::IsRunningMicrotasks(isolate)) { | |
UseCounter::Count(GetExecutionContext(), | |
WebFeature::kDuring_Microtask_SyncXHR); | |
} | |
} | |
error_ = false; | |
return true; | |
} | |
void XMLHttpRequest::send( | |
const ArrayBufferOrArrayBufferViewOrBlobOrDocumentOrStringOrFormDataOrURLSearchParams& | |
body, | |
ExceptionState& exception_state) { | |
probe::willSendXMLHttpOrFetchNetworkRequest(GetExecutionContext(), Url()); | |
if (body.IsNull()) { | |
send(String(), exception_state); | |
return; | |
} | |
if (body.IsArrayBuffer()) { | |
send(body.GetAsArrayBuffer(), exception_state); | |
return; | |
} | |
if (body.IsArrayBufferView()) { | |
send(body.GetAsArrayBufferView().View(), exception_state); | |
return; | |
} | |
if (body.IsBlob()) { | |
send(body.GetAsBlob(), exception_state); | |
return; | |
} | |
if (body.IsDocument()) { | |
send(body.GetAsDocument(), exception_state); | |
return; | |
} | |
if (body.IsFormData()) { | |
send(body.GetAsFormData(), exception_state); | |
return; | |
} | |
if (body.IsURLSearchParams()) { | |
send(body.GetAsURLSearchParams(), exception_state); | |
return; | |
} | |
DCHECK(body.IsString()); | |
send(body.GetAsString(), exception_state); | |
} | |
bool XMLHttpRequest::AreMethodAndURLValidForSend() { | |
return method_ != http_names::kGET && method_ != http_names::kHEAD && | |
url_.ProtocolIsInHTTPFamily(); | |
} | |
void XMLHttpRequest::send(Document* document, ExceptionState& exception_state) { | |
NETWORK_DVLOG(1) << this << " send() Document " | |
<< static_cast<void*>(document); | |
DCHECK(document); | |
if (!InitSend(exception_state)) | |
return; | |
scoped_refptr<EncodedFormData> http_body; | |
if (AreMethodAndURLValidForSend()) { | |
if (document->IsHTMLDocument()) | |
UpdateContentTypeAndCharset("text/html;charset=UTF-8", "UTF-8"); | |
else if (document->IsXMLDocument()) | |
UpdateContentTypeAndCharset("application/xml;charset=UTF-8", "UTF-8"); | |
String body = CreateMarkup(document); | |
http_body = EncodedFormData::Create( | |
UTF8Encoding().Encode(body, WTF::kNoUnencodables)); | |
} | |
CreateRequest(std::move(http_body), exception_state); | |
} | |
void XMLHttpRequest::send(const String& body, ExceptionState& exception_state) { | |
NETWORK_DVLOG(1) << this << " send() String " << body; | |
if (!InitSend(exception_state)) | |
return; | |
scoped_refptr<EncodedFormData> http_body; | |
if (!body.IsNull() && AreMethodAndURLValidForSend()) { | |
http_body = EncodedFormData::Create( | |
UTF8Encoding().Encode(body, WTF::kNoUnencodables)); | |
UpdateContentTypeAndCharset("text/plain;charset=UTF-8", "UTF-8"); | |
} | |
CreateRequest(std::move(http_body), exception_state); | |
} | |
void XMLHttpRequest::send(Blob* body, ExceptionState& exception_state) { | |
NETWORK_DVLOG(1) << this << " send() Blob " << body->Uuid(); | |
if (!InitSend(exception_state)) | |
return; | |
scoped_refptr<EncodedFormData> http_body; | |
if (AreMethodAndURLValidForSend()) { | |
if (!HasContentTypeRequestHeader()) { | |
const String& blob_type = FetchUtils::NormalizeHeaderValue(body->type()); | |
if (!blob_type.IsEmpty() && ParsedContentType(blob_type).IsValid()) { | |
SetRequestHeaderInternal(http_names::kContentType, | |
AtomicString(blob_type)); | |
} | |
} | |
// FIXME: add support for uploading bundles. | |
http_body = EncodedFormData::Create(); | |
if (body->HasBackingFile()) { | |
auto* file = To<File>(body); | |
if (!file->GetPath().IsEmpty()) | |
http_body->AppendFile(file->GetPath()); | |
else | |
NOTREACHED(); | |
} else { | |
http_body->AppendBlob(body->Uuid(), body->GetBlobDataHandle()); | |
} | |
} | |
CreateRequest(std::move(http_body), exception_state); | |
} | |
void XMLHttpRequest::send(FormData* body, ExceptionState& exception_state) { | |
NETWORK_DVLOG(1) << this << " send() FormData " << body; | |
if (!InitSend(exception_state)) | |
return; | |
scoped_refptr<EncodedFormData> http_body; | |
if (AreMethodAndURLValidForSend()) { | |
http_body = body->EncodeMultiPartFormData(); | |
// TODO (sof): override any author-provided charset= in the | |
// content type value to UTF-8 ? | |
if (!HasContentTypeRequestHeader()) { | |
AtomicString content_type = | |
AtomicString("multipart/form-data; boundary=") + | |
FetchUtils::NormalizeHeaderValue(http_body->Boundary().data()); | |
SetRequestHeaderInternal(http_names::kContentType, content_type); | |
} | |
} | |
CreateRequest(std::move(http_body), exception_state); | |
} | |
void XMLHttpRequest::send(URLSearchParams* body, | |
ExceptionState& exception_state) { | |
NETWORK_DVLOG(1) << this << " send() URLSearchParams " << body; | |
if (!InitSend(exception_state)) | |
return; | |
scoped_refptr<EncodedFormData> http_body; | |
if (AreMethodAndURLValidForSend()) { | |
http_body = body->ToEncodedFormData(); | |
UpdateContentTypeAndCharset( | |
"application/x-www-form-urlencoded;charset=UTF-8", "UTF-8"); | |
} | |
CreateRequest(std::move(http_body), exception_state); | |
} | |
void XMLHttpRequest::send(DOMArrayBuffer* body, | |
ExceptionState& exception_state) { | |
NETWORK_DVLOG(1) << this << " send() ArrayBuffer " << body; | |
SendBytesData(body->Data(), body->ByteLength(), exception_state); | |
} | |
void XMLHttpRequest::send(DOMArrayBufferView* body, | |
ExceptionState& exception_state) { | |
NETWORK_DVLOG(1) << this << " send() ArrayBufferView " << body; | |
SendBytesData(body->BaseAddress(), body->byteLength(), exception_state); | |
} | |
void XMLHttpRequest::SendBytesData(const void* data, | |
size_t length, | |
ExceptionState& exception_state) { | |
if (!InitSend(exception_state)) | |
return; | |
scoped_refptr<EncodedFormData> http_body; | |
if (AreMethodAndURLValidForSend()) { | |
http_body = EncodedFormData::Create(data, length); | |
} | |
CreateRequest(std::move(http_body), exception_state); | |
} | |
void XMLHttpRequest::SendForInspectorXHRReplay( | |
scoped_refptr<EncodedFormData> form_data, | |
ExceptionState& exception_state) { | |
CreateRequest(form_data ? form_data->DeepCopy() : nullptr, exception_state); | |
if (exception_state.HadException()) { | |
CHECK(IsDOMExceptionCode(exception_state.Code())); | |
exception_code_ = exception_state.CodeAs<DOMExceptionCode>(); | |
} | |
} | |
void XMLHttpRequest::ThrowForLoadFailureIfNeeded( | |
ExceptionState& exception_state, | |
const String& reason) { | |
if (error_ && exception_code_ == DOMExceptionCode::kNoError) | |
exception_code_ = DOMExceptionCode::kNetworkError; | |
if (exception_code_ == DOMExceptionCode::kNoError) | |
return; | |
String message = "Failed to load '" + url_.ElidedString() + "'"; | |
if (reason.IsNull()) { | |
message.append('.'); | |
} else { | |
message.append(": "); | |
message.append(reason); | |
} | |
exception_state.ThrowDOMException(exception_code_, message); | |
} | |
void XMLHttpRequest::CreateRequest(scoped_refptr<EncodedFormData> http_body, | |
ExceptionState& exception_state) { | |
// Only GET request is supported for blob URL. | |
if (url_.ProtocolIs("blob") && method_ != http_names::kGET) { | |
HandleNetworkError(); | |
if (!async_) { | |
ThrowForLoadFailureIfNeeded( | |
exception_state, | |
"'GET' is the only method allowed for 'blob:' URLs."); | |
} | |
return; | |
} | |
if (url_.ProtocolIs("ftp")) { | |
LogConsoleError(GetExecutionContext(), "FTP is not supported."); | |
HandleNetworkError(); | |
if (!async_) { | |
ThrowForLoadFailureIfNeeded( | |
exception_state, "Making a request to a FTP URL is not supported."); | |
} | |
return; | |
} | |
DCHECK(GetExecutionContext()); | |
ExecutionContext& execution_context = *GetExecutionContext(); | |
send_flag_ = true; | |
// The presence of upload event listeners forces us to use preflighting | |
// because POSTing to an URL that does not permit cross origin requests should | |
// look exactly like POSTing to an URL that does not respond at all. | |
// Also, only async requests support upload progress events. | |
bool upload_events = false; | |
if (async_) { | |
probe::AsyncTaskScheduled(&execution_context, "XMLHttpRequest.send", this); | |
DispatchProgressEvent(event_type_names::kLoadstart, 0, 0); | |
// Event handler could have invalidated this send operation, | |
// (re)setting the send flag and/or initiating another send | |
// operation; leave quietly if so. | |
if (!send_flag_ || loader_) | |
return; | |
if (http_body && upload_) { | |
upload_events = upload_->HasEventListeners(); | |
upload_->DispatchEvent( | |
*ProgressEvent::Create(event_type_names::kLoadstart, false, 0, 0)); | |
// See above. | |
if (!send_flag_ || loader_) | |
return; | |
} | |
} | |
same_origin_request_ = GetSecurityOrigin()->CanRequest(url_); | |
if (!same_origin_request_ && with_credentials_) { | |
UseCounter::Count(&execution_context, | |
WebFeature::kXMLHttpRequestCrossOriginWithCredentials); | |
} | |
// We also remember whether upload events should be allowed for this request | |
// in case the upload listeners are added after the request is started. | |
upload_events_allowed_ = | |
same_origin_request_ || upload_events || | |
!cors::IsCorsSafelistedMethod(method_) || | |
!cors::ContainsOnlyCorsSafelistedHeaders(request_headers_); | |
ResourceRequest request(url_); | |
request.SetRequestorOrigin(GetSecurityOrigin()); | |
request.SetHTTPMethod(method_); | |
request.SetRequestContext(mojom::RequestContextType::XML_HTTP_REQUEST); | |
request.SetFetchRequestMode( | |
upload_events ? network::mojom::FetchRequestMode::kCorsWithForcedPreflight | |
: network::mojom::FetchRequestMode::kCors); | |
request.SetFetchCredentialsMode( | |
with_credentials_ ? network::mojom::FetchCredentialsMode::kInclude | |
: network::mojom::FetchCredentialsMode::kSameOrigin); | |
request.SetSkipServiceWorker(is_isolated_world_); | |
request.SetExternalRequestStateFromRequestorAddressSpace( | |
execution_context.GetSecurityContext().AddressSpace()); | |
probe::willLoadXHR(&execution_context, method_, url_, async_, http_body.get(), | |
request_headers_, with_credentials_); | |
if (http_body) { | |
DCHECK_NE(method_, http_names::kGET); | |
DCHECK_NE(method_, http_names::kHEAD); | |
request.SetHTTPBody(std::move(http_body)); | |
} | |
if (request_headers_.size() > 0) | |
request.AddHTTPHeaderFields(request_headers_); | |
ResourceLoaderOptions resource_loader_options; | |
resource_loader_options.initiator_info.name = | |
fetch_initiator_type_names::kXmlhttprequest; | |
if (blob_url_loader_factory_) { | |
resource_loader_options.url_loader_factory = base::MakeRefCounted< | |
base::RefCountedData<network::mojom::blink::URLLoaderFactoryPtr>>( | |
std::move(blob_url_loader_factory_)); | |
} | |
// When responseType is set to "blob", we redirect the downloaded data to a | |
// blob directly, except for data: URLs, since those are loaded by | |
// renderer side code, and don't support being downloaded to a blob. | |
downloading_to_blob_ = | |
GetResponseTypeCode() == kResponseTypeBlob && !url_.ProtocolIsData(); | |
if (downloading_to_blob_) { | |
request.SetDownloadToBlob(true); | |
resource_loader_options.data_buffering_policy = kDoNotBufferData; | |
} | |
if (async_) { | |
resource_loader_options.data_buffering_policy = kDoNotBufferData; | |
} | |
if (async_) { | |
UseCounter::Count(&execution_context, | |
WebFeature::kXMLHttpRequestAsynchronous); | |
if (GetExecutionContext()->IsDocument()) { | |
// Update histogram for usage of async xhr within pagedismissal. | |
auto pagedismissal = GetDocument()->PageDismissalEventBeingDispatched(); | |
if (pagedismissal != Document::kNoDismissal) { | |
UseCounter::Count(GetDocument(), WebFeature::kAsyncXhrInPageDismissal); | |
DEFINE_STATIC_LOCAL(EnumerationHistogram, | |
asyncxhr_pagedismissal_histogram, | |
("XHR.Async.PageDismissal", 5)); | |
asyncxhr_pagedismissal_histogram.Count(pagedismissal); | |
} | |
} | |
if (upload_) | |
request.SetReportUploadProgress(true); | |
// TODO(yhirano): Turn this CHECK into DCHECK: see https://crbug.com/570946. | |
CHECK(!loader_); | |
DCHECK(send_flag_); | |
} else { | |
// Use count for XHR synchronous requests. | |
UseCounter::Count(&execution_context, WebFeature::kXMLHttpRequestSynchronous); | |
if (GetExecutionContext()->IsDocument()) { | |
// Update histogram for usage of sync xhr within pagedismissal. | |
auto pagedismissal = GetDocument()->PageDismissalEventBeingDispatched(); | |
if (pagedismissal != Document::kNoDismissal) { | |
// Disallow synchronous requests on page dismissal | |
if (base::FeatureList::IsEnabled( | |
features::kForbidSyncXHRInPageDismissal)) { | |
UseCounter::Count(GetDocument(), | |
WebFeature::kForbiddenSyncXhrInPageDismissal); | |
DEFINE_STATIC_LOCAL(EnumerationHistogram, | |
forbidden_syncxhr_pagedismissal_histogram, | |
("XHR.Sync.PageDismissal_forbidden", 5)); | |
forbidden_syncxhr_pagedismissal_histogram.Count(pagedismissal); | |
HandleNetworkError(); | |
ThrowForLoadFailureIfNeeded(exception_state, | |
"Synchronous XHR in page dismissal."); | |
return; | |
} else { | |
UseCounter::Count(GetDocument(), WebFeature::kSyncXhrInPageDismissal); | |
DEFINE_STATIC_LOCAL(EnumerationHistogram, | |
syncxhr_pagedismissal_histogram, | |
("XHR.Sync.PageDismissal", 5)); | |
syncxhr_pagedismissal_histogram.Count(pagedismissal); | |
} | |
} | |
} | |
resource_loader_options.synchronous_policy = kRequestSynchronously; | |
} | |
exception_code_ = DOMExceptionCode::kNoError; | |
error_ = false; | |
loader_ = MakeGarbageCollected<ThreadableLoader>(execution_context, this, | |
resource_loader_options); | |
loader_->SetTimeout(timeout_); | |
loader_->Start(request); | |
if (!async_) | |
ThrowForLoadFailureIfNeeded(exception_state, String()); | |
} | |
void XMLHttpRequest::abort() { | |
NETWORK_DVLOG(1) << this << " abort()"; | |
// internalAbort() clears the response. Save the data needed for | |
// dispatching ProgressEvents. | |
long long expected_length = response_.ExpectedContentLength(); | |
long long received_length = received_length_; | |
if (!InternalAbort()) | |
return; | |
// The script never gets any chance to call abort() on a sync XHR between | |
// send() call and transition to the DONE state. It's because a sync XHR | |
// doesn't dispatch any event between them. So, if |m_async| is false, we | |
// can skip the "request error steps" (defined in the XHR spec) without any | |
// state check. | |
// | |
// FIXME: It's possible open() is invoked in internalAbort() and |m_async| | |
// becomes true by that. We should implement more reliable treatment for | |
// nested method invocations at some point. | |
if (async_) { | |
if ((state_ == kOpened && send_flag_) || state_ == kHeadersReceived || | |
state_ == kLoading) { | |
DCHECK(!loader_); | |
HandleRequestError(DOMExceptionCode::kNoError, event_type_names::kAbort, | |
received_length, expected_length); | |
} | |
} | |
if (state_ == kDone) | |
state_ = kUnsent; | |
} | |
void XMLHttpRequest::Dispose() { | |
progress_event_throttle_->Stop(); | |
InternalAbort(); | |
// TODO(yhirano): Remove this CHECK: see https://crbug.com/570946. | |
CHECK(!loader_); | |
} | |
void XMLHttpRequest::ClearVariablesForLoading() { | |
if (blob_loader_) { | |
blob_loader_->Cancel(); | |
blob_loader_ = nullptr; | |
} | |
decoder_.reset(); | |
if (response_document_parser_) { | |
response_document_parser_->RemoveClient(this); | |
response_document_parser_->Detach(); | |
response_document_parser_ = nullptr; | |
} | |
} | |
bool XMLHttpRequest::InternalAbort() { | |
// If there is an existing pending abort event, cancel it. The caller of this | |
// function is responsible for firing any events on XMLHttpRequest, if | |
// needed. | |
pending_abort_event_.Cancel(); | |
// Fast path for repeated internalAbort()s; this | |
// will happen if an XHR object is notified of context | |
// destruction followed by finalization. | |
if (error_ && !loader_) | |
return true; | |
error_ = true; | |
if (response_document_parser_ && !response_document_parser_->IsStopped()) | |
response_document_parser_->StopParsing(); | |
ClearVariablesForLoading(); | |
ClearResponse(); | |
ClearRequest(); | |
if (!loader_) | |
return true; | |
// Cancelling the ThreadableLoader loader_ may result in calling | |
// window.onload synchronously. If such an onload handler contains open() | |
// call on the same XMLHttpRequest object, reentry happens. | |
// | |
// If, window.onload contains open() and send(), m_loader will be set to | |
// non 0 value. So, we cannot continue the outer open(). In such case, | |
// just abort the outer open() by returning false. | |
ThreadableLoader* loader = loader_.Release(); | |
loader->Cancel(); | |
// If abort() called internalAbort() and a nested open() ended up | |
// clearing the error flag, but didn't send(), make sure the error | |
// flag is still set. | |
bool new_load_started = loader_; | |
if (!new_load_started) | |
error_ = true; | |
return !new_load_started; | |
} | |
void XMLHttpRequest::ClearResponse() { | |
// FIXME: when we add the support for multi-part XHR, we will have to | |
// be careful with this initialization. | |
received_length_ = 0; | |
response_ = ResourceResponse(); | |
response_text_.Clear(); | |
parsed_response_ = false; | |
response_document_ = nullptr; | |
response_blob_ = nullptr; | |
length_downloaded_to_blob_ = 0; | |
downloading_to_blob_ = false; | |
// These variables may referred by the response accessors. So, we can clear | |
// this only when we clear the response holder variables above. | |
binary_response_builder_ = nullptr; | |
response_array_buffer_.Clear(); | |
response_array_buffer_failure_ = false; | |
ReportMemoryUsageToV8(); | |
} | |
void XMLHttpRequest::ClearRequest() { | |
request_headers_.Clear(); | |
} | |
void XMLHttpRequest::DispatchProgressEvent(const AtomicString& type, | |
long long received_length, | |
long long expected_length) { | |
bool length_computable = | |
expected_length > 0 && received_length <= expected_length; | |
unsigned long long loaded = | |
received_length >= 0 ? static_cast<unsigned long long>(received_length) | |
: 0; | |
unsigned long long total = | |
length_computable ? static_cast<unsigned long long>(expected_length) : 0; | |
ExecutionContext* context = GetExecutionContext(); | |
probe::AsyncTask async_task( | |
context, this, type == event_type_names::kLoadend ? nullptr : "progress", | |
async_); | |
progress_event_throttle_->DispatchProgressEvent(type, length_computable, | |
loaded, total); | |
} | |
void XMLHttpRequest::DispatchProgressEventFromSnapshot( | |
const AtomicString& type) { | |
DispatchProgressEvent(type, received_length_, | |
response_.ExpectedContentLength()); | |
} | |
void XMLHttpRequest::HandleNetworkError() { | |
NETWORK_DVLOG(1) << this << " handleNetworkError()"; | |
// Response is cleared next, save needed progress event data. | |
long long expected_length = response_.ExpectedContentLength(); | |
long long received_length = received_length_; | |
if (!InternalAbort()) | |
return; | |
HandleRequestError(DOMExceptionCode::kNetworkError, event_type_names::kError, | |
received_length, expected_length); | |
} | |
void XMLHttpRequest::HandleDidCancel() { | |
NETWORK_DVLOG(1) << this << " handleDidCancel()"; | |
// Response is cleared next, save needed progress event data. | |
long long expected_length = response_.ExpectedContentLength(); | |
long long received_length = received_length_; | |
if (!InternalAbort()) | |
return; | |
pending_abort_event_ = PostCancellableTask( | |
*GetExecutionContext()->GetTaskRunner(TaskType::kNetworking), FROM_HERE, | |
WTF::Bind(&XMLHttpRequest::HandleRequestError, WrapPersistent(this), | |
DOMExceptionCode::kAbortError, event_type_names::kAbort, | |
received_length, expected_length)); | |
} | |
void XMLHttpRequest::HandleRequestError(DOMExceptionCode exception_code, | |
const AtomicString& type, | |
long long received_length, | |
long long expected_length) { | |
NETWORK_DVLOG(1) << this << " handleRequestError()"; | |
probe::didFinishXHR(GetExecutionContext(), this); | |
send_flag_ = false; | |
if (!async_) { | |
DCHECK_NE(exception_code, DOMExceptionCode::kNoError); | |
state_ = kDone; | |
exception_code_ = exception_code; | |
return; | |
} | |
// With m_error set, the state change steps are minimal: any pending | |
// progress event is flushed + a readystatechange is dispatched. | |
// No new progress events dispatched; as required, that happens at | |
// the end here. | |
DCHECK(error_); | |
ChangeState(kDone); | |
if (!upload_complete_) { | |
upload_complete_ = true; | |
if (upload_ && upload_events_allowed_) | |
upload_->HandleRequestError(type); | |
} | |
// Note: The below event dispatch may be called while |hasPendingActivity() == | |
// false|, when |handleRequestError| is called after |internalAbort()|. This | |
// is safe, however, as |this| will be kept alive from a strong ref | |
// |Event::m_target|. | |
DispatchProgressEvent(type, received_length, expected_length); | |
DispatchProgressEvent(event_type_names::kLoadend, received_length, | |
expected_length); | |
} | |
// https://xhr.spec.whatwg.org/#the-overridemimetype()-method | |
void XMLHttpRequest::overrideMimeType(const AtomicString& mime_type, | |
ExceptionState& exception_state) { | |
if (state_ == kLoading || state_ == kDone) { | |
exception_state.ThrowDOMException( | |
DOMExceptionCode::kInvalidStateError, | |
"MimeType cannot be overridden when the state is LOADING or DONE."); | |
return; | |
} | |
mime_type_override_ = "application/octet-stream"; | |
if (ParsedContentType(mime_type).IsValid()) | |
mime_type_override_ = mime_type; | |
} | |
// https://xhr.spec.whatwg.org/#the-setrequestheader()-method | |
void XMLHttpRequest::setRequestHeader(const AtomicString& name, | |
const AtomicString& value, | |
ExceptionState& exception_state) { | |
// "1. If |state| is not "opened", throw an InvalidStateError exception. | |
// 2. If the send() flag is set, throw an InvalidStateError exception." | |
if (state_ != kOpened || send_flag_) { | |
exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError, | |
"The object's state must be OPENED."); | |
return; | |
} | |
// "3. Normalize |value|." | |
const String normalized_value = FetchUtils::NormalizeHeaderValue(value); | |
// "4. If |name| is not a name or |value| is not a value, throw a SyntaxError | |
// exception." | |
if (!IsValidHTTPToken(name)) { | |
exception_state.ThrowDOMException( | |
DOMExceptionCode::kSyntaxError, | |
"'" + name + "' is not a valid HTTP header field name."); | |
return; | |
} | |
if (!IsValidHTTPHeaderValue(normalized_value)) { | |
exception_state.ThrowDOMException( | |
DOMExceptionCode::kSyntaxError, | |
"'" + normalized_value + "' is not a valid HTTP header field value."); | |
return; | |
} | |
// "5. Terminate these steps if |name| is a forbidden header name." | |
// No script (privileged or not) can set unsafe headers. | |
if (cors::IsForbiddenHeaderName(name)) { | |
LogConsoleError(GetExecutionContext(), | |
"Refused to set unsafe header \"" + name + "\""); | |
return; | |
} | |
// "6. Combine |name|/|value| in author request headers." | |
SetRequestHeaderInternal(name, AtomicString(normalized_value)); | |
} | |
void XMLHttpRequest::SetRequestHeaderInternal(const AtomicString& name, | |
const AtomicString& value) { | |
DCHECK_EQ(value, FetchUtils::NormalizeHeaderValue(value)) | |
<< "Header values must be normalized"; | |
HTTPHeaderMap::AddResult result = request_headers_.Add(name, value); | |
if (!result.is_new_entry) { | |
AtomicString new_value = result.stored_value->value + ", " + value; | |
result.stored_value->value = new_value; | |
} | |
} | |
bool XMLHttpRequest::HasContentTypeRequestHeader() const { | |
return request_headers_.Find(http_names::kContentType) != | |
request_headers_.end(); | |
} | |
String XMLHttpRequest::getAllResponseHeaders() const { | |
if (state_ < kHeadersReceived || error_) | |
return ""; | |
StringBuilder string_builder; | |
WebHTTPHeaderSet access_control_expose_header_set = | |
cors::ExtractCorsExposedHeaderNamesList( | |
with_credentials_ ? network::mojom::FetchCredentialsMode::kInclude | |
: network::mojom::FetchCredentialsMode::kSameOrigin, | |
response_); | |
HTTPHeaderMap::const_iterator end = response_.HttpHeaderFields().end(); | |
for (HTTPHeaderMap::const_iterator it = response_.HttpHeaderFields().begin(); | |
it != end; ++it) { | |
// Hide any headers whose name is a forbidden response-header name. | |
// This is required for all kinds of filtered responses. | |
// | |
// TODO: Consider removing canLoadLocalResources() call. | |
// crbug.com/567527 | |
if (FetchUtils::IsForbiddenResponseHeaderName(it->key) && | |
!GetSecurityOrigin()->CanLoadLocalResources()) | |
continue; | |
if (!same_origin_request_ && | |
!cors::IsOnAccessControlResponseHeaderWhitelist(it->key) && | |
access_control_expose_header_set.find(it->key.Ascii().data()) == | |
access_control_expose_header_set.end()) | |
continue; | |
string_builder.Append(it->key.LowerASCII()); | |
string_builder.Append(':'); | |
string_builder.Append(' '); | |
string_builder.Append(it->value); | |
string_builder.Append('\r'); | |
string_builder.Append('\n'); | |
} | |
return string_builder.ToString(); | |
} | |
const AtomicString& XMLHttpRequest::getResponseHeader( | |
const AtomicString& name) const { | |
if (state_ < kHeadersReceived || error_) | |
return g_null_atom; | |
// See comment in getAllResponseHeaders above. | |
if (FetchUtils::IsForbiddenResponseHeaderName(name) && | |
!GetSecurityOrigin()->CanLoadLocalResources()) { | |
LogConsoleError(GetExecutionContext(), | |
"Refused to get unsafe header \"" + name + "\""); | |
return g_null_atom; | |
} | |
WebHTTPHeaderSet access_control_expose_header_set = | |
cors::ExtractCorsExposedHeaderNamesList( | |
with_credentials_ ? network::mojom::FetchCredentialsMode::kInclude | |
: network::mojom::FetchCredentialsMode::kSameOrigin, | |
response_); | |
if (!same_origin_request_ && | |
!cors::IsOnAccessControlResponseHeaderWhitelist(name) && | |
access_control_expose_header_set.find(name.Ascii().data()) == | |
access_control_expose_header_set.end()) { | |
LogConsoleError(GetExecutionContext(), | |
"Refused to get unsafe header \"" + name + "\""); | |
return g_null_atom; | |
} | |
return response_.HttpHeaderField(name); | |
} | |
AtomicString XMLHttpRequest::FinalResponseMIMEType() const { | |
AtomicString overridden_type = | |
ExtractMIMETypeFromMediaType(mime_type_override_); | |
if (!overridden_type.IsEmpty()) | |
return overridden_type; | |
if (response_.IsHTTP()) { | |
return ExtractMIMETypeFromMediaType( | |
response_.HttpHeaderField(http_names::kContentType)); | |
} | |
return response_.MimeType(); | |
} | |
AtomicString XMLHttpRequest::FinalResponseMIMETypeWithFallback() const { | |
AtomicString final_type = FinalResponseMIMEType(); | |
if (!final_type.IsEmpty()) | |
return final_type; | |
return AtomicString("text/xml"); | |
} | |
// https://xhr.spec.whatwg.org/#final-charset | |
WTF::TextEncoding XMLHttpRequest::FinalResponseCharset() const { | |
// 1. Let label be null. [spec text] | |
// | |
// 2. If response MIME type's parameters["charset"] exists, then set label to | |
// it. [spec text] | |
String label = response_.TextEncodingName(); | |
// 3. If override MIME type's parameters["charset"] exists, then set label to | |
// it. [spec text] | |
String override_response_charset = | |
ExtractCharsetFromMediaType(mime_type_override_); | |
if (!override_response_charset.IsEmpty()) | |
label = override_response_charset; | |
// 4. If label is null, then return null. [spec text] | |
// | |
// 5. Let encoding be the result of getting an encoding from label. [spec | |
// text] | |
// | |
// 6. If encoding is failure, then return null. [spec text] | |
// | |
// 7. Return encoding. [spec text] | |
// | |
// We rely on WTF::TextEncoding() to return invalid TextEncoding for | |
// null, empty, or invalid/unsupported |label|. | |
return WTF::TextEncoding(label); | |
} | |
void XMLHttpRequest::UpdateContentTypeAndCharset( | |
const AtomicString& default_content_type, | |
const String& charset) { | |
// http://xhr.spec.whatwg.org/#the-send()-method step 4's concilliation of | |
// "charset=" in any author-provided Content-Type: request header. | |
String content_type = request_headers_.Get(http_names::kContentType); | |
if (content_type.IsNull()) { | |
SetRequestHeaderInternal(http_names::kContentType, default_content_type); | |
return; | |
} | |
String original_content_type = content_type; | |
ReplaceCharsetInMediaType(content_type, charset); | |
request_headers_.Set(http_names::kContentType, AtomicString(content_type)); | |
if (original_content_type != content_type) { | |
UseCounter::Count(GetExecutionContext(), WebFeature::kReplaceCharsetInXHR); | |
if (!EqualIgnoringASCIICase(original_content_type, content_type)) { | |
UseCounter::Count(GetExecutionContext(), | |
WebFeature::kReplaceCharsetInXHRIgnoringCase); | |
} | |
} | |
} | |
bool XMLHttpRequest::ResponseIsXML() const { | |
return DOMImplementation::IsXMLMIMEType(FinalResponseMIMETypeWithFallback()); | |
} | |
bool XMLHttpRequest::ResponseIsHTML() const { | |
return EqualIgnoringASCIICase(FinalResponseMIMEType(), "text/html"); | |
} | |
int XMLHttpRequest::status() const { | |
if (state_ == kUnsent || state_ == kOpened || error_) | |
return 0; | |
if (response_.HttpStatusCode()) | |
return response_.HttpStatusCode(); | |
return 0; | |
} | |
String XMLHttpRequest::statusText() const { | |
if (state_ == kUnsent || state_ == kOpened || error_) | |
return String(); | |
if (!response_.HttpStatusText().IsNull()) | |
return response_.HttpStatusText(); | |
return String(); | |
} | |
void XMLHttpRequest::DidFail(const ResourceError& error) { | |
NETWORK_DVLOG(1) << this << " didFail()"; | |
ScopedEventDispatchProtect protect(&event_dispatch_recursion_level_); | |
// If we are already in an error state, for instance we called abort(), bail | |
// out early. | |
if (error_) | |
return; | |
// Internally, access check violations are considered `cancellations`, but | |
// at least the mixed-content and CSP specs require them to be surfaced as | |
// network errors to the page. See: | |
// [1] https://www.w3.org/TR/mixed-content/#algorithms, | |
// [2] https://www.w3.org/TR/CSP3/#fetch-integration. | |
if (error.IsCancellation() && !error.IsAccessCheck()) { | |
HandleDidCancel(); | |
return; | |
} | |
if (error.IsTimeout()) { | |
HandleDidTimeout(); | |
return; | |
} | |
HandleNetworkError(); | |
} | |
void XMLHttpRequest::DidFailRedirectCheck() { | |
NETWORK_DVLOG(1) << this << " didFailRedirectCheck()"; | |
ScopedEventDispatchProtect protect(&event_dispatch_recursion_level_); | |
HandleNetworkError(); | |
} | |
void XMLHttpRequest::DidFinishLoading(unsigned long identifier) { | |
NETWORK_DVLOG(1) << this << " didFinishLoading(" << identifier << ")"; | |
ScopedEventDispatchProtect protect(&event_dispatch_recursion_level_); | |
if (error_) | |
return; | |
if (state_ < kHeadersReceived) | |
ChangeState(kHeadersReceived); | |
if (downloading_to_blob_ && response_type_code_ != kResponseTypeBlob && | |
response_blob_) { | |
// In this case, we have sent the request with DownloadToBlob true, | |
// but the user changed the response type after that. Hence we need to | |
// read the response data and provide it to this object. | |
blob_loader_ = | |
BlobLoader::Create(this, response_blob_->GetBlobDataHandle()); | |
} else { | |
DidFinishLoadingInternal(); | |
} | |
} | |
void XMLHttpRequest::DidFinishLoadingInternal() { | |
if (response_document_parser_) { | |
// |DocumentParser::finish()| tells the parser that we have reached end of | |
// the data. When using |HTMLDocumentParser|, which works asynchronously, | |
// we do not have the complete document just after the | |
// |DocumentParser::finish()| call. Wait for the parser to call us back in | |
// |notifyParserStopped| to progress state. | |
response_document_parser_->Finish(); | |
DCHECK(response_document_); | |
return; | |
} | |
if (decoder_) { | |
auto text = decoder_->Flush(); | |
if (!text.IsEmpty() && !response_text_overflow_) { | |
response_text_.Concat(isolate_, text); | |
response_text_overflow_ = response_text_.IsEmpty(); | |
} | |
} | |
ClearVariablesForLoading(); | |
EndLoading(); | |
} | |
void XMLHttpRequest::DidFinishLoadingFromBlob() { | |
NETWORK_DVLOG(1) << this << " didFinishLoadingFromBlob"; | |
ScopedEventDispatchProtect protect(&event_dispatch_recursion_level_); | |
DidFinishLoadingInternal(); | |
} | |
void XMLHttpRequest::DidFailLoadingFromBlob() { | |
NETWORK_DVLOG(1) << this << " didFailLoadingFromBlob()"; | |
ScopedEventDispatchProtect protect(&event_dispatch_recursion_level_); | |
if (error_) | |
return; | |
HandleNetworkError(); | |
} | |
void XMLHttpRequest::NotifyParserStopped() { | |
ScopedEventDispatchProtect protect(&event_dispatch_recursion_level_); | |
// This should only be called when response document is parsed asynchronously. | |
DCHECK(response_document_parser_); | |
DCHECK(!response_document_parser_->IsParsing()); | |
// Do nothing if we are called from |internalAbort()|. | |
if (error_) | |
return; | |
ClearVariablesForLoading(); | |
if (!response_document_->WellFormed()) | |
response_document_ = nullptr; | |
parsed_response_ = true; | |
EndLoading(); | |
} | |
void XMLHttpRequest::EndLoading() { | |
probe::didFinishXHR(GetExecutionContext(), this); | |
if (loader_) { | |
// Set |m_error| in order to suppress the cancel notification (see | |
// XMLHttpRequest::didFail). | |
base::AutoReset<bool> scope(&error_, true); | |
loader_.Release()->Cancel(); | |
} | |
send_flag_ = false; | |
ChangeState(kDone); | |
if (!GetExecutionContext() || !GetExecutionContext()->IsDocument()) | |
return; | |
if (GetDocument() && GetDocument()->GetFrame() && | |
GetDocument()->GetFrame()->GetPage() && cors::IsOkStatus(status())) | |
GetDocument()->GetFrame()->GetPage()->GetChromeClient().AjaxSucceeded( | |
GetDocument()->GetFrame()); | |
} | |
void XMLHttpRequest::DidSendData(unsigned long long bytes_sent, | |
unsigned long long total_bytes_to_be_sent) { | |
NETWORK_DVLOG(1) << this << " didSendData(" << bytes_sent << ", " | |
<< total_bytes_to_be_sent << ")"; | |
ScopedEventDispatchProtect protect(&event_dispatch_recursion_level_); | |
if (!upload_) | |
return; | |
if (upload_events_allowed_) | |
upload_->DispatchProgressEvent(bytes_sent, total_bytes_to_be_sent); | |
if (bytes_sent == total_bytes_to_be_sent && !upload_complete_) { | |
upload_complete_ = true; | |
if (upload_events_allowed_) { | |
upload_->DispatchEventAndLoadEnd(event_type_names::kLoad, true, | |
bytes_sent, total_bytes_to_be_sent); | |
} | |
} | |
} | |
void XMLHttpRequest::DidReceiveResponse(unsigned long identifier, | |
const ResourceResponse& response) { | |
// TODO(yhirano): Remove this CHECK: see https://crbug.com/570946. | |
CHECK(&response); | |
NETWORK_DVLOG(1) << this << " didReceiveResponse(" << identifier << ")"; | |
ScopedEventDispatchProtect protect(&event_dispatch_recursion_level_); | |
response_ = response; | |
} | |
void XMLHttpRequest::ParseDocumentChunk(const char* data, unsigned len) { | |
if (!response_document_parser_) { | |
DCHECK(!response_document_); | |
InitResponseDocument(); | |
if (!response_document_) | |
return; | |
response_document_parser_ = | |
response_document_->ImplicitOpen(kAllowAsynchronousParsing); | |
response_document_parser_->AddClient(this); | |
} | |
DCHECK(response_document_parser_); | |
if (response_document_parser_->NeedsDecoder()) | |
response_document_parser_->SetDecoder(CreateDecoder()); | |
response_document_parser_->AppendBytes(data, len); | |
} | |
std::unique_ptr<TextResourceDecoder> XMLHttpRequest::CreateDecoder() const { | |
const TextResourceDecoderOptions decoder_options_for_utf8_plain_text( | |
TextResourceDecoderOptions::kPlainTextContent, UTF8Encoding()); | |
if (response_type_code_ == kResponseTypeJSON) | |
return TextResourceDecoder::Create(decoder_options_for_utf8_plain_text); | |
WTF::TextEncoding final_response_charset = FinalResponseCharset(); | |
if (final_response_charset.IsValid()) { | |
// If the final charset is given and valid, use the charset without | |
// sniffing the content. | |
return TextResourceDecoder::Create(TextResourceDecoderOptions( | |
TextResourceDecoderOptions::kPlainTextContent, final_response_charset)); | |
} | |
TextResourceDecoderOptions decoder_options_for_xml( | |
TextResourceDecoderOptions::kXMLContent); | |
// 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. | |
decoder_options_for_xml.SetUseLenientXMLDecoding(); | |
switch (response_type_code_) { | |
case kResponseTypeDefault: | |
if (ResponseIsXML()) | |
return TextResourceDecoder::Create(decoder_options_for_xml); | |
FALLTHROUGH; | |
case kResponseTypeText: | |
return TextResourceDecoder::Create(decoder_options_for_utf8_plain_text); | |
case kResponseTypeDocument: | |
if (ResponseIsHTML()) { | |
return TextResourceDecoder::Create(TextResourceDecoderOptions( | |
TextResourceDecoderOptions::kHTMLContent, UTF8Encoding())); | |
} | |
return TextResourceDecoder::Create(decoder_options_for_xml); | |
case kResponseTypeJSON: | |
case kResponseTypeBlob: | |
case kResponseTypeArrayBuffer: | |
NOTREACHED(); | |
break; | |
} | |
NOTREACHED(); | |
return nullptr; | |
} | |
void XMLHttpRequest::DidReceiveData(const char* data, unsigned len) { | |
ScopedEventDispatchProtect protect(&event_dispatch_recursion_level_); | |
if (error_) | |
return; | |
DCHECK(!downloading_to_blob_ || blob_loader_); | |
if (state_ < kHeadersReceived) | |
ChangeState(kHeadersReceived); | |
// We need to check for |m_error| again, because |changeState| may trigger | |
// readystatechange, and user javascript can cause |abort()|. | |
if (error_) | |
return; | |
if (!len) | |
return; | |
if (response_type_code_ == kResponseTypeDocument && ResponseIsHTML()) { | |
ParseDocumentChunk(data, len); | |
} else if (response_type_code_ == kResponseTypeDefault || | |
response_type_code_ == kResponseTypeText || | |
response_type_code_ == kResponseTypeJSON || | |
response_type_code_ == kResponseTypeDocument) { | |
if (!decoder_) | |
decoder_ = CreateDecoder(); | |
auto text = decoder_->Decode(data, len); | |
if (!text.IsEmpty() && !response_text_overflow_) { | |
response_text_.Concat(isolate_, text); | |
response_text_overflow_ = response_text_.IsEmpty(); | |
} | |
} else if (response_type_code_ == kResponseTypeArrayBuffer || | |
response_type_code_ == kResponseTypeBlob) { | |
// Buffer binary data. | |
if (!binary_response_builder_) | |
binary_response_builder_ = SharedBuffer::Create(); | |
binary_response_builder_->Append(data, len); | |
ReportMemoryUsageToV8(); | |
} | |
if (blob_loader_) { | |
// In this case, the data is provided by m_blobLoader. As progress | |
// events are already fired, we should return here. | |
return; | |
} | |
TrackProgress(len); | |
} | |
void XMLHttpRequest::DidDownloadData(unsigned long long data_length) { | |
ScopedEventDispatchProtect protect(&event_dispatch_recursion_level_); | |
if (error_) | |
return; | |
DCHECK(downloading_to_blob_); | |
if (state_ < kHeadersReceived) | |
ChangeState(kHeadersReceived); | |
if (!data_length) | |
return; | |
// readystatechange event handler may do something to put this XHR in error | |
// state. We need to check m_error again here. | |
if (error_) | |
return; | |
length_downloaded_to_blob_ += data_length; | |
ReportMemoryUsageToV8(); | |
TrackProgress(data_length); | |
} | |
void XMLHttpRequest::DidDownloadToBlob(scoped_refptr<BlobDataHandle> blob) { | |
ScopedEventDispatchProtect protect(&event_dispatch_recursion_level_); | |
if (error_) | |
return; | |
DCHECK(downloading_to_blob_); | |
if (!blob) { | |
// This generally indicates not enough quota for the blob, or somehow | |
// failing to write the blob to disk. Treat this as a network error. | |
// TODO(mek): Maybe print a more helpful/specific error message to the | |
// console, to distinguish this from true network errors? | |
// TODO(mek): This would best be treated as a network error, but for sync | |
// requests this could also just mean succesfully reading a zero-byte blob | |
// from a misbehaving URLLoader, so for now just ignore this and don't do | |
// anything, which will result in an empty blob being returned by XHR. | |
// HandleNetworkError(); | |
} else { | |
// Fix content type if overrides or fallbacks are in effect. | |
String mime_type = FinalResponseMIMETypeWithFallback().LowerASCII(); | |
if (blob->GetType() != mime_type) { | |
auto blob_size = blob->size(); | |
auto blob_data = BlobData::Create(); | |
blob_data->SetContentType(mime_type); | |
blob_data->AppendBlob(std::move(blob), 0, blob_size); | |
response_blob_ = | |
Blob::Create(BlobDataHandle::Create(std::move(blob_data), blob_size)); | |
} else { | |
response_blob_ = Blob::Create(std::move(blob)); | |
} | |
} | |
} | |
void XMLHttpRequest::HandleDidTimeout() { | |
NETWORK_DVLOG(1) << this << " handleDidTimeout()"; | |
// Response is cleared next, save needed progress event data. | |
long long expected_length = response_.ExpectedContentLength(); | |
long long received_length = received_length_; | |
if (!InternalAbort()) | |
return; | |
HandleRequestError(DOMExceptionCode::kTimeoutError, | |
event_type_names::kTimeout, received_length, | |
expected_length); | |
} | |
void XMLHttpRequest::ContextDestroyed(ExecutionContext*) { | |
Dispose(); | |
// In case we are in the middle of send() function, unset the send flag to | |
// stop the operation. | |
send_flag_ = false; | |
} | |
bool XMLHttpRequest::HasPendingActivity() const { | |
// Neither this object nor the JavaScript wrapper should be deleted while | |
// a request is in progress because we need to keep the listeners alive, | |
// and they are referenced by the JavaScript wrapper. | |
// |m_loader| is non-null while request is active and ThreadableLoaderClient | |
// callbacks may be called, and |m_responseDocumentParser| is non-null while | |
// DocumentParserClient callbacks may be called. | |
if (loader_ || response_document_parser_) | |
return true; | |
return event_dispatch_recursion_level_ > 0; | |
} | |
const AtomicString& XMLHttpRequest::InterfaceName() const { | |
return event_target_names::kXMLHttpRequest; | |
} | |
ExecutionContext* XMLHttpRequest::GetExecutionContext() const { | |
return ContextLifecycleObserver::GetExecutionContext(); | |
} | |
void XMLHttpRequest::ReportMemoryUsageToV8() { | |
// binary_response_builder_ | |
size_t size = binary_response_builder_ ? binary_response_builder_->size() : 0; | |
int64_t diff = | |
static_cast<int64_t>(size) - | |
static_cast<int64_t>(binary_response_builder_last_reported_size_); | |
binary_response_builder_last_reported_size_ = size; | |
// Blob (downloading_to_blob_, length_downloaded_to_blob_) | |
diff += static_cast<int64_t>(length_downloaded_to_blob_) - | |
static_cast<int64_t>(length_downloaded_to_blob_last_reported_); | |
length_downloaded_to_blob_last_reported_ = length_downloaded_to_blob_; | |
if (diff) | |
isolate_->AdjustAmountOfExternalAllocatedMemory(diff); | |
} | |
void XMLHttpRequest::Trace(blink::Visitor* visitor) { | |
visitor->Trace(response_blob_); | |
visitor->Trace(loader_); | |
visitor->Trace(response_document_); | |
visitor->Trace(response_document_parser_); | |
visitor->Trace(response_array_buffer_); | |
visitor->Trace(progress_event_throttle_); | |
visitor->Trace(upload_); | |
visitor->Trace(blob_loader_); | |
visitor->Trace(response_text_); | |
XMLHttpRequestEventTarget::Trace(visitor); | |
ThreadableLoaderClient::Trace(visitor); | |
DocumentParserClient::Trace(visitor); | |
ContextLifecycleObserver::Trace(visitor); | |
} | |
std::ostream& operator<<(std::ostream& ostream, const XMLHttpRequest* xhr) { | |
return ostream << "XMLHttpRequest " << static_cast<const void*>(xhr); | |
} | |
} // namespace blink |