electron/atom/common/node_bindings.cc

327 lines
10 KiB
C++
Raw Normal View History

// Copyright (c) 2013 GitHub, Inc.
2014-04-25 09:49:37 +00:00
// Use of this source code is governed by the MIT license that can be
2013-04-13 10:39:09 +00:00
// found in the LICENSE file.
2014-03-16 00:30:26 +00:00
#include "atom/common/node_bindings.h"
2013-04-13 10:39:09 +00:00
#include <string>
#include <vector>
#include "atom/common/api/event_emitter_caller.h"
#include "atom/common/api/locker.h"
2015-06-19 15:11:53 +00:00
#include "atom/common/atom_command_line.h"
2015-02-13 05:32:58 +00:00
#include "atom/common/native_mate_converters/file_path_converter.h"
#include "base/base_paths.h"
2016-08-26 22:30:02 +00:00
#include "base/command_line.h"
#include "base/environment.h"
#include "base/files/file_path.h"
#include "base/path_service.h"
2016-11-30 07:30:03 +00:00
#include "base/run_loop.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/trace_event/trace_event.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/common/content_paths.h"
2015-02-13 05:32:58 +00:00
#include "native_mate/dictionary.h"
2013-04-13 10:39:09 +00:00
#include "atom/common/node_includes.h"
// Force all builtin modules to be referenced so they can actually run their
// DSO constructors, see http://git.io/DRIqCg.
#define REFERENCE_MODULE(name) \
extern "C" void _register_ ## name(void); \
void (*fp_register_ ## name)(void) = _register_ ## name
2015-04-08 17:16:10 +00:00
// Electron's builtin modules.
REFERENCE_MODULE(atom_browser_app);
REFERENCE_MODULE(atom_browser_auto_updater);
Implement initial, experimental BrowserView API Right now, `<webview>` is the only way to embed additional content in a `BrowserWindow`. Unfortunately `<webview>` suffers from a [number of problems](https://github.com/electron/electron/issues?utf8=%E2%9C%93&q=is%3Aissue%20is%3Aopen%20label%3Awebview%20). To make matters worse, many of these are upstream Chromium bugs instead of Electron-specific bugs. For us at [Figma](https://www.figma.com), the main issue is very slow performance. Despite the upstream improvements to `<webview>` through the OOPIF work, it is probable that there will continue to be `<webview>`-specific bugs in the future. Therefore, this introduces a `<webview>` alternative to called `BrowserView`, which... - is a thin wrapper around `api::WebContents` (so bugs in `BrowserView` will likely also be bugs in `BrowserWindow` web contents) - is instantiated in the main process like `BrowserWindow` (and unlike `<webview>`, which lives in the DOM of a `BrowserWindow` web contents) - needs to be added to a `BrowserWindow` to display something on the screen This implements the most basic API. The API is expected to evolve and change in the near future and has consequently been marked as experimental. Please do not use this API in production unless you are prepared to deal with breaking changes. In the future, we will want to change the API to support multiple `BrowserView`s per window. We will also want to consider z-ordering auto-resizing, and possibly even nested views.
2017-04-11 17:47:30 +00:00
REFERENCE_MODULE(atom_browser_browser_view);
2014-07-31 07:08:54 +00:00
REFERENCE_MODULE(atom_browser_content_tracing);
2016-01-23 04:02:21 +00:00
REFERENCE_MODULE(atom_browser_debugger);
2015-10-02 09:50:10 +00:00
REFERENCE_MODULE(atom_browser_desktop_capturer);
Implement initial, experimental BrowserView API Right now, `<webview>` is the only way to embed additional content in a `BrowserWindow`. Unfortunately `<webview>` suffers from a [number of problems](https://github.com/electron/electron/issues?utf8=%E2%9C%93&q=is%3Aissue%20is%3Aopen%20label%3Awebview%20). To make matters worse, many of these are upstream Chromium bugs instead of Electron-specific bugs. For us at [Figma](https://www.figma.com), the main issue is very slow performance. Despite the upstream improvements to `<webview>` through the OOPIF work, it is probable that there will continue to be `<webview>`-specific bugs in the future. Therefore, this introduces a `<webview>` alternative to called `BrowserView`, which... - is a thin wrapper around `api::WebContents` (so bugs in `BrowserView` will likely also be bugs in `BrowserWindow` web contents) - is instantiated in the main process like `BrowserWindow` (and unlike `<webview>`, which lives in the DOM of a `BrowserWindow` web contents) - needs to be added to a `BrowserWindow` to display something on the screen This implements the most basic API. The API is expected to evolve and change in the near future and has consequently been marked as experimental. Please do not use this API in production unless you are prepared to deal with breaking changes. In the future, we will want to change the API to support multiple `BrowserView`s per window. We will also want to consider z-ordering auto-resizing, and possibly even nested views.
2017-04-11 17:47:30 +00:00
REFERENCE_MODULE(atom_browser_dialog);
REFERENCE_MODULE(atom_browser_download_item);
Implement initial, experimental BrowserView API Right now, `<webview>` is the only way to embed additional content in a `BrowserWindow`. Unfortunately `<webview>` suffers from a [number of problems](https://github.com/electron/electron/issues?utf8=%E2%9C%93&q=is%3Aissue%20is%3Aopen%20label%3Awebview%20). To make matters worse, many of these are upstream Chromium bugs instead of Electron-specific bugs. For us at [Figma](https://www.figma.com), the main issue is very slow performance. Despite the upstream improvements to `<webview>` through the OOPIF work, it is probable that there will continue to be `<webview>`-specific bugs in the future. Therefore, this introduces a `<webview>` alternative to called `BrowserView`, which... - is a thin wrapper around `api::WebContents` (so bugs in `BrowserView` will likely also be bugs in `BrowserWindow` web contents) - is instantiated in the main process like `BrowserWindow` (and unlike `<webview>`, which lives in the DOM of a `BrowserWindow` web contents) - needs to be added to a `BrowserWindow` to display something on the screen This implements the most basic API. The API is expected to evolve and change in the near future and has consequently been marked as experimental. Please do not use this API in production unless you are prepared to deal with breaking changes. In the future, we will want to change the API to support multiple `BrowserView`s per window. We will also want to consider z-ordering auto-resizing, and possibly even nested views.
2017-04-11 17:47:30 +00:00
REFERENCE_MODULE(atom_browser_global_shortcut);
REFERENCE_MODULE(atom_browser_menu);
REFERENCE_MODULE(atom_browser_net);
REFERENCE_MODULE(atom_browser_power_monitor);
2015-06-21 12:57:42 +00:00
REFERENCE_MODULE(atom_browser_power_save_blocker);
REFERENCE_MODULE(atom_browser_protocol);
REFERENCE_MODULE(atom_browser_render_process_preferences);
2015-09-01 10:02:14 +00:00
REFERENCE_MODULE(atom_browser_session);
2016-04-24 12:13:46 +00:00
REFERENCE_MODULE(atom_browser_system_preferences);
REFERENCE_MODULE(atom_browser_tray);
REFERENCE_MODULE(atom_browser_web_contents);
REFERENCE_MODULE(atom_browser_web_view_manager);
REFERENCE_MODULE(atom_browser_window);
REFERENCE_MODULE(atom_common_asar);
REFERENCE_MODULE(atom_common_clipboard);
REFERENCE_MODULE(atom_common_crash_reporter);
REFERENCE_MODULE(atom_common_native_image);
REFERENCE_MODULE(atom_common_notification);
REFERENCE_MODULE(atom_common_screen);
REFERENCE_MODULE(atom_common_shell);
REFERENCE_MODULE(atom_common_v8_util);
REFERENCE_MODULE(atom_renderer_ipc);
2014-10-24 10:24:12 +00:00
REFERENCE_MODULE(atom_renderer_web_frame);
#undef REFERENCE_MODULE
namespace {
void stop_and_close_uv_loop(uv_loop_t* loop) {
// Close any active handles
uv_stop(loop);
uv_walk(loop, [](uv_handle_t* handle, void*){
if (!uv_is_closing(handle)) {
uv_close(handle, nullptr);
}
}, nullptr);
// Run the loop to let it finish all the closing handles
// NB: after uv_stop(), uv_run(UV_RUN_DEFAULT) returns 0 when that's done
for (;;)
if (!uv_run(loop, UV_RUN_DEFAULT))
break;
DCHECK(!uv_loop_alive(loop));
uv_loop_close(loop);
}
} // namespace
2013-04-13 10:39:09 +00:00
namespace atom {
namespace {
// Convert the given vector to an array of C-strings. The strings in the
// returned vector are only guaranteed valid so long as the vector of strings
// is not modified.
2016-05-23 01:59:39 +00:00
std::unique_ptr<const char*[]> StringVectorToArgArray(
const std::vector<std::string>& vector) {
2016-05-23 01:59:39 +00:00
std::unique_ptr<const char*[]> array(new const char*[vector.size()]);
for (size_t i = 0; i < vector.size(); ++i) {
array[i] = vector[i].c_str();
}
2016-03-08 14:28:53 +00:00
return array;
}
2015-03-29 11:59:16 +00:00
base::FilePath GetResourcesPath(bool is_browser) {
auto command_line = base::CommandLine::ForCurrentProcess();
base::FilePath exec_path(command_line->GetProgram());
2015-02-13 05:32:58 +00:00
PathService::Get(base::FILE_EXE, &exec_path);
2015-03-29 11:59:16 +00:00
2015-02-13 05:32:58 +00:00
base::FilePath resources_path =
#if defined(OS_MACOSX)
is_browser ? exec_path.DirName().DirName().Append("Resources") :
exec_path.DirName().DirName().DirName().DirName().DirName()
.Append("Resources");
#else
exec_path.DirName().Append(FILE_PATH_LITERAL("resources"));
#endif
return resources_path;
}
} // namespace
2017-03-08 08:33:44 +00:00
NodeBindings::NodeBindings(BrowserEnvironment browser_env)
: browser_env_(browser_env),
embed_closed_(false),
uv_env_(nullptr),
weak_factory_(this) {
if (browser_env == WORKER) {
uv_loop_init(&worker_loop_);
uv_loop_ = &worker_loop_;
} else {
uv_loop_ = uv_default_loop();
}
2013-04-13 10:39:09 +00:00
}
NodeBindings::~NodeBindings() {
// Quit the embed thread.
embed_closed_ = true;
uv_sem_post(&embed_sem_);
WakeupEmbedThread();
// Wait for everything to be done.
uv_thread_join(&embed_thread_);
// Clear uv.
uv_sem_destroy(&embed_sem_);
2017-02-28 01:58:52 +00:00
uv_close(reinterpret_cast<uv_handle_t*>(&dummy_uv_handle_), nullptr);
// Clean up worker loop
if (uv_loop_ == &worker_loop_)
stop_and_close_uv_loop(uv_loop_);
2013-04-13 10:39:09 +00:00
}
void NodeBindings::Initialize() {
// Open node's error reporting system for browser process.
2017-03-08 08:33:44 +00:00
node::g_standalone_mode = browser_env_ == BROWSER;
node::g_upstream_node_mode = false;
2015-06-19 14:56:10 +00:00
#if defined(OS_LINUX)
// Get real command line in renderer process forked by zygote.
2017-03-08 08:33:44 +00:00
if (browser_env_ != BROWSER)
2015-06-19 14:56:10 +00:00
AtomCommandLine::InitializeFromCommandLine();
#endif
// Init node.
// (we assume node::Init would not modify the parameters under embedded mode).
node::Init(nullptr, nullptr, nullptr, nullptr);
#if defined(OS_WIN)
// uv_init overrides error mode to suppress the default crash dialog, bring
// it back if user wants to show it.
2016-05-23 01:59:39 +00:00
std::unique_ptr<base::Environment> env(base::Environment::Create());
2017-03-08 08:33:44 +00:00
if (browser_env_ == BROWSER || env->HasVar("ELECTRON_DEFAULT_ERROR_MODE"))
SetErrorMode(GetErrorMode() & ~SEM_NOGPFAULTERRORBOX);
#endif
}
node::Environment* NodeBindings::CreateEnvironment(
v8::Handle<v8::Context> context) {
auto args = AtomCommandLine::argv();
2013-04-13 10:39:09 +00:00
// Feed node the path to initialization script.
2017-03-08 08:33:44 +00:00
base::FilePath::StringType process_type;
switch (browser_env_) {
case BROWSER:
process_type = FILE_PATH_LITERAL("browser");
break;
case RENDERER:
process_type = FILE_PATH_LITERAL("renderer");
break;
case WORKER:
process_type = FILE_PATH_LITERAL("worker");
break;
}
base::FilePath resources_path = GetResourcesPath(browser_env_ == BROWSER);
base::FilePath script_path =
2016-03-31 16:26:11 +00:00
resources_path.Append(FILE_PATH_LITERAL("electron.asar"))
.Append(process_type)
.Append(FILE_PATH_LITERAL("init.js"));
std::string script_path_str = script_path.AsUTF8Unsafe();
args.insert(args.begin() + 1, script_path_str.c_str());
2016-05-23 01:59:39 +00:00
std::unique_ptr<const char*[]> c_argv = StringVectorToArgArray(args);
2015-03-29 11:59:16 +00:00
node::Environment* env = node::CreateEnvironment(
2017-03-10 06:22:25 +00:00
new node::IsolateData(context->GetIsolate(), uv_loop_), context,
args.size(), c_argv.get(), 0, nullptr);
2015-02-13 05:32:58 +00:00
2017-03-08 08:33:44 +00:00
if (browser_env_ == BROWSER) {
// SetAutorunMicrotasks is no longer called in node::CreateEnvironment
// so instead call it here to match expected node behavior
context->GetIsolate()->SetMicrotasksPolicy(v8::MicrotasksPolicy::kExplicit);
} else {
// Node uses the deprecated SetAutorunMicrotasks(false) mode, we should
// switch to use the scoped policy to match blink's behavior.
2016-08-22 10:34:59 +00:00
context->GetIsolate()->SetMicrotasksPolicy(v8::MicrotasksPolicy::kScoped);
}
2015-02-13 05:32:58 +00:00
mate::Dictionary process(context->GetIsolate(), env->process_object());
process.Set("type", process_type);
process.Set("resourcesPath", resources_path);
2016-04-03 06:05:47 +00:00
// Do not set DOM globals for renderer process.
2017-03-08 08:33:44 +00:00
if (browser_env_ != BROWSER)
2016-04-03 06:05:47 +00:00
process.Set("_noBrowserGlobals", resources_path);
// The path to helper app.
base::FilePath helper_exec_path;
PathService::Get(content::CHILD_PROCESS_EXE, &helper_exec_path);
process.Set("helperExecPath", helper_exec_path);
2015-02-13 05:32:58 +00:00
return env;
}
void NodeBindings::LoadEnvironment(node::Environment* env) {
node::LoadEnvironment(env);
mate::EmitEvent(env->isolate(), env->process_object(), "loaded");
}
void NodeBindings::PrepareMessageLoop() {
// Add dummy handle for libuv, otherwise libuv would quit when there is
// nothing to do.
2016-08-03 11:48:41 +00:00
uv_async_init(uv_loop_, &dummy_uv_handle_, nullptr);
// 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 NodeBindings::RunMessageLoop() {
// The MessageLoop should have been created, remember the one in main thread.
2016-11-30 07:30:03 +00:00
task_runner_ = base::ThreadTaskRunnerHandle::Get();
// Run uv loop for once to give the uv__io_poll a chance to add all events.
UvRunOnce();
}
void NodeBindings::UvRunOnce() {
2016-03-27 10:21:12 +00:00
node::Environment* env = uv_env();
2017-03-28 08:19:14 +00:00
// When doing navigation without restarting renderer process, it may happen
// that the node environment is destroyed but the message loop is still there.
// In this case we should not run uv loop.
if (!env)
return;
// Use Locker in browser process.
mate::Locker locker(env->isolate());
v8::HandleScope handle_scope(env->isolate());
// Enter node context while dealing with uv events.
v8::Context::Scope context_scope(env->context());
// Perform microtask checkpoint after running JavaScript.
v8::MicrotasksScope script_scope(env->isolate(),
v8::MicrotasksScope::kRunMicrotasks);
2017-03-08 08:33:44 +00:00
if (browser_env_ != BROWSER)
2016-12-12 21:49:58 +00:00
TRACE_EVENT_BEGIN0("devtools.timeline", "FunctionCall");
// Deal with uv events.
int r = uv_run(uv_loop_, UV_RUN_NOWAIT);
2017-03-08 08:33:44 +00:00
if (browser_env_ != BROWSER)
TRACE_EVENT_END0("devtools.timeline", "FunctionCall");
if (r == 0)
2016-11-30 07:30:03 +00:00
base::RunLoop().QuitWhenIdle(); // Quit from uv.
// Tell the worker thread to continue polling.
uv_sem_post(&embed_sem_);
}
void NodeBindings::WakeupMainThread() {
2016-11-30 07:30:03 +00:00
DCHECK(task_runner_);
task_runner_->PostTask(FROM_HERE, base::Bind(&NodeBindings::UvRunOnce,
weak_factory_.GetWeakPtr()));
}
void NodeBindings::WakeupEmbedThread() {
uv_async_send(&dummy_uv_handle_);
}
// static
void NodeBindings::EmbedThreadRunner(void *arg) {
NodeBindings* self = static_cast<NodeBindings*>(arg);
while (true) {
// Wait for the main loop to deal with events.
uv_sem_wait(&self->embed_sem_);
if (self->embed_closed_)
break;
// Wait for something to happen in uv loop.
// Note that the PollEvents() is implemented by derived classes, so when
// this class is being destructed the PollEvents() would not be available
// anymore. Because of it we must make sure we only invoke PollEvents()
// when this class is alive.
self->PollEvents();
if (self->embed_closed_)
break;
// Deal with event in main thread.
self->WakeupMainThread();
}
}
2013-04-13 13:10:41 +00:00
} // namespace atom