diff --git a/atom/browser/BUILD.gn b/atom/browser/BUILD.gn index 0e367dbbdc..a09de17327 100644 --- a/atom/browser/BUILD.gn +++ b/atom/browser/BUILD.gn @@ -85,6 +85,8 @@ source_set("browser") { "api/event.h", "api/event_emitter.cc", "api/event_emitter.h", + "api/event_subscriber.cc", + "api/event_subscriber.h", "api/trackable_object.cc", "api/trackable_object.h", "api/save_page_handler.cc", @@ -151,6 +153,8 @@ source_set("browser") { "net/url_request_buffer_job.h", "net/url_request_fetch_job.cc", "net/url_request_fetch_job.h", + "net/url_request_stream_job.cc", + "net/url_request_stream_job.h", "relauncher.cc", "relauncher.h", "ui/accelerator_util.cc", diff --git a/atom/browser/api/atom_api_protocol.cc b/atom/browser/api/atom_api_protocol.cc index 604f032cda..bd89ae8b9b 100644 --- a/atom/browser/api/atom_api_protocol.cc +++ b/atom/browser/api/atom_api_protocol.cc @@ -9,6 +9,7 @@ #include "atom/browser/browser.h" #include "atom/browser/net/url_request_buffer_job.h" #include "atom/browser/net/url_request_fetch_job.h" +#include "atom/browser/net/url_request_stream_job.h" #include "atom/browser/net/url_request_string_job.h" #include "atom/common/native_mate_converters/callback.h" #include "atom/common/native_mate_converters/v8_value_converter.h" @@ -259,6 +260,8 @@ void Protocol::BuildPrototype( &Protocol::RegisterProtocol) .SetMethod("registerBufferProtocol", &Protocol::RegisterProtocol) + .SetMethod("registerStreamProtocol", + &Protocol::RegisterProtocol) .SetMethod("registerHttpProtocol", &Protocol::RegisterProtocol) .SetMethod("unregisterProtocol", &Protocol::UnregisterProtocol) diff --git a/atom/browser/api/event_subscriber.cc b/atom/browser/api/event_subscriber.cc new file mode 100644 index 0000000000..66334aa0b0 --- /dev/null +++ b/atom/browser/api/event_subscriber.cc @@ -0,0 +1,122 @@ +// Copyright (c) 2017 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. +#include +#include + +#include "atom/browser/api/event_subscriber.h" +#include "atom/common/native_mate_converters/callback.h" + +namespace { + +// A FunctionTemplate lifetime is bound to the v8 context, so it can be safely +// stored as a global here since there's only one for the main process. +v8::Global g_cached_template; + +struct JSHandlerData { + JSHandlerData(v8::Isolate* isolate, + mate::internal::EventSubscriberBase* subscriber) + : handle_(isolate, v8::External::New(isolate, this)), + subscriber_(subscriber) { + handle_.SetWeak(this, GC, v8::WeakCallbackType::kFinalizer); + } + + static void GC(const v8::WeakCallbackInfo& data) { + delete data.GetParameter(); + } + + v8::Global handle_; + mate::internal::EventSubscriberBase* subscriber_; +}; + +void InvokeCallback(const v8::FunctionCallbackInfo& info) { + v8::Locker locker(info.GetIsolate()); + v8::HandleScope handle_scope(info.GetIsolate()); + v8::Local context = info.GetIsolate()->GetCurrentContext(); + v8::Context::Scope context_scope(context); + mate::Arguments args(info); + v8::Local handler, event; + args.GetNext(&handler); + args.GetNext(&event); + DCHECK(handler->IsExternal()); + DCHECK(event->IsString()); + JSHandlerData* handler_data = static_cast( + v8::Local::Cast(handler)->Value()); + handler_data->subscriber_->EventEmitted(mate::V8ToString(event), &args); +} + +} // namespace + +namespace mate { + +namespace internal { + +EventSubscriberBase::EventSubscriberBase(v8::Isolate* isolate, + v8::Local emitter) + : isolate_(isolate), emitter_(isolate, emitter) { + if (g_cached_template.IsEmpty()) { + g_cached_template = v8::Global( + isolate_, v8::FunctionTemplate::New(isolate_, InvokeCallback)); + } +} + +EventSubscriberBase::~EventSubscriberBase() { + if (!isolate_) { + return; + } + RemoveAllListeners(); + emitter_.Reset(); + DCHECK_EQ(js_handlers_.size(), 0); +} + +void EventSubscriberBase::On(const std::string& event_name) { + DCHECK(js_handlers_.find(event_name) == js_handlers_.end()); + v8::Locker locker(isolate_); + v8::Isolate::Scope isolate_scope(isolate_); + v8::HandleScope handle_scope(isolate_); + auto fn_template = g_cached_template.Get(isolate_); + auto event = mate::StringToV8(isolate_, event_name); + auto js_handler_data = new JSHandlerData(isolate_, this); + v8::Local fn = internal::BindFunctionWith( + isolate_, isolate_->GetCurrentContext(), fn_template->GetFunction(), + js_handler_data->handle_.Get(isolate_), event); + js_handlers_.insert( + std::make_pair(event_name, v8::Global(isolate_, fn))); + internal::ValueVector converted_args = {event, fn}; + internal::CallMethodWithArgs(isolate_, emitter_.Get(isolate_), "on", + &converted_args); +} + +void EventSubscriberBase::Off(const std::string& event_name) { + v8::Locker locker(isolate_); + v8::Isolate::Scope isolate_scope(isolate_); + v8::HandleScope handle_scope(isolate_); + auto js_handler = js_handlers_.find(event_name); + DCHECK(js_handler != js_handlers_.end()); + RemoveListener(js_handler); +} + +void EventSubscriberBase::RemoveAllListeners() { + v8::Locker locker(isolate_); + v8::Isolate::Scope isolate_scope(isolate_); + v8::HandleScope handle_scope(isolate_); + while (!js_handlers_.empty()) { + RemoveListener(js_handlers_.begin()); + } +} + +std::map>::iterator +EventSubscriberBase::RemoveListener( + std::map>::iterator it) { + internal::ValueVector args = {StringToV8(isolate_, it->first), + it->second.Get(isolate_)}; + internal::CallMethodWithArgs( + isolate_, v8::Local::Cast(emitter_.Get(isolate_)), + "removeListener", &args); + it->second.Reset(); + return js_handlers_.erase(it); +} + +} // namespace internal + +} // namespace mate diff --git a/atom/browser/api/event_subscriber.h b/atom/browser/api/event_subscriber.h new file mode 100644 index 0000000000..2609cbed4e --- /dev/null +++ b/atom/browser/api/event_subscriber.h @@ -0,0 +1,134 @@ +// Copyright (c) 2017 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#ifndef ATOM_BROWSER_API_EVENT_SUBSCRIBER_H_ +#define ATOM_BROWSER_API_EVENT_SUBSCRIBER_H_ + +#include +#include +#include +#include + +#include "atom/common/api/event_emitter_caller.h" +#include "base/synchronization/lock.h" +#include "content/public/browser/browser_thread.h" +#include "native_mate/native_mate/arguments.h" + +namespace mate { + +namespace internal { + +class EventSubscriberBase { + public: + EventSubscriberBase(v8::Isolate* isolate, v8::Local emitter); + virtual ~EventSubscriberBase(); + virtual void EventEmitted(const std::string& event_name, + mate::Arguments* args) = 0; + + protected: + void On(const std::string& event_name); + void Off(const std::string& event_name); + void RemoveAllListeners(); + + private: + std::map>::iterator RemoveListener( + std::map>::iterator it); + + v8::Isolate* isolate_; + v8::Global emitter_; + std::map> js_handlers_; + + DISALLOW_COPY_AND_ASSIGN(EventSubscriberBase); +}; + +} // namespace internal + +template +class EventSubscriber : internal::EventSubscriberBase { + public: + using EventCallback = void (HandlerType::*)(mate::Arguments* args); + // Alias to unique_ptr with deleter. + using unique_ptr = std::unique_ptr, + void (*)(EventSubscriber*)>; + // EventSubscriber should only be created/deleted in the main thread since it + // communicates with the V8 engine. This smart pointer makes it simpler to + // bind the lifetime of EventSubscriber with a class whose lifetime is managed + // by a non-UI thread. + class SafePtr : public unique_ptr { + public: + SafePtr() : SafePtr(nullptr) {} + explicit SafePtr(EventSubscriber* ptr) + : unique_ptr(ptr, Deleter) {} + + private: + // Custom deleter that schedules destructor invocation to the main thread. + static void Deleter(EventSubscriber* ptr) { + DCHECK( + !::content::BrowserThread::CurrentlyOn(::content::BrowserThread::UI)); + DCHECK(ptr); + // Acquire handler lock and reset handler_ to ensure that any new events + // emitted will be ignored after this function returns + base::AutoLock auto_lock(ptr->handler_lock_); + ptr->handler_ = nullptr; + content::BrowserThread::PostTask( + content::BrowserThread::UI, FROM_HERE, + base::Bind( + [](EventSubscriber* subscriber) { + delete subscriber; + }, + ptr)); + } + }; + + EventSubscriber(HandlerType* handler, + v8::Isolate* isolate, + v8::Local emitter) + : EventSubscriberBase(isolate, emitter), handler_(handler) { + DCHECK_CURRENTLY_ON(::content::BrowserThread::UI); + } + + void On(const std::string& event, EventCallback callback) { + DCHECK_CURRENTLY_ON(::content::BrowserThread::UI); + EventSubscriberBase::On(event); + callbacks_.insert(std::make_pair(event, callback)); + } + + void Off(const std::string& event) { + DCHECK_CURRENTLY_ON(::content::BrowserThread::UI); + EventSubscriberBase::Off(event); + DCHECK(callbacks_.find(event) != callbacks_.end()); + callbacks_.erase(callbacks_.find(event)); + } + + void RemoveAllListeners() { + DCHECK_CURRENTLY_ON(::content::BrowserThread::UI); + EventSubscriberBase::RemoveAllListeners(); + callbacks_.clear(); + } + + private: + void EventEmitted(const std::string& event_name, + mate::Arguments* args) override { + DCHECK_CURRENTLY_ON(::content::BrowserThread::UI); + base::AutoLock auto_lock(handler_lock_); + if (!handler_) { + // handler_ was probably destroyed by another thread and we should not + // access it. + return; + } + auto it = callbacks_.find(event_name); + if (it != callbacks_.end()) { + auto method = it->second; + (handler_->*method)(args); + } + } + + HandlerType* handler_; + base::Lock handler_lock_; + std::map callbacks_; +}; + +} // namespace mate + +#endif // ATOM_BROWSER_API_EVENT_SUBSCRIBER_H_ diff --git a/atom/browser/net/js_asker.h b/atom/browser/net/js_asker.h index fb5007905a..2053ddd1ba 100644 --- a/atom/browser/net/js_asker.h +++ b/atom/browser/net/js_asker.h @@ -73,6 +73,7 @@ class JsAsker : public RequestJob { void Start() override { std::unique_ptr request_details( new base::DictionaryValue); + request_start_time_ = base::TimeTicks::Now(); FillRequestDetails(request_details.get(), RequestJob::request()); content::BrowserThread::PostTask( content::BrowserThread::UI, FROM_HERE, @@ -85,6 +86,16 @@ class JsAsker : public RequestJob { base::Bind(&JsAsker::OnResponse, weak_factory_.GetWeakPtr()))); } + + // NOTE: We have to implement this method or risk a crash in blink for + // redirects! + void GetLoadTimingInfo(net::LoadTimingInfo* load_timing_info) const override { + load_timing_info->send_start = request_start_time_; + load_timing_info->send_end = request_start_time_; + load_timing_info->request_start = request_start_time_; + load_timing_info->receive_headers_end = response_start_time_; + } + void GetResponseInfo(net::HttpResponseInfo* info) override { info->headers = new net::HttpResponseHeaders(""); } @@ -92,6 +103,7 @@ class JsAsker : public RequestJob { // Called when the JS handler has sent the response, we need to decide whether // to start, or fail the job. void OnResponse(bool success, std::unique_ptr value) { + response_start_time_ = base::TimeTicks::Now(); int error = net::ERR_NOT_IMPLEMENTED; if (success && value && !internal::IsErrorOptions(value.get(), &error)) { StartAsync(std::move(value)); @@ -104,6 +116,8 @@ class JsAsker : public RequestJob { v8::Isolate* isolate_; net::URLRequestContextGetter* request_context_getter_; JavaScriptHandler handler_; + base::TimeTicks request_start_time_; + base::TimeTicks response_start_time_; base::WeakPtrFactory weak_factory_; diff --git a/atom/browser/net/url_request_stream_job.cc b/atom/browser/net/url_request_stream_job.cc new file mode 100644 index 0000000000..423dd06de3 --- /dev/null +++ b/atom/browser/net/url_request_stream_job.cc @@ -0,0 +1,206 @@ +// Copyright (c) 2017 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#include +#include +#include +#include +#include + +#include "atom/browser/net/url_request_stream_job.h" +#include "atom/common/api/event_emitter_caller.h" +#include "atom/common/atom_constants.h" +#include "atom/common/native_mate_converters/net_converter.h" +#include "atom/common/node_includes.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_util.h" +#include "base/time/time.h" +#include "net/filter/gzip_source_stream.h" + +namespace atom { + +URLRequestStreamJob::URLRequestStreamJob(net::URLRequest* request, + net::NetworkDelegate* network_delegate) + : JsAsker(request, network_delegate), + ended_(false), + errored_(false), + pending_io_buf_(nullptr), + pending_io_buf_size_(0), + response_headers_(nullptr), + weak_factory_(this) {} + +void URLRequestStreamJob::BeforeStartInUI(v8::Isolate* isolate, + v8::Local value) { + if (value->IsNull() || value->IsUndefined() || !value->IsObject()) { + // Invalid opts. + ended_ = true; + errored_ = true; + return; + } + + mate::Dictionary opts(isolate, v8::Local::Cast(value)); + int status_code; + if (!opts.Get("statusCode", &status_code)) { + // assume HTTP OK if statusCode is not passed. + status_code = 200; + } + std::string status("HTTP/1.1 "); + status.append(base::IntToString(status_code)); + status.append(" "); + status.append( + net::GetHttpReasonPhrase(static_cast(status_code))); + status.append("\0\0", 2); + response_headers_ = new net::HttpResponseHeaders(status); + + if (opts.Get("headers", &value)) { + mate::Converter::FromV8(isolate, value, + response_headers_.get()); + } + + if (!opts.Get("data", &value)) { + // Assume the opts is already a stream + value = opts.GetHandle(); + } else if (value->IsNullOrUndefined()) { + // "data" was explicitly passed as null or undefined, assume the user wants + // to send an empty body. + ended_ = true; + return; + } + + mate::Dictionary data(isolate, v8::Local::Cast(value)); + if (!data.Get("on", &value) || !value->IsFunction() || + !data.Get("removeListener", &value) || !value->IsFunction()) { + // If data is passed but it is not a stream, signal an error. + ended_ = true; + errored_ = true; + return; + } + + subscriber_.reset(new mate::EventSubscriber( + this, isolate, data.GetHandle())); + subscriber_->On("data", &URLRequestStreamJob::OnData); + subscriber_->On("end", &URLRequestStreamJob::OnEnd); + subscriber_->On("error", &URLRequestStreamJob::OnError); +} + +void URLRequestStreamJob::StartAsync(std::unique_ptr options) { + NotifyHeadersComplete(); +} + +void URLRequestStreamJob::OnData(mate::Arguments* args) { + v8::Local node_data; + args->GetNext(&node_data); + if (node_data->IsUint8Array()) { + const char* data = node::Buffer::Data(node_data); + size_t data_size = node::Buffer::Length(node_data); + std::copy(data, data + data_size, std::back_inserter(buffer_)); + } else { + NOTREACHED(); + } + if (pending_io_buf_) { + CopyMoreData(pending_io_buf_, pending_io_buf_size_); + } +} + +void URLRequestStreamJob::OnEnd(mate::Arguments* args) { + ended_ = true; + if (pending_io_buf_) { + CopyMoreData(pending_io_buf_, pending_io_buf_size_); + } +} + +void URLRequestStreamJob::OnError(mate::Arguments* args) { + errored_ = true; + if (pending_io_buf_) { + CopyMoreData(pending_io_buf_, pending_io_buf_size_); + } +} + +int URLRequestStreamJob::ReadRawData(net::IOBuffer* dest, int dest_size) { + content::BrowserThread::PostTask( + content::BrowserThread::UI, FROM_HERE, + base::Bind(&URLRequestStreamJob::CopyMoreData, weak_factory_.GetWeakPtr(), + base::WrapRefCounted(dest), dest_size)); + return net::ERR_IO_PENDING; +} + +void URLRequestStreamJob::DoneReading() { + subscriber_.reset(); + buffer_.clear(); + ended_ = true; +} + +void URLRequestStreamJob::DoneReadingRedirectResponse() { + DoneReading(); +} + +void URLRequestStreamJob::CopyMoreDataDone(scoped_refptr io_buf, + int status) { + if (status <= 0) { + subscriber_.reset(); + } + ReadRawDataComplete(status); + io_buf = nullptr; +} + +void URLRequestStreamJob::CopyMoreData(scoped_refptr io_buf, + int io_buf_size) { + // reset any instance references to io_buf + pending_io_buf_ = nullptr; + pending_io_buf_size_ = 0; + + int read_count = 0; + if (buffer_.size()) { + size_t count = std::min((size_t)io_buf_size, buffer_.size()); + std::copy(buffer_.begin(), buffer_.begin() + count, io_buf->data()); + buffer_.erase(buffer_.begin(), buffer_.begin() + count); + read_count = count; + } else if (!ended_ && !errored_) { + // No data available yet, save references to the IOBuffer, which will be + // passed back to this function when OnData/OnEnd/OnError are called + pending_io_buf_ = io_buf; + pending_io_buf_size_ = io_buf_size; + } + + if (!pending_io_buf_) { + // Only call CopyMoreDataDone if we have read something. + int status = (errored_ && !read_count) ? net::ERR_FAILED : read_count; + content::BrowserThread::PostTask( + content::BrowserThread::IO, FROM_HERE, + base::Bind(&URLRequestStreamJob::CopyMoreDataDone, + weak_factory_.GetWeakPtr(), io_buf, status)); + } +} + +std::unique_ptr URLRequestStreamJob::SetUpSourceStream() { + std::unique_ptr source = + net::URLRequestJob::SetUpSourceStream(); + size_t i = 0; + std::string type; + while (response_headers_->EnumerateHeader(&i, "Content-Encoding", &type)) { + if (base::LowerCaseEqualsASCII(type, "gzip") || + base::LowerCaseEqualsASCII(type, "x-gzip")) { + return net::GzipSourceStream::Create(std::move(source), + net::SourceStream::TYPE_GZIP); + } else if (base::LowerCaseEqualsASCII(type, "deflate")) { + return net::GzipSourceStream::Create(std::move(source), + net::SourceStream::TYPE_DEFLATE); + } + } + return source; +} + +bool URLRequestStreamJob::GetMimeType(std::string* mime_type) const { + return response_headers_->GetMimeType(mime_type); +} + +int URLRequestStreamJob::GetResponseCode() const { + return response_headers_->response_code(); +} + +void URLRequestStreamJob::GetResponseInfo(net::HttpResponseInfo* info) { + info->headers = response_headers_; +} + +} // namespace atom diff --git a/atom/browser/net/url_request_stream_job.h b/atom/browser/net/url_request_stream_job.h new file mode 100644 index 0000000000..f959fac42f --- /dev/null +++ b/atom/browser/net/url_request_stream_job.h @@ -0,0 +1,67 @@ +// Copyright (c) 2017 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#ifndef ATOM_BROWSER_NET_URL_REQUEST_STREAM_JOB_H_ +#define ATOM_BROWSER_NET_URL_REQUEST_STREAM_JOB_H_ + +#include +#include +#include + +#include "atom/browser/api/event_subscriber.h" +#include "atom/browser/net/js_asker.h" +#include "base/memory/ref_counted_memory.h" +#include "native_mate/persistent_dictionary.h" +#include "net/base/io_buffer.h" +#include "net/http/http_status_code.h" +#include "net/url_request/url_request_context_getter.h" +#include "v8/include/v8.h" + +namespace atom { + +class URLRequestStreamJob : public JsAsker { + public: + URLRequestStreamJob(net::URLRequest* request, + net::NetworkDelegate* network_delegate); + + void OnData(mate::Arguments* args); + void OnEnd(mate::Arguments* args); + void OnError(mate::Arguments* args); + + // URLRequestJob + void GetResponseInfo(net::HttpResponseInfo* info) override; + + protected: + // URLRequestJob + int ReadRawData(net::IOBuffer* buf, int buf_size) override; + void DoneReading() override; + void DoneReadingRedirectResponse() override; + std::unique_ptr SetUpSourceStream() override; + bool GetMimeType(std::string* mime_type) const override; + int GetResponseCode() const override; + + private: + // JSAsker + void BeforeStartInUI(v8::Isolate*, v8::Local) override; + void StartAsync(std::unique_ptr options) override; + void OnResponse(bool success, std::unique_ptr value); + + // Callback after data is asynchronously read from the file into |buf|. + void CopyMoreData(scoped_refptr io_buf, int io_buf_size); + void CopyMoreDataDone(scoped_refptr io_buf, int read_count); + + std::deque buffer_; + bool ended_; + bool errored_; + scoped_refptr pending_io_buf_; + int pending_io_buf_size_; + scoped_refptr response_headers_; + mate::EventSubscriber::SafePtr subscriber_; + base::WeakPtrFactory weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(URLRequestStreamJob); +}; +} // namespace atom + +#endif // ATOM_BROWSER_NET_URL_REQUEST_STREAM_JOB_H_ diff --git a/atom/common/api/event_emitter_caller.cc b/atom/common/api/event_emitter_caller.cc index 40448cad10..ff4bece992 100644 --- a/atom/common/api/event_emitter_caller.cc +++ b/atom/common/api/event_emitter_caller.cc @@ -11,8 +11,9 @@ namespace mate { namespace internal { -v8::Local CallEmitWithArgs(v8::Isolate* isolate, +v8::Local CallMethodWithArgs(v8::Isolate* isolate, v8::Local obj, + const char* method, ValueVector* args) { // Perform microtask checkpoint after running JavaScript. v8::MicrotasksScope script_scope( @@ -20,7 +21,7 @@ v8::Local CallEmitWithArgs(v8::Isolate* isolate, // Use node::MakeCallback to call the callback, and it will also run pending // tasks in Node.js. return node::MakeCallback( - isolate, obj, "emit", args->size(), &args->front()); + isolate, obj, method, args->size(), &args->front()); } } // namespace internal diff --git a/atom/common/api/event_emitter_caller.h b/atom/common/api/event_emitter_caller.h index e9e73dcfd6..9f33603dc0 100644 --- a/atom/common/api/event_emitter_caller.h +++ b/atom/common/api/event_emitter_caller.h @@ -30,8 +30,9 @@ namespace internal { using ValueVector = std::vector>; -v8::Local CallEmitWithArgs(v8::Isolate* isolate, +v8::Local CallMethodWithArgs(v8::Isolate* isolate, v8::Local obj, + const char* method, ValueVector* args); } // namespace internal @@ -46,7 +47,7 @@ v8::Local EmitEvent(v8::Isolate* isolate, internal::ValueVector concatenated_args = { gin::StringToV8(isolate, name) }; concatenated_args.reserve(1 + args.size()); concatenated_args.insert(concatenated_args.end(), args.begin(), args.end()); - return internal::CallEmitWithArgs(isolate, obj, &concatenated_args); + return internal::CallMethodWithArgs(isolate, obj, "emit", &concatenated_args); } // obj.emit(name, args...); @@ -60,7 +61,7 @@ v8::Local EmitEvent(v8::Isolate* isolate, gin::StringToV8(isolate, name), mate::ConvertToV8(isolate, args)..., }; - return internal::CallEmitWithArgs(isolate, obj, &converted_args); + return internal::CallMethodWithArgs(isolate, obj, "emit", &converted_args); } } // namespace mate diff --git a/atom/common/api/resources/protocol_bindings.js b/atom/common/api/resources/protocol_bindings.js index 61848a820d..145e37af2e 100644 --- a/atom/common/api/resources/protocol_bindings.js +++ b/atom/common/api/resources/protocol_bindings.js @@ -9,6 +9,40 @@ var protocol = { handler(request, cb) }) ipc.send('register-protocol-string-handler', scheme) + }, + + registerStreamProtocol: function (scheme, handler) { + ipc.on('chrome-protocol-stream-handler-' + scheme, function (evt, request, requestId) { + const cb = (res) => { + if (res == null || typeof res.pipe === 'function') { + res = { data: res } + } + + res.headers = res.headers || {} + res.statusCode = res.statusCode || 200 + + ipc.send(`chrome-protocol-stream-handled-${requestId}-headers`, { headers: res.headers, statusCode: res.statusCode }) + + if (res.data == null) { + return ipc.send(`chrome-protocol-stream-handled-${requestId}-stream-end`) + } + + res.data + .on('data', (chunk) => { + const data = JSON.stringify(chunk) + ipc.send(`chrome-protocol-stream-handled-${requestId}-stream-data`, data) + }) + .on('error', (err) => { + const data = { message: err.message, stack: err.stack } + ipc.send(`chrome-protocol-stream-handled-${requestId}-stream-error`, data) + }) + .on('end', () => { + ipc.send(`chrome-protocol-stream-handled-${requestId}-stream-end`) + }) + } + handler(request, cb) + }) + ipc.send('register-protocol-stream-handler', scheme) } } diff --git a/atom/common/native_mate_converters/callback.cc b/atom/common/native_mate_converters/callback.cc index dc6d30fd23..b6270f0a24 100644 --- a/atom/common/native_mate_converters/callback.cc +++ b/atom/common/native_mate_converters/callback.cc @@ -40,22 +40,6 @@ void CallTranslater(v8::Local external, delete holder; } -// func.bind(func, arg1). -// NB(zcbenz): Using C++11 version crashes VS. -v8::Local BindFunctionWith(v8::Isolate* isolate, - v8::Local context, - v8::Local func, - v8::Local arg1, - v8::Local arg2) { - v8::MaybeLocal bind = func->Get(mate::StringToV8(isolate, "bind")); - CHECK(!bind.IsEmpty()); - v8::Local bind_func = - v8::Local::Cast(bind.ToLocalChecked()); - v8::Local converted[] = { func, arg1, arg2 }; - return bind_func->Call( - context, func, arraysize(converted), converted).ToLocalChecked(); -} - } // namespace // Destroy the class on UI thread when possible. @@ -132,6 +116,22 @@ v8::Local CreateFunctionFromTranslater( v8::Object::New(isolate)); } +// func.bind(func, arg1). +// NB(zcbenz): Using C++11 version crashes VS. +v8::Local BindFunctionWith(v8::Isolate* isolate, + v8::Local context, + v8::Local func, + v8::Local arg1, + v8::Local arg2) { + v8::MaybeLocal bind = func->Get(mate::StringToV8(isolate, "bind")); + CHECK(!bind.IsEmpty()); + v8::Local bind_func = + v8::Local::Cast(bind.ToLocalChecked()); + v8::Local converted[] = {func, arg1, arg2}; + return bind_func->Call(context, func, arraysize(converted), converted) + .ToLocalChecked(); +} + } // namespace internal } // namespace mate diff --git a/atom/common/native_mate_converters/callback.h b/atom/common/native_mate_converters/callback.h index a7d55fa07e..6ec8d060bc 100644 --- a/atom/common/native_mate_converters/callback.h +++ b/atom/common/native_mate_converters/callback.h @@ -108,6 +108,11 @@ struct V8FunctionInvoker { using Translater = base::Callback; v8::Local CreateFunctionFromTranslater( v8::Isolate* isolate, const Translater& translater); +v8::Local BindFunctionWith(v8::Isolate* isolate, + v8::Local context, + v8::Local func, + v8::Local arg1, + v8::Local arg2); // Calls callback with Arguments. template