Merge pull request #8852 from electron/web-worker-hook

Add Node.js integration to WebWorker
This commit is contained in:
Cheng Zhao 2017-03-22 09:01:42 -07:00 committed by GitHub
commit e539129db2
35 changed files with 408 additions and 64 deletions

View file

@ -14,6 +14,7 @@
#include "atom/browser/javascript_environment.h"
#include "atom/browser/node_debugger.h"
#include "atom/common/api/atom_bindings.h"
#include "atom/common/asar/asar_util.h"
#include "atom/common/node_bindings.h"
#include "atom/common/node_includes.h"
#include "base/command_line.h"
@ -60,8 +61,8 @@ AtomBrowserMainParts::AtomBrowserMainParts()
: fake_browser_process_(new BrowserProcess),
exit_code_(nullptr),
browser_(new Browser),
node_bindings_(NodeBindings::Create(true)),
atom_bindings_(new AtomBindings),
node_bindings_(NodeBindings::Create(NodeBindings::BROWSER)),
atom_bindings_(new AtomBindings(uv_default_loop())),
gc_timer_(true, true) {
DCHECK(!self_) << "Cannot have two AtomBrowserMainParts";
self_ = this;
@ -71,6 +72,7 @@ AtomBrowserMainParts::AtomBrowserMainParts()
}
AtomBrowserMainParts::~AtomBrowserMainParts() {
asar::ClearArchives();
// Leak the JavascriptEnvironment on exit.
// This is to work around the bug that V8 would be waiting for background
// tasks to finish on exit, while somehow it waits forever in Electron, more

View file

@ -97,6 +97,10 @@ void WebContentsPreferences::AppendExtraCommandLineSwitches(
command_line->AppendSwitchASCII(switches::kNodeIntegration,
node_integration ? "true" : "false");
// Whether to enable node integration in Worker.
if (web_preferences.GetBoolean(options::kNodeIntegrationInWorker, &b) && b)
command_line->AppendSwitch(switches::kNodeIntegrationInWorker);
// If the `sandbox` option was passed to the BrowserWindow's webPreferences,
// pass `--enable-sandbox` to the renderer so it won't have any node.js
// integration.

View file

@ -141,15 +141,16 @@ void InitAsarSupport(v8::Isolate* isolate,
v8::Local<v8::Value> result = asar_init->Run();
// Initialize asar support.
base::Callback<void(v8::Local<v8::Value>,
v8::Local<v8::Value>,
std::string)> init;
if (mate::ConvertFromV8(isolate, result, &init)) {
if (result->IsFunction()) {
const char* asar_native = reinterpret_cast<const char*>(
static_cast<const unsigned char*>(node::asar_data));
init.Run(process,
require,
std::string(asar_native, sizeof(node::asar_data) - 1));
base::StringPiece asar_data(asar_native, sizeof(node::asar_data) - 1);
v8::Local<v8::Value> args[] = {
process,
require,
mate::ConvertToV8(isolate, asar_data),
};
result.As<v8::Function>()->Call(result, 3, args);
}
}

View file

@ -78,8 +78,8 @@ void FatalErrorCallback(const char* location, const char* message) {
} // namespace
AtomBindings::AtomBindings() {
uv_async_init(uv_default_loop(), &call_next_tick_async_, OnCallNextTick);
AtomBindings::AtomBindings(uv_loop_t* loop) {
uv_async_init(loop, &call_next_tick_async_, OnCallNextTick);
call_next_tick_async_.data = this;
}

View file

@ -20,7 +20,7 @@ namespace atom {
class AtomBindings {
public:
AtomBindings();
explicit AtomBindings(uv_loop_t* loop);
virtual ~AtomBindings();
// Add process.atomBinding function, which behaves like process.binding but

View file

@ -12,6 +12,7 @@
#include "base/files/file_util.h"
#include "base/lazy_instance.h"
#include "base/stl_util.h"
#include "base/threading/thread_local.h"
namespace asar {
@ -19,14 +20,17 @@ namespace {
// The global instance of ArchiveMap, will be destroyed on exit.
typedef std::map<base::FilePath, std::shared_ptr<Archive>> ArchiveMap;
static base::LazyInstance<ArchiveMap> g_archive_map = LAZY_INSTANCE_INITIALIZER;
base::LazyInstance<base::ThreadLocalPointer<ArchiveMap>>::Leaky
g_archive_map_tls = LAZY_INSTANCE_INITIALIZER;
const base::FilePath::CharType kAsarExtension[] = FILE_PATH_LITERAL(".asar");
} // namespace
std::shared_ptr<Archive> GetOrCreateAsarArchive(const base::FilePath& path) {
ArchiveMap& archive_map = *g_archive_map.Pointer();
if (!g_archive_map_tls.Pointer()->Get())
g_archive_map_tls.Pointer()->Set(new ArchiveMap);
ArchiveMap& archive_map = *g_archive_map_tls.Pointer()->Get();
if (!ContainsKey(archive_map, path)) {
std::shared_ptr<Archive> archive(new Archive(path));
if (!archive->Init())
@ -36,6 +40,11 @@ std::shared_ptr<Archive> GetOrCreateAsarArchive(const base::FilePath& path) {
return archive_map[path];
}
void ClearArchives() {
if (g_archive_map_tls.Pointer()->Get())
delete g_archive_map_tls.Pointer()->Get();
}
bool GetAsarArchivePath(const base::FilePath& full_path,
base::FilePath* asar_path,
base::FilePath* relative_path) {

View file

@ -19,6 +19,9 @@ class Archive;
// Gets or creates a new Archive from the path.
std::shared_ptr<Archive> GetOrCreateAsarArchive(const base::FilePath& path);
// Destroy cached Archive objects.
void ClearArchives();
// Separates the path to Archive out.
bool GetAsarArchivePath(const base::FilePath& full_path,
base::FilePath* asar_path,

View file

@ -4,8 +4,6 @@
#include "atom/common/native_mate_converters/callback.h"
#include "content/public/browser/browser_thread.h"
using content::BrowserThread;
namespace mate {

View file

@ -11,6 +11,8 @@
#include "base/bind.h"
#include "base/callback.h"
#include "base/memory/weak_ptr.h"
#include "base/message_loop/message_loop.h"
#include "content/public/browser/browser_thread.h"
#include "native_mate/function_template.h"
#include "native_mate/scoped_persistent.h"

View file

@ -25,8 +25,6 @@
#include "atom/common/node_includes.h"
using content::BrowserThread;
// Force all builtin modules to be referenced so they can actually run their
// DSO constructors, see http://git.io/DRIqCg.
#define REFERENCE_MODULE(name) \
@ -98,9 +96,9 @@ base::FilePath GetResourcesPath(bool is_browser) {
} // namespace
NodeBindings::NodeBindings(bool is_browser)
: is_browser_(is_browser),
uv_loop_(uv_default_loop()),
NodeBindings::NodeBindings(BrowserEnvironment browser_env)
: browser_env_(browser_env),
uv_loop_(browser_env == WORKER ? uv_loop_new() : uv_default_loop()),
embed_closed_(false),
uv_env_(nullptr),
weak_factory_(this) {
@ -118,16 +116,20 @@ NodeBindings::~NodeBindings() {
// Clear uv.
uv_sem_destroy(&embed_sem_);
uv_close(reinterpret_cast<uv_handle_t*>(&dummy_uv_handle_), nullptr);
// Destroy loop.
if (uv_loop_ != uv_default_loop())
uv_loop_delete(uv_loop_);
}
void NodeBindings::Initialize() {
// Open node's error reporting system for browser process.
node::g_standalone_mode = is_browser_;
node::g_standalone_mode = browser_env_ == BROWSER;
node::g_upstream_node_mode = false;
#if defined(OS_LINUX)
// Get real command line in renderer process forked by zygote.
if (!is_browser_)
if (browser_env_ != BROWSER)
AtomCommandLine::InitializeFromCommandLine();
#endif
@ -139,7 +141,7 @@ void NodeBindings::Initialize() {
// uv_init overrides error mode to suppress the default crash dialog, bring
// it back if user wants to show it.
std::unique_ptr<base::Environment> env(base::Environment::Create());
if (is_browser_ || env->HasVar("ELECTRON_DEFAULT_ERROR_MODE"))
if (browser_env_ == BROWSER || env->HasVar("ELECTRON_DEFAULT_ERROR_MODE"))
SetErrorMode(GetErrorMode() & ~SEM_NOGPFAULTERRORBOX);
#endif
}
@ -149,9 +151,19 @@ node::Environment* NodeBindings::CreateEnvironment(
auto args = AtomCommandLine::argv();
// Feed node the path to initialization script.
base::FilePath::StringType process_type = is_browser_ ?
FILE_PATH_LITERAL("browser") : FILE_PATH_LITERAL("renderer");
base::FilePath resources_path = GetResourcesPath(is_browser_);
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 =
resources_path.Append(FILE_PATH_LITERAL("electron.asar"))
.Append(process_type)
@ -161,10 +173,10 @@ node::Environment* NodeBindings::CreateEnvironment(
std::unique_ptr<const char*[]> c_argv = StringVectorToArgArray(args);
node::Environment* env = node::CreateEnvironment(
new node::IsolateData(context->GetIsolate(), uv_default_loop()), context,
new node::IsolateData(context->GetIsolate(), uv_loop_), context,
args.size(), c_argv.get(), 0, nullptr);
if (is_browser_) {
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);
@ -178,7 +190,7 @@ node::Environment* NodeBindings::CreateEnvironment(
process.Set("type", process_type);
process.Set("resourcesPath", resources_path);
// Do not set DOM globals for renderer process.
if (!is_browser_)
if (browser_env_ != BROWSER)
process.Set("_noBrowserGlobals", resources_path);
// The path to helper app.
base::FilePath helper_exec_path;
@ -187,7 +199,7 @@ node::Environment* NodeBindings::CreateEnvironment(
// Set process._debugWaitConnect if --debug-brk was specified to stop
// the debugger on the first line
if (is_browser_ &&
if (browser_env_ == BROWSER &&
base::CommandLine::ForCurrentProcess()->HasSwitch("debug-brk"))
process.Set("_debugWaitConnect", true);
@ -200,8 +212,6 @@ void NodeBindings::LoadEnvironment(node::Environment* env) {
}
void NodeBindings::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(uv_loop_, &dummy_uv_handle_, nullptr);
@ -212,8 +222,6 @@ void NodeBindings::PrepareMessageLoop() {
}
void NodeBindings::RunMessageLoop() {
DCHECK(!is_browser_ || BrowserThread::CurrentlyOn(BrowserThread::UI));
// The MessageLoop should have been created, remember the one in main thread.
task_runner_ = base::ThreadTaskRunnerHandle::Get();
@ -222,8 +230,6 @@ void NodeBindings::RunMessageLoop() {
}
void NodeBindings::UvRunOnce() {
DCHECK(!is_browser_ || BrowserThread::CurrentlyOn(BrowserThread::UI));
node::Environment* env = uv_env();
// Use Locker in browser process.
@ -237,13 +243,13 @@ void NodeBindings::UvRunOnce() {
v8::MicrotasksScope script_scope(env->isolate(),
v8::MicrotasksScope::kRunMicrotasks);
if (!is_browser_)
if (browser_env_ != BROWSER)
TRACE_EVENT_BEGIN0("devtools.timeline", "FunctionCall");
// Deal with uv events.
int r = uv_run(uv_loop_, UV_RUN_NOWAIT);
if (!is_browser_)
if (browser_env_ != BROWSER)
TRACE_EVENT_END0("devtools.timeline", "FunctionCall");
if (r == 0)

View file

@ -23,7 +23,13 @@ namespace atom {
class NodeBindings {
public:
static NodeBindings* Create(bool is_browser);
enum BrowserEnvironment {
BROWSER,
RENDERER,
WORKER,
};
static NodeBindings* Create(BrowserEnvironment browser_env);
virtual ~NodeBindings();
@ -46,8 +52,10 @@ class NodeBindings {
void set_uv_env(node::Environment* env) { uv_env_ = env; }
node::Environment* uv_env() const { return uv_env_; }
uv_loop_t* uv_loop() const { return uv_loop_; }
protected:
explicit NodeBindings(bool is_browser);
explicit NodeBindings(BrowserEnvironment browser_env);
// Called to poll events in new thread.
virtual void PollEvents() = 0;
@ -61,13 +69,13 @@ class NodeBindings {
// Interrupt the PollEvents.
void WakeupEmbedThread();
// Are we running in browser.
bool is_browser_;
// Which environment we are running.
BrowserEnvironment browser_env_;
// Main thread's MessageLoop.
// Current thread's MessageLoop.
scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
// Main thread's libuv loop.
// Current thread's libuv loop.
uv_loop_t* uv_loop_;
private:

View file

@ -8,8 +8,8 @@
namespace atom {
NodeBindingsLinux::NodeBindingsLinux(bool is_browser)
: NodeBindings(is_browser),
NodeBindingsLinux::NodeBindingsLinux(BrowserEnvironment browser_env)
: NodeBindings(browser_env),
epoll_(epoll_create(1)) {
int backend_fd = uv_backend_fd(uv_loop_);
struct epoll_event ev = { 0 };
@ -50,8 +50,8 @@ void NodeBindingsLinux::PollEvents() {
}
// static
NodeBindings* NodeBindings::Create(bool is_browser) {
return new NodeBindingsLinux(is_browser);
NodeBindings* NodeBindings::Create(BrowserEnvironment browser_env) {
return new NodeBindingsLinux(browser_env);
}
} // namespace atom

View file

@ -12,7 +12,7 @@ namespace atom {
class NodeBindingsLinux : public NodeBindings {
public:
explicit NodeBindingsLinux(bool is_browser);
explicit NodeBindingsLinux(BrowserEnvironment browser_env);
virtual ~NodeBindingsLinux();
void RunMessageLoop() override;

View file

@ -14,8 +14,8 @@
namespace atom {
NodeBindingsMac::NodeBindingsMac(bool is_browser)
: NodeBindings(is_browser) {
NodeBindingsMac::NodeBindingsMac(BrowserEnvironment browser_env)
: NodeBindings(browser_env) {
}
NodeBindingsMac::~NodeBindingsMac() {
@ -60,8 +60,8 @@ void NodeBindingsMac::PollEvents() {
}
// static
NodeBindings* NodeBindings::Create(bool is_browser) {
return new NodeBindingsMac(is_browser);
NodeBindings* NodeBindings::Create(BrowserEnvironment browser_env) {
return new NodeBindingsMac(browser_env);
}
} // namespace atom

View file

@ -12,7 +12,7 @@ namespace atom {
class NodeBindingsMac : public NodeBindings {
public:
explicit NodeBindingsMac(bool is_browser);
explicit NodeBindingsMac(BrowserEnvironment browser_env);
virtual ~NodeBindingsMac();
void RunMessageLoop() override;

View file

@ -14,8 +14,8 @@ extern "C" {
namespace atom {
NodeBindingsWin::NodeBindingsWin(bool is_browser)
: NodeBindings(is_browser) {
NodeBindingsWin::NodeBindingsWin(BrowserEnvironment browser_env)
: NodeBindings(browser_env) {
}
NodeBindingsWin::~NodeBindingsWin() {
@ -45,8 +45,8 @@ void NodeBindingsWin::PollEvents() {
}
// static
NodeBindings* NodeBindings::Create(bool is_browser) {
return new NodeBindingsWin(is_browser);
NodeBindings* NodeBindings::Create(BrowserEnvironment browser_env) {
return new NodeBindingsWin(browser_env);
}
} // namespace atom

View file

@ -12,7 +12,7 @@ namespace atom {
class NodeBindingsWin : public NodeBindings {
public:
explicit NodeBindingsWin(bool is_browser);
explicit NodeBindingsWin(BrowserEnvironment browser_env);
virtual ~NodeBindingsWin();
private:

View file

@ -122,6 +122,9 @@ const char kBlinkFeatures[] = "blinkFeatures";
// Disable blink features.
const char kDisableBlinkFeatures[] = "disableBlinkFeatures";
// Enable the node integration in WebWorker.
const char kNodeIntegrationInWorker[] = "nodeIntegrationInWorker";
} // namespace options
namespace switches {
@ -164,6 +167,9 @@ const char kOpenerID[] = "opener-id";
const char kScrollBounce[] = "scroll-bounce";
const char kHiddenPage[] = "hidden-page";
// Command switch passed to renderer process to control nodeIntegration.
const char kNodeIntegrationInWorker[] = "node-integration-in-worker";
// Widevine options
// Path to Widevine CDM binaries.
const char kWidevineCdmPath[] = "widevine-cdm-path";

View file

@ -62,6 +62,7 @@ extern const char kOpenerID[];
extern const char kScrollBounce[];
extern const char kBlinkFeatures[];
extern const char kDisableBlinkFeatures[];
extern const char kNodeIntegrationInWorker[];
} // namespace options
@ -89,6 +90,7 @@ extern const char kGuestInstanceID[];
extern const char kOpenerID[];
extern const char kScrollBounce[];
extern const char kHiddenPage[];
extern const char kNodeIntegrationInWorker[];
extern const char kWidevineCdmPath[];
extern const char kWidevineCdmVersion[];

View file

@ -12,6 +12,7 @@
#include "atom/common/api/api_messages.h"
#include "atom/common/api/atom_bindings.h"
#include "atom/common/api/event_emitter_caller.h"
#include "atom/common/asar/asar_util.h"
#include "atom/common/atom_constants.h"
#include "atom/common/color_util.h"
#include "atom/common/native_mate_converters/value_converter.h"
@ -23,6 +24,7 @@
#include "atom/renderer/guest_view_container.h"
#include "atom/renderer/node_array_buffer_bridge.h"
#include "atom/renderer/preferences_manager.h"
#include "atom/renderer/web_worker_observer.h"
#include "base/command_line.h"
#include "chrome/renderer/media/chrome_key_systems.h"
#include "chrome/renderer/pepper/pepper_helper.h"
@ -215,8 +217,8 @@ std::vector<std::string> ParseSchemesCLISwitch(const char* switch_name) {
AtomRendererClient::AtomRendererClient()
: node_integration_initialized_(false),
node_bindings_(NodeBindings::Create(false)),
atom_bindings_(new AtomBindings) {
node_bindings_(NodeBindings::Create(NodeBindings::RENDERER)),
atom_bindings_(new AtomBindings(uv_default_loop())) {
isolated_world_ = base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kContextIsolation);
// Parse --standard-schemes=scheme1,scheme2
@ -227,6 +229,7 @@ AtomRendererClient::AtomRendererClient()
}
AtomRendererClient::~AtomRendererClient() {
asar::ClearArchives();
}
void AtomRendererClient::RenderThreadStarted() {
@ -436,6 +439,22 @@ void AtomRendererClient::AddSupportedKeySystems(
AddChromeKeySystems(key_systems);
}
void AtomRendererClient::DidInitializeWorkerContextOnWorkerThread(
v8::Local<v8::Context> context) {
if (base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kNodeIntegrationInWorker)) {
WebWorkerObserver::GetCurrent()->ContextCreated(context);
}
}
void AtomRendererClient::WillDestroyWorkerContextOnWorkerThread(
v8::Local<v8::Context> context) {
if (base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kNodeIntegrationInWorker)) {
WebWorkerObserver::GetCurrent()->ContextWillDestroy(context);
}
}
v8::Local<v8::Context> AtomRendererClient::GetContext(
blink::WebFrame* frame, v8::Isolate* isolate) {
if (isolated_world())

View file

@ -65,6 +65,10 @@ class AtomRendererClient : public content::ContentRendererClient {
void AddSupportedKeySystems(
std::vector<std::unique_ptr<::media::KeySystemProperties>>* key_systems)
override;
void DidInitializeWorkerContextOnWorkerThread(
v8::Local<v8::Context> context) override;
void WillDestroyWorkerContextOnWorkerThread(
v8::Local<v8::Context> context) override;
// Whether the node integration has been initialized.
bool node_integration_initialized_;

View file

@ -0,0 +1,73 @@
// 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 "atom/renderer/web_worker_observer.h"
#include "atom/common/api/atom_bindings.h"
#include "atom/common/api/event_emitter_caller.h"
#include "atom/common/asar/asar_util.h"
#include "atom/common/node_bindings.h"
#include "base/lazy_instance.h"
#include "base/threading/thread_local.h"
#include "atom/common/node_includes.h"
namespace atom {
namespace {
static base::LazyInstance<base::ThreadLocalPointer<WebWorkerObserver>>
lazy_tls = LAZY_INSTANCE_INITIALIZER;
} // namespace
// static
WebWorkerObserver* WebWorkerObserver::GetCurrent() {
WebWorkerObserver* self = lazy_tls.Pointer()->Get();
return self ? self : new WebWorkerObserver;
}
WebWorkerObserver::WebWorkerObserver()
: node_bindings_(NodeBindings::Create(NodeBindings::WORKER)),
atom_bindings_(new AtomBindings(node_bindings_->uv_loop())) {
lazy_tls.Pointer()->Set(this);
}
WebWorkerObserver::~WebWorkerObserver() {
lazy_tls.Pointer()->Set(nullptr);
node::FreeEnvironment(node_bindings_->uv_env());
asar::ClearArchives();
}
void WebWorkerObserver::ContextCreated(v8::Local<v8::Context> context) {
v8::Context::Scope context_scope(context);
// Start the embed thread.
node_bindings_->PrepareMessageLoop();
// Setup node environment for each window.
node::Environment* env = node_bindings_->CreateEnvironment(context);
// Add Electron extended APIs.
atom_bindings_->BindTo(env->isolate(), env->process_object());
// Load everything.
node_bindings_->LoadEnvironment(env);
// Make uv loop being wrapped by window context.
node_bindings_->set_uv_env(env);
// Give the node loop a run to make sure everything is ready.
node_bindings_->RunMessageLoop();
}
void WebWorkerObserver::ContextWillDestroy(v8::Local<v8::Context> context) {
node::Environment* env = node::Environment::GetCurrent(context);
if (env)
mate::EmitEvent(env->isolate(), env->process_object(), "exit");
delete this;
}
} // namespace atom

View file

@ -0,0 +1,37 @@
// 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_RENDERER_WEB_WORKER_OBSERVER_H_
#define ATOM_RENDERER_WEB_WORKER_OBSERVER_H_
#include "base/macros.h"
#include "v8/include/v8.h"
namespace atom {
class AtomBindings;
class NodeBindings;
// Watches for WebWorker and insert node integration to it.
class WebWorkerObserver {
public:
// Returns the WebWorkerObserver for current worker thread.
static WebWorkerObserver* GetCurrent();
void ContextCreated(v8::Local<v8::Context> context);
void ContextWillDestroy(v8::Local<v8::Context> context);
private:
WebWorkerObserver();
~WebWorkerObserver();
std::unique_ptr<NodeBindings> node_bindings_;
std::unique_ptr<AtomBindings> atom_bindings_;
DISALLOW_COPY_AND_ASSIGN(WebWorkerObserver);
};
} // namespace atom
#endif // ATOM_RENDERER_WEB_WORKER_OBSERVER_H_