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_

View file

@ -15,6 +15,9 @@ exports.load = (appUrl) => {
height: 600,
autoHideMenuBar: true,
backgroundColor: '#FFFFFF',
webPreferences: {
nodeIntegrationInWorker: true
},
useContentSize: true
}
if (process.platform === 'linux') {

View file

@ -215,6 +215,9 @@ It creates a new `BrowserWindow` with native properties as set by the `options`.
* `devTools` Boolean (optional) - Whether to enable DevTools. If it is set to `false`, can not use `BrowserWindow.webContents.openDevTools()` to open DevTools. Default is `true`.
* `nodeIntegration` Boolean (optional) - Whether node integration is enabled. Default
is `true`.
* `nodeIntegrationInWorker` Boolean (optional) - Whether node integration is
enabled in web workers. Default is `false`. More about this can be found
in [Multithreading](../tutorial/multithreading.md).
* `preload` String (optional) - Specifies a script that will be loaded before other
scripts run in the page. This script will always have access to node APIs
no matter whether node integration is turned on or off. The value should

View file

@ -0,0 +1,50 @@
# Multithreading
With [Web Workers][web-workers], it is possible to run JavaScript in OS-level
threads.
## Multi-threaded Node.js
It is possible to use Node.js features in Electron's Web Workers, to do
so the `nodeIntegrationInWorker` option should be set to `true` in
`webPreferences`.
```javascript
let win = new BrowserWindow({
webPreferences: {
nodeIntegrationInWorker: true
}
})
```
The `nodeIntegrationInWorker` can be used independent of `nodeIntegration`, but
`sandbox` must not be set to `true`.
## Available APIs
All built-in modules of Node.js are supported in Web Workers, and `asar`
archives can still be read with Node.js APIs. However none of Electron's
built-in modules can be used in a multi-threaded environment.
## Native Node.js modules
Any native Node.js module can be loaded directly in Web Workers, but it is
strongly recommended not to do so. Most existing native modules have been
written assuming single-threaded environment, using them in Web Workers will
lead to crashes and memory corruptions.
Note that even if a native Node.js module is thread-safe it's still not safe to
load it in a Web Worker because the `process.dlopen` function is not thread
safe.
The only way to load a native module safely for now, is to make sure the app
loads no native modules after the Web Workers get started.
```javascript
process.dlopen = () => {
throw new Error('Load native module is not safe')
}
let worker = new Worker('script.js')
```
[web-workers]: https://developer.mozilla.org/en/docs/Web/API/Web_Workers_API/Using_web_workers

View file

@ -76,6 +76,7 @@
'lib/renderer/extensions/i18n.js',
'lib/renderer/extensions/storage.js',
'lib/renderer/extensions/web-navigation.js',
'lib/worker/init.js',
],
'js2c_sources': [
'lib/common/asar.js',
@ -479,6 +480,8 @@
'atom/renderer/node_array_buffer_bridge.h',
'atom/renderer/preferences_manager.cc',
'atom/renderer/preferences_manager.h',
'atom/renderer/web_worker_observer.cc',
'atom/renderer/web_worker_observer.h',
'atom/utility/atom_content_utility_client.cc',
'atom/utility/atom_content_utility_client.h',
'chromium_src/chrome/browser/browser_process.cc',

View file

@ -6,7 +6,7 @@ const Module = require('module')
const resolvePromise = Promise.resolve.bind(Promise)
// We modified the original process.argv to let node.js load the
// atom-renderer.js, we need to restore it here.
// init.js, we need to restore it here.
process.argv.splice(1, 1)
// Clear search paths.

37
lib/worker/init.js Normal file
View file

@ -0,0 +1,37 @@
'use strict'
const path = require('path')
const Module = require('module')
// We modified the original process.argv to let node.js load the
// init.js, we need to restore it here.
process.argv.splice(1, 1)
// Clear search paths.
require('../common/reset-search-paths')
// Import common settings.
require('../common/init')
// Expose public APIs.
Module.globalPaths.push(path.join(__dirname, 'api', 'exports'))
// Export node bindings to global.
global.require = require
global.module = module
// Set the __filename to the path of html file if it is file: protocol.
if (self.location.protocol === 'file:') {
let pathname = process.platform === 'win32' && self.location.pathname[0] === '/' ? self.location.pathname.substr(1) : self.location.pathname
global.__filename = path.normalize(decodeURIComponent(pathname))
global.__dirname = path.dirname(global.__filename)
// Set module's filename so relative require can work as expected.
module.filename = global.__filename
// Also search for module under the html file.
module.paths = module.paths.concat(Module._nodeModulePaths(global.__dirname))
} else {
global.__filename = __filename
global.__dirname = __dirname
}

View file

@ -590,6 +590,27 @@ describe('chromium feature', function () {
worker.postMessage(message)
})
it('Worker has no node integration by default', function (done) {
let worker = new Worker('../fixtures/workers/worker_node.js')
worker.onmessage = function (event) {
assert.equal(event.data, 'undefined undefined undefined undefined')
worker.terminate()
done()
}
})
it('Worker has node integration with nodeIntegrationInWorker', function (done) {
let webview = new WebView()
webview.addEventListener('ipc-message', function (e) {
assert.equal(e.channel, 'object function object function')
webview.remove()
done()
})
webview.src = 'file://' + fixtures + '/pages/worker.html'
webview.setAttribute('webpreferences', 'nodeIntegration, nodeIntegrationInWorker')
document.body.appendChild(webview)
})
it('SharedWorker can work', function (done) {
var worker = new SharedWorker('../fixtures/workers/shared_worker.js')
var message = 'ping'
@ -599,6 +620,29 @@ describe('chromium feature', function () {
}
worker.port.postMessage(message)
})
it('SharedWorker has no node integration by default', function (done) {
let worker = new SharedWorker('../fixtures/workers/shared_worker_node.js')
worker.port.onmessage = function (event) {
assert.equal(event.data, 'undefined undefined undefined undefined')
done()
}
})
it('SharedWorker has node integration with nodeIntegrationInWorker', function (done) {
let webview = new WebView()
webview.addEventListener('console-message', function (e) {
console.log(e)
})
webview.addEventListener('ipc-message', function (e) {
assert.equal(e.channel, 'object function object function')
webview.remove()
done()
})
webview.src = 'file://' + fixtures + '/pages/shared_worker.html'
webview.setAttribute('webpreferences', 'nodeIntegration, nodeIntegrationInWorker')
document.body.appendChild(webview)
})
})
describe('iframe', function () {

12
spec/fixtures/pages/shared_worker.html vendored Normal file
View file

@ -0,0 +1,12 @@
<html>
<body>
<script type="text/javascript" charset="utf-8">
const {ipcRenderer} = require('electron')
// Pass a random parameter to create independent worker.
let worker = new SharedWorker(`../workers/shared_worker_node.js?a={Math.random()}`)
worker.port.onmessage = function (event) {
ipcRenderer.sendToHost(event.data)
}
</script>
</body>
</html>

12
spec/fixtures/pages/worker.html vendored Normal file
View file

@ -0,0 +1,12 @@
<html>
<body>
<script type="text/javascript" charset="utf-8">
const {ipcRenderer} = require('electron')
let worker = new Worker(`../workers/worker_node.js`)
worker.onmessage = function (event) {
ipcRenderer.sendToHost(event.data)
worker.terminate()
}
</script>
</body>
</html>

View file

@ -0,0 +1,5 @@
self.onconnect = function (event) {
let port = event.ports[0]
port.start()
port.postMessage([typeof process, typeof setImmediate, typeof global, typeof Buffer].join(' '))
}

1
spec/fixtures/workers/worker_node.js vendored Normal file
View file

@ -0,0 +1 @@
self.postMessage([typeof process, typeof setImmediate, typeof global, typeof Buffer].join(' '))

2
vendor/native_mate vendored

@ -1 +1 @@
Subproject commit ad0fd825663932ee3fa29ff935dfec99933bdd8c
Subproject commit fd0e7dc4ab778f0d1ccda6c9640464ea06ee771e