refactor: rewrite the net module to simplify state tracking (#21244)

This commit is contained in:
Jeremy Apthorp 2019-11-26 17:01:13 -08:00 committed by GitHub
parent 4149d76890
commit d25256dcf5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 1092 additions and 1033 deletions

View file

@ -4,9 +4,11 @@
#include "shell/browser/api/atom_api_net.h"
#include <string>
#include "gin/handle.h"
#include "services/network/public/cpp/features.h"
#include "shell/browser/api/atom_api_url_request.h"
#include "shell/browser/api/atom_api_url_loader.h"
#include "shell/common/gin_helper/dictionary.h"
#include "shell/common/gin_helper/object_template_builder.h"
@ -32,12 +34,12 @@ void Net::BuildPrototype(v8::Isolate* isolate,
v8::Local<v8::FunctionTemplate> prototype) {
prototype->SetClassName(gin::StringToV8(isolate, "Net"));
gin_helper::ObjectTemplateBuilder(isolate, prototype->PrototypeTemplate())
.SetProperty("URLRequest", &Net::URLRequest);
.SetProperty("URLLoader", &Net::URLLoader);
}
v8::Local<v8::Value> Net::URLRequest(v8::Isolate* isolate) {
v8::Local<v8::Value> Net::URLLoader(v8::Isolate* isolate) {
v8::Local<v8::FunctionTemplate> constructor;
constructor = URLRequest::GetConstructor(isolate);
constructor = SimpleURLLoaderWrapper::GetConstructor(isolate);
return constructor->GetFunction(isolate->GetCurrentContext())
.ToLocalChecked();
}
@ -48,8 +50,16 @@ v8::Local<v8::Value> Net::URLRequest(v8::Isolate* isolate) {
namespace {
bool IsValidHeaderName(std::string header_name) {
return net::HttpUtil::IsValidHeaderName(header_name);
}
bool IsValidHeaderValue(std::string header_value) {
return net::HttpUtil::IsValidHeaderValue(header_value);
}
using electron::api::Net;
using electron::api::URLRequest;
using electron::api::SimpleURLLoaderWrapper;
void Initialize(v8::Local<v8::Object> exports,
v8::Local<v8::Value> unused,
@ -57,12 +67,15 @@ void Initialize(v8::Local<v8::Object> exports,
void* priv) {
v8::Isolate* isolate = context->GetIsolate();
URLRequest::SetConstructor(isolate, base::BindRepeating(URLRequest::New));
SimpleURLLoaderWrapper::SetConstructor(
isolate, base::BindRepeating(SimpleURLLoaderWrapper::New));
gin_helper::Dictionary dict(isolate, exports);
dict.Set("net", Net::Create(isolate));
dict.Set("Net",
Net::GetConstructor(isolate)->GetFunction(context).ToLocalChecked());
dict.SetMethod("_isValidHeaderName", &IsValidHeaderName);
dict.SetMethod("_isValidHeaderValue", &IsValidHeaderValue);
}
} // namespace

View file

@ -18,7 +18,7 @@ class Net : public mate::Wrappable<Net> {
static void BuildPrototype(v8::Isolate* isolate,
v8::Local<v8::FunctionTemplate> prototype);
v8::Local<v8::Value> URLRequest(v8::Isolate* isolate);
v8::Local<v8::Value> URLLoader(v8::Isolate* isolate);
protected:
explicit Net(v8::Isolate* isolate);

View file

@ -0,0 +1,448 @@
// Copyright (c) 2019 Slack Technologies, 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_loader.h"
#include <algorithm>
#include <map>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "base/containers/id_map.h"
#include "gin/handle.h"
#include "gin/object_template_builder.h"
#include "gin/wrappable.h"
#include "mojo/public/cpp/system/data_pipe_producer.h"
#include "services/network/public/cpp/resource_request.h"
#include "services/network/public/cpp/simple_url_loader.h"
#include "services/network/public/mojom/chunked_data_pipe_getter.mojom.h"
#include "services/network/public/mojom/url_loader_factory.mojom.h"
#include "shell/browser/api/atom_api_session.h"
#include "shell/browser/atom_browser_context.h"
#include "shell/common/gin_converters/callback_converter.h"
#include "shell/common/gin_converters/gurl_converter.h"
#include "shell/common/gin_converters/net_converter.h"
#include "shell/common/gin_helper/dictionary.h"
#include "shell/common/gin_helper/object_template_builder.h"
#include "shell/common/node_includes.h"
class BufferDataSource : public mojo::DataPipeProducer::DataSource {
public:
explicit BufferDataSource(base::span<char> buffer) {
buffer_.resize(buffer.size());
memcpy(buffer_.data(), buffer.data(), buffer_.size());
}
~BufferDataSource() override = default;
private:
// mojo::DataPipeProducer::DataSource:
uint64_t GetLength() const override { return buffer_.size(); }
ReadResult Read(uint64_t offset, base::span<char> buffer) override {
ReadResult result;
if (offset <= buffer_.size()) {
size_t readable_size = buffer_.size() - offset;
size_t writable_size = buffer.size();
size_t copyable_size = std::min(readable_size, writable_size);
memcpy(buffer.data(), &buffer_[offset], copyable_size);
result.bytes_read = copyable_size;
} else {
NOTREACHED();
result.result = MOJO_RESULT_OUT_OF_RANGE;
}
return result;
}
std::vector<char> buffer_;
};
class JSChunkedDataPipeGetter : public gin::Wrappable<JSChunkedDataPipeGetter>,
public network::mojom::ChunkedDataPipeGetter {
public:
static gin::Handle<JSChunkedDataPipeGetter> Create(
v8::Isolate* isolate,
v8::Local<v8::Function> body_func,
mojo::PendingReceiver<network::mojom::ChunkedDataPipeGetter>
chunked_data_pipe_getter) {
return gin::CreateHandle(
isolate, new JSChunkedDataPipeGetter(
isolate, body_func, std::move(chunked_data_pipe_getter)));
}
// gin::Wrappable
gin::ObjectTemplateBuilder GetObjectTemplateBuilder(
v8::Isolate* isolate) override {
return gin::Wrappable<JSChunkedDataPipeGetter>::GetObjectTemplateBuilder(
isolate)
.SetMethod("write", &JSChunkedDataPipeGetter::WriteChunk)
.SetMethod("done", &JSChunkedDataPipeGetter::Done);
}
static gin::WrapperInfo kWrapperInfo;
~JSChunkedDataPipeGetter() override = default;
private:
JSChunkedDataPipeGetter(
v8::Isolate* isolate,
v8::Local<v8::Function> body_func,
mojo::PendingReceiver<network::mojom::ChunkedDataPipeGetter>
chunked_data_pipe_getter)
: isolate_(isolate), body_func_(isolate, body_func) {
receiver_.Bind(std::move(chunked_data_pipe_getter));
}
// network::mojom::ChunkedDataPipeGetter:
void GetSize(GetSizeCallback callback) override {
size_callback_ = std::move(callback);
}
void StartReading(mojo::ScopedDataPipeProducerHandle pipe) override {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (body_func_.IsEmpty()) {
LOG(ERROR) << "Tried to read twice from a JSChunkedDataPipeGetter";
// Drop the handle on the floor.
return;
}
data_producer_ = std::make_unique<mojo::DataPipeProducer>(std::move(pipe));
v8::HandleScope handle_scope(isolate_);
v8::MicrotasksScope script_scope(isolate_,
v8::MicrotasksScope::kRunMicrotasks);
auto maybe_wrapper = GetWrapper(isolate_);
v8::Local<v8::Value> wrapper;
if (!maybe_wrapper.ToLocal(&wrapper)) {
return;
}
v8::Local<v8::Value> argv[] = {wrapper};
node::Environment* env = node::Environment::GetCurrent(isolate_);
auto global = env->context()->Global();
node::MakeCallback(isolate_, global, body_func_.Get(isolate_),
node::arraysize(argv), argv, {0, 0});
}
v8::Local<v8::Promise> WriteChunk(v8::Local<v8::Value> buffer_val) {
gin_helper::Promise<void> promise(isolate_);
v8::Local<v8::Promise> handle = promise.GetHandle();
if (!buffer_val->IsArrayBufferView()) {
promise.RejectWithErrorMessage("Expected an ArrayBufferView");
return handle;
}
if (is_writing_) {
promise.RejectWithErrorMessage("Only one write can be pending at a time");
return handle;
}
if (!size_callback_) {
promise.RejectWithErrorMessage("Can't write after calling done()");
return handle;
}
auto buffer = buffer_val.As<v8::ArrayBufferView>();
is_writing_ = true;
bytes_written_ += buffer->ByteLength();
auto backing_store = buffer->Buffer()->GetBackingStore();
auto buffer_span = base::make_span(
static_cast<char*>(backing_store->Data()) + buffer->ByteOffset(),
buffer->ByteLength());
auto buffer_source = std::make_unique<BufferDataSource>(buffer_span);
data_producer_->Write(
std::move(buffer_source),
base::BindOnce(&JSChunkedDataPipeGetter::OnWriteChunkComplete,
// We're OK to use Unretained here because we own
// |data_producer_|.
base::Unretained(this), std::move(promise)));
return handle;
}
void OnWriteChunkComplete(gin_helper::Promise<void> promise,
MojoResult result) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
is_writing_ = false;
if (result == MOJO_RESULT_OK) {
promise.Resolve();
} else {
promise.RejectWithErrorMessage("mojo result not ok");
Finished();
}
}
// TODO(nornagon): accept a net error here to allow the data provider to
// cancel the request with an error.
void Done() {
if (size_callback_) {
std::move(size_callback_).Run(net::OK, bytes_written_);
Finished();
}
}
void Finished() {
size_callback_.Reset();
body_func_.Reset();
receiver_.reset();
data_producer_.reset();
}
GetSizeCallback size_callback_;
mojo::Receiver<network::mojom::ChunkedDataPipeGetter> receiver_{this};
std::unique_ptr<mojo::DataPipeProducer> data_producer_;
bool is_writing_ = false;
uint64_t bytes_written_ = 0;
v8::Isolate* isolate_;
v8::Global<v8::Function> body_func_;
};
gin::WrapperInfo JSChunkedDataPipeGetter::kWrapperInfo = {
gin::kEmbedderNativeGin};
namespace electron {
namespace api {
namespace {
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."
})");
base::IDMap<SimpleURLLoaderWrapper*>& GetAllRequests() {
static base::NoDestructor<base::IDMap<SimpleURLLoaderWrapper*>>
s_all_requests;
return *s_all_requests;
}
} // namespace
SimpleURLLoaderWrapper::SimpleURLLoaderWrapper(
std::unique_ptr<network::ResourceRequest> request,
network::mojom::URLLoaderFactory* url_loader_factory)
: id_(GetAllRequests().Add(this)) {
// We slightly abuse the |render_frame_id| field in ResourceRequest so that
// we can correlate any authentication events that arrive with this request.
request->render_frame_id = id_;
// SimpleURLLoader wants to control the request body itself. We have other
// ideas.
auto request_body = std::move(request->request_body);
auto* request_ref = request.get();
loader_ =
network::SimpleURLLoader::Create(std::move(request), kTrafficAnnotation);
if (request_body) {
request_ref->request_body = std::move(request_body);
}
loader_->SetAllowHttpErrorResults(true);
loader_->SetOnResponseStartedCallback(base::BindOnce(
&SimpleURLLoaderWrapper::OnResponseStarted, base::Unretained(this)));
loader_->SetOnRedirectCallback(base::BindRepeating(
&SimpleURLLoaderWrapper::OnRedirect, base::Unretained(this)));
loader_->SetOnUploadProgressCallback(base::BindRepeating(
&SimpleURLLoaderWrapper::OnUploadProgress, base::Unretained(this)));
loader_->SetOnDownloadProgressCallback(base::BindRepeating(
&SimpleURLLoaderWrapper::OnDownloadProgress, base::Unretained(this)));
loader_->DownloadAsStream(url_loader_factory, this);
}
void SimpleURLLoaderWrapper::Pin() {
// Prevent ourselves from being GC'd until the request is complete.
// Must be called after InitWithArgs(), otherwise GetWrapper() isn't
// initialized.
pinned_wrapper_.Reset(isolate(), GetWrapper());
}
void SimpleURLLoaderWrapper::PinBodyGetter(v8::Local<v8::Value> body_getter) {
pinned_chunk_pipe_getter_.Reset(isolate(), body_getter);
}
SimpleURLLoaderWrapper::~SimpleURLLoaderWrapper() {
GetAllRequests().Remove(id_);
}
// static
SimpleURLLoaderWrapper* SimpleURLLoaderWrapper::FromID(uint32_t id) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
return GetAllRequests().Lookup(id);
}
void SimpleURLLoaderWrapper::OnAuthRequired(
const GURL& url,
bool first_auth_attempt,
net::AuthChallengeInfo auth_info,
network::mojom::URLResponseHeadPtr head,
mojo::PendingRemote<network::mojom::AuthChallengeResponder>
auth_challenge_responder) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
mojo::Remote<network::mojom::AuthChallengeResponder> auth_responder(
std::move(auth_challenge_responder));
// WeakPtr because if we're Cancel()ed while waiting for auth, and the
// network service also decides to cancel at the same time and kill this
// pipe, we might end up trying to call Cancel again on dead memory.
auth_responder.set_disconnect_handler(base::BindOnce(
&SimpleURLLoaderWrapper::Cancel, weak_factory_.GetWeakPtr()));
auto cb = base::BindOnce(
[](mojo::Remote<network::mojom::AuthChallengeResponder> auth_responder,
gin::Arguments* args) {
base::string16 username_str, password_str;
if (!args->GetNext(&username_str) || !args->GetNext(&password_str)) {
auth_responder->OnAuthCredentials(base::nullopt);
return;
}
auth_responder->OnAuthCredentials(
net::AuthCredentials(username_str, password_str));
},
std::move(auth_responder));
Emit("login", auth_info, base::AdaptCallbackForRepeating(std::move(cb)));
}
void SimpleURLLoaderWrapper::Cancel() {
loader_.reset();
pinned_wrapper_.Reset();
pinned_chunk_pipe_getter_.Reset();
// This ensures that no further callbacks will be called, so there's no need
// for additional guards.
}
// static
mate::WrappableBase* SimpleURLLoaderWrapper::New(gin::Arguments* args) {
gin_helper::Dictionary opts;
if (!args->GetNext(&opts)) {
args->ThrowTypeError("Expected a dictionary");
return nullptr;
}
auto request = std::make_unique<network::ResourceRequest>();
opts.Get("method", &request->method);
opts.Get("url", &request->url);
std::map<std::string, std::string> extra_headers;
if (opts.Get("extraHeaders", &extra_headers)) {
for (const auto& it : extra_headers) {
if (!net::HttpUtil::IsValidHeaderName(it.first) ||
!net::HttpUtil::IsValidHeaderValue(it.second)) {
args->ThrowTypeError("Invalid header name or value");
return nullptr;
}
request->headers.SetHeader(it.first, it.second);
}
}
v8::Local<v8::Value> body;
v8::Local<v8::Value> chunk_pipe_getter;
if (opts.Get("body", &body)) {
if (body->IsArrayBufferView()) {
auto buffer_body = body.As<v8::ArrayBufferView>();
auto backing_store = buffer_body->Buffer()->GetBackingStore();
request->request_body = network::ResourceRequestBody::CreateFromBytes(
static_cast<char*>(backing_store->Data()) + buffer_body->ByteOffset(),
buffer_body->ByteLength());
} else if (body->IsFunction()) {
auto body_func = body.As<v8::Function>();
mojo::PendingRemote<network::mojom::ChunkedDataPipeGetter>
data_pipe_getter;
chunk_pipe_getter = JSChunkedDataPipeGetter::Create(
args->isolate(), body_func,
data_pipe_getter.InitWithNewPipeAndPassReceiver())
.ToV8();
request->request_body = new network::ResourceRequestBody();
request->request_body->SetToChunkedDataPipe(std::move(data_pipe_getter));
}
}
std::string partition;
gin::Handle<Session> session;
if (!opts.Get("session", &session)) {
if (opts.Get("partition", &partition))
session = Session::FromPartition(args->isolate(), partition);
else // default session
session = Session::FromPartition(args->isolate(), "");
}
auto url_loader_factory = session->browser_context()->GetURLLoaderFactory();
auto* ret =
new SimpleURLLoaderWrapper(std::move(request), url_loader_factory.get());
ret->InitWithArgs(args);
ret->Pin();
if (!chunk_pipe_getter.IsEmpty()) {
ret->PinBodyGetter(chunk_pipe_getter);
}
return ret;
}
void SimpleURLLoaderWrapper::OnDataReceived(base::StringPiece string_piece,
base::OnceClosure resume) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
v8::HandleScope handle_scope(isolate());
auto array_buffer = v8::ArrayBuffer::New(isolate(), string_piece.size());
auto backing_store = array_buffer->GetBackingStore();
memcpy(backing_store->Data(), string_piece.data(), string_piece.size());
Emit("data", array_buffer);
std::move(resume).Run();
}
void SimpleURLLoaderWrapper::OnComplete(bool success) {
if (success) {
Emit("complete");
} else {
Emit("error", net::ErrorToString(loader_->NetError()));
}
loader_.reset();
pinned_wrapper_.Reset();
pinned_chunk_pipe_getter_.Reset();
}
void SimpleURLLoaderWrapper::OnRetry(base::OnceClosure start_retry) {}
void SimpleURLLoaderWrapper::OnResponseStarted(
const GURL& final_url,
const network::mojom::URLResponseHead& response_head) {
gin::Dictionary dict = gin::Dictionary::CreateEmpty(isolate());
dict.Set("statusCode", response_head.headers->response_code());
dict.Set("statusMessage", response_head.headers->GetStatusText());
dict.Set("headers", response_head.headers.get());
dict.Set("httpVersion", response_head.headers->GetHttpVersion());
Emit("response-started", final_url, dict);
}
void SimpleURLLoaderWrapper::OnRedirect(
const net::RedirectInfo& redirect_info,
const network::mojom::URLResponseHead& response_head,
std::vector<std::string>* removed_headers) {
Emit("redirect", redirect_info, response_head.headers.get());
}
void SimpleURLLoaderWrapper::OnUploadProgress(uint64_t position,
uint64_t total) {
Emit("upload-progress", position, total);
}
void SimpleURLLoaderWrapper::OnDownloadProgress(uint64_t current) {
Emit("download-progress", current);
}
// static
void SimpleURLLoaderWrapper::BuildPrototype(
v8::Isolate* isolate,
v8::Local<v8::FunctionTemplate> prototype) {
prototype->SetClassName(gin::StringToV8(isolate, "SimpleURLLoaderWrapper"));
gin_helper::ObjectTemplateBuilder(isolate, prototype->PrototypeTemplate())
.SetMethod("cancel", &SimpleURLLoaderWrapper::Cancel);
}
} // namespace api
} // namespace electron

View file

@ -0,0 +1,93 @@
// Copyright (c) 2019 Slack Technologies, 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_LOADER_H_
#define SHELL_BROWSER_API_ATOM_API_URL_LOADER_H_
#include <memory>
#include <string>
#include <vector>
#include "base/memory/weak_ptr.h"
#include "net/base/auth.h"
#include "services/network/public/cpp/simple_url_loader_stream_consumer.h"
#include "services/network/public/mojom/network_context.mojom.h"
#include "services/network/public/mojom/url_loader_factory.mojom-forward.h"
#include "services/network/public/mojom/url_response_head.mojom.h"
#include "shell/common/gin_helper/event_emitter.h"
#include "url/gurl.h"
#include "v8/include/v8.h"
namespace gin {
class Arguments;
}
namespace network {
class SimpleURLLoader;
struct ResourceRequest;
} // namespace network
namespace electron {
namespace api {
/** Wraps a SimpleURLLoader to make it usable from JavaScript */
class SimpleURLLoaderWrapper
: public gin_helper::EventEmitter<SimpleURLLoaderWrapper>,
public network::SimpleURLLoaderStreamConsumer {
public:
~SimpleURLLoaderWrapper() override;
static mate::WrappableBase* New(gin::Arguments* args);
static void BuildPrototype(v8::Isolate* isolate,
v8::Local<v8::FunctionTemplate> prototype);
static SimpleURLLoaderWrapper* FromID(uint32_t id);
void OnAuthRequired(
const GURL& url,
bool first_auth_attempt,
net::AuthChallengeInfo auth_info,
network::mojom::URLResponseHeadPtr head,
mojo::PendingRemote<network::mojom::AuthChallengeResponder>
auth_challenge_responder);
void Cancel();
private:
SimpleURLLoaderWrapper(std::unique_ptr<network::ResourceRequest> loader,
network::mojom::URLLoaderFactory* url_loader_factory);
// SimpleURLLoaderStreamConsumer:
void OnDataReceived(base::StringPiece string_piece,
base::OnceClosure resume) override;
void OnComplete(bool success) override;
void OnRetry(base::OnceClosure start_retry) override;
// SimpleURLLoader callbacks
void OnResponseStarted(const GURL& final_url,
const network::mojom::URLResponseHead& response_head);
void OnRedirect(const net::RedirectInfo& redirect_info,
const network::mojom::URLResponseHead& response_head,
std::vector<std::string>* removed_headers);
void OnUploadProgress(uint64_t position, uint64_t total);
void OnDownloadProgress(uint64_t current);
void Start();
void Pin();
void PinBodyGetter(v8::Local<v8::Value>);
uint32_t id_;
std::unique_ptr<network::SimpleURLLoader> loader_;
v8::Global<v8::Value> pinned_wrapper_;
v8::Global<v8::Value> pinned_chunk_pipe_getter_;
base::WeakPtrFactory<SimpleURLLoaderWrapper> weak_factory_{this};
};
} // namespace api
} // namespace electron
#endif // SHELL_BROWSER_API_ATOM_API_URL_LOADER_H_

View file

@ -1,597 +0,0 @@
// 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.h"
#include <utility>
#include "base/containers/id_map.h"
#include "gin/handle.h"
#include "mojo/public/cpp/bindings/receiver_set.h"
#include "mojo/public/cpp/system/string_data_source.h"
#include "net/http/http_util.h"
#include "services/network/public/mojom/chunked_data_pipe_getter.mojom.h"
#include "shell/browser/api/atom_api_session.h"
#include "shell/browser/atom_browser_context.h"
#include "shell/common/gin_converters/callback_converter.h"
#include "shell/common/gin_converters/gurl_converter.h"
#include "shell/common/gin_converters/net_converter.h"
#include "shell/common/gin_helper/dictionary.h"
#include "shell/common/gin_helper/event_emitter_caller.h"
#include "shell/common/gin_helper/object_template_builder.h"
#include "shell/common/node_includes.h"
namespace gin {
template <>
struct Converter<network::mojom::RedirectMode> {
static bool FromV8(v8::Isolate* isolate,
v8::Local<v8::Value> 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 gin
namespace electron {
namespace api {
namespace {
base::IDMap<URLRequest*>& GetAllRequests() {
static base::NoDestructor<base::IDMap<URLRequest*>> s_all_requests;
return *s_all_requests;
}
// 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(URLRequest* 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<mojo::DataPipeProducer>(std::move(pipe));
request_->StartWriting();
}
private:
URLRequest* request_;
DISALLOW_COPY_AND_ASSIGN(UploadDataPipeGetter);
};
// Streaming multipart data to NetworkService.
class MultipartDataPipeGetter : public UploadDataPipeGetter,
public network::mojom::DataPipeGetter {
public:
explicit MultipartDataPipeGetter(URLRequest* request)
: UploadDataPipeGetter(request) {}
~MultipartDataPipeGetter() override = default;
void AttachToRequestBody(network::ResourceRequestBody* body) override {
mojo::PendingRemote<network::mojom::DataPipeGetter> data_pipe_getter_remote;
receivers_.Add(this,
data_pipe_getter_remote.InitWithNewPipeAndPassReceiver());
body->AppendDataPipe(std::move(data_pipe_getter_remote));
}
private:
// network::mojom::DataPipeGetter:
void Read(mojo::ScopedDataPipeProducerHandle pipe,
ReadCallback callback) override {
SetCallback(std::move(callback));
SetPipe(std::move(pipe));
}
void Clone(
mojo::PendingReceiver<network::mojom::DataPipeGetter> receiver) override {
receivers_.Add(this, std::move(receiver));
}
mojo::ReceiverSet<network::mojom::DataPipeGetter> receivers_;
};
// Streaming chunked data to NetworkService.
class ChunkedDataPipeGetter : public UploadDataPipeGetter,
public network::mojom::ChunkedDataPipeGetter {
public:
explicit ChunkedDataPipeGetter(URLRequest* request)
: UploadDataPipeGetter(request) {}
~ChunkedDataPipeGetter() override = default;
void AttachToRequestBody(network::ResourceRequestBody* body) override {
mojo::PendingRemote<network::mojom::ChunkedDataPipeGetter>
data_pipe_getter_remote;
receiver_set_.Add(this,
data_pipe_getter_remote.InitWithNewPipeAndPassReceiver());
body->SetToChunkedDataPipe(std::move(data_pipe_getter_remote));
}
private:
// network::mojom::ChunkedDataPipeGetter:
void GetSize(GetSizeCallback callback) override {
SetCallback(std::move(callback));
}
void StartReading(mojo::ScopedDataPipeProducerHandle pipe) override {
SetPipe(std::move(pipe));
}
mojo::ReceiverSet<network::mojom::ChunkedDataPipeGetter> receiver_set_;
};
URLRequest::URLRequest(gin::Arguments* args)
: id_(GetAllRequests().Add(this)), weak_factory_(this) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
request_ = std::make_unique<network::ResourceRequest>();
gin_helper::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_;
}
request_->render_frame_id = id_;
std::string partition;
gin::Handle<api::Session> 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(), "");
}
url_loader_factory_ = session->browser_context()->GetURLLoaderFactory();
InitWithArgs(args);
}
URLRequest::~URLRequest() = default;
URLRequest* URLRequest::FromID(uint32_t id) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
return GetAllRequests().Lookup(id);
}
void URLRequest::OnAuthRequired(
const GURL& url,
bool first_auth_attempt,
net::AuthChallengeInfo auth_info,
network::mojom::URLResponseHeadPtr head,
mojo::PendingRemote<network::mojom::AuthChallengeResponder>
auth_challenge_responder) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
mojo::Remote<network::mojom::AuthChallengeResponder> auth_responder(
std::move(auth_challenge_responder));
auth_responder.set_disconnect_handler(
base::BindOnce(&URLRequest::Cancel, weak_factory_.GetWeakPtr()));
auto cb = base::BindOnce(
[](mojo::Remote<network::mojom::AuthChallengeResponder> auth_responder,
gin::Arguments* args) {
base::string16 username_str, password_str;
if (!args->GetNext(&username_str) || !args->GetNext(&password_str)) {
auth_responder->OnAuthCredentials(base::nullopt);
return;
}
auth_responder->OnAuthCredentials(
net::AuthCredentials(username_str, password_str));
},
std::move(auth_responder));
Emit("login", auth_info, base::AdaptCallbackForRepeating(std::move(cb)));
}
bool URLRequest::NotStarted() const {
return request_state_ == 0;
}
bool URLRequest::Finished() const {
return request_state_ & STATE_FINISHED;
}
void URLRequest::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 URLRequest::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 URLRequest::Write(v8::Local<v8::Value> 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_->SetAllowHttpErrorResults(true);
loader_->SetOnResponseStartedCallback(
base::Bind(&URLRequest::OnResponseStarted, weak_factory_.GetWeakPtr()));
loader_->SetOnRedirectCallback(
base::Bind(&URLRequest::OnRedirect, weak_factory_.GetWeakPtr()));
loader_->SetOnUploadProgressCallback(
base::Bind(&URLRequest::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<ChunkedDataPipeGetter>(this);
else
data_pipe_getter_ = std::make_unique<MultipartDataPipeGetter>(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 URLRequest::FollowRedirect() {
if (request_state_ & (STATE_CANCELED | STATE_CLOSED))
return;
follow_redirect_ = true;
}
bool URLRequest::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 URLRequest::RemoveExtraHeader(const std::string& name) {
if (request_)
request_->headers.RemoveHeader(name);
}
void URLRequest::SetChunkedUpload(bool is_chunked_upload) {
if (request_)
is_chunked_upload_ = is_chunked_upload;
}
gin::Dictionary URLRequest::GetUploadProgress() {
gin::Dictionary progress = gin::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 URLRequest::StatusCode() const {
if (response_headers_)
return response_headers_->response_code();
return -1;
}
std::string URLRequest::StatusMessage() const {
if (response_headers_)
return response_headers_->GetStatusText();
return "";
}
net::HttpResponseHeaders* URLRequest::RawResponseHeaders() const {
return response_headers_.get();
}
uint32_t URLRequest::ResponseHttpVersionMajor() const {
if (response_headers_)
return response_headers_->GetHttpVersion().major_value();
return 0;
}
uint32_t URLRequest::ResponseHttpVersionMinor() const {
if (response_headers_)
return response_headers_->GetHttpVersion().minor_value();
return 0;
}
void URLRequest::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<v8::Value> buffer;
auto maybe = node::Buffer::Copy(isolate(), data.data(), data.size());
if (maybe.ToLocal(&buffer))
Emit("data", buffer);
}
std::move(resume).Run();
}
void URLRequest::OnRetry(base::OnceClosure start_retry) {}
void URLRequest::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 URLRequest::OnResponseStarted(
const GURL& final_url,
const network::mojom::URLResponseHead& 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 URLRequest::OnRedirect(
const net::RedirectInfo& redirect_info,
const network::mojom::URLResponseHead& response_head,
std::vector<std::string>* 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:
Cancel();
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 URLRequest::OnUploadProgress(uint64_t position, uint64_t total) {
upload_position_ = position;
upload_total_ = total;
}
void URLRequest::OnWrite(MojoResult result) {
if (result != MOJO_RESULT_OK)
return;
// Continue the pending writes.
pending_writes_.pop_front();
if (!pending_writes_.empty())
DoWrite();
}
void URLRequest::DoWrite() {
DCHECK(producer_);
DCHECK(!pending_writes_.empty());
producer_->Write(
std::make_unique<mojo::StringDataSource>(
pending_writes_.front(), mojo::StringDataSource::AsyncWritingMode::
STRING_STAYS_VALID_UNTIL_COMPLETION),
base::BindOnce(&URLRequest::OnWrite, weak_factory_.GetWeakPtr()));
}
void URLRequest::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 URLRequest::Pin() {
if (wrapper_.IsEmpty()) {
wrapper_.Reset(isolate(), GetWrapper());
}
}
void URLRequest::Unpin() {
wrapper_.Reset();
}
void URLRequest::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(gin::StringToV8(isolate(), message));
EmitEvent(type, false, "error", error);
}
template <typename... Args>
void URLRequest::EmitEvent(EventType type, Args&&... args) {
const char* method =
type == EventType::kRequest ? "_emitRequestEvent" : "_emitResponseEvent";
v8::HandleScope handle_scope(isolate());
gin_helper::CustomEmit(isolate(), GetWrapper(), method,
std::forward<Args>(args)...);
}
// static
mate::WrappableBase* URLRequest::New(gin::Arguments* args) {
return new URLRequest(args);
}
// static
void URLRequest::BuildPrototype(v8::Isolate* isolate,
v8::Local<v8::FunctionTemplate> prototype) {
prototype->SetClassName(gin::StringToV8(isolate, "URLRequest"));
gin_helper::Destroyable::MakeDestroyable(isolate, prototype);
gin_helper::ObjectTemplateBuilder(isolate, prototype->PrototypeTemplate())
.SetMethod("write", &URLRequest::Write)
.SetMethod("cancel", &URLRequest::Cancel)
.SetMethod("setExtraHeader", &URLRequest::SetExtraHeader)
.SetMethod("removeExtraHeader", &URLRequest::RemoveExtraHeader)
.SetMethod("setChunkedUpload", &URLRequest::SetChunkedUpload)
.SetMethod("followRedirect", &URLRequest::FollowRedirect)
.SetMethod("getUploadProgress", &URLRequest::GetUploadProgress)
.SetProperty("notStarted", &URLRequest::NotStarted)
.SetProperty("finished", &URLRequest::Finished)
.SetProperty("statusCode", &URLRequest::StatusCode)
.SetProperty("statusMessage", &URLRequest::StatusMessage)
.SetProperty("rawResponseHeaders", &URLRequest::RawResponseHeaders)
.SetProperty("httpVersionMajor", &URLRequest::ResponseHttpVersionMajor)
.SetProperty("httpVersionMinor", &URLRequest::ResponseHttpVersionMinor);
}
} // namespace api
} // namespace electron

View file

@ -1,156 +0,0 @@
// 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_H_
#define SHELL_BROWSER_API_ATOM_API_URL_REQUEST_H_
#include <list>
#include <memory>
#include <string>
#include <vector>
#include "gin/arguments.h"
#include "gin/dictionary.h"
#include "mojo/public/cpp/system/data_pipe_producer.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 "services/network/public/mojom/network_context.mojom.h"
#include "shell/common/gin_helper/event_emitter.h"
namespace electron {
namespace api {
class UploadDataPipeGetter;
class URLRequest : public gin_helper::EventEmitter<URLRequest>,
public network::SimpleURLLoaderStreamConsumer {
public:
static mate::WrappableBase* New(gin::Arguments* args);
static void BuildPrototype(v8::Isolate* isolate,
v8::Local<v8::FunctionTemplate> prototype);
static URLRequest* FromID(uint32_t id);
void OnAuthRequired(
const GURL& url,
bool first_auth_attempt,
net::AuthChallengeInfo auth_info,
network::mojom::URLResponseHeadPtr head,
mojo::PendingRemote<network::mojom::AuthChallengeResponder>
auth_challenge_responder);
protected:
explicit URLRequest(gin::Arguments* args);
~URLRequest() override;
bool NotStarted() const;
bool Finished() const;
void Cancel();
void Close();
bool Write(v8::Local<v8::Value> 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);
gin::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::mojom::URLResponseHead& response_head);
void OnRedirect(const net::RedirectInfo& redirect_info,
const network::mojom::URLResponseHead& response_head,
std::vector<std::string>* 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 <typename... Args>
void EmitEvent(EventType type, Args&&... args);
std::unique_ptr<network::ResourceRequest> request_;
std::unique_ptr<network::SimpleURLLoader> loader_;
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory_;
scoped_refptr<net::HttpResponseHeaders> 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<UploadDataPipeGetter> data_pipe_getter_;
// Passed from DataPipeGetter for streaming data.
network::mojom::DataPipeGetter::ReadCallback size_callback_;
std::unique_ptr<mojo::DataPipeProducer> 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<std::string> pending_writes_;
// Used by pin/unpin to manage lifetime.
v8::Global<v8::Object> wrapper_;
uint32_t id_;
base::WeakPtrFactory<URLRequest> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(URLRequest);
};
} // namespace api
} // namespace electron
#endif // SHELL_BROWSER_API_ATOM_API_URL_REQUEST_H_

View file

@ -33,7 +33,7 @@
#include "services/network/public/cpp/features.h"
#include "services/network/public/cpp/wrapper_shared_url_loader_factory.h"
#include "services/network/public/mojom/network_context.mojom.h"
#include "shell/browser/api/atom_api_url_request.h"
#include "shell/browser/api/atom_api_url_loader.h"
#include "shell/browser/atom_browser_client.h"
#include "shell/browser/atom_browser_main_parts.h"
#include "shell/browser/atom_download_manager_delegate.h"
@ -362,11 +362,12 @@ class AuthResponder : public network::mojom::TrustedAuthClient {
::network::mojom::URLResponseHeadPtr head,
mojo::PendingRemote<network::mojom::AuthChallengeResponder>
auth_challenge_responder) override {
api::URLRequest* url_request = api::URLRequest::FromID(routing_id);
if (url_request) {
url_request->OnAuthRequired(url, first_auth_attempt, auth_info,
std::move(head),
std::move(auth_challenge_responder));
api::SimpleURLLoaderWrapper* url_loader =
api::SimpleURLLoaderWrapper::FromID(routing_id);
if (url_loader) {
url_loader->OnAuthRequired(url, first_auth_attempt, auth_info,
std::move(head),
std::move(auth_challenge_responder));
}
}
};

View file

@ -17,6 +17,8 @@
#include "net/cert/x509_certificate.h"
#include "net/cert/x509_util.h"
#include "net/http/http_response_headers.h"
#include "net/http/http_version.h"
#include "net/url_request/redirect_info.h"
#include "services/network/public/cpp/resource_request.h"
#include "shell/browser/api/atom_api_data_pipe_holder.h"
#include "shell/common/gin_converters/gurl_converter.h"
@ -337,7 +339,7 @@ bool Converter<scoped_refptr<network::ResourceRequestBody>>::FromV8(
v8::Local<v8::Value> Converter<network::ResourceRequest>::ToV8(
v8::Isolate* isolate,
const network::ResourceRequest& val) {
gin::Dictionary dict(isolate, v8::Object::New(isolate));
gin::Dictionary dict = gin::Dictionary::CreateEmpty(isolate);
dict.Set("method", val.method);
dict.Set("url", val.url.spec());
dict.Set("referrer", val.referrer.spec());
@ -359,4 +361,32 @@ v8::Local<v8::Value> Converter<electron::VerifyRequestParams>::ToV8(
return ConvertToV8(isolate, dict);
}
// static
v8::Local<v8::Value> Converter<net::HttpVersion>::ToV8(
v8::Isolate* isolate,
const net::HttpVersion& val) {
gin::Dictionary dict = gin::Dictionary::CreateEmpty(isolate);
dict.Set("major", static_cast<uint32_t>(val.major_value()));
dict.Set("minor", static_cast<uint32_t>(val.minor_value()));
return ConvertToV8(isolate, dict);
}
// static
v8::Local<v8::Value> Converter<net::RedirectInfo>::ToV8(
v8::Isolate* isolate,
const net::RedirectInfo& val) {
gin::Dictionary dict = gin::Dictionary::CreateEmpty(isolate);
dict.Set("statusCode", val.status_code);
dict.Set("newMethod", val.new_method);
dict.Set("newUrl", val.new_url);
dict.Set("newSiteForCookies", val.new_site_for_cookies);
dict.Set("newReferrer", val.new_referrer);
dict.Set("insecureSchemeWasUpgraded", val.insecure_scheme_was_upgraded);
dict.Set("isSignedExchangeFallbackRedirect",
val.is_signed_exchange_fallback_redirect);
return ConvertToV8(isolate, dict);
}
} // namespace gin

View file

@ -5,7 +5,10 @@
#ifndef SHELL_COMMON_GIN_CONVERTERS_NET_CONVERTER_H_
#define SHELL_COMMON_GIN_CONVERTERS_NET_CONVERTER_H_
#include <string>
#include "gin/converter.h"
#include "services/network/public/mojom/fetch_api.mojom.h"
#include "shell/browser/net/cert_verifier_client.h"
namespace base {
@ -19,6 +22,7 @@ class URLRequest;
class X509Certificate;
class HttpResponseHeaders;
struct CertPrincipal;
class HttpVersion;
} // namespace net
namespace network {
@ -96,6 +100,18 @@ struct Converter<electron::VerifyRequestParams> {
electron::VerifyRequestParams val);
};
template <>
struct Converter<net::HttpVersion> {
static v8::Local<v8::Value> ToV8(v8::Isolate* isolate,
const net::HttpVersion& val);
};
template <>
struct Converter<net::RedirectInfo> {
static v8::Local<v8::Value> ToV8(v8::Isolate* isolate,
const net::RedirectInfo& val);
};
} // namespace gin
#endif // SHELL_COMMON_GIN_CONVERTERS_NET_CONVERTER_H_

View file

@ -67,7 +67,7 @@ void InvokeIpcCallback(v8::Local<v8::Context> context,
.ToLocalChecked();
auto callback_value = ipcNative->Get(context, callback_key).ToLocalChecked();
DCHECK(callback_value->IsFunction()); // set by init.ts
auto callback = v8::Local<v8::Function>::Cast(callback_value);
auto callback = callback_value.As<v8::Function>();
ignore_result(callback->Call(context, ipcNative, args.size(), args.data()));
}