diff --git a/atom.gyp b/atom.gyp index fccacc4ef6c2..5a8cab832024 100644 --- a/atom.gyp +++ b/atom.gyp @@ -20,6 +20,8 @@ 'browser/native_window_mac.mm', 'common/node_bindings.cc', 'common/node_bindings.h', + 'common/node_bindings_mac.h', + 'common/node_bindings_mac.mm', 'common/options_switches.cc', 'common/options_switches.h', 'renderer/atom_render_view_observer.cc', diff --git a/browser/atom_browser_main_parts.cc b/browser/atom_browser_main_parts.cc index 6d05af74668a..c26eaf421f03 100644 --- a/browser/atom_browser_main_parts.cc +++ b/browser/atom_browser_main_parts.cc @@ -15,19 +15,29 @@ namespace atom { AtomBrowserMainParts::AtomBrowserMainParts() - : node_bindings_(new NodeBindings(true)) { + : node_bindings_(NodeBindings::Create(true)) { } AtomBrowserMainParts::~AtomBrowserMainParts() { } void AtomBrowserMainParts::PostEarlyInitialization() { + brightray::BrowserMainParts::PostEarlyInitialization(); + node_bindings_->Initialize(); } +void AtomBrowserMainParts::PreMainMessageLoopStart() { + brightray::BrowserMainParts::PreMainMessageLoopStart(); + + node_bindings_->PrepareMessageLoop(); +} + void AtomBrowserMainParts::PreMainMessageLoopRun() { brightray::BrowserMainParts::PreMainMessageLoopRun(); + node_bindings_->RunMessageLoop(); + scoped_ptr options(new base::DictionaryValue); options->SetInteger("width", 800); options->SetInteger("height", 600); @@ -38,7 +48,7 @@ void AtomBrowserMainParts::PreMainMessageLoopRun() { window->InitFromOptions(options.get()); window->GetWebContents()->GetController().LoadURL( - GURL("http://adam.roben.org/brightray_example/start.html"), + GURL("http://localhost"), content::Referrer(), content::PAGE_TRANSITION_AUTO_TOPLEVEL, std::string()); diff --git a/browser/atom_browser_main_parts.h b/browser/atom_browser_main_parts.h index c0d43e2f58e1..fc92f1c69c19 100644 --- a/browser/atom_browser_main_parts.h +++ b/browser/atom_browser_main_parts.h @@ -19,6 +19,7 @@ class AtomBrowserMainParts : public brightray::BrowserMainParts { protected: virtual void PostEarlyInitialization() OVERRIDE; + virtual void PreMainMessageLoopStart() OVERRIDE; virtual void PreMainMessageLoopRun() OVERRIDE; private: diff --git a/common/node_bindings.h b/common/node_bindings.h index c73e79844c18..4cfbbaaf85ba 100644 --- a/common/node_bindings.h +++ b/common/node_bindings.h @@ -11,13 +11,22 @@ namespace atom { class NodeBindings { public: - NodeBindings(bool is_browser); + static NodeBindings* Create(bool is_browser); + virtual ~NodeBindings(); // Setup everything including V8, libuv and node.js main script. - void Initialize(); + virtual void Initialize(); + + // Prepare for message loop integration. + virtual void PrepareMessageLoop() = 0; + + // Do message loop integration. + virtual void RunMessageLoop() = 0; + + protected: + explicit NodeBindings(bool is_browser); - private: bool is_browser_; DISALLOW_COPY_AND_ASSIGN(NodeBindings); diff --git a/common/node_bindings_mac.h b/common/node_bindings_mac.h new file mode 100644 index 000000000000..6b365cdc4332 --- /dev/null +++ b/common/node_bindings_mac.h @@ -0,0 +1,66 @@ +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef ATOM_COMMON_NODE_BINDINGS_MAC_ +#define ATOM_COMMON_NODE_BINDINGS_MAC_ + +#include +#include +#include + +#include "base/compiler_specific.h" +#include "common/node_bindings.h" +#include "vendor/node/deps/uv/include/uv.h" + +namespace atom { + +class NodeBindingsMac : public NodeBindings { + public: + explicit NodeBindingsMac(bool is_browser); + virtual ~NodeBindingsMac(); + + virtual void PrepareMessageLoop() OVERRIDE; + virtual void RunMessageLoop() OVERRIDE; + + private: + // Run the libuv loop for once. + void UvRunOnce(); + + // Run pending one shot events if we have. + void DealWithPendingEvent(); + + // Called when kqueue notifies new event. + void OnKqueueHasNewEvents(); + + // Thread to poll uv events. + static void EmbedThreadRunner(void *arg); + + // Called when uv's watcher queue changes. + static void OnWatcherQueueChanged(uv_loop_t* loop); + + // Main thread's loop. + uv_loop_t* loop_; + + // Dummy handle to make uv's loop not quit. + uv_async_t dummy_uv_handle_; + + // Thread for polling events. + uv_thread_t embed_thread_; + + // Whether we're done. + bool embed_closed_; + + // Semaphore to wait for main loop in the embed thread. + uv_sem_t embed_sem_; + + // Captured oneshot event in embed thread. + bool has_pending_event_; + struct ::kevent event_; + + DISALLOW_COPY_AND_ASSIGN(NodeBindingsMac); +}; + +} // namespace atom + +#endif // ATOM_COMMON_NODE_BINDINGS_MAC_ diff --git a/common/node_bindings_mac.mm b/common/node_bindings_mac.mm new file mode 100644 index 000000000000..cc203c02d185 --- /dev/null +++ b/common/node_bindings_mac.mm @@ -0,0 +1,179 @@ +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "node_bindings_mac.h" + +#include "base/message_loop.h" +#include "content/public/browser/browser_thread.h" +#include "vendor/node/src/node.h" +#include "vendor/node/src/node_internals.h" + +#define UV__POLLIN 1 +#define UV__POLLOUT 2 +#define UV__POLLERR 4 + +using content::BrowserThread; + +namespace atom { + +namespace { + +void UvNoOp(uv_async_t* handle, int status) { +} + +} + +NodeBindingsMac::NodeBindingsMac(bool is_browser) + : NodeBindings(is_browser), + loop_(uv_default_loop()), + embed_closed_(false), + has_pending_event_(false) { +} + +NodeBindingsMac::~NodeBindingsMac() { + // Clear uv. + embed_closed_ = true; + uv_thread_join(&embed_thread_); + uv_sem_destroy(&embed_sem_); +} + +void NodeBindingsMac::PrepareMessageLoop() { + DCHECK(!is_browser_ || BrowserThread::CurrentlyOn(BrowserThread::UI)); + + // Add dummy handle for libuv, otherwise libuv would quit when there is + // nothing to do. + uv_async_init(loop_, &dummy_uv_handle_, UvNoOp); + + // Start worker that will interrupt main loop when having uv events. + uv_sem_init(&embed_sem_, 0); + uv_thread_create(&embed_thread_, EmbedThreadRunner, this); +} + +void NodeBindingsMac::RunMessageLoop() { + DCHECK(!is_browser_ || BrowserThread::CurrentlyOn(BrowserThread::UI)); + + loop_->data = this; + loop_->on_watcher_queue_updated = OnWatcherQueueChanged; + + // Run uv loop for once to give the uv__io_poll a chance to add all events. + UvRunOnce(); +} + +void NodeBindingsMac::OnKqueueHasNewEvents() { + DCHECK(!is_browser_ || BrowserThread::CurrentlyOn(BrowserThread::UI)); + + DealWithPendingEvent(); + UvRunOnce(); +} + +void NodeBindingsMac::UvRunOnce() { + DCHECK(!is_browser_ || BrowserThread::CurrentlyOn(BrowserThread::UI)); + + // Enter node context while dealing with uv events. + v8::Context::Scope context_scope(node::g_context); + + // Deal with uv events. + int r = uv_run(loop_, (uv_run_mode)(UV_RUN_ONCE | UV_RUN_NOWAIT)); + if (r == 0 || loop_->stop_flag != 0) + MessageLoop::current()->QuitWhenIdle(); // Quit from uv. + + // Tell the worker thread to continue polling. + uv_sem_post(&embed_sem_); +} + +void NodeBindingsMac::DealWithPendingEvent() { + DCHECK(!is_browser_ || BrowserThread::CurrentlyOn(BrowserThread::UI)); + + if (has_pending_event_) { + has_pending_event_ = false; + + // Enter node context while dealing with uv events. + v8::Context::Scope context_scope(node::g_context); + + // The pending event must be a one shot event, so we don't need to + // delete it from kqueue. + uv__io_t* w = loop_->watchers[event_.ident]; + if (w) { + unsigned int revents; + + if (event_.filter == EVFILT_VNODE) { + w->cb(loop_, w, event_.fflags); + return; + } + + revents = 0; + + if (event_.filter == EVFILT_READ && (w->events & UV__POLLIN)) { + revents |= UV__POLLIN; + w->rcount = event_.data; + } + + if (event_.filter == EVFILT_WRITE && (w->events & UV__POLLOUT)) { + revents |= UV__POLLOUT; + w->wcount = event_.data; + } + + if (event_.flags & EV_ERROR) + revents |= UV__POLLERR; + + if (revents == 0) + return; + + w->cb(loop_, w, revents); + } + } +} + +void NodeBindingsMac::EmbedThreadRunner(void *arg) { + NodeBindingsMac* self = static_cast(arg); + + while (!self->embed_closed_) { + // Wait for the main loop to deal with events. + uv_sem_wait(&self->embed_sem_); + + uv_loop_t* loop = self->loop_; + + struct timespec spec; + int timeout = uv_backend_timeout(loop); + if (timeout != -1) { + spec.tv_sec = timeout / 1000; + spec.tv_nsec = (timeout % 1000) * 1000000; + } + + // Wait for new libuv events. + int r; + do { + r = ::kevent(uv_backend_fd(loop), NULL, 0, &self->event_, 1, + timeout == -1 ? NULL : &spec); + } while (r == -1 && errno == EINTR); + + // We captured a one shot event, we should deal it in main thread because + // next kevent call will not catch it. + if (r == 1 && (self->event_.flags & EV_ONESHOT)) + self->has_pending_event_ = true; + + // Deal with event in main thread. + dispatch_async(dispatch_get_main_queue(), ^{ + self->OnKqueueHasNewEvents(); + }); + } +} + +// static +void NodeBindingsMac::OnWatcherQueueChanged(uv_loop_t* loop) { + NodeBindingsMac* self = static_cast(loop->data); + + DCHECK(!self->is_browser_ || BrowserThread::CurrentlyOn(BrowserThread::UI)); + + // We need to break the io polling in the kqueue thread when loop's watcher + // queue changes, otherwise new events cannot be notified. + uv_async_send(&self->dummy_uv_handle_); +} + +// static +NodeBindings* NodeBindings::Create(bool is_browser) { + return new NodeBindingsMac(is_browser); +} + +} // namespace atom