Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: implement ses.getBlobData() for NetworkService #20041

Merged
merged 4 commits into from Sep 3, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 2 additions & 2 deletions filenames.gni
Expand Up @@ -47,6 +47,8 @@ filenames = {
"shell/browser/api/atom_api_content_tracing.cc",
"shell/browser/api/atom_api_cookies.cc",
"shell/browser/api/atom_api_cookies.h",
"shell/browser/api/atom_api_data_pipe_holder.cc",
"shell/browser/api/atom_api_data_pipe_holder.h",
"shell/browser/api/atom_api_debugger.cc",
"shell/browser/api/atom_api_debugger.h",
"shell/browser/api/atom_api_dialog.cc",
Expand Down Expand Up @@ -129,8 +131,6 @@ filenames = {
"shell/browser/atom_autofill_driver_factory.h",
"shell/browser/atom_autofill_driver.cc",
"shell/browser/atom_autofill_driver.h",
"shell/browser/atom_blob_reader.cc",
"shell/browser/atom_blob_reader.h",
"shell/browser/atom_browser_client.cc",
"shell/browser/atom_browser_client.h",
"shell/browser/atom_browser_context.cc",
Expand Down
185 changes: 185 additions & 0 deletions shell/browser/api/atom_api_data_pipe_holder.cc
@@ -0,0 +1,185 @@
// 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_data_pipe_holder.h"

#include <utility>
#include <vector>

#include "base/memory/weak_ptr.h"
#include "base/strings/string_number_conversions.h"
#include "mojo/public/cpp/system/data_pipe.h"
#include "mojo/public/cpp/system/simple_watcher.h"
#include "net/base/net_errors.h"
#include "shell/common/key_weak_map.h"
#include "shell/common/promise_util.h"

#include "shell/common/node_includes.h"

namespace electron {

namespace api {

namespace {

// Incremental ID.
int g_next_id = 0;

// Map that manages all the DataPipeHolder objects.
KeyWeakMap<std::string> g_weak_map;

// Utility class to read from data pipe.
class DataPipeReader {
public:
DataPipeReader(util::Promise<v8::Local<v8::Value>> promise,
network::mojom::DataPipeGetterPtr data_pipe_getter)
: promise_(std::move(promise)),
data_pipe_getter_(std::move(data_pipe_getter)),
handle_watcher_(FROM_HERE,
mojo::SimpleWatcher::ArmingPolicy::MANUAL,
base::SequencedTaskRunnerHandle::Get()) {
// Get a new data pipe and start.
mojo::DataPipe data_pipe;
data_pipe_getter_->Read(std::move(data_pipe.producer_handle),
base::BindOnce(&DataPipeReader::ReadCallback,
weak_factory_.GetWeakPtr()));
data_pipe_ = std::move(data_pipe.consumer_handle);
handle_watcher_.Watch(data_pipe_.get(), MOJO_HANDLE_SIGNAL_READABLE,
base::BindRepeating(&DataPipeReader::OnHandleReadable,
weak_factory_.GetWeakPtr()));
}

~DataPipeReader() = default;

private:
// Callback invoked by DataPipeGetter::Read.
void ReadCallback(int32_t status, uint64_t size) {
if (status != net::OK) {
OnFailure();
return;
}
buffer_.resize(size);
head_ = &buffer_.front();
remaining_size_ = size;
handle_watcher_.ArmOrNotify();
}

// Called by |handle_watcher_| when data is available or the pipe was closed,
// and there's a pending Read() call.
void OnHandleReadable(MojoResult result) {
if (result != MOJO_RESULT_OK) {
OnFailure();
return;
}

// Read.
uint32_t length = remaining_size_;
result = data_pipe_->ReadData(head_, &length, MOJO_READ_DATA_FLAG_NONE);
if (result == MOJO_RESULT_OK) { // success
remaining_size_ -= length;
head_ += length;
if (remaining_size_ == 0)
OnSuccess();
} else if (result == MOJO_RESULT_SHOULD_WAIT) { // IO pending
handle_watcher_.ArmOrNotify();
} else { // error
OnFailure();
}
}

void OnFailure() {
promise_.RejectWithErrorMessage("Could not get blob data");
delete this;
}

void OnSuccess() {
// Pass the buffer to JS.
//
// Note that the lifetime of the native buffer belongs to us, and we will
// free memory when JS buffer gets garbage collected.
v8::Locker locker(promise_.isolate());
v8::HandleScope handle_scope(promise_.isolate());
v8::Local<v8::Value> buffer =
node::Buffer::New(promise_.isolate(), &buffer_.front(), buffer_.size(),
&DataPipeReader::FreeBuffer, this)
.ToLocalChecked();
promise_.Resolve(buffer);

deepak1556 marked this conversation as resolved.
Show resolved Hide resolved
// Destroy data pipe.
handle_watcher_.Cancel();
data_pipe_.reset();
data_pipe_getter_ = nullptr;
}

static void FreeBuffer(char* data, void* self) {
delete static_cast<DataPipeReader*>(self);
}

util::Promise<v8::Local<v8::Value>> promise_;

network::mojom::DataPipeGetterPtr data_pipe_getter_;
mojo::ScopedDataPipeConsumerHandle data_pipe_;
mojo::SimpleWatcher handle_watcher_;

// Stores read data.
std::vector<char> buffer_;

// The head of buffer.
char* head_ = nullptr;

// Remaining data to read.
uint64_t remaining_size_ = 0;

base::WeakPtrFactory<DataPipeReader> weak_factory_{this};

DISALLOW_COPY_AND_ASSIGN(DataPipeReader);
};

} // namespace

gin::WrapperInfo DataPipeHolder::kWrapperInfo = {gin::kEmbedderNativeGin};

DataPipeHolder::DataPipeHolder(const network::DataElement& element)
: id_(base::NumberToString(++g_next_id)),
data_pipe_(element.CloneDataPipeGetter()) {}

DataPipeHolder::~DataPipeHolder() = default;

v8::Local<v8::Promise> DataPipeHolder::ReadAll(v8::Isolate* isolate) {
util::Promise<v8::Local<v8::Value>> promise(isolate);
v8::Local<v8::Promise> handle = promise.GetHandle();
if (!data_pipe_) {
promise.RejectWithErrorMessage("Could not get blob data");
return handle;
}

new DataPipeReader(std::move(promise), std::move(data_pipe_));
return handle;
}

// static
gin::Handle<DataPipeHolder> DataPipeHolder::Create(
v8::Isolate* isolate,
const network::DataElement& element) {
auto handle = gin::CreateHandle(isolate, new DataPipeHolder(element));
g_weak_map.Set(isolate, handle->id(),
handle->GetWrapper(isolate).ToLocalChecked());
return handle;
}

// static
gin::Handle<DataPipeHolder> DataPipeHolder::From(v8::Isolate* isolate,
const std::string& id) {
v8::MaybeLocal<v8::Object> object = g_weak_map.Get(isolate, id);
if (!object.IsEmpty()) {
gin::Handle<DataPipeHolder> handle;
if (gin::ConvertFromV8(isolate, object.ToLocalChecked(), &handle))
return handle;
}
return gin::Handle<DataPipeHolder>();
}

} // namespace api

} // namespace electron
53 changes: 53 additions & 0 deletions shell/browser/api/atom_api_data_pipe_holder.h
@@ -0,0 +1,53 @@
// 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_DATA_PIPE_HOLDER_H_
#define SHELL_BROWSER_API_ATOM_API_DATA_PIPE_HOLDER_H_

#include <string>

#include "gin/handle.h"
#include "gin/wrappable.h"
#include "services/network/public/cpp/data_element.h"
#include "services/network/public/mojom/data_pipe_getter.mojom.h"

namespace electron {

namespace api {

// Retains reference to the data pipe.
class DataPipeHolder : public gin::Wrappable<DataPipeHolder> {
public:
static gin::WrapperInfo kWrapperInfo;

static gin::Handle<DataPipeHolder> Create(
v8::Isolate* isolate,
const network::DataElement& element);
static gin::Handle<DataPipeHolder> From(v8::Isolate* isolate,
const std::string& id);

// Read all data at once.
//
// TODO(zcbenz): This is apparently not suitable for really large data, but
// no one has complained about it yet.
v8::Local<v8::Promise> ReadAll(v8::Isolate* isolate);

// The unique ID that can be used to receive the object.
const std::string& id() const { return id_; }

private:
explicit DataPipeHolder(const network::DataElement& element);
~DataPipeHolder() override;

std::string id_;
network::mojom::DataPipeGetterPtr data_pipe_;

DISALLOW_COPY_AND_ASSIGN(DataPipeHolder);
};

} // namespace api

} // namespace electron

#endif // SHELL_BROWSER_API_ATOM_API_DATA_PIPE_HOLDER_H_
16 changes: 8 additions & 8 deletions shell/browser/api/atom_api_session.cc
Expand Up @@ -41,6 +41,7 @@
#include "services/network/network_service.h"
#include "services/network/public/cpp/features.h"
#include "shell/browser/api/atom_api_cookies.h"
#include "shell/browser/api/atom_api_data_pipe_holder.h"
#include "shell/browser/api/atom_api_download_item.h"
#include "shell/browser/api/atom_api_net_log.h"
#include "shell/browser/api/atom_api_protocol_ns.h"
Expand Down Expand Up @@ -502,15 +503,14 @@ std::string Session::GetUserAgent() {

v8::Local<v8::Promise> Session::GetBlobData(v8::Isolate* isolate,
const std::string& uuid) {
util::Promise<v8::Local<v8::Value>> promise(isolate);
v8::Local<v8::Promise> handle = promise.GetHandle();
gin::Handle<DataPipeHolder> holder = DataPipeHolder::From(isolate, uuid);
if (holder.IsEmpty()) {
util::Promise<v8::Local<v8::Value>> promise(isolate);
promise.RejectWithErrorMessage("Could not get blob data handle");
return promise.GetHandle();
}

AtomBlobReader* blob_reader = browser_context()->GetBlobReader();
base::PostTaskWithTraits(
FROM_HERE, {BrowserThread::IO},
base::BindOnce(&AtomBlobReader::StartReading,
base::Unretained(blob_reader), uuid, std::move(promise)));
return handle;
return holder->ReadAll(isolate);
}

void Session::DownloadURL(const GURL& url) {
Expand Down
1 change: 0 additions & 1 deletion shell/browser/api/atom_api_session.h
Expand Up @@ -13,7 +13,6 @@
#include "electron/buildflags/buildflags.h"
#include "native_mate/handle.h"
#include "shell/browser/api/trackable_object.h"
#include "shell/browser/atom_blob_reader.h"
#include "shell/browser/net/resolve_proxy_helper.h"
#include "shell/common/promise_util.h"

Expand Down