From 2a3793485f71b8ec102a324ab61804c8c093a21e Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Thu, 4 Jul 2019 10:56:19 +0900 Subject: [PATCH] feat: implement net module with NetworkService (#19094) * Stub class for NetworkService-based URLRequest * Create SimpleURLLoader * Implement downloading * Implement response event * Only write data afte previous one finished * Use DataPipeGetter for uploading data * Support chunked upload data * Call size callback at last * Simplify UploadDataPipeGetter * Implement cancelling and closing * Handle redirection * Fix uploading large data * Emit error when request fails * Emit error for redirection error * Simplify emitting error * "follow" should also emit "redirect" event * SetLoadFlags is not really used * Implement GetUploadProgress * Implement FollowRedirect * Fix exception with multiple redirections * Reduce number of EmitEvent methods * Emit response errors * FetchRedirectMode => RedirectMode --- filenames.gni | 2 + shell/browser/api/atom_api_net.cc | 21 +- shell/browser/api/atom_api_url_request.h | 3 +- shell/browser/api/atom_api_url_request_ns.cc | 544 +++++++++++++++++++ shell/browser/api/atom_api_url_request_ns.h | 143 +++++ 5 files changed, 708 insertions(+), 5 deletions(-) create mode 100644 shell/browser/api/atom_api_url_request_ns.cc create mode 100644 shell/browser/api/atom_api_url_request_ns.h diff --git a/filenames.gni b/filenames.gni index ef18228df675..901c768f7c17 100644 --- a/filenames.gni +++ b/filenames.gni @@ -93,6 +93,8 @@ filenames = { "shell/browser/api/atom_api_tray.h", "shell/browser/api/atom_api_url_request.cc", "shell/browser/api/atom_api_url_request.h", + "shell/browser/api/atom_api_url_request_ns.cc", + "shell/browser/api/atom_api_url_request_ns.h", "shell/browser/api/atom_api_view.cc", "shell/browser/api/atom_api_view.h", "shell/browser/api/atom_api_web_contents.cc", diff --git a/shell/browser/api/atom_api_net.cc b/shell/browser/api/atom_api_net.cc index 3282f3f44ab7..df0c481a0b9e 100644 --- a/shell/browser/api/atom_api_net.cc +++ b/shell/browser/api/atom_api_net.cc @@ -3,8 +3,12 @@ // found in the LICENSE file. #include "shell/browser/api/atom_api_net.h" + #include "native_mate/dictionary.h" +#include "services/network/public/cpp/features.h" #include "shell/browser/api/atom_api_url_request.h" +#include "shell/browser/api/atom_api_url_request_ns.h" + #include "shell/common/node_includes.h" namespace electron { @@ -31,8 +35,12 @@ void Net::BuildPrototype(v8::Isolate* isolate, } v8::Local Net::URLRequest(v8::Isolate* isolate) { - return URLRequest::GetConstructor(isolate) - ->GetFunction(isolate->GetCurrentContext()) + v8::Local constructor; + if (base::FeatureList::IsEnabled(network::features::kNetworkService)) + constructor = URLRequestNS::GetConstructor(isolate); + else + constructor = URLRequest::GetConstructor(isolate); + return constructor->GetFunction(isolate->GetCurrentContext()) .ToLocalChecked(); } @@ -44,6 +52,7 @@ namespace { using electron::api::Net; using electron::api::URLRequest; +using electron::api::URLRequestNS; void Initialize(v8::Local exports, v8::Local unused, @@ -51,12 +60,18 @@ void Initialize(v8::Local exports, void* priv) { v8::Isolate* isolate = context->GetIsolate(); - URLRequest::SetConstructor(isolate, base::BindRepeating(URLRequest::New)); + if (base::FeatureList::IsEnabled(network::features::kNetworkService)) + URLRequestNS::SetConstructor(isolate, + base::BindRepeating(URLRequestNS::New)); + else + URLRequest::SetConstructor(isolate, base::BindRepeating(URLRequest::New)); mate::Dictionary dict(isolate, exports); dict.Set("net", Net::Create(isolate)); dict.Set("Net", Net::GetConstructor(isolate)->GetFunction(context).ToLocalChecked()); + dict.Set("isNetworkServiceEnabled", + base::FeatureList::IsEnabled(network::features::kNetworkService)); } } // namespace diff --git a/shell/browser/api/atom_api_url_request.h b/shell/browser/api/atom_api_url_request.h index 8cf182a5c141..de45ab08225a 100644 --- a/shell/browser/api/atom_api_url_request.h +++ b/shell/browser/api/atom_api_url_request.h @@ -25,7 +25,6 @@ class AtomURLRequest; namespace api { -// // The URLRequest class implements the V8 binding between the JavaScript API // and Chromium native net library. It is responsible for handling HTTP/HTTPS // requests. @@ -114,7 +113,7 @@ class URLRequest : public mate::EventEmitter { mate::Dictionary GetUploadProgress(v8::Isolate* isolate); protected: - explicit URLRequest(v8::Isolate* isolate, v8::Local wrapper); + URLRequest(v8::Isolate* isolate, v8::Local wrapper); ~URLRequest() override; private: diff --git a/shell/browser/api/atom_api_url_request_ns.cc b/shell/browser/api/atom_api_url_request_ns.cc new file mode 100644 index 000000000000..cb307e87d317 --- /dev/null +++ b/shell/browser/api/atom_api_url_request_ns.cc @@ -0,0 +1,544 @@ +// Copyright (c) 2019 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#include "shell/browser/api/atom_api_url_request_ns.h" + +#include + +#include "content/public/browser/storage_partition.h" +#include "native_mate/dictionary.h" +#include "native_mate/object_template_builder.h" +#include "shell/browser/api/atom_api_session.h" +#include "shell/browser/atom_browser_context.h" +#include "shell/common/native_mate_converters/gurl_converter.h" +#include "shell/common/native_mate_converters/net_converter.h" + +#include "shell/common/node_includes.h" + +namespace mate { + +template <> +struct Converter { + static bool FromV8(v8::Isolate* isolate, + v8::Local val, + network::mojom::RedirectMode* out) { + std::string mode; + if (!ConvertFromV8(isolate, val, &mode)) + return false; + if (mode == "follow") + *out = network::mojom::RedirectMode::kFollow; + else if (mode == "error") + *out = network::mojom::RedirectMode::kError; + else if (mode == "manual") + *out = network::mojom::RedirectMode::kManual; + else + return false; + return true; + } +}; + +} // namespace mate + +namespace electron { + +namespace api { + +namespace { + +// Network state for request and response. +enum State { + STATE_STARTED = 1 << 0, + STATE_FINISHED = 1 << 1, + STATE_CANCELED = 1 << 2, + STATE_FAILED = 1 << 3, + STATE_CLOSED = 1 << 4, + STATE_ERROR = STATE_CANCELED | STATE_FAILED | STATE_CLOSED, +}; + +// Annotation tag passed to NetworkService. +const net::NetworkTrafficAnnotationTag kTrafficAnnotation = + net::DefineNetworkTrafficAnnotation("electron_net_module", R"( + semantics { + sender: "Electron Net module" + description: + "Issue HTTP/HTTPS requests using Chromium's native networking " + "library." + trigger: "Using the Net module" + data: "Anything the user wants to send." + destination: OTHER + } + policy { + cookies_allowed: YES + cookies_store: "user" + setting: "This feature cannot be disabled." + })"); + +} // namespace + +// Common class for streaming data. +class UploadDataPipeGetter { + public: + explicit UploadDataPipeGetter(URLRequestNS* request) : request_(request) {} + virtual ~UploadDataPipeGetter() = default; + + virtual void AttachToRequestBody(network::ResourceRequestBody* body) = 0; + + protected: + void SetCallback(network::mojom::DataPipeGetter::ReadCallback callback) { + request_->size_callback_ = std::move(callback); + } + + void SetPipe(mojo::ScopedDataPipeProducerHandle pipe) { + request_->producer_ = + std::make_unique(std::move(pipe)); + request_->StartWriting(); + } + + private: + URLRequestNS* request_; + + DISALLOW_COPY_AND_ASSIGN(UploadDataPipeGetter); +}; + +// Streaming multipart data to NetworkService. +class MultipartDataPipeGetter : public UploadDataPipeGetter, + public network::mojom::DataPipeGetter { + public: + explicit MultipartDataPipeGetter(URLRequestNS* request) + : UploadDataPipeGetter(request) {} + ~MultipartDataPipeGetter() override = default; + + void AttachToRequestBody(network::ResourceRequestBody* body) override { + network::mojom::DataPipeGetterPtr data_pipe_getter; + binding_set_.AddBinding(this, mojo::MakeRequest(&data_pipe_getter)); + body->AppendDataPipe(std::move(data_pipe_getter)); + } + + private: + // network::mojom::DataPipeGetter: + void Read(mojo::ScopedDataPipeProducerHandle pipe, + ReadCallback callback) override { + SetCallback(std::move(callback)); + SetPipe(std::move(pipe)); + } + + void Clone(network::mojom::DataPipeGetterRequest request) override { + binding_set_.AddBinding(this, std::move(request)); + } + + mojo::BindingSet binding_set_; +}; + +// Streaming chunked data to NetworkService. +class ChunkedDataPipeGetter : public UploadDataPipeGetter, + public network::mojom::ChunkedDataPipeGetter { + public: + explicit ChunkedDataPipeGetter(URLRequestNS* request) + : UploadDataPipeGetter(request) {} + ~ChunkedDataPipeGetter() override = default; + + void AttachToRequestBody(network::ResourceRequestBody* body) override { + network::mojom::ChunkedDataPipeGetterPtr data_pipe_getter; + binding_set_.AddBinding(this, mojo::MakeRequest(&data_pipe_getter)); + body->SetToChunkedDataPipe(std::move(data_pipe_getter)); + } + + private: + // network::mojom::ChunkedDataPipeGetter: + void GetSize(GetSizeCallback callback) override { + SetCallback(std::move(callback)); + } + + void StartReading(mojo::ScopedDataPipeProducerHandle pipe) override { + SetPipe(std::move(pipe)); + } + + mojo::BindingSet binding_set_; +}; + +URLRequestNS::URLRequestNS(mate::Arguments* args) : weak_factory_(this) { + request_ = std::make_unique(); + mate::Dictionary dict; + if (args->GetNext(&dict)) { + dict.Get("method", &request_->method); + dict.Get("url", &request_->url); + dict.Get("redirect", &redirect_mode_); + request_->redirect_mode = redirect_mode_; + } + + std::string partition; + mate::Handle session; + if (!dict.Get("session", &session)) { + if (dict.Get("partition", &partition)) + session = Session::FromPartition(args->isolate(), partition); + else // default session + session = Session::FromPartition(args->isolate(), ""); + } + + auto* browser_context = session->browser_context(); + url_loader_factory_ = + content::BrowserContext::GetDefaultStoragePartition(browser_context) + ->GetURLLoaderFactoryForBrowserProcess(); + + InitWith(args->isolate(), args->GetThis()); +} + +URLRequestNS::~URLRequestNS() {} + +bool URLRequestNS::NotStarted() const { + return request_state_ == 0; +} + +bool URLRequestNS::Finished() const { + return request_state_ & STATE_FINISHED; +} + +void URLRequestNS::Cancel() { + // Cancel only once. + if (request_state_ & (STATE_CANCELED | STATE_CLOSED)) + return; + + // Mark as canceled. + request_state_ |= STATE_CANCELED; + EmitEvent(EventType::kRequest, true, "abort"); + + if ((response_state_ & STATE_STARTED) && !(response_state_ & STATE_FINISHED)) + EmitEvent(EventType::kResponse, true, "aborted"); + + Close(); +} + +void URLRequestNS::Close() { + if (!(request_state_ & STATE_CLOSED)) { + request_state_ |= STATE_CLOSED; + if (response_state_ & STATE_STARTED) { + // Emit a close event if we really have a response object. + EmitEvent(EventType::kResponse, true, "close"); + } + EmitEvent(EventType::kRequest, true, "close"); + } + Unpin(); + loader_.reset(); +} + +bool URLRequestNS::Write(v8::Local data, bool is_last) { + if (request_state_ & (STATE_FINISHED | STATE_ERROR)) + return false; + + size_t length = node::Buffer::Length(data); + + if (!loader_) { + // Pin on first write. + request_state_ = STATE_STARTED; + Pin(); + + // Create the loader. + network::ResourceRequest* request_ref = request_.get(); + loader_ = network::SimpleURLLoader::Create(std::move(request_), + kTrafficAnnotation); + loader_->SetOnResponseStartedCallback(base::Bind( + &URLRequestNS::OnResponseStarted, weak_factory_.GetWeakPtr())); + loader_->SetOnRedirectCallback( + base::Bind(&URLRequestNS::OnRedirect, weak_factory_.GetWeakPtr())); + loader_->SetOnUploadProgressCallback(base::Bind( + &URLRequestNS::OnUploadProgress, weak_factory_.GetWeakPtr())); + + // Create upload data pipe if we have data to write. + if (length > 0) { + request_ref->request_body = new network::ResourceRequestBody(); + if (is_chunked_upload_) + data_pipe_getter_ = std::make_unique(this); + else + data_pipe_getter_ = std::make_unique(this); + data_pipe_getter_->AttachToRequestBody(request_ref->request_body.get()); + } + + // Start downloading. + loader_->DownloadAsStream(url_loader_factory_.get(), this); + } + + if (length > 0) + pending_writes_.emplace_back(node::Buffer::Data(data), length); + + if (is_last) { + // The ElementsUploadDataStream requires the knowledge of content length + // before doing upload, while Node's stream does not give us any size + // information. So the only option left for us is to keep all the write + // data in memory and flush them after the write is done. + // + // While this looks frustrating, it is actually the behavior of the non- + // NetworkService implementation, and we are not breaking anything. + if (!pending_writes_.empty()) { + last_chunk_written_ = true; + StartWriting(); + } + + request_state_ |= STATE_FINISHED; + EmitEvent(EventType::kRequest, true, "finish"); + } + return true; +} + +void URLRequestNS::FollowRedirect() { + if (request_state_ & (STATE_CANCELED | STATE_CLOSED)) + return; + follow_redirect_ = true; +} + +bool URLRequestNS::SetExtraHeader(const std::string& name, + const std::string& value) { + if (!request_) + return false; + if (!net::HttpUtil::IsValidHeaderName(name)) + return false; + if (!net::HttpUtil::IsValidHeaderValue(value)) + return false; + request_->headers.SetHeader(name, value); + return true; +} + +void URLRequestNS::RemoveExtraHeader(const std::string& name) { + if (request_) + request_->headers.RemoveHeader(name); +} + +void URLRequestNS::SetChunkedUpload(bool is_chunked_upload) { + if (request_) + is_chunked_upload_ = is_chunked_upload; +} + +mate::Dictionary URLRequestNS::GetUploadProgress() { + mate::Dictionary progress = mate::Dictionary::CreateEmpty(isolate()); + if (loader_) { + if (request_) + progress.Set("started", false); + else + progress.Set("started", true); + progress.Set("current", upload_position_); + progress.Set("total", upload_total_); + progress.Set("active", true); + } else { + progress.Set("active", false); + } + return progress; +} + +int URLRequestNS::StatusCode() const { + if (response_headers_) + return response_headers_->response_code(); + return -1; +} + +std::string URLRequestNS::StatusMessage() const { + if (response_headers_) + return response_headers_->GetStatusText(); + return ""; +} + +net::HttpResponseHeaders* URLRequestNS::RawResponseHeaders() const { + return response_headers_.get(); +} + +uint32_t URLRequestNS::ResponseHttpVersionMajor() const { + if (response_headers_) + return response_headers_->GetHttpVersion().major_value(); + return 0; +} + +uint32_t URLRequestNS::ResponseHttpVersionMinor() const { + if (response_headers_) + return response_headers_->GetHttpVersion().minor_value(); + return 0; +} + +void URLRequestNS::OnDataReceived(base::StringPiece data, + base::OnceClosure resume) { + // In case we received an unexpected event from Chromium net, don't emit any + // data event after request cancel/error/close. + if (!(request_state_ & STATE_ERROR) && !(response_state_ & STATE_ERROR)) { + v8::HandleScope handle_scope(isolate()); + v8::Local buffer; + auto maybe = node::Buffer::Copy(isolate(), data.data(), data.size()); + if (maybe.ToLocal(&buffer)) + Emit("data", buffer); + } + std::move(resume).Run(); +} + +void URLRequestNS::OnRetry(base::OnceClosure start_retry) {} + +void URLRequestNS::OnComplete(bool success) { + if (success) { + // In case we received an unexpected event from Chromium net, don't emit any + // data event after request cancel/error/close. + if (!(request_state_ & STATE_ERROR) && !(response_state_ & STATE_ERROR)) { + response_state_ |= STATE_FINISHED; + Emit("end"); + } + } else { // failed + // If response is started then emit response event, else emit request error. + // + // Error is only emitted when there is no previous failure. This is to align + // with the behavior of non-NetworkService implementation. + std::string error = net::ErrorToString(loader_->NetError()); + if (response_state_ & STATE_STARTED) { + if (!(response_state_ & STATE_FAILED)) + EmitError(EventType::kResponse, error); + } else { + if (!(request_state_ & STATE_FAILED)) + EmitError(EventType::kRequest, error); + } + } + + Close(); +} + +void URLRequestNS::OnResponseStarted( + const GURL& final_url, + const network::ResourceResponseHead& response_head) { + // Don't emit any event after request cancel. + if (request_state_ & STATE_ERROR) + return; + + response_headers_ = response_head.headers; + response_state_ |= STATE_STARTED; + Emit("response"); +} + +void URLRequestNS::OnRedirect( + const net::RedirectInfo& redirect_info, + const network::ResourceResponseHead& response_head, + std::vector* to_be_removed_headers) { + if (!loader_) + return; + + if (request_state_ & (STATE_CLOSED | STATE_CANCELED)) { + NOTREACHED(); + Cancel(); + return; + } + + switch (redirect_mode_) { + case network::mojom::RedirectMode::kError: + EmitError( + EventType::kRequest, + "Request cannot follow redirect with the current redirect mode"); + break; + case network::mojom::RedirectMode::kManual: + // When redirect mode is "manual", the user has to explicitly call the + // FollowRedirect method to continue redirecting, otherwise the request + // would be cancelled. + // + // Note that the SimpleURLLoader always calls FollowRedirect and does not + // provide a formal way for us to cancel redirection, we have to cancel + // the request to prevent the redirection. + follow_redirect_ = false; + EmitEvent(EventType::kRequest, false, "redirect", + redirect_info.status_code, redirect_info.new_method, + redirect_info.new_url, response_head.headers.get()); + if (!follow_redirect_) + Cancel(); + break; + case network::mojom::RedirectMode::kFollow: + EmitEvent(EventType::kRequest, false, "redirect", + redirect_info.status_code, redirect_info.new_method, + redirect_info.new_url, response_head.headers.get()); + break; + } +} + +void URLRequestNS::OnUploadProgress(uint64_t position, uint64_t total) { + upload_position_ = position; + upload_total_ = total; +} + +void URLRequestNS::OnWrite(MojoResult result) { + if (result != MOJO_RESULT_OK) + return; + + // Continue the pending writes. + pending_writes_.pop_front(); + if (!pending_writes_.empty()) + DoWrite(); +} + +void URLRequestNS::DoWrite() { + DCHECK(producer_); + DCHECK(!pending_writes_.empty()); + producer_->Write( + pending_writes_.front(), + mojo::StringDataPipeProducer::AsyncWritingMode:: + STRING_STAYS_VALID_UNTIL_COMPLETION, + base::BindOnce(&URLRequestNS::OnWrite, weak_factory_.GetWeakPtr())); +} + +void URLRequestNS::StartWriting() { + if (!last_chunk_written_ || size_callback_.is_null()) + return; + + size_t size = 0; + for (const auto& data : pending_writes_) + size += data.size(); + std::move(size_callback_).Run(net::OK, size); + DoWrite(); +} + +void URLRequestNS::Pin() { + if (wrapper_.IsEmpty()) { + wrapper_.Reset(isolate(), GetWrapper()); + } +} + +void URLRequestNS::Unpin() { + wrapper_.Reset(); +} + +void URLRequestNS::EmitError(EventType type, base::StringPiece message) { + if (type == EventType::kRequest) + request_state_ |= STATE_FAILED; + else + response_state_ |= STATE_FAILED; + v8::HandleScope handle_scope(isolate()); + auto error = v8::Exception::Error(mate::StringToV8(isolate(), message)); + EmitEvent(type, false, "error", error); +} + +template +void URLRequestNS::EmitEvent(EventType type, Args... args) { + const char* method = + type == EventType::kRequest ? "_emitRequestEvent" : "_emitResponseEvent"; + v8::HandleScope handle_scope(isolate()); + mate::CustomEmit(isolate(), GetWrapper(), method, args...); +} + +// static +mate::WrappableBase* URLRequestNS::New(mate::Arguments* args) { + return new URLRequestNS(args); +} + +// static +void URLRequestNS::BuildPrototype(v8::Isolate* isolate, + v8::Local prototype) { + prototype->SetClassName(mate::StringToV8(isolate, "URLRequest")); + mate::ObjectTemplateBuilder(isolate, prototype->PrototypeTemplate()) + .MakeDestroyable() + .SetMethod("write", &URLRequestNS::Write) + .SetMethod("cancel", &URLRequestNS::Cancel) + .SetMethod("setExtraHeader", &URLRequestNS::SetExtraHeader) + .SetMethod("removeExtraHeader", &URLRequestNS::RemoveExtraHeader) + .SetMethod("setChunkedUpload", &URLRequestNS::SetChunkedUpload) + .SetMethod("followRedirect", &URLRequestNS::FollowRedirect) + .SetMethod("getUploadProgress", &URLRequestNS::GetUploadProgress) + .SetProperty("notStarted", &URLRequestNS::NotStarted) + .SetProperty("finished", &URLRequestNS::Finished) + .SetProperty("statusCode", &URLRequestNS::StatusCode) + .SetProperty("statusMessage", &URLRequestNS::StatusMessage) + .SetProperty("rawResponseHeaders", &URLRequestNS::RawResponseHeaders) + .SetProperty("httpVersionMajor", &URLRequestNS::ResponseHttpVersionMajor) + .SetProperty("httpVersionMinor", &URLRequestNS::ResponseHttpVersionMinor); +} + +} // namespace api + +} // namespace electron diff --git a/shell/browser/api/atom_api_url_request_ns.h b/shell/browser/api/atom_api_url_request_ns.h new file mode 100644 index 000000000000..bd15ce536f7c --- /dev/null +++ b/shell/browser/api/atom_api_url_request_ns.h @@ -0,0 +1,143 @@ +// Copyright (c) 2019 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#ifndef SHELL_BROWSER_API_ATOM_API_URL_REQUEST_NS_H_ +#define SHELL_BROWSER_API_ATOM_API_URL_REQUEST_NS_H_ + +#include +#include +#include +#include + +#include "mojo/public/cpp/system/string_data_pipe_producer.h" +#include "native_mate/dictionary.h" +#include "services/network/public/cpp/shared_url_loader_factory.h" +#include "services/network/public/cpp/simple_url_loader.h" +#include "services/network/public/cpp/simple_url_loader_stream_consumer.h" +#include "services/network/public/mojom/data_pipe_getter.mojom.h" +#include "shell/browser/api/event_emitter.h" + +namespace electron { + +namespace api { + +class UploadDataPipeGetter; + +class URLRequestNS : public mate::EventEmitter, + public network::SimpleURLLoaderStreamConsumer { + public: + static mate::WrappableBase* New(mate::Arguments* args); + + static void BuildPrototype(v8::Isolate* isolate, + v8::Local prototype); + + protected: + explicit URLRequestNS(mate::Arguments* args); + ~URLRequestNS() override; + + bool NotStarted() const; + bool Finished() const; + + void Cancel(); + void Close(); + + bool Write(v8::Local data, bool is_last); + void FollowRedirect(); + bool SetExtraHeader(const std::string& name, const std::string& value); + void RemoveExtraHeader(const std::string& name); + void SetChunkedUpload(bool is_chunked_upload); + mate::Dictionary GetUploadProgress(); + int StatusCode() const; + std::string StatusMessage() const; + net::HttpResponseHeaders* RawResponseHeaders() const; + uint32_t ResponseHttpVersionMajor() const; + uint32_t ResponseHttpVersionMinor() const; + + // SimpleURLLoaderStreamConsumer: + void OnDataReceived(base::StringPiece string_piece, + base::OnceClosure resume) override; + void OnComplete(bool success) override; + void OnRetry(base::OnceClosure start_retry) override; + + private: + friend class UploadDataPipeGetter; + + void OnResponseStarted(const GURL& final_url, + const network::ResourceResponseHead& response_head); + void OnRedirect(const net::RedirectInfo& redirect_info, + const network::ResourceResponseHead& response_head, + std::vector* to_be_removed_headers); + void OnUploadProgress(uint64_t position, uint64_t total); + void OnWrite(MojoResult result); + + // Write the first data of |pending_writes_|. + void DoWrite(); + + // Start streaming. + void StartWriting(); + + // Manage lifetime of wrapper. + void Pin(); + void Unpin(); + + // Emit events. + enum class EventType { + kRequest, + kResponse, + }; + void EmitError(EventType type, base::StringPiece error); + template + void EmitEvent(EventType type, Args... args); + + std::unique_ptr request_; + std::unique_ptr loader_; + scoped_refptr url_loader_factory_; + scoped_refptr response_headers_; + + // Redirect mode. + // + // Note that we store it ourselves instead of reading from the one stored in + // |request_|, this is because with multiple redirections, the original one + // might be modified. + network::mojom::RedirectMode redirect_mode_ = + network::mojom::RedirectMode::kFollow; + + // The DataPipeGetter passed to reader. + bool is_chunked_upload_ = false; + std::unique_ptr data_pipe_getter_; + + // Passed from DataPipeGetter for streaming data. + network::mojom::DataPipeGetter::ReadCallback size_callback_; + std::unique_ptr producer_; + + // Whether request.end() has been called. + bool last_chunk_written_ = false; + + // Whether the redirect should be followed. + bool follow_redirect_ = true; + + // Upload progress. + uint64_t upload_position_ = 0; + uint64_t upload_total_ = 0; + + // Current status. + int request_state_ = 0; + int response_state_ = 0; + + // Pending writes that not yet sent to NetworkService. + std::list pending_writes_; + + // Used by pin/unpin to manage lifetime. + v8::Global wrapper_; + + base::WeakPtrFactory weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(URLRequestNS); +}; + +} // namespace api + +} // namespace electron + +#endif // SHELL_BROWSER_API_ATOM_API_URL_REQUEST_NS_H_