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 8 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
5 changes: 5 additions & 0 deletions atom/browser/api/atom_api_protocol.h
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,11 @@ class Protocol : public mate::TrackableObject<Protocol> {
net::URLRequestJob* MaybeCreateJob(
net::URLRequest* request,
net::NetworkDelegate* network_delegate) const override {
// hander not triggered if enabled.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yes, that can go, it's just a left over from porting electron/electron#11008

// if (!request->initiator().has_value()) {
// // Don't intercept this request as it was created by `net.request`.
// return nullptr;
// }
RequestJob* request_job = new RequestJob(request, network_delegate);
request_job->SetHandlerInfo(isolate_, request_context_.get(), handler_);
return request_job;
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