Skip to content
This repository has been archived by the owner on Jan 4, 2019. It is now read-only.

Add protocol.registerStreamProtocol #507

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions atom/browser/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down
3 changes: 3 additions & 0 deletions atom/browser/api/atom_api_protocol.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -259,6 +260,8 @@ void Protocol::BuildPrototype(
&Protocol::RegisterProtocol<URLRequestStringJob>)
.SetMethod("registerBufferProtocol",
&Protocol::RegisterProtocol<URLRequestBufferJob>)
.SetMethod("registerStreamProtocol",
&Protocol::RegisterProtocol<URLRequestStreamJob>)
.SetMethod("registerHttpProtocol",
&Protocol::RegisterProtocol<URLRequestFetchJob>)
.SetMethod("unregisterProtocol", &Protocol::UnregisterProtocol)
Expand Down
122 changes: 122 additions & 0 deletions atom/browser/api/event_subscriber.cc
Original file line number Diff line number Diff line change
@@ -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 <string>
#include <utility>

#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<v8::FunctionTemplate> 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<JSHandlerData>& data) {
delete data.GetParameter();
}

v8::Global<v8::External> handle_;
mate::internal::EventSubscriberBase* subscriber_;
};

void InvokeCallback(const v8::FunctionCallbackInfo<v8::Value>& info) {
v8::Locker locker(info.GetIsolate());
v8::HandleScope handle_scope(info.GetIsolate());
v8::Local<v8::Context> context = info.GetIsolate()->GetCurrentContext();
v8::Context::Scope context_scope(context);
mate::Arguments args(info);
v8::Local<v8::Value> handler, event;
args.GetNext(&handler);
args.GetNext(&event);
DCHECK(handler->IsExternal());
DCHECK(event->IsString());
JSHandlerData* handler_data = static_cast<JSHandlerData*>(
v8::Local<v8::External>::Cast(handler)->Value());
handler_data->subscriber_->EventEmitted(mate::V8ToString(event), &args);
}

} // namespace

namespace mate {

namespace internal {

EventSubscriberBase::EventSubscriberBase(v8::Isolate* isolate,
v8::Local<v8::Object> emitter)
: isolate_(isolate), emitter_(isolate, emitter) {
if (g_cached_template.IsEmpty()) {
g_cached_template = v8::Global<v8::FunctionTemplate>(
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<v8::Value> 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<v8::Value>(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<std::string, v8::Global<v8::Value>>::iterator
EventSubscriberBase::RemoveListener(
std::map<std::string, v8::Global<v8::Value>>::iterator it) {
internal::ValueVector args = {StringToV8(isolate_, it->first),
it->second.Get(isolate_)};
internal::CallMethodWithArgs(
isolate_, v8::Local<v8::Object>::Cast(emitter_.Get(isolate_)),
"removeListener", &args);
it->second.Reset();
return js_handlers_.erase(it);
}

} // namespace internal

} // namespace mate
134 changes: 134 additions & 0 deletions atom/browser/api/event_subscriber.h
Original file line number Diff line number Diff line change
@@ -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 <map>
#include <memory>
#include <string>
#include <utility>

#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<v8::Object> 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<std::string, v8::Global<v8::Value>>::iterator RemoveListener(
std::map<std::string, v8::Global<v8::Value>>::iterator it);

v8::Isolate* isolate_;
v8::Global<v8::Object> emitter_;
std::map<std::string, v8::Global<v8::Value>> js_handlers_;

DISALLOW_COPY_AND_ASSIGN(EventSubscriberBase);
};

} // namespace internal

template <typename HandlerType>
class EventSubscriber : internal::EventSubscriberBase {
public:
using EventCallback = void (HandlerType::*)(mate::Arguments* args);
// Alias to unique_ptr with deleter.
using unique_ptr = std::unique_ptr<EventSubscriber<HandlerType>,
void (*)(EventSubscriber<HandlerType>*)>;
// 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<HandlerType>* ptr)
: unique_ptr(ptr, Deleter) {}

private:
// Custom deleter that schedules destructor invocation to the main thread.
static void Deleter(EventSubscriber<HandlerType>* 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<HandlerType>* subscriber) {
delete subscriber;
},
ptr));
}
};

EventSubscriber(HandlerType* handler,
v8::Isolate* isolate,
v8::Local<v8::Object> 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<std::string, EventCallback> callbacks_;
};

} // namespace mate

#endif // ATOM_BROWSER_API_EVENT_SUBSCRIBER_H_
14 changes: 14 additions & 0 deletions atom/browser/net/js_asker.h
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ class JsAsker : public RequestJob {
void Start() override {
std::unique_ptr<base::DictionaryValue> 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,
Expand All @@ -85,13 +86,24 @@ 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("");
}

// 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<base::Value> 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));
Expand All @@ -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<JsAsker> weak_factory_;

Expand Down
Loading