| /* |
| * 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_( |
| FileReaderLoader::Create(FileReaderLoader::kReadByClient, this)) { |
| 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()) { |
| File* file = ToFile(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, this, this, 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"); |
| } |
| |
| String XMLHttpRequest::FinalResponseCharset() const { |
| String override_response_charset = |
| ExtractCharsetFromMediaType(mime_type_override_); |
| if (!override_response_charset.IsEmpty()) |
| return override_response_charset; |
| return response_.TextEncodingName(); |
| } |
| |
| 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, |
| std::unique_ptr<WebDataConsumerHandle> handle) { |
| // TODO(yhirano): Remove this CHECK: see https://crbug.com/570946. |
| CHECK(&response); |
| |
| ALLOW_UNUSED_LOCAL(handle); |
| DCHECK(!handle); |
| 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); |
| |
| String final_response_charset = FinalResponseCharset(); |
| if (!final_response_charset.IsEmpty()) { |
| // If the final charset is given, use the charset without sniffing the |
| // content. |
| // TODO(crbug/905968): If WTF::TextEncoding::IsValid() is false, this |
| // currently falls back to Latin1Encoding(). Fallback to UTF-8 instead. |
| return TextResourceDecoder::Create(TextResourceDecoderOptions( |
| TextResourceDecoderOptions::kPlainTextContent, |
| WTF::TextEncoding(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 |