diff --git a/atom/browser/api/atom_api_session.cc b/atom/browser/api/atom_api_session.cc index 1bd944a3c03d..45405099ddef 100644 --- a/atom/browser/api/atom_api_session.cc +++ b/atom/browser/api/atom_api_session.cc @@ -504,6 +504,21 @@ std::string Session::GetUserAgent() { return browser_context_->GetUserAgent(); } +void Session::GetBlobData( + const std::string& uuid, + const AtomBlobReader::CompletionCallback& callback) { + if (callback.is_null()) + return; + + AtomBlobReader* blob_reader = + browser_context()->GetBlobReader(); + BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, + base::Bind(&AtomBlobReader::StartReading, + base::Unretained(blob_reader), + uuid, + callback)); +} + v8::Local Session::Cookies(v8::Isolate* isolate) { if (cookies_.IsEmpty()) { auto handle = atom::api::Cookies::Create(isolate, browser_context()); @@ -586,6 +601,7 @@ void Session::BuildPrototype(v8::Isolate* isolate, &Session::AllowNTLMCredentialsForDomains) .SetMethod("setUserAgent", &Session::SetUserAgent) .SetMethod("getUserAgent", &Session::GetUserAgent) + .SetMethod("getBlobData", &Session::GetBlobData) .SetProperty("cookies", &Session::Cookies) .SetProperty("protocol", &Session::Protocol) .SetProperty("webRequest", &Session::WebRequest); diff --git a/atom/browser/api/atom_api_session.h b/atom/browser/api/atom_api_session.h index 18189cf7e5c2..8fbdb75fd6ff 100644 --- a/atom/browser/api/atom_api_session.h +++ b/atom/browser/api/atom_api_session.h @@ -8,6 +8,7 @@ #include #include "atom/browser/api/trackable_object.h" +#include "atom/browser/atom_blob_reader.h" #include "base/values.h" #include "content/public/browser/download_manager.h" #include "native_mate/handle.h" @@ -76,6 +77,8 @@ class Session: public mate::TrackableObject, void AllowNTLMCredentialsForDomains(const std::string& domains); void SetUserAgent(const std::string& user_agent, mate::Arguments* args); std::string GetUserAgent(); + void GetBlobData(const std::string& uuid, + const AtomBlobReader::CompletionCallback& callback); v8::Local Cookies(v8::Isolate* isolate); v8::Local Protocol(v8::Isolate* isolate); v8::Local WebRequest(v8::Isolate* isolate); diff --git a/atom/browser/atom_blob_reader.cc b/atom/browser/atom_blob_reader.cc new file mode 100644 index 000000000000..360024a0743f --- /dev/null +++ b/atom/browser/atom_blob_reader.cc @@ -0,0 +1,133 @@ +// Copyright (c) 2016 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#include "atom/browser/atom_blob_reader.h" + +#include "content/browser/blob_storage/chrome_blob_storage_context.h" +#include "content/public/browser/browser_thread.h" +#include "net/base/io_buffer.h" +#include "net/base/net_errors.h" +#include "storage/browser/blob/blob_data_handle.h" +#include "storage/browser/blob/blob_reader.h" +#include "storage/browser/blob/blob_storage_context.h" +#include "storage/browser/fileapi/file_system_context.h" + +#include "atom/common/node_includes.h" + +using content::BrowserThread; + +namespace atom { + +namespace { + +void RunCallbackInUI( + const AtomBlobReader::CompletionCallback& callback, + char* blob_data, + int size) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + + v8::Isolate* isolate = v8::Isolate::GetCurrent(); + v8::Locker locker(isolate); + v8::HandleScope handle_scope(isolate); + if (blob_data) { + v8::Local buffer = node::Buffer::New(isolate, + blob_data, static_cast(size)).ToLocalChecked(); + callback.Run(buffer); + } else { + callback.Run(v8::Null(isolate)); + } +} + +} // namespace + +AtomBlobReader::AtomBlobReader( + content::ChromeBlobStorageContext* blob_context, + storage::FileSystemContext* file_system_context) + : blob_context_(blob_context), + file_system_context_(file_system_context) { +} + +AtomBlobReader::~AtomBlobReader() { +} + +void AtomBlobReader::StartReading( + const std::string& uuid, + const AtomBlobReader::CompletionCallback& completion_callback) { + DCHECK_CURRENTLY_ON(BrowserThread::IO); + + auto blob_data_handle = + blob_context_->context()->GetBlobDataFromUUID(uuid); + auto callback = base::Bind(&RunCallbackInUI, + completion_callback); + if (!blob_data_handle) { + BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, + base::Bind(callback, nullptr, 0)); + return; + } + + auto blob_reader = blob_data_handle->CreateReader( + file_system_context_.get(), + BrowserThread::GetMessageLoopProxyForThread(BrowserThread::FILE).get()); + BlobReadHelper* blob_read_helper = + new BlobReadHelper(std::move(blob_reader), callback); + blob_read_helper->Read(); +} + +AtomBlobReader::BlobReadHelper::BlobReadHelper( + std::unique_ptr blob_reader, + const BlobReadHelper::CompletionCallback& callback) + : blob_reader_(std::move(blob_reader)), + completion_callback_(callback) { +} + +AtomBlobReader::BlobReadHelper::~BlobReadHelper() { +} + +void AtomBlobReader::BlobReadHelper::Read() { + DCHECK_CURRENTLY_ON(BrowserThread::IO); + + storage::BlobReader::Status size_status = blob_reader_->CalculateSize( + base::Bind(&AtomBlobReader::BlobReadHelper::DidCalculateSize, + base::Unretained(this))); + if (size_status != storage::BlobReader::Status::IO_PENDING) + DidCalculateSize(net::OK); +} + +void AtomBlobReader::BlobReadHelper::DidCalculateSize(int result) { + DCHECK_CURRENTLY_ON(BrowserThread::IO); + + if (result != net::OK) { + DidReadBlobData(nullptr, 0); + return; + } + + uint64_t total_size = blob_reader_->total_size(); + int bytes_read = 0; + scoped_refptr blob_data = + new net::IOBuffer(static_cast(total_size)); + auto callback = base::Bind(&AtomBlobReader::BlobReadHelper::DidReadBlobData, + base::Unretained(this), + base::RetainedRef(blob_data)); + storage::BlobReader::Status read_status = blob_reader_->Read( + blob_data.get(), + total_size, + &bytes_read, + callback); + if (read_status != storage::BlobReader::Status::IO_PENDING) + callback.Run(bytes_read); +} + +void AtomBlobReader::BlobReadHelper::DidReadBlobData( + const scoped_refptr& blob_data, + int size) { + DCHECK_CURRENTLY_ON(BrowserThread::IO); + + char* data = new char[size]; + memcpy(data, blob_data->data(), size); + BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, + base::Bind(completion_callback_, data, size)); + delete this; +} + +} // namespace atom diff --git a/atom/browser/atom_blob_reader.h b/atom/browser/atom_blob_reader.h new file mode 100644 index 000000000000..41d9cf9194df --- /dev/null +++ b/atom/browser/atom_blob_reader.h @@ -0,0 +1,80 @@ +// Copyright (c) 2016 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#ifndef ATOM_BROWSER_ATOM_BLOB_READER_H_ +#define ATOM_BROWSER_ATOM_BLOB_READER_H_ + +#include + +#include "base/callback.h" + +namespace content { +class ChromeBlobStorageContext; +} + +namespace net { +class IOBuffer; +} + +namespace storage { +class BlobDataHandle; +class BlobReader; +class FileSystemContext; +} + +namespace v8 { +template +class Local; +class Value; +} + +namespace atom { + +// A class to keep track of the blob context. All methods, +// except Ctor are expected to be called on IO thread. +class AtomBlobReader { + public: + using CompletionCallback = base::Callback)>; + + AtomBlobReader(content::ChromeBlobStorageContext* blob_context, + storage::FileSystemContext* file_system_context); + ~AtomBlobReader(); + + void StartReading( + const std::string& uuid, + const AtomBlobReader::CompletionCallback& callback); + + private: + // A self-destroyed helper class to read the blob data. + // Must be accessed on IO thread. + class BlobReadHelper { + public: + using CompletionCallback = base::Callback; + + BlobReadHelper(std::unique_ptr blob_reader, + const BlobReadHelper::CompletionCallback& callback); + ~BlobReadHelper(); + + void Read(); + + private: + void DidCalculateSize(int result); + void DidReadBlobData(const scoped_refptr& blob_data, + int bytes_read); + + std::unique_ptr blob_reader_; + BlobReadHelper::CompletionCallback completion_callback_; + + DISALLOW_COPY_AND_ASSIGN(BlobReadHelper); + }; + + scoped_refptr blob_context_; + scoped_refptr file_system_context_; + + DISALLOW_COPY_AND_ASSIGN(AtomBlobReader); +}; + +} // namespace atom + +#endif // ATOM_BROWSER_ATOM_BLOB_READER_H_ diff --git a/atom/browser/atom_browser_context.cc b/atom/browser/atom_browser_context.cc index c3945e48adce..0b8146995d08 100644 --- a/atom/browser/atom_browser_context.cc +++ b/atom/browser/atom_browser_context.cc @@ -5,6 +5,7 @@ #include "atom/browser/atom_browser_context.h" #include "atom/browser/api/atom_api_protocol.h" +#include "atom/browser/atom_blob_reader.h" #include "atom/browser/atom_browser_main_parts.h" #include "atom/browser/atom_download_manager_delegate.h" #include "atom/browser/atom_permission_manager.h" @@ -30,7 +31,9 @@ #include "chrome/common/chrome_paths.h" #include "chrome/common/pref_names.h" #include "components/prefs/pref_registry_simple.h" +#include "content/browser/blob_storage/chrome_blob_storage_context.h" #include "content/public/browser/browser_thread.h" +#include "content/public/browser/storage_partition.h" #include "content/public/common/url_constants.h" #include "content/public/common/user_agent.h" #include "net/ftp/ftp_network_layer.h" @@ -207,6 +210,19 @@ void AtomBrowserContext::RegisterPrefs(PrefRegistrySimple* pref_registry) { pref_registry->RegisterDictionaryPref(prefs::kDevToolsFileSystemPaths); } +AtomBlobReader* AtomBrowserContext::GetBlobReader() { + if (!blob_reader_.get()) { + content::ChromeBlobStorageContext* blob_context = + content::ChromeBlobStorageContext::GetFor(this); + storage::FileSystemContext* file_system_context = + content::BrowserContext::GetStoragePartition( + this, nullptr)->GetFileSystemContext(); + blob_reader_.reset(new AtomBlobReader(blob_context, + file_system_context)); + } + return blob_reader_.get(); +} + // static scoped_refptr AtomBrowserContext::From( const std::string& partition, bool in_memory, diff --git a/atom/browser/atom_browser_context.h b/atom/browser/atom_browser_context.h index fa3186396ca0..23d8c64b62c9 100644 --- a/atom/browser/atom_browser_context.h +++ b/atom/browser/atom_browser_context.h @@ -12,6 +12,7 @@ namespace atom { +class AtomBlobReader; class AtomDownloadManagerDelegate; class AtomNetworkDelegate; class AtomPermissionManager; @@ -47,6 +48,7 @@ class AtomBrowserContext : public brightray::BrowserContext { // brightray::BrowserContext: void RegisterPrefs(PrefRegistrySimple* pref_registry) override; + AtomBlobReader* GetBlobReader(); AtomNetworkDelegate* network_delegate() const { return network_delegate_; } protected: @@ -58,6 +60,7 @@ class AtomBrowserContext : public brightray::BrowserContext { std::unique_ptr download_manager_delegate_; std::unique_ptr guest_manager_; std::unique_ptr permission_manager_; + std::unique_ptr blob_reader_; std::string user_agent_; bool use_cache_; diff --git a/atom/common/native_mate_converters/net_converter.cc b/atom/common/native_mate_converters/net_converter.cc index 3106eb0a78d7..d74356e956f0 100644 --- a/atom/common/native_mate_converters/net_converter.cc +++ b/atom/common/native_mate_converters/net_converter.cc @@ -19,6 +19,7 @@ #include "net/cert/x509_certificate.h" #include "net/http/http_response_headers.h" #include "net/url_request/url_request.h" +#include "storage/browser/blob/upload_blob_element_reader.h" #include "atom/common/node_includes.h" @@ -96,6 +97,10 @@ void GetUploadData(base::ListValue* upload_data_list, reader->AsFileReader(); auto file_path = file_reader->path().AsUTF8Unsafe(); upload_data_dict->SetStringWithoutPathExpansion("file", file_path); + } else { + const storage::UploadBlobElementReader* blob_reader = + static_cast(reader.get()); + upload_data_dict->SetString("blobUUID", blob_reader->uuid()); } upload_data_list->Append(std::move(upload_data_dict)); } diff --git a/docs/api/protocol.md b/docs/api/protocol.md index 853cb6aba032..5864cf173fbe 100644 --- a/docs/api/protocol.md +++ b/docs/api/protocol.md @@ -93,6 +93,8 @@ The `uploadData` is an array of `data` objects: * `data` Object * `bytes` Buffer - Content being sent. * `file` String - Path of file being uploaded. + * `blobUUID` String - UUID of blob data. Use [ses.getBlobData](session.md#sesgetblobdataidentifier-callback) method + to retrieve the data. To handle the `request`, the `callback` should be called with either the file's path or an object that has a `path` property, e.g. `callback(filePath)` or diff --git a/docs/api/session.md b/docs/api/session.md index ee509e1b8293..22b07afa497f 100644 --- a/docs/api/session.md +++ b/docs/api/session.md @@ -326,6 +326,14 @@ This doesn't affect existing `WebContents`, and each `WebContents` can use Returns a `String` representing the user agent for this session. +#### `ses.getBlobData(identifier, callback)` + +* `identifier` String - Valid UUID. +* `callback` Function + * `result` Buffer - Blob data. + +Returns the blob data associated with the `identifier`. + ### Instance Properties The following properties are available on instances of `Session`: @@ -513,6 +521,8 @@ The `uploadData` is an array of `data` objects: * `data` Object * `bytes` Buffer - Content being sent. * `file` String - Path of file being uploaded. + * `blobUUID` String - UUID of blob data. Use [ses.getBlobData](session.md#sesgetblobdataidentifier-callback) method + to retrieve the data. The `callback` has to be called with an `response` object: diff --git a/filenames.gypi b/filenames.gypi index 9e2058249e8b..1f4c9e440f2c 100644 --- a/filenames.gypi +++ b/filenames.gypi @@ -152,6 +152,8 @@ 'atom/browser/auto_updater_mac.mm', 'atom/browser/atom_access_token_store.cc', 'atom/browser/atom_access_token_store.h', + 'atom/browser/atom_blob_reader.cc', + 'atom/browser/atom_blob_reader.h', 'atom/browser/atom_browser_client.cc', 'atom/browser/atom_browser_client.h', 'atom/browser/atom_browser_context.cc', diff --git a/spec/api-session-spec.js b/spec/api-session-spec.js index 1caa08d1219e..2eb696ea7e8c 100644 --- a/spec/api-session-spec.js +++ b/spec/api-session-spec.js @@ -402,4 +402,55 @@ describe('session module', function () { }) }) }) + + describe('ses.getblobData(identifier, callback)', function () { + it('returns blob data for uuid', function (done) { + const scheme = 'temp' + const protocol = session.defaultSession.protocol + const url = scheme + '://host' + before(function () { + if (w != null) w.destroy() + w = new BrowserWindow({show: false}) + }) + + after(function (done) { + protocol.unregisterProtocol(scheme, () => { + closeWindow(w).then(() => { + w = null + done() + }) + }) + }) + + const postData = JSON.stringify({ + type: 'blob', + value: 'hello' + }) + const content = ` + + ` + + protocol.registerStringProtocol(scheme, function (request, callback) { + if (request.method === 'GET') { + callback({data: content, mimeType: 'text/html'}) + } else if (request.method === 'POST') { + let uuid = request.uploadData[1].blobUUID + assert(uuid) + session.defaultSession.getBlobData(uuid, function (result) { + assert.equal(result.toString(), postData) + done() + }) + } + }, function (error) { + if (error) return done(error) + w.loadURL(url) + }) + }) + }) })