From 736befe90f74ef6c27e5f6ecad7fa9a3c7ef8509 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 8 Dec 2016 12:55:00 -0800 Subject: [PATCH 01/44] Add initial support for loading into isolated world --- atom/renderer/atom_isolated_world.cc | 61 +++++++++++++++++++++++++++ atom/renderer/atom_isolated_world.h | 35 +++++++++++++++ atom/renderer/atom_renderer_client.cc | 5 ++- atom/renderer/atom_renderer_client.h | 2 + filenames.gypi | 2 + 5 files changed, 104 insertions(+), 1 deletion(-) create mode 100644 atom/renderer/atom_isolated_world.cc create mode 100644 atom/renderer/atom_isolated_world.h diff --git a/atom/renderer/atom_isolated_world.cc b/atom/renderer/atom_isolated_world.cc new file mode 100644 index 0000000000..a0754627e3 --- /dev/null +++ b/atom/renderer/atom_isolated_world.cc @@ -0,0 +1,61 @@ +// Copyright (c) 2016 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#include "atom/renderer/atom_isolated_world.h" + +#include "third_party/WebKit/public/web/WebLocalFrame.h" +#include "third_party/WebKit/public/web/WebScriptSource.h" + +namespace atom { + +NodeBindings* AtomIsolatedWorld::node_bindings_ = nullptr; +node::Environment* AtomIsolatedWorld::env_ = nullptr; + +AtomIsolatedWorld::AtomIsolatedWorld(NodeBindings* node_bindings) : + v8::Extension("ElectronIsolatedWorldExtension", + "native function SetupNode();") { + node_bindings_ = node_bindings; + env_ = nullptr; +} + +AtomIsolatedWorld::~AtomIsolatedWorld() { + node_bindings_ = nullptr; + env_ = nullptr; +} + +node::Environment* AtomIsolatedWorld::CreateEnvironment( + content::RenderFrame* frame) { + blink::WebScriptSource source("SetupNode()"); + frame->GetWebFrame()->executeScriptInIsolatedWorld( + 1, + &source, + 1, + 1, + nullptr); + return env_; +} + +v8::Local AtomIsolatedWorld::GetNativeFunctionTemplate( + v8::Isolate* isolate, + v8::Local name) { + if (name->Equals(v8::String::NewFromUtf8(isolate, "SetupNode"))) + return v8::FunctionTemplate::New(isolate, SetupNode); + return v8::Local(); +} + +// static +void AtomIsolatedWorld::SetupNode( + const v8::FunctionCallbackInfo& args) { + env_ = node_bindings_->CreateEnvironment( + args.GetIsolate()->GetCurrentContext()); +} + +// static +AtomIsolatedWorld* AtomIsolatedWorld::Create(NodeBindings* node_bindings) { + AtomIsolatedWorld* world = new AtomIsolatedWorld(node_bindings); + content::RenderThread::Get()->RegisterExtension(world); + return world; +} + +} // namespace atom diff --git a/atom/renderer/atom_isolated_world.h b/atom/renderer/atom_isolated_world.h new file mode 100644 index 0000000000..e6b4028087 --- /dev/null +++ b/atom/renderer/atom_isolated_world.h @@ -0,0 +1,35 @@ +// Copyright (c) 2016 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#ifndef ATOM_RENDERER_ATOM_ISOLATED_WORLD_H_ +#define ATOM_RENDERER_ATOM_ISOLATED_WORLD_H_ + +#include "atom/common/node_bindings.h" +#include "content/public/renderer/render_frame.h" +#include "content/public/renderer/render_thread.h" + +namespace atom { + +class AtomIsolatedWorld : public v8::Extension { + public: + explicit AtomIsolatedWorld(NodeBindings* node_bindings); + ~AtomIsolatedWorld() override; + node::Environment* CreateEnvironment(content::RenderFrame* frame); + v8::Local GetNativeFunctionTemplate( + v8::Isolate* isolate, + v8::Local name) override; + + static AtomIsolatedWorld* Create(NodeBindings* node_bindings); + + private: + static void SetupNode(const v8::FunctionCallbackInfo& args); + + private: + static NodeBindings* node_bindings_; + static node::Environment* env_; +}; + +} // namespace atom + +#endif // ATOM_RENDERER_ATOM_ISOLATED_WORLD_H_ diff --git a/atom/renderer/atom_renderer_client.cc b/atom/renderer/atom_renderer_client.cc index 5f2aec74e3..1f7de16869 100644 --- a/atom/renderer/atom_renderer_client.cc +++ b/atom/renderer/atom_renderer_client.cc @@ -14,6 +14,7 @@ #include "atom/common/native_mate_converters/value_converter.h" #include "atom/common/node_bindings.h" #include "atom/common/options_switches.h" +#include "atom/renderer/atom_isolated_world.h" #include "atom/renderer/atom_render_view_observer.h" #include "atom/renderer/content_settings_observer.h" #include "atom/renderer/guest_view_container.h" @@ -147,6 +148,8 @@ void AtomRendererClient::RenderThreadStarted() { blink::WebCustomElement::addEmbedderCustomElementName("webview"); blink::WebCustomElement::addEmbedderCustomElementName("browserplugin"); + isolated_world_.reset(AtomIsolatedWorld::Create(node_bindings_.get())); + OverrideNodeArrayBuffer(); preferences_manager_.reset(new PreferencesManager); @@ -275,7 +278,7 @@ void AtomRendererClient::DidCreateScriptContext( } // Setup node environment for each window. - node::Environment* env = node_bindings_->CreateEnvironment(context); + node::Environment* env = isolated_world_->CreateEnvironment(render_frame); // Add Electron extended APIs. atom_bindings_->BindTo(env->isolate(), env->process_object()); diff --git a/atom/renderer/atom_renderer_client.h b/atom/renderer/atom_renderer_client.h index 5419692d2a..9f47678fec 100644 --- a/atom/renderer/atom_renderer_client.h +++ b/atom/renderer/atom_renderer_client.h @@ -13,6 +13,7 @@ namespace atom { class AtomBindings; +class AtomIsolatedWorld; class PreferencesManager; class NodeBindings; @@ -64,6 +65,7 @@ class AtomRendererClient : public content::ContentRendererClient { std::unique_ptr node_bindings_; std::unique_ptr atom_bindings_; std::unique_ptr preferences_manager_; + std::unique_ptr isolated_world_; DISALLOW_COPY_AND_ASSIGN(AtomRendererClient); }; diff --git a/filenames.gypi b/filenames.gypi index b7a53a4840..e1310e2444 100644 --- a/filenames.gypi +++ b/filenames.gypi @@ -453,6 +453,8 @@ 'atom/renderer/api/atom_api_spell_check_client.h', 'atom/renderer/api/atom_api_web_frame.cc', 'atom/renderer/api/atom_api_web_frame.h', + 'atom/renderer/atom_isolated_world.cc', + 'atom/renderer/atom_isolated_world.h', 'atom/renderer/atom_render_view_observer.cc', 'atom/renderer/atom_render_view_observer.h', 'atom/renderer/atom_renderer_client.cc', From 08b203fed10d149458da89148b2479cd5b103258 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 8 Dec 2016 13:01:21 -0800 Subject: [PATCH 02/44] Match extension name to class name --- atom/renderer/atom_isolated_world.cc | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/atom/renderer/atom_isolated_world.cc b/atom/renderer/atom_isolated_world.cc index a0754627e3..1815194ef5 100644 --- a/atom/renderer/atom_isolated_world.cc +++ b/atom/renderer/atom_isolated_world.cc @@ -13,8 +13,7 @@ NodeBindings* AtomIsolatedWorld::node_bindings_ = nullptr; node::Environment* AtomIsolatedWorld::env_ = nullptr; AtomIsolatedWorld::AtomIsolatedWorld(NodeBindings* node_bindings) : - v8::Extension("ElectronIsolatedWorldExtension", - "native function SetupNode();") { + v8::Extension("AtomIsolatedWorld", "native function SetupNode();") { node_bindings_ = node_bindings; env_ = nullptr; } From cdf33ff3dc3ca6a0eb6493ee21e54893aee83e8a Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 8 Dec 2016 13:03:39 -0800 Subject: [PATCH 03/44] Add null guards --- atom/renderer/atom_isolated_world.cc | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/atom/renderer/atom_isolated_world.cc b/atom/renderer/atom_isolated_world.cc index 1815194ef5..2cd8be5e71 100644 --- a/atom/renderer/atom_isolated_world.cc +++ b/atom/renderer/atom_isolated_world.cc @@ -15,16 +15,15 @@ node::Environment* AtomIsolatedWorld::env_ = nullptr; AtomIsolatedWorld::AtomIsolatedWorld(NodeBindings* node_bindings) : v8::Extension("AtomIsolatedWorld", "native function SetupNode();") { node_bindings_ = node_bindings; - env_ = nullptr; } AtomIsolatedWorld::~AtomIsolatedWorld() { node_bindings_ = nullptr; - env_ = nullptr; } node::Environment* AtomIsolatedWorld::CreateEnvironment( content::RenderFrame* frame) { + env_ = nullptr; blink::WebScriptSource source("SetupNode()"); frame->GetWebFrame()->executeScriptInIsolatedWorld( 1, @@ -46,8 +45,9 @@ v8::Local AtomIsolatedWorld::GetNativeFunctionTemplate( // static void AtomIsolatedWorld::SetupNode( const v8::FunctionCallbackInfo& args) { - env_ = node_bindings_->CreateEnvironment( - args.GetIsolate()->GetCurrentContext()); + if (node_bindings_ != nullptr) + env_ = node_bindings_->CreateEnvironment( + args.GetIsolate()->GetCurrentContext()); } // static From 4bca6fe6728f68d83ae66c4164cb3f3654f88ecb Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 8 Dec 2016 15:33:51 -0800 Subject: [PATCH 04/44] Add isolated world web preference option --- atom/browser/web_contents_preferences.cc | 5 +++++ atom/common/options_switches.cc | 1 + atom/common/options_switches.h | 1 + 3 files changed, 7 insertions(+) diff --git a/atom/browser/web_contents_preferences.cc b/atom/browser/web_contents_preferences.cc index dec52501cf..300c2b88ae 100644 --- a/atom/browser/web_contents_preferences.cc +++ b/atom/browser/web_contents_preferences.cc @@ -119,6 +119,11 @@ void WebContentsPreferences::AppendExtraCommandLineSwitches( LOG(ERROR) << "preload url must be file:// protocol."; } + // Run Electron APIs and preload script in isolated world + bool isolated; + if (web_preferences.GetBoolean("isolated", &isolated) && isolated) + command_line->AppendSwitch(switches::kIsolatedWorld); + // --background-color. std::string color; if (web_preferences.GetString(options::kBackgroundColor, &color)) diff --git a/atom/common/options_switches.cc b/atom/common/options_switches.cc index 30aa48b987..cb33972dc4 100644 --- a/atom/common/options_switches.cc +++ b/atom/common/options_switches.cc @@ -163,6 +163,7 @@ const char kZoomFactor[] = "zoom-factor"; const char kPreloadScript[] = "preload"; const char kPreloadURL[] = "preload-url"; const char kNodeIntegration[] = "node-integration"; +const char kIsolatedWorld[] = "isolated-world"; const char kGuestInstanceID[] = "guest-instance-id"; const char kOpenerID[] = "opener-id"; const char kScrollBounce[] = "scroll-bounce"; diff --git a/atom/common/options_switches.h b/atom/common/options_switches.h index c930cabe34..3ce8dadb32 100644 --- a/atom/common/options_switches.h +++ b/atom/common/options_switches.h @@ -86,6 +86,7 @@ extern const char kZoomFactor[]; extern const char kPreloadScript[]; extern const char kPreloadURL[]; extern const char kNodeIntegration[]; +extern const char kIsolatedWorld[]; extern const char kGuestInstanceID[]; extern const char kOpenerID[]; extern const char kScrollBounce[]; From d194a84ae40ee175ba60244f430fa4194ba43626 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 8 Dec 2016 15:51:17 -0800 Subject: [PATCH 05/44] Setup isolated context from AtomRenderFrameObserver --- atom/renderer/atom_isolated_world.cc | 60 ----------------------- atom/renderer/atom_isolated_world.h | 35 -------------- atom/renderer/atom_renderer_client.cc | 68 +++++++++++++++++++++------ atom/renderer/atom_renderer_client.h | 2 - filenames.gypi | 2 - 5 files changed, 53 insertions(+), 114 deletions(-) delete mode 100644 atom/renderer/atom_isolated_world.cc delete mode 100644 atom/renderer/atom_isolated_world.h diff --git a/atom/renderer/atom_isolated_world.cc b/atom/renderer/atom_isolated_world.cc deleted file mode 100644 index 2cd8be5e71..0000000000 --- a/atom/renderer/atom_isolated_world.cc +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright (c) 2016 GitHub, Inc. -// Use of this source code is governed by the MIT license that can be -// found in the LICENSE file. - -#include "atom/renderer/atom_isolated_world.h" - -#include "third_party/WebKit/public/web/WebLocalFrame.h" -#include "third_party/WebKit/public/web/WebScriptSource.h" - -namespace atom { - -NodeBindings* AtomIsolatedWorld::node_bindings_ = nullptr; -node::Environment* AtomIsolatedWorld::env_ = nullptr; - -AtomIsolatedWorld::AtomIsolatedWorld(NodeBindings* node_bindings) : - v8::Extension("AtomIsolatedWorld", "native function SetupNode();") { - node_bindings_ = node_bindings; -} - -AtomIsolatedWorld::~AtomIsolatedWorld() { - node_bindings_ = nullptr; -} - -node::Environment* AtomIsolatedWorld::CreateEnvironment( - content::RenderFrame* frame) { - env_ = nullptr; - blink::WebScriptSource source("SetupNode()"); - frame->GetWebFrame()->executeScriptInIsolatedWorld( - 1, - &source, - 1, - 1, - nullptr); - return env_; -} - -v8::Local AtomIsolatedWorld::GetNativeFunctionTemplate( - v8::Isolate* isolate, - v8::Local name) { - if (name->Equals(v8::String::NewFromUtf8(isolate, "SetupNode"))) - return v8::FunctionTemplate::New(isolate, SetupNode); - return v8::Local(); -} - -// static -void AtomIsolatedWorld::SetupNode( - const v8::FunctionCallbackInfo& args) { - if (node_bindings_ != nullptr) - env_ = node_bindings_->CreateEnvironment( - args.GetIsolate()->GetCurrentContext()); -} - -// static -AtomIsolatedWorld* AtomIsolatedWorld::Create(NodeBindings* node_bindings) { - AtomIsolatedWorld* world = new AtomIsolatedWorld(node_bindings); - content::RenderThread::Get()->RegisterExtension(world); - return world; -} - -} // namespace atom diff --git a/atom/renderer/atom_isolated_world.h b/atom/renderer/atom_isolated_world.h deleted file mode 100644 index e6b4028087..0000000000 --- a/atom/renderer/atom_isolated_world.h +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (c) 2016 GitHub, Inc. -// Use of this source code is governed by the MIT license that can be -// found in the LICENSE file. - -#ifndef ATOM_RENDERER_ATOM_ISOLATED_WORLD_H_ -#define ATOM_RENDERER_ATOM_ISOLATED_WORLD_H_ - -#include "atom/common/node_bindings.h" -#include "content/public/renderer/render_frame.h" -#include "content/public/renderer/render_thread.h" - -namespace atom { - -class AtomIsolatedWorld : public v8::Extension { - public: - explicit AtomIsolatedWorld(NodeBindings* node_bindings); - ~AtomIsolatedWorld() override; - node::Environment* CreateEnvironment(content::RenderFrame* frame); - v8::Local GetNativeFunctionTemplate( - v8::Isolate* isolate, - v8::Local name) override; - - static AtomIsolatedWorld* Create(NodeBindings* node_bindings); - - private: - static void SetupNode(const v8::FunctionCallbackInfo& args); - - private: - static NodeBindings* node_bindings_; - static node::Environment* env_; -}; - -} // namespace atom - -#endif // ATOM_RENDERER_ATOM_ISOLATED_WORLD_H_ diff --git a/atom/renderer/atom_renderer_client.cc b/atom/renderer/atom_renderer_client.cc index 1f7de16869..e2b5459c6f 100644 --- a/atom/renderer/atom_renderer_client.cc +++ b/atom/renderer/atom_renderer_client.cc @@ -14,7 +14,6 @@ #include "atom/common/native_mate_converters/value_converter.h" #include "atom/common/node_bindings.h" #include "atom/common/options_switches.h" -#include "atom/renderer/atom_isolated_world.h" #include "atom/renderer/atom_render_view_observer.h" #include "atom/renderer/content_settings_observer.h" #include "atom/renderer/guest_view_container.h" @@ -62,10 +61,13 @@ namespace { class AtomRenderFrameObserver : public content::RenderFrameObserver { public: AtomRenderFrameObserver(content::RenderFrame* frame, - AtomRendererClient* renderer_client) + AtomRendererClient* renderer_client, + bool isolated_world) : content::RenderFrameObserver(frame), render_frame_(frame), - world_id_(-1), + isolated_world_(isolated_world), + main_context_created_(false), + isolated_context_created_(false), renderer_client_(renderer_client) {} // content::RenderFrameObserver: @@ -73,19 +75,51 @@ class AtomRenderFrameObserver : public content::RenderFrameObserver { renderer_client_->DidClearWindowObject(render_frame_); } + void CreateIsolatedWorldContext() { + blink::WebScriptSource source("void 0"); + render_frame_->GetWebFrame()->executeScriptInIsolatedWorld( + 1, + &source, + 1, + 1, + nullptr); + } + + bool IsMainWorld(int world_id) { + return world_id == 0; + } + + bool IsIsolatedWorld(int world_id) { + return world_id == 1; + } + void DidCreateScriptContext(v8::Handle context, int extension_group, int world_id) override { - if (world_id_ != -1 && world_id_ != world_id) - return; - world_id_ = world_id; - renderer_client_->DidCreateScriptContext(context, render_frame_); + if (!main_context_created_ && IsMainWorld(world_id)) { + main_context_created_ = true; + if (isolated_world_) { + CreateIsolatedWorldContext(); + } else { + renderer_client_->DidCreateScriptContext(context, render_frame_); + } + } + + if (isolated_world_ && !isolated_context_created_ + && IsIsolatedWorld(world_id)) { + isolated_context_created_ = true; + renderer_client_->DidCreateScriptContext(context, render_frame_); + } } + void WillReleaseScriptContext(v8::Local context, int world_id) override { - if (world_id_ != world_id) - return; - renderer_client_->WillReleaseScriptContext(context, render_frame_); + if (isolated_world_) { + if (IsIsolatedWorld(world_id)) + renderer_client_->WillReleaseScriptContext(context, render_frame_); + } else if (IsMainWorld(world_id)) { + renderer_client_->WillReleaseScriptContext(context, render_frame_); + } } void OnDestruct() override { @@ -94,7 +128,9 @@ class AtomRenderFrameObserver : public content::RenderFrameObserver { private: content::RenderFrame* render_frame_; - int world_id_; + bool isolated_world_; + bool main_context_created_; + bool isolated_context_created_; AtomRendererClient* renderer_client_; DISALLOW_COPY_AND_ASSIGN(AtomRenderFrameObserver); @@ -148,8 +184,6 @@ void AtomRendererClient::RenderThreadStarted() { blink::WebCustomElement::addEmbedderCustomElementName("webview"); blink::WebCustomElement::addEmbedderCustomElementName("browserplugin"); - isolated_world_.reset(AtomIsolatedWorld::Create(node_bindings_.get())); - OverrideNodeArrayBuffer(); preferences_manager_.reset(new PreferencesManager); @@ -181,7 +215,11 @@ void AtomRendererClient::RenderThreadStarted() { void AtomRendererClient::RenderFrameCreated( content::RenderFrame* render_frame) { new PepperHelper(render_frame); - new AtomRenderFrameObserver(render_frame, this); + + bool isolated_world = base::CommandLine::ForCurrentProcess()->HasSwitch( + switches::kIsolatedWorld); + new AtomRenderFrameObserver(render_frame, this, isolated_world); + new ContentSettingsObserver(render_frame); // Allow file scheme to handle service worker by default. @@ -278,7 +316,7 @@ void AtomRendererClient::DidCreateScriptContext( } // Setup node environment for each window. - node::Environment* env = isolated_world_->CreateEnvironment(render_frame); + node::Environment* env = node_bindings_->CreateEnvironment(context); // Add Electron extended APIs. atom_bindings_->BindTo(env->isolate(), env->process_object()); diff --git a/atom/renderer/atom_renderer_client.h b/atom/renderer/atom_renderer_client.h index 9f47678fec..5419692d2a 100644 --- a/atom/renderer/atom_renderer_client.h +++ b/atom/renderer/atom_renderer_client.h @@ -13,7 +13,6 @@ namespace atom { class AtomBindings; -class AtomIsolatedWorld; class PreferencesManager; class NodeBindings; @@ -65,7 +64,6 @@ class AtomRendererClient : public content::ContentRendererClient { std::unique_ptr node_bindings_; std::unique_ptr atom_bindings_; std::unique_ptr preferences_manager_; - std::unique_ptr isolated_world_; DISALLOW_COPY_AND_ASSIGN(AtomRendererClient); }; diff --git a/filenames.gypi b/filenames.gypi index e1310e2444..b7a53a4840 100644 --- a/filenames.gypi +++ b/filenames.gypi @@ -453,8 +453,6 @@ 'atom/renderer/api/atom_api_spell_check_client.h', 'atom/renderer/api/atom_api_web_frame.cc', 'atom/renderer/api/atom_api_web_frame.h', - 'atom/renderer/atom_isolated_world.cc', - 'atom/renderer/atom_isolated_world.h', 'atom/renderer/atom_render_view_observer.cc', 'atom/renderer/atom_render_view_observer.h', 'atom/renderer/atom_renderer_client.cc', From 073d8c21774a5873bc054602532ed46c92917d4f Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 8 Dec 2016 16:06:17 -0800 Subject: [PATCH 06/44] Add world id constants --- atom/renderer/atom_renderer_client.cc | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/atom/renderer/atom_renderer_client.cc b/atom/renderer/atom_renderer_client.cc index e2b5459c6f..ac66403d4f 100644 --- a/atom/renderer/atom_renderer_client.cc +++ b/atom/renderer/atom_renderer_client.cc @@ -78,19 +78,15 @@ class AtomRenderFrameObserver : public content::RenderFrameObserver { void CreateIsolatedWorldContext() { blink::WebScriptSource source("void 0"); render_frame_->GetWebFrame()->executeScriptInIsolatedWorld( - 1, - &source, - 1, - 1, - nullptr); + kIsolatedWorldId, &source, 1, 1); } bool IsMainWorld(int world_id) { - return world_id == 0; + return world_id == kMainWorldId; } bool IsIsolatedWorld(int world_id) { - return world_id == 1; + return world_id == kIsolatedWorldId; } void DidCreateScriptContext(v8::Handle context, @@ -133,6 +129,9 @@ class AtomRenderFrameObserver : public content::RenderFrameObserver { bool isolated_context_created_; AtomRendererClient* renderer_client_; + const int kMainWorldId = 0; + const int kIsolatedWorldId = 999; + DISALLOW_COPY_AND_ASSIGN(AtomRenderFrameObserver); }; From 78e0b80dc73773e37d03b66ab98bd4549dd07e2b Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 8 Dec 2016 16:58:27 -0800 Subject: [PATCH 07/44] Support IPC messages in isolated context --- atom/renderer/atom_render_view_observer.cc | 3 ++- atom/renderer/atom_render_view_observer.h | 2 ++ atom/renderer/atom_renderer_client.cc | 4 ++++ atom/renderer/atom_renderer_client.h | 2 ++ 4 files changed, 10 insertions(+), 1 deletion(-) diff --git a/atom/renderer/atom_render_view_observer.cc b/atom/renderer/atom_render_view_observer.cc index edc7306b5d..65cc9b5c51 100644 --- a/atom/renderer/atom_render_view_observer.cc +++ b/atom/renderer/atom_render_view_observer.cc @@ -76,6 +76,7 @@ AtomRenderViewObserver::AtomRenderViewObserver( content::RenderView* render_view, AtomRendererClient* renderer_client) : content::RenderViewObserver(render_view), + renderer_client_(renderer_client), document_created_(false) { // Initialise resource for directory listing. net::NetModule::SetResourceProvider(NetResourceProvider); @@ -93,7 +94,7 @@ void AtomRenderViewObserver::EmitIPCEvent(blink::WebFrame* frame, v8::Isolate* isolate = blink::mainThreadIsolate(); v8::HandleScope handle_scope(isolate); - v8::Local context = frame->mainWorldScriptContext(); + v8::Local context = renderer_client_->GetContext(); v8::Context::Scope context_scope(context); // Only emit IPC event for context with node integration. diff --git a/atom/renderer/atom_render_view_observer.h b/atom/renderer/atom_render_view_observer.h index ce426060b6..e642bbe942 100644 --- a/atom/renderer/atom_render_view_observer.h +++ b/atom/renderer/atom_render_view_observer.h @@ -40,6 +40,8 @@ class AtomRenderViewObserver : public content::RenderViewObserver { const base::string16& channel, const base::ListValue& args); + AtomRendererClient* renderer_client_; + // Whether the document object has been created. bool document_created_; diff --git a/atom/renderer/atom_renderer_client.cc b/atom/renderer/atom_renderer_client.cc index ac66403d4f..4339a19d47 100644 --- a/atom/renderer/atom_renderer_client.cc +++ b/atom/renderer/atom_renderer_client.cc @@ -334,6 +334,10 @@ void AtomRendererClient::DidCreateScriptContext( } } +v8::Local AtomRendererClient::GetContext() { + return node_bindings_->uv_env()->context(); +} + void AtomRendererClient::WillReleaseScriptContext( v8::Handle context, content::RenderFrame* render_frame) { // Only allow node integration for the main frame, unless it is a devtools diff --git a/atom/renderer/atom_renderer_client.h b/atom/renderer/atom_renderer_client.h index 5419692d2a..7fab5dde86 100644 --- a/atom/renderer/atom_renderer_client.h +++ b/atom/renderer/atom_renderer_client.h @@ -27,6 +27,8 @@ class AtomRendererClient : public content::ContentRendererClient { void WillReleaseScriptContext( v8::Handle context, content::RenderFrame* render_frame); + v8::Local GetContext(); + private: enum NodeIntegration { ALL, From c5e68ec16554fa23fb13bdfaccfb6ff6cef60327 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 13 Dec 2016 11:07:17 -0800 Subject: [PATCH 08/44] :art: --- atom/renderer/atom_renderer_client.cc | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/atom/renderer/atom_renderer_client.cc b/atom/renderer/atom_renderer_client.cc index 4339a19d47..1215c80ca8 100644 --- a/atom/renderer/atom_renderer_client.cc +++ b/atom/renderer/atom_renderer_client.cc @@ -110,12 +110,10 @@ class AtomRenderFrameObserver : public content::RenderFrameObserver { void WillReleaseScriptContext(v8::Local context, int world_id) override { - if (isolated_world_) { - if (IsIsolatedWorld(world_id)) - renderer_client_->WillReleaseScriptContext(context, render_frame_); - } else if (IsMainWorld(world_id)) { + bool notify_client = + isolated_world_ ? IsIsolatedWorld(world_id) : IsMainWorld(world_id); + if (notify_client) renderer_client_->WillReleaseScriptContext(context, render_frame_); - } } void OnDestruct() override { From 5da4f032c3875169d6530a1620567802a8b99124 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 13 Dec 2016 11:30:34 -0800 Subject: [PATCH 09/44] Notify client each time main context is created --- atom/renderer/atom_renderer_client.cc | 25 +++++++------------------ 1 file changed, 7 insertions(+), 18 deletions(-) diff --git a/atom/renderer/atom_renderer_client.cc b/atom/renderer/atom_renderer_client.cc index 1215c80ca8..6415bee31b 100644 --- a/atom/renderer/atom_renderer_client.cc +++ b/atom/renderer/atom_renderer_client.cc @@ -66,8 +66,6 @@ class AtomRenderFrameObserver : public content::RenderFrameObserver { : content::RenderFrameObserver(frame), render_frame_(frame), isolated_world_(isolated_world), - main_context_created_(false), - isolated_context_created_(false), renderer_client_(renderer_client) {} // content::RenderFrameObserver: @@ -92,26 +90,19 @@ class AtomRenderFrameObserver : public content::RenderFrameObserver { void DidCreateScriptContext(v8::Handle context, int extension_group, int world_id) override { - if (!main_context_created_ && IsMainWorld(world_id)) { - main_context_created_ = true; - if (isolated_world_) { - CreateIsolatedWorldContext(); - } else { - renderer_client_->DidCreateScriptContext(context, render_frame_); - } - } - - if (isolated_world_ && !isolated_context_created_ - && IsIsolatedWorld(world_id)) { - isolated_context_created_ = true; + bool notify_client = + isolated_world_ ? IsIsolatedWorld(world_id) : IsMainWorld(world_id); + if (notify_client) renderer_client_->DidCreateScriptContext(context, render_frame_); - } + + if (isolated_world_ && IsMainWorld(world_id)) + CreateIsolatedWorldContext(); } void WillReleaseScriptContext(v8::Local context, int world_id) override { bool notify_client = - isolated_world_ ? IsIsolatedWorld(world_id) : IsMainWorld(world_id); + isolated_world_ ? IsIsolatedWorld(world_id) : IsMainWorld(world_id); if (notify_client) renderer_client_->WillReleaseScriptContext(context, render_frame_); } @@ -123,8 +114,6 @@ class AtomRenderFrameObserver : public content::RenderFrameObserver { private: content::RenderFrame* render_frame_; bool isolated_world_; - bool main_context_created_; - bool isolated_context_created_; AtomRendererClient* renderer_client_; const int kMainWorldId = 0; From 2928fe5c439ddf9d0e350dfef71015e9db05034a Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 13 Dec 2016 11:47:54 -0800 Subject: [PATCH 10/44] Add initial isolated world spec --- spec/api-browser-window-spec.js | 33 +++++++++++++++++++++++++++ spec/fixtures/api/isolated-preload.js | 15 ++++++++++++ spec/fixtures/api/isolated.html | 19 +++++++++++++++ 3 files changed, 67 insertions(+) create mode 100644 spec/fixtures/api/isolated-preload.js create mode 100644 spec/fixtures/api/isolated.html diff --git a/spec/api-browser-window-spec.js b/spec/api-browser-window-spec.js index ab5d6d2d0d..2dd7fc81b7 100644 --- a/spec/api-browser-window-spec.js +++ b/spec/api-browser-window-spec.js @@ -1835,6 +1835,39 @@ describe('BrowserWindow module', function () { }) }) + describe('isolated option', () => { + it('separates the page context from the Electron/preload context', (done) => { + if (w != null) w.destroy() + + ipcMain.once('isolated-world', (event, data) => { + assert.deepEqual(data, { + preloadContext: { + preloadProperty: 'number', + pageProperty: 'undefined', + typeofRequire: 'function', + typeofProcess: 'object' + }, + pageContext: { + preloadProperty: 'undefined', + pageProperty: 'string', + typeofRequire: 'undefined', + typeofProcess: 'undefined' + } + }) + done() + }) + + w = new BrowserWindow({ + show: false, + webPreferences: { + isolated: true, + preload: path.join(fixtures, 'api', 'isolated-preload.js') + } + }) + w.loadURL('file://' + fixtures + '/api/isolated.html') + }) + }) + describe('offscreen rendering', function () { beforeEach(function () { if (w != null) w.destroy() diff --git a/spec/fixtures/api/isolated-preload.js b/spec/fixtures/api/isolated-preload.js new file mode 100644 index 0000000000..8cb80d3a13 --- /dev/null +++ b/spec/fixtures/api/isolated-preload.js @@ -0,0 +1,15 @@ +const {ipcRenderer} = require('electron') + +window.foo = 3 + +window.addEventListener('message', (event) => { + ipcRenderer.send('isolated-world', { + preloadContext: { + preloadProperty: typeof window.foo, + pageProperty: typeof window.hello, + typeofRequire: typeof require, + typeofProcess: typeof process + }, + pageContext: event.data + }) +}) diff --git a/spec/fixtures/api/isolated.html b/spec/fixtures/api/isolated.html new file mode 100644 index 0000000000..bb0aca9461 --- /dev/null +++ b/spec/fixtures/api/isolated.html @@ -0,0 +1,19 @@ + + + + + Isolated World + + + + + + From 5b6397aaa6527dad49aabf616de0b885a6ee9b88 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 13 Dec 2016 14:04:17 -0800 Subject: [PATCH 11/44] Store context that API is running in to deliver IPC events --- atom/renderer/atom_render_view_observer.cc | 2 +- atom/renderer/atom_renderer_client.cc | 11 +++++++---- atom/renderer/atom_renderer_client.h | 4 +++- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/atom/renderer/atom_render_view_observer.cc b/atom/renderer/atom_render_view_observer.cc index 65cc9b5c51..12e8c1eae1 100644 --- a/atom/renderer/atom_render_view_observer.cc +++ b/atom/renderer/atom_render_view_observer.cc @@ -94,7 +94,7 @@ void AtomRenderViewObserver::EmitIPCEvent(blink::WebFrame* frame, v8::Isolate* isolate = blink::mainThreadIsolate(); v8::HandleScope handle_scope(isolate); - v8::Local context = renderer_client_->GetContext(); + v8::Local context = renderer_client_->GetAPIContext(isolate); v8::Context::Scope context_scope(context); // Only emit IPC event for context with node integration. diff --git a/atom/renderer/atom_renderer_client.cc b/atom/renderer/atom_renderer_client.cc index 6415bee31b..336ec7e6f8 100644 --- a/atom/renderer/atom_renderer_client.cc +++ b/atom/renderer/atom_renderer_client.cc @@ -292,6 +292,9 @@ void AtomRendererClient::DidCreateScriptContext( if (!render_frame->IsMainFrame() && !IsDevToolsExtension(render_frame)) return; + api_context_.Reset(context->GetIsolate(), context); + api_context_.SetWeak(); + // Whether the node binding has been initialized. bool first_time = node_bindings_->uv_env() == nullptr; @@ -321,10 +324,6 @@ void AtomRendererClient::DidCreateScriptContext( } } -v8::Local AtomRendererClient::GetContext() { - return node_bindings_->uv_env()->context(); -} - void AtomRendererClient::WillReleaseScriptContext( v8::Handle context, content::RenderFrame* render_frame) { // Only allow node integration for the main frame, unless it is a devtools @@ -367,4 +366,8 @@ void AtomRendererClient::AddSupportedKeySystems( AddChromeKeySystems(key_systems); } +v8::Local AtomRendererClient::GetAPIContext(v8::Isolate* isolate) { + return api_context_.Get(isolate); +} + } // namespace atom diff --git a/atom/renderer/atom_renderer_client.h b/atom/renderer/atom_renderer_client.h index 7fab5dde86..755cc8fd7d 100644 --- a/atom/renderer/atom_renderer_client.h +++ b/atom/renderer/atom_renderer_client.h @@ -27,7 +27,8 @@ class AtomRendererClient : public content::ContentRendererClient { void WillReleaseScriptContext( v8::Handle context, content::RenderFrame* render_frame); - v8::Local GetContext(); + // Get the context that the Electron API is running in. + v8::Local GetAPIContext(v8::Isolate* isolate); private: enum NodeIntegration { @@ -66,6 +67,7 @@ class AtomRendererClient : public content::ContentRendererClient { std::unique_ptr node_bindings_; std::unique_ptr atom_bindings_; std::unique_ptr preferences_manager_; + v8::Persistent api_context_; DISALLOW_COPY_AND_ASSIGN(AtomRendererClient); }; From 4f5c725dde176e4d919255beb230da5a8445e732 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 13 Dec 2016 14:24:20 -0800 Subject: [PATCH 12/44] :art: Use enum for world ids --- atom/renderer/atom_renderer_client.cc | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/atom/renderer/atom_renderer_client.cc b/atom/renderer/atom_renderer_client.cc index 336ec7e6f8..9f6c4605b6 100644 --- a/atom/renderer/atom_renderer_client.cc +++ b/atom/renderer/atom_renderer_client.cc @@ -76,15 +76,15 @@ class AtomRenderFrameObserver : public content::RenderFrameObserver { void CreateIsolatedWorldContext() { blink::WebScriptSource source("void 0"); render_frame_->GetWebFrame()->executeScriptInIsolatedWorld( - kIsolatedWorldId, &source, 1, 1); + World::ISOLATED, &source, 1, 1); } bool IsMainWorld(int world_id) { - return world_id == kMainWorldId; + return world_id == World::MAIN; } bool IsIsolatedWorld(int world_id) { - return world_id == kIsolatedWorldId; + return world_id == World::ISOLATED; } void DidCreateScriptContext(v8::Handle context, @@ -116,8 +116,10 @@ class AtomRenderFrameObserver : public content::RenderFrameObserver { bool isolated_world_; AtomRendererClient* renderer_client_; - const int kMainWorldId = 0; - const int kIsolatedWorldId = 999; + enum World { + MAIN = 0, + ISOLATED = 999 + }; DISALLOW_COPY_AND_ASSIGN(AtomRenderFrameObserver); }; From 309ac7528483bf4e67a84cf5a1f20cc21af479c7 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 15 Dec 2016 08:16:26 -0800 Subject: [PATCH 13/44] Upgrade libcc for worlds patch --- script/lib/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/script/lib/config.py b/script/lib/config.py index 624c8fc9db..580bf84f95 100644 --- a/script/lib/config.py +++ b/script/lib/config.py @@ -9,7 +9,7 @@ import sys BASE_URL = os.getenv('LIBCHROMIUMCONTENT_MIRROR') or \ 'https://s3.amazonaws.com/github-janky-artifacts/libchromiumcontent' LIBCHROMIUMCONTENT_COMMIT = os.getenv('LIBCHROMIUMCONTENT_COMMIT') or \ - '2c8173b64b7fbc50e7190a6982e6db6b3eda0582' + '2667f2d495e545b3cb2d3435481d69cc30b6f69a' PLATFORM = { 'cygwin': 'win32', From 2e7dbe6c6b5a2b4c6110333edd2554a159128fc0 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 15 Dec 2016 08:26:37 -0800 Subject: [PATCH 14/44] Use patch worldScriptContext to get isolated context --- atom/renderer/atom_render_view_observer.cc | 2 +- atom/renderer/atom_renderer_client.cc | 61 ++++++++++++---------- atom/renderer/atom_renderer_client.h | 6 ++- 3 files changed, 38 insertions(+), 31 deletions(-) diff --git a/atom/renderer/atom_render_view_observer.cc b/atom/renderer/atom_render_view_observer.cc index 12e8c1eae1..b96c6ea672 100644 --- a/atom/renderer/atom_render_view_observer.cc +++ b/atom/renderer/atom_render_view_observer.cc @@ -94,7 +94,7 @@ void AtomRenderViewObserver::EmitIPCEvent(blink::WebFrame* frame, v8::Isolate* isolate = blink::mainThreadIsolate(); v8::HandleScope handle_scope(isolate); - v8::Local context = renderer_client_->GetAPIContext(isolate); + v8::Local context = renderer_client_->GetContext(frame, isolate); v8::Context::Scope context_scope(context); // Only emit IPC event for context with node integration. diff --git a/atom/renderer/atom_renderer_client.cc b/atom/renderer/atom_renderer_client.cc index 9f6c4605b6..594a517bdc 100644 --- a/atom/renderer/atom_renderer_client.cc +++ b/atom/renderer/atom_renderer_client.cc @@ -57,15 +57,22 @@ namespace atom { namespace { +enum World { + MAIN_WORLD = 0, + ISOLATED_WORLD = 999 +}; + +enum ExtensionGroup { + MAIN_GROUP = 1 +}; + // Helper class to forward the messages to the client. class AtomRenderFrameObserver : public content::RenderFrameObserver { public: AtomRenderFrameObserver(content::RenderFrame* frame, - AtomRendererClient* renderer_client, - bool isolated_world) + AtomRendererClient* renderer_client) : content::RenderFrameObserver(frame), render_frame_(frame), - isolated_world_(isolated_world), renderer_client_(renderer_client) {} // content::RenderFrameObserver: @@ -76,34 +83,37 @@ class AtomRenderFrameObserver : public content::RenderFrameObserver { void CreateIsolatedWorldContext() { blink::WebScriptSource source("void 0"); render_frame_->GetWebFrame()->executeScriptInIsolatedWorld( - World::ISOLATED, &source, 1, 1); + World::ISOLATED_WORLD, &source, 1, ExtensionGroup::MAIN_GROUP); } bool IsMainWorld(int world_id) { - return world_id == World::MAIN; + return world_id == World::MAIN_WORLD; } bool IsIsolatedWorld(int world_id) { - return world_id == World::ISOLATED; + return world_id == World::ISOLATED_WORLD; + } + + bool NotifyClient(int world_id) { + if (renderer_client_->isolated_world()) + return IsIsolatedWorld(world_id); + else + return IsMainWorld(world_id); } void DidCreateScriptContext(v8::Handle context, int extension_group, int world_id) override { - bool notify_client = - isolated_world_ ? IsIsolatedWorld(world_id) : IsMainWorld(world_id); - if (notify_client) + if (NotifyClient(world_id)) renderer_client_->DidCreateScriptContext(context, render_frame_); - if (isolated_world_ && IsMainWorld(world_id)) + if (renderer_client_->isolated_world() && IsMainWorld(world_id)) CreateIsolatedWorldContext(); } void WillReleaseScriptContext(v8::Local context, int world_id) override { - bool notify_client = - isolated_world_ ? IsIsolatedWorld(world_id) : IsMainWorld(world_id); - if (notify_client) + if (NotifyClient(world_id)) renderer_client_->WillReleaseScriptContext(context, render_frame_); } @@ -113,14 +123,8 @@ class AtomRenderFrameObserver : public content::RenderFrameObserver { private: content::RenderFrame* render_frame_; - bool isolated_world_; AtomRendererClient* renderer_client_; - enum World { - MAIN = 0, - ISOLATED = 999 - }; - DISALLOW_COPY_AND_ASSIGN(AtomRenderFrameObserver); }; @@ -158,6 +162,8 @@ std::vector ParseSchemesCLISwitch(const char* switch_name) { AtomRendererClient::AtomRendererClient() : node_bindings_(NodeBindings::Create(false)), atom_bindings_(new AtomBindings) { + isolated_world_ = base::CommandLine::ForCurrentProcess()->HasSwitch( + switches::kIsolatedWorld); // Parse --standard-schemes=scheme1,scheme2 std::vector standard_schemes_list = ParseSchemesCLISwitch(switches::kStandardSchemes); @@ -203,10 +209,7 @@ void AtomRendererClient::RenderThreadStarted() { void AtomRendererClient::RenderFrameCreated( content::RenderFrame* render_frame) { new PepperHelper(render_frame); - - bool isolated_world = base::CommandLine::ForCurrentProcess()->HasSwitch( - switches::kIsolatedWorld); - new AtomRenderFrameObserver(render_frame, this, isolated_world); + new AtomRenderFrameObserver(render_frame, this); new ContentSettingsObserver(render_frame); @@ -294,9 +297,6 @@ void AtomRendererClient::DidCreateScriptContext( if (!render_frame->IsMainFrame() && !IsDevToolsExtension(render_frame)) return; - api_context_.Reset(context->GetIsolate(), context); - api_context_.SetWeak(); - // Whether the node binding has been initialized. bool first_time = node_bindings_->uv_env() == nullptr; @@ -368,8 +368,13 @@ void AtomRendererClient::AddSupportedKeySystems( AddChromeKeySystems(key_systems); } -v8::Local AtomRendererClient::GetAPIContext(v8::Isolate* isolate) { - return api_context_.Get(isolate); +v8::Local AtomRendererClient::GetContext( + blink::WebFrame* frame, v8::Isolate* isolate) { + if (isolated_world_) + return frame->worldScriptContext( + isolate, World::ISOLATED_WORLD, ExtensionGroup::MAIN_GROUP); + else + return frame->mainWorldScriptContext(); } } // namespace atom diff --git a/atom/renderer/atom_renderer_client.h b/atom/renderer/atom_renderer_client.h index 755cc8fd7d..a693262fed 100644 --- a/atom/renderer/atom_renderer_client.h +++ b/atom/renderer/atom_renderer_client.h @@ -28,7 +28,9 @@ class AtomRendererClient : public content::ContentRendererClient { v8::Handle context, content::RenderFrame* render_frame); // Get the context that the Electron API is running in. - v8::Local GetAPIContext(v8::Isolate* isolate); + v8::Local GetContext( + blink::WebFrame* frame, v8::Isolate* isolate); + bool isolated_world() { return isolated_world_; } private: enum NodeIntegration { @@ -67,7 +69,7 @@ class AtomRendererClient : public content::ContentRendererClient { std::unique_ptr node_bindings_; std::unique_ptr atom_bindings_; std::unique_ptr preferences_manager_; - v8::Persistent api_context_; + bool isolated_world_; DISALLOW_COPY_AND_ASSIGN(AtomRendererClient); }; From b56bdc83afa22cf9ce09757dcea9a7b8aff34ba2 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 15 Dec 2016 08:29:38 -0800 Subject: [PATCH 15/44] :art: --- atom/renderer/atom_renderer_client.cc | 1 - 1 file changed, 1 deletion(-) diff --git a/atom/renderer/atom_renderer_client.cc b/atom/renderer/atom_renderer_client.cc index 594a517bdc..2e51eb539d 100644 --- a/atom/renderer/atom_renderer_client.cc +++ b/atom/renderer/atom_renderer_client.cc @@ -210,7 +210,6 @@ void AtomRendererClient::RenderFrameCreated( content::RenderFrame* render_frame) { new PepperHelper(render_frame); new AtomRenderFrameObserver(render_frame, this); - new ContentSettingsObserver(render_frame); // Allow file scheme to handle service worker by default. From ad3b837ad5b2698361fe1a8ad1c6a3f0d156673c Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 15 Dec 2016 13:20:17 -0800 Subject: [PATCH 16/44] Rename option to contextIsolation --- atom/browser/web_contents_preferences.cc | 5 +++-- atom/common/options_switches.cc | 21 ++++++++++++--------- atom/common/options_switches.h | 3 ++- atom/renderer/atom_renderer_client.cc | 2 +- spec/api-browser-window-spec.js | 4 ++-- 5 files changed, 20 insertions(+), 15 deletions(-) diff --git a/atom/browser/web_contents_preferences.cc b/atom/browser/web_contents_preferences.cc index 300c2b88ae..d5bde07250 100644 --- a/atom/browser/web_contents_preferences.cc +++ b/atom/browser/web_contents_preferences.cc @@ -121,8 +121,9 @@ void WebContentsPreferences::AppendExtraCommandLineSwitches( // Run Electron APIs and preload script in isolated world bool isolated; - if (web_preferences.GetBoolean("isolated", &isolated) && isolated) - command_line->AppendSwitch(switches::kIsolatedWorld); + if (web_preferences.GetBoolean(options::kContextIsolation, &isolated) && + isolated) + command_line->AppendSwitch(switches::kContextIsolation); // --background-color. std::string color; diff --git a/atom/common/options_switches.cc b/atom/common/options_switches.cc index cb33972dc4..f1eecdbd47 100644 --- a/atom/common/options_switches.cc +++ b/atom/common/options_switches.cc @@ -99,6 +99,9 @@ const char kPreloadURL[] = "preloadURL"; // Enable the node integration. const char kNodeIntegration[] = "nodeIntegration"; +// Enable context isolation of Electron APIs and preload script +const char kContextIsolation[] = "contextIsolation"; + // Instancd ID of guest WebContents. const char kGuestInstanceID[] = "guestInstanceId"; @@ -158,15 +161,15 @@ const char kCipherSuiteBlacklist[] = "cipher-suite-blacklist"; const char kAppUserModelId[] = "app-user-model-id"; // The command line switch versions of the options. -const char kBackgroundColor[] = "background-color"; -const char kZoomFactor[] = "zoom-factor"; -const char kPreloadScript[] = "preload"; -const char kPreloadURL[] = "preload-url"; -const char kNodeIntegration[] = "node-integration"; -const char kIsolatedWorld[] = "isolated-world"; -const char kGuestInstanceID[] = "guest-instance-id"; -const char kOpenerID[] = "opener-id"; -const char kScrollBounce[] = "scroll-bounce"; +const char kBackgroundColor[] = "background-color"; +const char kZoomFactor[] = "zoom-factor"; +const char kPreloadScript[] = "preload"; +const char kPreloadURL[] = "preload-url"; +const char kNodeIntegration[] = "node-integration"; +const char kContextIsolation[] = "context-isolation"; +const char kGuestInstanceID[] = "guest-instance-id"; +const char kOpenerID[] = "opener-id"; +const char kScrollBounce[] = "scroll-bounce"; // Widevine options // Path to Widevine CDM binaries. diff --git a/atom/common/options_switches.h b/atom/common/options_switches.h index 3ce8dadb32..2742d0c825 100644 --- a/atom/common/options_switches.h +++ b/atom/common/options_switches.h @@ -54,6 +54,7 @@ extern const char kZoomFactor[]; extern const char kPreloadScript[]; extern const char kPreloadURL[]; extern const char kNodeIntegration[]; +extern const char kContextIsolation[]; extern const char kGuestInstanceID[]; extern const char kExperimentalFeatures[]; extern const char kExperimentalCanvasFeatures[]; @@ -86,7 +87,7 @@ extern const char kZoomFactor[]; extern const char kPreloadScript[]; extern const char kPreloadURL[]; extern const char kNodeIntegration[]; -extern const char kIsolatedWorld[]; +extern const char kContextIsolation[]; extern const char kGuestInstanceID[]; extern const char kOpenerID[]; extern const char kScrollBounce[]; diff --git a/atom/renderer/atom_renderer_client.cc b/atom/renderer/atom_renderer_client.cc index 2e51eb539d..7802700837 100644 --- a/atom/renderer/atom_renderer_client.cc +++ b/atom/renderer/atom_renderer_client.cc @@ -163,7 +163,7 @@ AtomRendererClient::AtomRendererClient() : node_bindings_(NodeBindings::Create(false)), atom_bindings_(new AtomBindings) { isolated_world_ = base::CommandLine::ForCurrentProcess()->HasSwitch( - switches::kIsolatedWorld); + switches::kContextIsolation); // Parse --standard-schemes=scheme1,scheme2 std::vector standard_schemes_list = ParseSchemesCLISwitch(switches::kStandardSchemes); diff --git a/spec/api-browser-window-spec.js b/spec/api-browser-window-spec.js index 2dd7fc81b7..6332406a85 100644 --- a/spec/api-browser-window-spec.js +++ b/spec/api-browser-window-spec.js @@ -1835,7 +1835,7 @@ describe('BrowserWindow module', function () { }) }) - describe('isolated option', () => { + describe('contextIsolation option', () => { it('separates the page context from the Electron/preload context', (done) => { if (w != null) w.destroy() @@ -1860,7 +1860,7 @@ describe('BrowserWindow module', function () { w = new BrowserWindow({ show: false, webPreferences: { - isolated: true, + contextIsolation: true, preload: path.join(fixtures, 'api', 'isolated-preload.js') } }) From ea2273dde59891211bfde8b5dc602dba677a8f5e Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 15 Dec 2016 15:23:45 -0800 Subject: [PATCH 17/44] Assert built-in prototype isolation --- spec/api-browser-window-spec.js | 8 ++++++-- spec/fixtures/api/isolated-preload.js | 4 +++- spec/fixtures/api/isolated.html | 6 +++++- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/spec/api-browser-window-spec.js b/spec/api-browser-window-spec.js index 6332406a85..39d262b596 100644 --- a/spec/api-browser-window-spec.js +++ b/spec/api-browser-window-spec.js @@ -1845,13 +1845,17 @@ describe('BrowserWindow module', function () { preloadProperty: 'number', pageProperty: 'undefined', typeofRequire: 'function', - typeofProcess: 'object' + typeofProcess: 'object', + typeofArrayPush: 'function', + typeofFunctionApply: 'function', }, pageContext: { preloadProperty: 'undefined', pageProperty: 'string', typeofRequire: 'undefined', - typeofProcess: 'undefined' + typeofProcess: 'undefined', + typeofArrayPush: 'number', + typeofFunctionApply: 'boolean', } }) done() diff --git a/spec/fixtures/api/isolated-preload.js b/spec/fixtures/api/isolated-preload.js index 8cb80d3a13..c92be35d48 100644 --- a/spec/fixtures/api/isolated-preload.js +++ b/spec/fixtures/api/isolated-preload.js @@ -8,7 +8,9 @@ window.addEventListener('message', (event) => { preloadProperty: typeof window.foo, pageProperty: typeof window.hello, typeofRequire: typeof require, - typeofProcess: typeof process + typeofProcess: typeof process, + typeofArrayPush: typeof Array.prototype.push, + typeofFunctionApply: typeof Function.prototype.apply }, pageContext: event.data }) diff --git a/spec/fixtures/api/isolated.html b/spec/fixtures/api/isolated.html index bb0aca9461..b2d2b4e8c5 100644 --- a/spec/fixtures/api/isolated.html +++ b/spec/fixtures/api/isolated.html @@ -5,11 +5,15 @@ Isolated World From b348cdeae8186b9f993e44d9c20f18ec69a5fcbb Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 5 Jan 2017 09:33:54 -0800 Subject: [PATCH 18/44] Set page variable using webFrame.executeJavaScript --- spec/api-browser-window-spec.js | 1 + spec/fixtures/api/isolated-preload.js | 4 +++- spec/fixtures/api/isolated.html | 3 ++- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/spec/api-browser-window-spec.js b/spec/api-browser-window-spec.js index 39d262b596..c08113fd8e 100644 --- a/spec/api-browser-window-spec.js +++ b/spec/api-browser-window-spec.js @@ -1856,6 +1856,7 @@ describe('BrowserWindow module', function () { typeofProcess: 'undefined', typeofArrayPush: 'number', typeofFunctionApply: 'boolean', + typeofPreloadExecuteJavaScriptProperty: 'number' } }) done() diff --git a/spec/fixtures/api/isolated-preload.js b/spec/fixtures/api/isolated-preload.js index c92be35d48..9ff121929c 100644 --- a/spec/fixtures/api/isolated-preload.js +++ b/spec/fixtures/api/isolated-preload.js @@ -1,7 +1,9 @@ -const {ipcRenderer} = require('electron') +const {ipcRenderer, webFrame} = require('electron') window.foo = 3 +webFrame.executeJavaScript('window.preloadExecuteJavaScriptProperty = 1234;') + window.addEventListener('message', (event) => { ipcRenderer.send('isolated-world', { preloadContext: { diff --git a/spec/fixtures/api/isolated.html b/spec/fixtures/api/isolated.html index b2d2b4e8c5..a0cc84770e 100644 --- a/spec/fixtures/api/isolated.html +++ b/spec/fixtures/api/isolated.html @@ -13,7 +13,8 @@ typeofRequire: typeof require, typeofProcess: typeof process, typeofArrayPush: typeof Array.prototype.push, - typeofFunctionApply: typeof Function.prototype.apply + typeofFunctionApply: typeof Function.prototype.apply, + typeofPreloadExecuteJavaScriptProperty: typeof window.preloadExecuteJavaScriptProperty }, '*') From 572fc058d3311647ac016c19e80a7062f2e438bb Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 5 Jan 2017 09:47:03 -0800 Subject: [PATCH 19/44] Document contextIsolation option --- docs/api/browser-window.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/api/browser-window.md b/docs/api/browser-window.md index 720cf7a7de..aaded4efbe 100644 --- a/docs/api/browser-window.md +++ b/docs/api/browser-window.md @@ -282,6 +282,17 @@ It creates a new `BrowserWindow` with native properties as set by the `options`. [offscreen rendering tutorial](../tutorial/offscreen-rendering.md) for more details. * `sandbox` Boolean (optional) - Whether to enable Chromium OS-level sandbox. + * `contextIsolation` Boolean (optional) - Whether to run Electron APIs and + the specified `preload` script in a separate JavaScript context. Defaults + to `false`. The context that the `preload` script runs in will still + have full access to the `document` and `window` globals but it will use + its own set of JavaScript builtins (`Array`, `Object`, `JSON`, etc.) + and will be isolated from any changes made to the global environment + by the loaded page. The Electron API will only be available in the + `preload` script and not the loaded page. This option should be used when + loading potentially untrusted remote content to ensure the loaded content + cannot tamper with the `preload` script and any Electron APIs being used. + This option uses the same technique used by [Chrome Content Scripts][chrome-content-scripts]. When setting minimum or maximum window size with `minWidth`/`maxWidth`/ `minHeight`/`maxHeight`, it only constrains the users. It won't prevent you from @@ -1254,3 +1265,4 @@ will remove the vibrancy effect on the window. [quick-look]: https://en.wikipedia.org/wiki/Quick_Look [vibrancy-docs]: https://developer.apple.com/reference/appkit/nsvisualeffectview?language=objc [window-levels]: https://developer.apple.com/reference/appkit/nswindow/1664726-window_levels +[chrome-content-scripts]: https://developer.chrome.com/extensions/content_scripts#execution-environment From 802ed62d5b6aad3a93f8d4abd6c980f6245af55c Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 5 Jan 2017 10:08:35 -0800 Subject: [PATCH 20/44] Remove trailing comma --- spec/api-browser-window-spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/api-browser-window-spec.js b/spec/api-browser-window-spec.js index c08113fd8e..02e9ce67ad 100644 --- a/spec/api-browser-window-spec.js +++ b/spec/api-browser-window-spec.js @@ -1847,7 +1847,7 @@ describe('BrowserWindow module', function () { typeofRequire: 'function', typeofProcess: 'object', typeofArrayPush: 'function', - typeofFunctionApply: 'function', + typeofFunctionApply: 'function' }, pageContext: { preloadProperty: 'undefined', From 14a1e673c62f7b3edda9cf372a36e52cd7d0fbd7 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 5 Jan 2017 10:17:06 -0800 Subject: [PATCH 21/44] Add spec for webview context isolation --- spec/webview-spec.js | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/spec/webview-spec.js b/spec/webview-spec.js index da91659ef0..2d3ce3c06e 100644 --- a/spec/webview-spec.js +++ b/spec/webview-spec.js @@ -429,6 +429,36 @@ describe(' tag', function () { webview.src = 'data:text/html;base64,' + encoded document.body.appendChild(webview) }) + + it('can enable context isolation', (done) => { + ipcMain.once('isolated-world', (event, data) => { + assert.deepEqual(data, { + preloadContext: { + preloadProperty: 'number', + pageProperty: 'undefined', + typeofRequire: 'function', + typeofProcess: 'object', + typeofArrayPush: 'function', + typeofFunctionApply: 'function' + }, + pageContext: { + preloadProperty: 'undefined', + pageProperty: 'string', + typeofRequire: 'undefined', + typeofProcess: 'undefined', + typeofArrayPush: 'number', + typeofFunctionApply: 'boolean', + typeofPreloadExecuteJavaScriptProperty: 'number' + } + }) + done() + }) + + webview.setAttribute('preload', path.join(fixtures, 'api', 'isolated-preload.js')) + webview.setAttribute('webpreferences', 'contextIsolation=yes') + webview.src = 'file://' + fixtures + '/api/isolated.html' + document.body.appendChild(webview) + }) }) describe('new-window event', function () { From 0f7af8043a81874855c49fdb97f5daba2369f0ad Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 9 Jan 2017 09:23:03 -0800 Subject: [PATCH 22/44] Upgrade libcc for world context patch --- script/lib/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/script/lib/config.py b/script/lib/config.py index 580bf84f95..5cd3ef5e80 100644 --- a/script/lib/config.py +++ b/script/lib/config.py @@ -9,7 +9,7 @@ import sys BASE_URL = os.getenv('LIBCHROMIUMCONTENT_MIRROR') or \ 'https://s3.amazonaws.com/github-janky-artifacts/libchromiumcontent' LIBCHROMIUMCONTENT_COMMIT = os.getenv('LIBCHROMIUMCONTENT_COMMIT') or \ - '2667f2d495e545b3cb2d3435481d69cc30b6f69a' + 'f14fb5fb9cb3c3a57a2ac1a9725fd9373ef043d2' PLATFORM = { 'cygwin': 'win32', From 3ac6019f4240fb329a5bc1d621d759af5ec47a10 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 9 Jan 2017 09:25:34 -0800 Subject: [PATCH 23/44] Mention context isolation --- docs/tutorial/security.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/tutorial/security.md b/docs/tutorial/security.md index a70256e435..b0ebf8dc9b 100644 --- a/docs/tutorial/security.md +++ b/docs/tutorial/security.md @@ -55,7 +55,9 @@ This is not bulletproof, but at the least, you should attempt the following: * Only display secure (https) content * Disable the Node integration in all renderers that display remote content - (using `webPreferences`) + (setting `nodeIntegration` to `false` in `webPreferences`) +* Enable context isolation in all rendererers that display remote content + (setting `contextIsolation` to `true` in `webPreferences`) * Do not disable `webSecurity`. Disabling it will disable the same-origin policy. * Define a [`Content-Security-Policy`](http://www.html5rocks.com/en/tutorials/security/content-security-policy/) , and use restrictive rules (i.e. `script-src 'self'`) From dacfb2f5964ab49cd3c0a099d204da7e2ae759e4 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 9 Jan 2017 09:26:42 -0800 Subject: [PATCH 24/44] Mention contextIsolation is experimental --- docs/api/browser-window.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/api/browser-window.md b/docs/api/browser-window.md index aaded4efbe..61a41e6ad0 100644 --- a/docs/api/browser-window.md +++ b/docs/api/browser-window.md @@ -293,6 +293,8 @@ It creates a new `BrowserWindow` with native properties as set by the `options`. loading potentially untrusted remote content to ensure the loaded content cannot tamper with the `preload` script and any Electron APIs being used. This option uses the same technique used by [Chrome Content Scripts][chrome-content-scripts]. + This option is experimental and may undergo further API changes before + Electron 2.0. When setting minimum or maximum window size with `minWidth`/`maxWidth`/ `minHeight`/`maxHeight`, it only constrains the users. It won't prevent you from From 33b6ab11f2d333b4ce2ff998a39c0ab257989c24 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 9 Jan 2017 15:18:47 -0800 Subject: [PATCH 25/44] Assert context state after reload --- spec/api-browser-window-spec.js | 46 ++++++++++++++++++++++++++++----- 1 file changed, 39 insertions(+), 7 deletions(-) diff --git a/spec/api-browser-window-spec.js b/spec/api-browser-window-spec.js index 02e9ce67ad..f28759ccbb 100644 --- a/spec/api-browser-window-spec.js +++ b/spec/api-browser-window-spec.js @@ -1836,9 +1836,18 @@ describe('BrowserWindow module', function () { }) describe('contextIsolation option', () => { - it('separates the page context from the Electron/preload context', (done) => { + beforeEach(() => { if (w != null) w.destroy() + w = new BrowserWindow({ + show: false, + webPreferences: { + contextIsolation: true, + preload: path.join(fixtures, 'api', 'isolated-preload.js') + } + }) + }) + it('separates the page context from the Electron/preload context', (done) => { ipcMain.once('isolated-world', (event, data) => { assert.deepEqual(data, { preloadContext: { @@ -1862,12 +1871,35 @@ describe('BrowserWindow module', function () { done() }) - w = new BrowserWindow({ - show: false, - webPreferences: { - contextIsolation: true, - preload: path.join(fixtures, 'api', 'isolated-preload.js') - } + w.loadURL('file://' + fixtures + '/api/isolated.html') + }) + + it('recreates the contexts on reload', (done) => { + w.webContents.once('did-finish-load', () => { + ipcMain.once('isolated-world', (event, data) => { + assert.deepEqual(data, { + preloadContext: { + preloadProperty: 'number', + pageProperty: 'undefined', + typeofRequire: 'function', + typeofProcess: 'object', + typeofArrayPush: 'function', + typeofFunctionApply: 'function' + }, + pageContext: { + preloadProperty: 'undefined', + pageProperty: 'string', + typeofRequire: 'undefined', + typeofProcess: 'undefined', + typeofArrayPush: 'number', + typeofFunctionApply: 'boolean', + typeofPreloadExecuteJavaScriptProperty: 'number' + } + }) + done() + }) + + w.webContents.reload() }) w.loadURL('file://' + fixtures + '/api/isolated.html') }) From eef72647b4be2b8be68bd39d24d30137e1c8aa18 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 10 Jan 2017 09:25:10 -0800 Subject: [PATCH 26/44] Set human readable context name --- atom/renderer/atom_renderer_client.cc | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/atom/renderer/atom_renderer_client.cc b/atom/renderer/atom_renderer_client.cc index 7802700837..e785e26c15 100644 --- a/atom/renderer/atom_renderer_client.cc +++ b/atom/renderer/atom_renderer_client.cc @@ -81,6 +81,10 @@ class AtomRenderFrameObserver : public content::RenderFrameObserver { } void CreateIsolatedWorldContext() { + render_frame_->GetWebFrame()->setIsolatedWorldHumanReadableName( + World::ISOLATED_WORLD, + blink::WebString::fromUTF8("Electron Isolated Context")); + blink::WebScriptSource source("void 0"); render_frame_->GetWebFrame()->executeScriptInIsolatedWorld( World::ISOLATED_WORLD, &source, 1, ExtensionGroup::MAIN_GROUP); From 95054f443f2e9a7d70482ffc49a2d23f2e35fe6b Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 10 Jan 2017 09:37:38 -0800 Subject: [PATCH 27/44] Enable context isolation on child windows --- lib/browser/guest-window-manager.js | 5 +++++ spec/api-browser-window-spec.js | 9 +++++++++ 2 files changed, 14 insertions(+) diff --git a/lib/browser/guest-window-manager.js b/lib/browser/guest-window-manager.js index c0a5efb262..62abc8663c 100644 --- a/lib/browser/guest-window-manager.js +++ b/lib/browser/guest-window-manager.js @@ -47,6 +47,11 @@ const mergeBrowserWindowOptions = function (embedder, options) { options.webPreferences.nodeIntegration = false } + // Enable context isolation on child window if enable on parent window + if (embedder.getWebPreferences().contextIsolation === true) { + options.webPreferences.contextIsolation = true + } + // Sets correct openerId here to give correct options to 'new-window' event handler options.webPreferences.openerId = embedder.id diff --git a/spec/api-browser-window-spec.js b/spec/api-browser-window-spec.js index f28759ccbb..b777c684d1 100644 --- a/spec/api-browser-window-spec.js +++ b/spec/api-browser-window-spec.js @@ -1903,6 +1903,15 @@ describe('BrowserWindow module', function () { }) w.loadURL('file://' + fixtures + '/api/isolated.html') }) + + it('enables context isolation on child windows', function (done) { + app.once('browser-window-created', function (event, window) { + assert.equal(window.webContents.getWebPreferences().contextIsolation, true) + done() + }) + + w.loadURL('file://' + fixtures + '/pages/window-open.html') + }) }) describe('offscreen rendering', function () { From 3f7b3c4bd7d3fe2babadc96c0e4b019526d4100f Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 11 Jan 2017 16:36:59 -0800 Subject: [PATCH 28/44] Implement window overrides in main context --- atom/browser/web_contents_preferences.cc | 2 +- atom/common/options_switches.cc | 3 +- atom/common/options_switches.h | 1 + atom/renderer/atom_renderer_client.cc | 40 +++- electron.gyp | 24 ++- filenames.gypi | 5 + lib/browser/guest-window-manager.js | 69 ++++++- lib/isolated_renderer/init.js | 22 +++ lib/renderer/override.js | 242 +---------------------- lib/renderer/window-setup.js | 163 +++++++++++++++ spec/api-browser-window-spec.js | 64 +++--- spec/fixtures/api/isolated.html | 7 +- spec/webview-spec.js | 4 +- 13 files changed, 357 insertions(+), 289 deletions(-) create mode 100644 lib/isolated_renderer/init.js create mode 100644 lib/renderer/window-setup.js diff --git a/atom/browser/web_contents_preferences.cc b/atom/browser/web_contents_preferences.cc index d5bde07250..23f834e8e3 100644 --- a/atom/browser/web_contents_preferences.cc +++ b/atom/browser/web_contents_preferences.cc @@ -196,7 +196,7 @@ void WebContentsPreferences::AppendExtraCommandLineSwitches( if (window) { bool visible = window->IsVisible() && !window->IsMinimized(); if (!visible) // Default state is visible. - command_line->AppendSwitch("hidden-page"); + command_line->AppendSwitch(switches::kHiddenPage); } // Use frame scheduling for offscreen renderers. diff --git a/atom/common/options_switches.cc b/atom/common/options_switches.cc index f1eecdbd47..f247025f36 100644 --- a/atom/common/options_switches.cc +++ b/atom/common/options_switches.cc @@ -102,7 +102,7 @@ const char kNodeIntegration[] = "nodeIntegration"; // Enable context isolation of Electron APIs and preload script const char kContextIsolation[] = "contextIsolation"; -// Instancd ID of guest WebContents. +// Instance ID of guest WebContents. const char kGuestInstanceID[] = "guestInstanceId"; // Web runtime features. @@ -170,6 +170,7 @@ const char kContextIsolation[] = "context-isolation"; const char kGuestInstanceID[] = "guest-instance-id"; const char kOpenerID[] = "opener-id"; const char kScrollBounce[] = "scroll-bounce"; +const char kHiddenPage[] = "hidden-page"; // Widevine options // Path to Widevine CDM binaries. diff --git a/atom/common/options_switches.h b/atom/common/options_switches.h index 2742d0c825..e368b0a5fc 100644 --- a/atom/common/options_switches.h +++ b/atom/common/options_switches.h @@ -91,6 +91,7 @@ extern const char kContextIsolation[]; extern const char kGuestInstanceID[]; extern const char kOpenerID[]; extern const char kScrollBounce[]; +extern const char kHiddenPage[]; extern const char kWidevineCdmPath[]; extern const char kWidevineCdmVersion[]; diff --git a/atom/renderer/atom_renderer_client.cc b/atom/renderer/atom_renderer_client.cc index e785e26c15..7ef04732c4 100644 --- a/atom/renderer/atom_renderer_client.cc +++ b/atom/renderer/atom_renderer_client.cc @@ -4,6 +4,8 @@ #include "atom/renderer/atom_renderer_client.h" +#include "atom_natives.h" // NOLINT: This file is generated with js2c + #include #include @@ -14,6 +16,7 @@ #include "atom/common/native_mate_converters/value_converter.h" #include "atom/common/node_bindings.h" #include "atom/common/options_switches.h" +#include "atom/renderer/api/atom_api_renderer_ipc.h" #include "atom/renderer/atom_render_view_observer.h" #include "atom/renderer/content_settings_observer.h" #include "atom/renderer/guest_view_container.h" @@ -90,6 +93,39 @@ class AtomRenderFrameObserver : public content::RenderFrameObserver { World::ISOLATED_WORLD, &source, 1, ExtensionGroup::MAIN_GROUP); } + void SetupMainWorldOverrides(v8::Handle context) { + // Setup window overrides in the main world context + v8::Isolate* isolate = context->GetIsolate(); + + // Wrap the bundle into a function that receives the binding object as + // an argument. + std::string bundle(node::isolated_bundle_native, + node::isolated_bundle_native + sizeof(node::isolated_bundle_native)); + std::string wrapper = "(function (binding) {\n" + bundle + "\n})"; + auto script = v8::Script::Compile( + mate::ConvertToV8(isolate, wrapper)->ToString()); + auto func = v8::Handle::Cast( + script->Run(context).ToLocalChecked()); + + auto binding = v8::Object::New(isolate); + api::Initialize(binding, v8::Null(isolate), context, nullptr); + + // Pass in CLI flags needed to setup window + base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); + mate::Dictionary dict(isolate, binding); + if (command_line->HasSwitch(switches::kGuestInstanceID)) + dict.Set("guestInstanceId", + command_line->GetSwitchValueASCII(switches::kGuestInstanceID)); + if (command_line->HasSwitch(switches::kOpenerID)) + dict.Set("openerId", + command_line->GetSwitchValueASCII(switches::kOpenerID)); + dict.Set("hiddenPage", command_line->HasSwitch(switches::kHiddenPage)); + + v8::Local args[] = { binding }; + + ignore_result(func->Call(context, v8::Null(isolate), 1, args)); + } + bool IsMainWorld(int world_id) { return world_id == World::MAIN_WORLD; } @@ -111,8 +147,10 @@ class AtomRenderFrameObserver : public content::RenderFrameObserver { if (NotifyClient(world_id)) renderer_client_->DidCreateScriptContext(context, render_frame_); - if (renderer_client_->isolated_world() && IsMainWorld(world_id)) + if (renderer_client_->isolated_world() && IsMainWorld(world_id)) { CreateIsolatedWorldContext(); + SetupMainWorldOverrides(context); + } } void WillReleaseScriptContext(v8::Local context, diff --git a/electron.gyp b/electron.gyp index 495d0fd087..3ca6717b98 100644 --- a/electron.gyp +++ b/electron.gyp @@ -433,7 +433,7 @@ ], 'actions': [ { - 'action_name': 'atom_browserify', + 'action_name': 'atom_browserify_sandbox', 'inputs': [ '<@(browserify_entries)', ], @@ -450,7 +450,26 @@ '-o', '<@(_outputs)', ], - } + }, + { + 'action_name': 'atom_browserify_isolated_context', + 'inputs': [ + '<@(isolated_context_browserify_entries)', + ], + 'outputs': [ + '<(js2c_input_dir)/isolated_bundle.js', + ], + 'action': [ + 'npm', + 'run', + '--silent', + 'browserify', + '--', + 'lib/isolated_renderer/init.js', + '-o', + '<@(_outputs)', + ], + }, ], }, # target atom_browserify { @@ -467,6 +486,7 @@ # List all input files that should trigger a rebuild with js2c '<@(js2c_sources)', '<(js2c_input_dir)/preload_bundle.js', + '<(js2c_input_dir)/isolated_bundle.js', ], 'outputs': [ '<(SHARED_INTERMEDIATE_DIR)/atom_natives.h', diff --git a/filenames.gypi b/filenames.gypi index b7a53a4840..6fe7612a55 100644 --- a/filenames.gypi +++ b/filenames.gypi @@ -56,6 +56,7 @@ 'lib/renderer/init.js', 'lib/renderer/inspector.js', 'lib/renderer/override.js', + 'lib/renderer/window-setup.js', 'lib/renderer/web-view/guest-view-internal.js', 'lib/renderer/web-view/web-view.js', 'lib/renderer/web-view/web-view-attributes.js', @@ -76,6 +77,10 @@ 'lib/renderer/api/ipc-renderer-setup.js', 'lib/sandboxed_renderer/init.js', ], + 'isolated_context_browserify_entries': [ + 'lib/renderer/window-setup.js', + 'lib/isolated_renderer/init.js', + ], 'js2c_sources': [ 'lib/common/asar.js', 'lib/common/asar_init.js', diff --git a/lib/browser/guest-window-manager.js b/lib/browser/guest-window-manager.js index 62abc8663c..e5bfa74123 100644 --- a/lib/browser/guest-window-manager.js +++ b/lib/browser/guest-window-manager.js @@ -2,6 +2,7 @@ const {BrowserWindow, ipcMain, webContents} = require('electron') const {isSameOrigin} = process.atomBinding('v8_util') +const parseFeaturesString = require('../common/parse-features-string') const hasProp = {}.hasOwnProperty const frameToGuest = {} @@ -176,8 +177,68 @@ const canAccessWindow = function (sender, target) { isSameOrigin(sender.getURL(), target.getURL()) } -// Routed window.open messages. -ipcMain.on('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_OPEN', function (event, url, frameName, +// Routed window.open messages with raw options +ipcMain.on('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_OPEN', (event, url, frameName, features) => { + if (url == null || url === '') url = 'about:blank' + if (frameName == null) frameName = '' + if (features == null) features = '' + + const options = {} + + const ints = ['x', 'y', 'width', 'height', 'minWidth', 'maxWidth', 'minHeight', 'maxHeight', 'zoomFactor'] + const webPreferences = ['zoomFactor', 'nodeIntegration', 'preload'] + const disposition = 'new-window' + + // Used to store additional features + const additionalFeatures = [] + + // Parse the features + parseFeaturesString(features, function (key, value) { + if (value === undefined) { + additionalFeatures.push(key) + } else { + if (webPreferences.includes(key)) { + if (options.webPreferences == null) { + options.webPreferences = {} + } + options.webPreferences[key] = value + } else { + options[key] = value + } + } + }) + if (options.left) { + if (options.x == null) { + options.x = options.left + } + } + if (options.top) { + if (options.y == null) { + options.y = options.top + } + } + if (options.title == null) { + options.title = frameName + } + if (options.width == null) { + options.width = 800 + } + if (options.height == null) { + options.height = 600 + } + + for (const name of ints) { + if (options[name] != null) { + options[name] = parseInt(options[name], 10) + } + } + + ipcMain.emit('ELECTRON_GUEST_WINDOW_MANAGER_INTERNAL_WINDOW_OPEN', event, + url, frameName, disposition, options, additionalFeatures) +}) + +// Routed window.open messages with fully parsed options +ipcMain.on('ELECTRON_GUEST_WINDOW_MANAGER_INTERNAL_WINDOW_OPEN', function (event, url, frameName, disposition, options, additionalFeatures, postData) { options = mergeBrowserWindowOptions(event.sender, options) @@ -229,6 +290,10 @@ ipcMain.on('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_METHOD', function (event, guest }) ipcMain.on('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_POSTMESSAGE', function (event, guestId, message, targetOrigin, sourceOrigin) { + if (targetOrigin == null) { + targetOrigin = '*' + } + const guestContents = webContents.fromId(guestId) if (guestContents == null) return diff --git a/lib/isolated_renderer/init.js b/lib/isolated_renderer/init.js new file mode 100644 index 0000000000..01e3cac301 --- /dev/null +++ b/lib/isolated_renderer/init.js @@ -0,0 +1,22 @@ +/* global binding */ + +'use strict' + +const {guestInstanceId, hiddenPage, openerId, send, sendSync} = binding +const {parse} = JSON + +const ipcRenderer = { + send (...args) { + return send('ipc-message', args) + }, + + sendSync (...args) { + return parse(sendSync('ipc-message-sync', args)) + }, + + // No-ops since events aren't received + on () {}, + once () {} +} + +require('../renderer/window-setup')(ipcRenderer, guestInstanceId, openerId, hiddenPage) diff --git a/lib/renderer/override.js b/lib/renderer/override.js index a05eb898bb..f31e9c0e8c 100644 --- a/lib/renderer/override.js +++ b/lib/renderer/override.js @@ -1,244 +1,8 @@ 'use strict' const {ipcRenderer} = require('electron') -const parseFeaturesString = require('../common/parse-features-string') -const {defineProperty} = Object +const {guestInstanceId, openerId} = process +const hiddenPage = process.argv.includes('--hidden-page') -// Helper function to resolve relative url. -const a = window.top.document.createElement('a') -const resolveURL = function (url) { - a.href = url - return a.href -} - -// Window object returned by "window.open". -const BrowserWindowProxy = (function () { - BrowserWindowProxy.proxies = {} - - BrowserWindowProxy.getOrCreate = function (guestId) { - let proxy = this.proxies[guestId] - if (proxy == null) { - proxy = new BrowserWindowProxy(guestId) - this.proxies[guestId] = proxy - } - return proxy - } - - BrowserWindowProxy.remove = function (guestId) { - delete this.proxies[guestId] - } - - function BrowserWindowProxy (guestId1) { - defineProperty(this, 'guestId', { - configurable: false, - enumerable: true, - writeable: false, - value: guestId1 - }) - - this.closed = false - ipcRenderer.once('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_CLOSED_' + this.guestId, () => { - BrowserWindowProxy.remove(this.guestId) - this.closed = true - }) - } - - BrowserWindowProxy.prototype.close = function () { - ipcRenderer.send('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_CLOSE', this.guestId) - } - - BrowserWindowProxy.prototype.focus = function () { - ipcRenderer.send('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_METHOD', this.guestId, 'focus') - } - - BrowserWindowProxy.prototype.blur = function () { - ipcRenderer.send('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_METHOD', this.guestId, 'blur') - } - - BrowserWindowProxy.prototype.print = function () { - ipcRenderer.send('ELECTRON_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD', this.guestId, 'print') - } - - defineProperty(BrowserWindowProxy.prototype, 'location', { - get: function () { - return ipcRenderer.sendSync('ELECTRON_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD_SYNC', this.guestId, 'getURL') - }, - set: function (url) { - url = resolveURL(url) - return ipcRenderer.sendSync('ELECTRON_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD_SYNC', this.guestId, 'loadURL', url) - } - }) - - BrowserWindowProxy.prototype.postMessage = function (message, targetOrigin) { - if (targetOrigin == null) { - targetOrigin = '*' - } - ipcRenderer.send('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_POSTMESSAGE', this.guestId, message, targetOrigin, window.location.origin) - } - - BrowserWindowProxy.prototype['eval'] = function (...args) { - ipcRenderer.send('ELECTRON_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD', this.guestId, 'executeJavaScript', ...args) - } - - return BrowserWindowProxy -})() - -if (process.guestInstanceId == null) { - // Override default window.close. - window.close = function () { - ipcRenderer.sendSync('ELECTRON_BROWSER_WINDOW_CLOSE') - } -} - -// Make the browser window or guest view emit "new-window" event. -window.open = function (url, frameName, features) { - let guestId, j, len1, name, options, additionalFeatures - if (frameName == null) { - frameName = '' - } - if (features == null) { - features = '' - } - options = {} - - const ints = ['x', 'y', 'width', 'height', 'minWidth', 'maxWidth', 'minHeight', 'maxHeight', 'zoomFactor'] - const webPreferences = ['zoomFactor', 'nodeIntegration', 'preload'] - const disposition = 'new-window' - - // Used to store additional features - additionalFeatures = [] - - // Parse the features - parseFeaturesString(features, function (key, value) { - if (value === undefined) { - additionalFeatures.push(key) - } else { - if (webPreferences.includes(key)) { - if (options.webPreferences == null) { - options.webPreferences = {} - } - options.webPreferences[key] = value - } else { - options[key] = value - } - } - }) - if (options.left) { - if (options.x == null) { - options.x = options.left - } - } - if (options.top) { - if (options.y == null) { - options.y = options.top - } - } - if (options.title == null) { - options.title = frameName - } - if (options.width == null) { - options.width = 800 - } - if (options.height == null) { - options.height = 600 - } - - // Resolve relative urls. - if (url == null || url === '') { - url = 'about:blank' - } else { - url = resolveURL(url) - } - for (j = 0, len1 = ints.length; j < len1; j++) { - name = ints[j] - if (options[name] != null) { - options[name] = parseInt(options[name], 10) - } - } - guestId = ipcRenderer.sendSync('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_OPEN', url, frameName, disposition, options, additionalFeatures) - if (guestId) { - return BrowserWindowProxy.getOrCreate(guestId) - } else { - return null - } -} - -window.alert = function (message, title) { - ipcRenderer.sendSync('ELECTRON_BROWSER_WINDOW_ALERT', message, title) -} - -window.confirm = function (message, title) { - return ipcRenderer.sendSync('ELECTRON_BROWSER_WINDOW_CONFIRM', message, title) -} - -// But we do not support prompt(). -window.prompt = function () { - throw new Error('prompt() is and will not be supported.') -} - -if (process.openerId != null) { - window.opener = BrowserWindowProxy.getOrCreate(process.openerId) -} - -ipcRenderer.on('ELECTRON_GUEST_WINDOW_POSTMESSAGE', function (event, sourceId, message, sourceOrigin) { - // Manually dispatch event instead of using postMessage because we also need to - // set event.source. - event = document.createEvent('Event') - event.initEvent('message', false, false) - event.data = message - event.origin = sourceOrigin - event.source = BrowserWindowProxy.getOrCreate(sourceId) - window.dispatchEvent(event) -}) - -// Forward history operations to browser. -const sendHistoryOperation = function (...args) { - ipcRenderer.send('ELECTRON_NAVIGATION_CONTROLLER', ...args) -} - -const getHistoryOperation = function (...args) { - return ipcRenderer.sendSync('ELECTRON_SYNC_NAVIGATION_CONTROLLER', ...args) -} - -window.history.back = function () { - sendHistoryOperation('goBack') -} - -window.history.forward = function () { - sendHistoryOperation('goForward') -} - -window.history.go = function (offset) { - sendHistoryOperation('goToOffset', offset) -} - -defineProperty(window.history, 'length', { - get: function () { - return getHistoryOperation('length') - } -}) - -// The initial visibilityState. -let cachedVisibilityState = process.argv.includes('--hidden-page') ? 'hidden' : 'visible' - -// Subscribe to visibilityState changes. -ipcRenderer.on('ELECTRON_RENDERER_WINDOW_VISIBILITY_CHANGE', function (event, visibilityState) { - if (cachedVisibilityState !== visibilityState) { - cachedVisibilityState = visibilityState - document.dispatchEvent(new Event('visibilitychange')) - } -}) - -// Make document.hidden and document.visibilityState return the correct value. -defineProperty(document, 'hidden', { - get: function () { - return cachedVisibilityState !== 'visible' - } -}) - -defineProperty(document, 'visibilityState', { - get: function () { - return cachedVisibilityState - } -}) +require('./window-setup')(ipcRenderer, guestInstanceId, openerId, hiddenPage) diff --git a/lib/renderer/window-setup.js b/lib/renderer/window-setup.js new file mode 100644 index 0000000000..3aa451d164 --- /dev/null +++ b/lib/renderer/window-setup.js @@ -0,0 +1,163 @@ +// This file should have no requires since it is used by the isolated context +// preload bundle. Instead arguments should be passed in for everything it +// needs. + +'use strict' + +const {defineProperty} = Object + +// Helper function to resolve relative url. +const a = window.top.document.createElement('a') +const resolveURL = function (url) { + a.href = url + return a.href +} + +const windowProxies = {} + +module.exports = (ipcRenderer, guestInstanceId, openerId, hiddenPage) => { + const getOrCreateProxy = (guestId) => { + let proxy = windowProxies[guestId] + if (proxy == null) { + proxy = new BrowserWindowProxy(guestId) + windowProxies[guestId] = proxy + } + return proxy + } + + const removeProxy = (guestId) => { + delete windowProxies[guestId] + } + + function BrowserWindowProxy (guestId) { + this.closed = false + + ipcRenderer.once(`ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_CLOSED_${guestId}`, () => { + removeProxy(this.guestId) + this.closed = true + }) + + this.close = () => { + ipcRenderer.send('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_CLOSE', guestId) + } + + this.focus = () => { + ipcRenderer.send('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_METHOD', guestId, 'focus') + } + + this.blur = () => { + ipcRenderer.send('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_METHOD', guestId, 'blur') + } + + this.print = () => { + ipcRenderer.send('ELECTRON_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD', guestId, 'print') + } + + this.postMessage = (message, targetOrigin) => { + ipcRenderer.send('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_POSTMESSAGE', guestId, message, targetOrigin, window.location.origin) + } + + this.eval = (...args) => { + ipcRenderer.send('ELECTRON_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD', guestId, 'executeJavaScript', ...args) + } + } + + if (guestInstanceId == null) { + // Override default window.close. + window.close = function () { + ipcRenderer.sendSync('ELECTRON_BROWSER_WINDOW_CLOSE') + } + } + + // Make the browser window or guest view emit "new-window" event. + window.open = function (url, frameName, features) { + if (url != null && url.length > 0) { + url = resolveURL(url) + } + const guestId = ipcRenderer.sendSync('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_OPEN', url, frameName, features) + if (guestId != null) { + return getOrCreateProxy(guestId) + } else { + return null + } + } + + window.alert = function (message, title) { + ipcRenderer.sendSync('ELECTRON_BROWSER_WINDOW_ALERT', message, title) + } + + window.confirm = function (message, title) { + return ipcRenderer.sendSync('ELECTRON_BROWSER_WINDOW_CONFIRM', message, title) + } + + // But we do not support prompt(). + window.prompt = function () { + throw new Error('prompt() is and will not be supported.') + } + + if (openerId != null) { + window.opener = getOrCreateProxy(openerId) + } + + ipcRenderer.on('ELECTRON_GUEST_WINDOW_POSTMESSAGE', function (event, sourceId, message, sourceOrigin) { + // Manually dispatch event instead of using postMessage because we also need to + // set event.source. + event = document.createEvent('Event') + event.initEvent('message', false, false) + event.data = message + event.origin = sourceOrigin + event.source = BrowserWindowProxy.getOrCreate(sourceId) + window.dispatchEvent(event) + }) + + // Forward history operations to browser. + const sendHistoryOperation = function (...args) { + ipcRenderer.send('ELECTRON_NAVIGATION_CONTROLLER', ...args) + } + + const getHistoryOperation = function (...args) { + return ipcRenderer.sendSync('ELECTRON_SYNC_NAVIGATION_CONTROLLER', ...args) + } + + window.history.back = function () { + sendHistoryOperation('goBack') + } + + window.history.forward = function () { + sendHistoryOperation('goForward') + } + + window.history.go = function (offset) { + sendHistoryOperation('goToOffset', offset) + } + + defineProperty(window.history, 'length', { + get: function () { + return getHistoryOperation('length') + } + }) + + // The initial visibilityState. + let cachedVisibilityState = hiddenPage ? 'hidden' : 'visible' + + // Subscribe to visibilityState changes. + ipcRenderer.on('ELECTRON_RENDERER_WINDOW_VISIBILITY_CHANGE', function (event, visibilityState) { + if (cachedVisibilityState !== visibilityState) { + cachedVisibilityState = visibilityState + document.dispatchEvent(new Event('visibilitychange')) + } + }) + + // Make document.hidden and document.visibilityState return the correct value. + defineProperty(document, 'hidden', { + get: function () { + return cachedVisibilityState !== 'visible' + } + }) + + defineProperty(document, 'visibilityState', { + get: function () { + return cachedVisibilityState + } + }) +} diff --git a/spec/api-browser-window-spec.js b/spec/api-browser-window-spec.js index b777c684d1..8fd05a327f 100644 --- a/spec/api-browser-window-spec.js +++ b/spec/api-browser-window-spec.js @@ -1836,6 +1836,27 @@ describe('BrowserWindow module', function () { }) describe('contextIsolation option', () => { + const expectedContextData = { + preloadContext: { + preloadProperty: 'number', + pageProperty: 'undefined', + typeofRequire: 'function', + typeofProcess: 'object', + typeofArrayPush: 'function', + typeofFunctionApply: 'function' + }, + pageContext: { + preloadProperty: 'undefined', + pageProperty: 'string', + typeofRequire: 'undefined', + typeofProcess: 'undefined', + typeofArrayPush: 'number', + typeofFunctionApply: 'boolean', + typeofPreloadExecuteJavaScriptProperty: 'number', + typeofOpenedWindow: 'object' + } + } + beforeEach(() => { if (w != null) w.destroy() w = new BrowserWindow({ @@ -1849,56 +1870,18 @@ describe('BrowserWindow module', function () { it('separates the page context from the Electron/preload context', (done) => { ipcMain.once('isolated-world', (event, data) => { - assert.deepEqual(data, { - preloadContext: { - preloadProperty: 'number', - pageProperty: 'undefined', - typeofRequire: 'function', - typeofProcess: 'object', - typeofArrayPush: 'function', - typeofFunctionApply: 'function' - }, - pageContext: { - preloadProperty: 'undefined', - pageProperty: 'string', - typeofRequire: 'undefined', - typeofProcess: 'undefined', - typeofArrayPush: 'number', - typeofFunctionApply: 'boolean', - typeofPreloadExecuteJavaScriptProperty: 'number' - } - }) + assert.deepEqual(data, expectedContextData) done() }) - w.loadURL('file://' + fixtures + '/api/isolated.html') }) it('recreates the contexts on reload', (done) => { w.webContents.once('did-finish-load', () => { ipcMain.once('isolated-world', (event, data) => { - assert.deepEqual(data, { - preloadContext: { - preloadProperty: 'number', - pageProperty: 'undefined', - typeofRequire: 'function', - typeofProcess: 'object', - typeofArrayPush: 'function', - typeofFunctionApply: 'function' - }, - pageContext: { - preloadProperty: 'undefined', - pageProperty: 'string', - typeofRequire: 'undefined', - typeofProcess: 'undefined', - typeofArrayPush: 'number', - typeofFunctionApply: 'boolean', - typeofPreloadExecuteJavaScriptProperty: 'number' - } - }) + assert.deepEqual(data, expectedContextData) done() }) - w.webContents.reload() }) w.loadURL('file://' + fixtures + '/api/isolated.html') @@ -1909,7 +1892,6 @@ describe('BrowserWindow module', function () { assert.equal(window.webContents.getWebPreferences().contextIsolation, true) done() }) - w.loadURL('file://' + fixtures + '/pages/window-open.html') }) }) diff --git a/spec/fixtures/api/isolated.html b/spec/fixtures/api/isolated.html index a0cc84770e..562bf01b7c 100644 --- a/spec/fixtures/api/isolated.html +++ b/spec/fixtures/api/isolated.html @@ -7,6 +7,10 @@ window.hello = 'world' Array.prototype.push = 3 Function.prototype.apply = true + + const opened = window.open() + opened.close() + window.postMessage({ preloadProperty: typeof window.foo, pageProperty: typeof window.hello, @@ -14,7 +18,8 @@ typeofProcess: typeof process, typeofArrayPush: typeof Array.prototype.push, typeofFunctionApply: typeof Function.prototype.apply, - typeofPreloadExecuteJavaScriptProperty: typeof window.preloadExecuteJavaScriptProperty + typeofPreloadExecuteJavaScriptProperty: typeof window.preloadExecuteJavaScriptProperty, + typeofOpenedWindow: typeof opened }, '*') diff --git a/spec/webview-spec.js b/spec/webview-spec.js index 2d3ce3c06e..616fc45934 100644 --- a/spec/webview-spec.js +++ b/spec/webview-spec.js @@ -448,13 +448,15 @@ describe(' tag', function () { typeofProcess: 'undefined', typeofArrayPush: 'number', typeofFunctionApply: 'boolean', - typeofPreloadExecuteJavaScriptProperty: 'number' + typeofPreloadExecuteJavaScriptProperty: 'number', + typeofOpenedWindow: 'object' } }) done() }) webview.setAttribute('preload', path.join(fixtures, 'api', 'isolated-preload.js')) + webview.setAttribute('allowpopups', 'yes') webview.setAttribute('webpreferences', 'contextIsolation=yes') webview.src = 'file://' + fixtures + '/api/isolated.html' document.body.appendChild(webview) From bb260343de206b70abc5ed9bbb1687a727bafdde Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 11 Jan 2017 17:04:20 -0800 Subject: [PATCH 29/44] Move more functions to outer scope --- lib/renderer/window-setup.js | 124 +++++++++++++++++------------------ 1 file changed, 62 insertions(+), 62 deletions(-) diff --git a/lib/renderer/window-setup.js b/lib/renderer/window-setup.js index 3aa451d164..f7328c9b0c 100644 --- a/lib/renderer/window-setup.js +++ b/lib/renderer/window-setup.js @@ -15,53 +15,62 @@ const resolveURL = function (url) { const windowProxies = {} +const getOrCreateProxy = (ipcRenderer, guestId) => { + let proxy = windowProxies[guestId] + if (proxy == null) { + proxy = new BrowserWindowProxy(ipcRenderer, guestId) + windowProxies[guestId] = proxy + } + return proxy +} + +const removeProxy = (guestId) => { + delete windowProxies[guestId] +} + +function BrowserWindowProxy (ipcRenderer, guestId) { + this.closed = false + + ipcRenderer.once(`ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_CLOSED_${guestId}`, () => { + removeProxy(this.guestId) + this.closed = true + }) + + this.close = () => { + ipcRenderer.send('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_CLOSE', guestId) + } + + this.focus = () => { + ipcRenderer.send('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_METHOD', guestId, 'focus') + } + + this.blur = () => { + ipcRenderer.send('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_METHOD', guestId, 'blur') + } + + this.print = () => { + ipcRenderer.send('ELECTRON_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD', guestId, 'print') + } + + this.postMessage = (message, targetOrigin) => { + ipcRenderer.send('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_POSTMESSAGE', guestId, message, targetOrigin, window.location.origin) + } + + this.eval = (...args) => { + ipcRenderer.send('ELECTRON_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD', guestId, 'executeJavaScript', ...args) + } +} + +// Forward history operations to browser. +const sendHistoryOperation = function (ipcRenderer, ...args) { + ipcRenderer.send('ELECTRON_NAVIGATION_CONTROLLER', ...args) +} + +const getHistoryOperation = function (ipcRenderer, ...args) { + return ipcRenderer.sendSync('ELECTRON_SYNC_NAVIGATION_CONTROLLER', ...args) +} + module.exports = (ipcRenderer, guestInstanceId, openerId, hiddenPage) => { - const getOrCreateProxy = (guestId) => { - let proxy = windowProxies[guestId] - if (proxy == null) { - proxy = new BrowserWindowProxy(guestId) - windowProxies[guestId] = proxy - } - return proxy - } - - const removeProxy = (guestId) => { - delete windowProxies[guestId] - } - - function BrowserWindowProxy (guestId) { - this.closed = false - - ipcRenderer.once(`ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_CLOSED_${guestId}`, () => { - removeProxy(this.guestId) - this.closed = true - }) - - this.close = () => { - ipcRenderer.send('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_CLOSE', guestId) - } - - this.focus = () => { - ipcRenderer.send('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_METHOD', guestId, 'focus') - } - - this.blur = () => { - ipcRenderer.send('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_METHOD', guestId, 'blur') - } - - this.print = () => { - ipcRenderer.send('ELECTRON_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD', guestId, 'print') - } - - this.postMessage = (message, targetOrigin) => { - ipcRenderer.send('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_POSTMESSAGE', guestId, message, targetOrigin, window.location.origin) - } - - this.eval = (...args) => { - ipcRenderer.send('ELECTRON_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD', guestId, 'executeJavaScript', ...args) - } - } - if (guestInstanceId == null) { // Override default window.close. window.close = function () { @@ -76,7 +85,7 @@ module.exports = (ipcRenderer, guestInstanceId, openerId, hiddenPage) => { } const guestId = ipcRenderer.sendSync('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_OPEN', url, frameName, features) if (guestId != null) { - return getOrCreateProxy(guestId) + return getOrCreateProxy(ipcRenderer, guestId) } else { return null } @@ -96,7 +105,7 @@ module.exports = (ipcRenderer, guestInstanceId, openerId, hiddenPage) => { } if (openerId != null) { - window.opener = getOrCreateProxy(openerId) + window.opener = getOrCreateProxy(ipcRenderer, openerId) } ipcRenderer.on('ELECTRON_GUEST_WINDOW_POSTMESSAGE', function (event, sourceId, message, sourceOrigin) { @@ -106,34 +115,25 @@ module.exports = (ipcRenderer, guestInstanceId, openerId, hiddenPage) => { event.initEvent('message', false, false) event.data = message event.origin = sourceOrigin - event.source = BrowserWindowProxy.getOrCreate(sourceId) + event.source = getOrCreateProxy(ipcRenderer, sourceId) window.dispatchEvent(event) }) - // Forward history operations to browser. - const sendHistoryOperation = function (...args) { - ipcRenderer.send('ELECTRON_NAVIGATION_CONTROLLER', ...args) - } - - const getHistoryOperation = function (...args) { - return ipcRenderer.sendSync('ELECTRON_SYNC_NAVIGATION_CONTROLLER', ...args) - } - window.history.back = function () { - sendHistoryOperation('goBack') + sendHistoryOperation(ipcRenderer, 'goBack') } window.history.forward = function () { - sendHistoryOperation('goForward') + sendHistoryOperation(ipcRenderer, 'goForward') } window.history.go = function (offset) { - sendHistoryOperation('goToOffset', offset) + sendHistoryOperation(ipcRenderer, 'goToOffset', offset) } defineProperty(window.history, 'length', { get: function () { - return getHistoryOperation('length') + return getHistoryOperation(ipcRenderer, 'length') } }) From 2e6d08c652336683946dc7033ae034fef2b786fd Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 11 Jan 2017 17:06:27 -0800 Subject: [PATCH 30/44] Remove unneeded this prefix --- lib/renderer/window-setup.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/renderer/window-setup.js b/lib/renderer/window-setup.js index f7328c9b0c..530907856e 100644 --- a/lib/renderer/window-setup.js +++ b/lib/renderer/window-setup.js @@ -32,7 +32,7 @@ function BrowserWindowProxy (ipcRenderer, guestId) { this.closed = false ipcRenderer.once(`ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_CLOSED_${guestId}`, () => { - removeProxy(this.guestId) + removeProxy(guestId) this.closed = true }) From f3852c57fc6aba00bf77c47ae3e3443f05aae284 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Wed, 11 Jan 2017 17:07:05 -0800 Subject: [PATCH 31/44] Use empty string for comparison --- lib/renderer/window-setup.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/renderer/window-setup.js b/lib/renderer/window-setup.js index 530907856e..1e1d1792c5 100644 --- a/lib/renderer/window-setup.js +++ b/lib/renderer/window-setup.js @@ -80,7 +80,7 @@ module.exports = (ipcRenderer, guestInstanceId, openerId, hiddenPage) => { // Make the browser window or guest view emit "new-window" event. window.open = function (url, frameName, features) { - if (url != null && url.length > 0) { + if (url != null && url !== '') { url = resolveURL(url) } const guestId = ipcRenderer.sendSync('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_OPEN', url, frameName, features) From de4be56b096c3ee4f18387498a935fbc8bcba5d3 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 12 Jan 2017 12:50:14 -0800 Subject: [PATCH 32/44] Use internal open event name with fully parsed options --- lib/browser/api/browser-window.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/browser/api/browser-window.js b/lib/browser/api/browser-window.js index 9cf1a8163c..6528da1ba0 100644 --- a/lib/browser/api/browser-window.js +++ b/lib/browser/api/browser-window.js @@ -26,7 +26,7 @@ BrowserWindow.prototype._init = function () { width: 800, height: 600 } - ipcMain.emit('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_OPEN', + ipcMain.emit('ELECTRON_GUEST_WINDOW_MANAGER_INTERNAL_WINDOW_OPEN', event, url, frameName, disposition, options, additionalFeatures, postData) }) @@ -56,7 +56,8 @@ BrowserWindow.prototype._init = function () { height: height || 600, webContents: webContents } - ipcMain.emit('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_OPEN', event, url, frameName, disposition, options) + ipcMain.emit('ELECTRON_GUEST_WINDOW_MANAGER_INTERNAL_WINDOW_OPEN', + event, url, frameName, disposition, options) }) // window.resizeTo(...) From fbcbfbda6aceeb5951dd01a8b6fc00c4df297310 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 12 Jan 2017 13:04:18 -0800 Subject: [PATCH 33/44] Add back BrowserWindowProxy location property --- lib/renderer/window-setup.js | 10 ++++++++++ spec/chromium-spec.js | 20 ++++++++------------ 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/lib/renderer/window-setup.js b/lib/renderer/window-setup.js index 1e1d1792c5..df1109e795 100644 --- a/lib/renderer/window-setup.js +++ b/lib/renderer/window-setup.js @@ -31,6 +31,16 @@ const removeProxy = (guestId) => { function BrowserWindowProxy (ipcRenderer, guestId) { this.closed = false + defineProperty(this, 'location', { + get: function () { + return ipcRenderer.sendSync('ELECTRON_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD_SYNC', guestId, 'getURL') + }, + set: function (url) { + url = resolveURL(url) + return ipcRenderer.sendSync('ELECTRON_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD_SYNC', guestId, 'loadURL', url) + } + }) + ipcRenderer.once(`ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_CLOSED_${guestId}`, () => { removeProxy(guestId) this.closed = true diff --git a/spec/chromium-spec.js b/spec/chromium-spec.js index 58efdc745c..997eff8eae 100644 --- a/spec/chromium-spec.js +++ b/spec/chromium-spec.js @@ -6,7 +6,7 @@ const url = require('url') const {ipcRenderer, remote} = require('electron') const {closeWindow} = require('./window-helpers') -const {BrowserWindow, ipcMain, protocol, session, webContents} = remote +const {app, BrowserWindow, ipcMain, protocol, session, webContents} = remote const isCI = remote.getGlobal('isCi') @@ -197,12 +197,6 @@ describe('chromium feature', function () { var b = window.open('about:blank', '', 'show=no') assert.equal(b.closed, false) assert.equal(b.constructor.name, 'BrowserWindowProxy') - - // Check that guestId is not writeable - assert(b.guestId) - b.guestId = 'anotherValue' - assert.notEqual(b.guestId, 'anoterValue') - b.close() }) @@ -295,12 +289,14 @@ describe('chromium feature', function () { } else { targetURL = 'file://' + fixtures + '/pages/base-page.html' } - b = window.open(targetURL) - webContents.fromId(b.guestId).once('did-finish-load', function () { - assert.equal(b.location, targetURL) - b.close() - done() + app.once('browser-window-created', (event, window) => { + window.webContents.once('did-finish-load', () => { + assert.equal(b.location, targetURL) + b.close() + done() + }) }) + b = window.open(targetURL) }) it('defines a window.location setter', function (done) { From 37b7dda3c5d7424c35391889237bc9e01a4940a2 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 12 Jan 2017 13:19:27 -0800 Subject: [PATCH 34/44] Remove use of now private guestId --- spec/chromium-spec.js | 61 +++++++++++-------- .../pages/window-open-postMessage.html | 3 +- 2 files changed, 36 insertions(+), 28 deletions(-) diff --git a/spec/chromium-spec.js b/spec/chromium-spec.js index 997eff8eae..bb33b272ec 100644 --- a/spec/chromium-spec.js +++ b/spec/chromium-spec.js @@ -300,34 +300,43 @@ describe('chromium feature', function () { }) it('defines a window.location setter', function (done) { - // Load a page that definitely won't redirect - var b = window.open('about:blank') - webContents.fromId(b.guestId).once('did-finish-load', function () { - // When it loads, redirect - b.location = 'file://' + fixtures + '/pages/base-page.html' - webContents.fromId(b.guestId).once('did-finish-load', function () { - // After our second redirect, cleanup and callback - b.close() - done() + let b + app.once('browser-window-created', (event, {webContents}) => { + webContents.once('did-finish-load', function () { + // When it loads, redirect + b.location = 'file://' + fixtures + '/pages/base-page.html' + webContents.once('did-finish-load', function () { + // After our second redirect, cleanup and callback + b.close() + done() + }) }) }) + // Load a page that definitely won't redirect + b = window.open('about:blank') }) it('open a blank page when no URL is specified', function (done) { - let b = window.open() - webContents.fromId(b.guestId).once('did-finish-load', function () { - const {location} = b - b.close() - assert.equal(location, 'about:blank') - - let c = window.open('') - webContents.fromId(c.guestId).once('did-finish-load', function () { - const {location} = c - c.close() + let b + app.once('browser-window-created', (event, {webContents}) => { + webContents.once('did-finish-load', function () { + const {location} = b + b.close() assert.equal(location, 'about:blank') - done() + + let c + app.once('browser-window-created', (event, {webContents}) => { + webContents.once('did-finish-load', function () { + const {location} = c + c.close() + assert.equal(location, 'about:blank') + done() + }) + }) + c = window.open('') }) }) + b = window.open() }) }) @@ -492,8 +501,7 @@ describe('chromium feature', function () { describe('window.postMessage', function () { it('sets the source and origin correctly', function (done) { - var b, sourceId - sourceId = remote.getCurrentWindow().id + var b listener = function (event) { window.removeEventListener('message', listener) b.close() @@ -501,15 +509,16 @@ describe('chromium feature', function () { assert.equal(message.data, 'testing') assert.equal(message.origin, 'file://') assert.equal(message.sourceEqualsOpener, true) - assert.equal(message.sourceId, sourceId) assert.equal(event.origin, 'file://') done() } window.addEventListener('message', listener) - b = window.open('file://' + fixtures + '/pages/window-open-postMessage.html', '', 'show=no') - webContents.fromId(b.guestId).once('did-finish-load', function () { - b.postMessage('testing', '*') + app.once('browser-window-created', (event, {webContents}) => { + webContents.once('did-finish-load', function () { + b.postMessage('testing', '*') + }) }) + b = window.open('file://' + fixtures + '/pages/window-open-postMessage.html', '', 'show=no') }) }) diff --git a/spec/fixtures/pages/window-open-postMessage.html b/spec/fixtures/pages/window-open-postMessage.html index 0e7e4d8cce..009ddb6bb4 100644 --- a/spec/fixtures/pages/window-open-postMessage.html +++ b/spec/fixtures/pages/window-open-postMessage.html @@ -5,8 +5,7 @@ window.opener.postMessage(JSON.stringify({ origin: e.origin, data: e.data, - sourceEqualsOpener: e.source === window.opener, - sourceId: e.source.guestId + sourceEqualsOpener: e.source === window.opener }), '*'); }); From f4f01747464294f23f3a2771a7daccfabb24a683 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Thu, 12 Jan 2017 13:28:48 -0800 Subject: [PATCH 35/44] Parse guestInstanceId and opener as ints --- lib/isolated_renderer/init.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/isolated_renderer/init.js b/lib/isolated_renderer/init.js index 01e3cac301..943713d985 100644 --- a/lib/isolated_renderer/init.js +++ b/lib/isolated_renderer/init.js @@ -2,7 +2,7 @@ 'use strict' -const {guestInstanceId, hiddenPage, openerId, send, sendSync} = binding +const {send, sendSync} = binding const {parse} = JSON const ipcRenderer = { @@ -19,4 +19,8 @@ const ipcRenderer = { once () {} } +let {guestInstanceId, hiddenPage, openerId} = binding +if (guestInstanceId != null) guestInstanceId = parseInt(guestInstanceId) +if (openerId != null) openerId = parseInt(openerId) + require('../renderer/window-setup')(ipcRenderer, guestInstanceId, openerId, hiddenPage) From 6bcfd0630c96653db319859572726d6e788c7784 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Fri, 13 Jan 2017 10:53:56 -0800 Subject: [PATCH 36/44] Document implemented APIs at the top --- lib/renderer/window-setup.js | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/lib/renderer/window-setup.js b/lib/renderer/window-setup.js index df1109e795..21f0741a22 100644 --- a/lib/renderer/window-setup.js +++ b/lib/renderer/window-setup.js @@ -2,6 +2,25 @@ // preload bundle. Instead arguments should be passed in for everything it // needs. +// This file implements the following APIs: +// - window.alert() +// - window.confirm() +// - window.history.back() +// - window.history.forward() +// - window.history.go() +// - window.history.length +// - window.open() +// - window.opener.blur() +// - window.opener.close() +// - window.opener.eval() +// - window.opener.focus() +// - window.opener.location +// - window.opener.print() +// - window.opener.postMessage() +// - window.prompt() +// - document.hidden +// - document.visibilityState + 'use strict' const {defineProperty} = Object From f35224b0e44cd8f3e9e82a37dcaac7b8e202d19d Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Fri, 13 Jan 2017 10:58:33 -0800 Subject: [PATCH 37/44] :art: --- atom/renderer/atom_renderer_client.cc | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/atom/renderer/atom_renderer_client.cc b/atom/renderer/atom_renderer_client.cc index 7ef04732c4..73f04732b2 100644 --- a/atom/renderer/atom_renderer_client.cc +++ b/atom/renderer/atom_renderer_client.cc @@ -62,6 +62,8 @@ namespace { enum World { MAIN_WORLD = 0, + // Use a high number far away from 0 to not collide with any other world + // IDs created internally by Chrome. ISOLATED_WORLD = 999 }; @@ -84,6 +86,8 @@ class AtomRenderFrameObserver : public content::RenderFrameObserver { } void CreateIsolatedWorldContext() { + // This maps to the name shown in the context combo box in the Console tab + // of the dev tools. render_frame_->GetWebFrame()->setIsolatedWorldHumanReadableName( World::ISOLATED_WORLD, blink::WebString::fromUTF8("Electron Isolated Context")); @@ -134,7 +138,7 @@ class AtomRenderFrameObserver : public content::RenderFrameObserver { return world_id == World::ISOLATED_WORLD; } - bool NotifyClient(int world_id) { + bool ShouldNotifyClient(int world_id) { if (renderer_client_->isolated_world()) return IsIsolatedWorld(world_id); else @@ -144,7 +148,7 @@ class AtomRenderFrameObserver : public content::RenderFrameObserver { void DidCreateScriptContext(v8::Handle context, int extension_group, int world_id) override { - if (NotifyClient(world_id)) + if (ShouldNotifyClient(world_id)) renderer_client_->DidCreateScriptContext(context, render_frame_); if (renderer_client_->isolated_world() && IsMainWorld(world_id)) { @@ -155,7 +159,7 @@ class AtomRenderFrameObserver : public content::RenderFrameObserver { void WillReleaseScriptContext(v8::Local context, int world_id) override { - if (NotifyClient(world_id)) + if (ShouldNotifyClient(world_id)) renderer_client_->WillReleaseScriptContext(context, render_frame_); } From 13acf7a6a3b8eec7c5652808c3ccce8a65783db9 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Fri, 13 Jan 2017 11:01:46 -0800 Subject: [PATCH 38/44] Mention accessing context in dev tools --- docs/api/browser-window.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/api/browser-window.md b/docs/api/browser-window.md index 61a41e6ad0..306a365a58 100644 --- a/docs/api/browser-window.md +++ b/docs/api/browser-window.md @@ -293,8 +293,10 @@ It creates a new `BrowserWindow` with native properties as set by the `options`. loading potentially untrusted remote content to ensure the loaded content cannot tamper with the `preload` script and any Electron APIs being used. This option uses the same technique used by [Chrome Content Scripts][chrome-content-scripts]. - This option is experimental and may undergo further API changes before - Electron 2.0. + You can access this context in the dev tools by selecting the + 'Electron Isolated Context' entry in the combo box at the top of the + Console tab. **Note:** This option is currently experimental and may + change or be removed in future Electron releases. When setting minimum or maximum window size with `minWidth`/`maxWidth`/ `minHeight`/`maxHeight`, it only constrains the users. It won't prevent you from From 2e62d81c246ef8e43b74ab6b05668fb03ad9c0d2 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Fri, 13 Jan 2017 11:06:19 -0800 Subject: [PATCH 39/44] Access getter instead of variable --- atom/renderer/atom_renderer_client.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/atom/renderer/atom_renderer_client.cc b/atom/renderer/atom_renderer_client.cc index 73f04732b2..337b1d124c 100644 --- a/atom/renderer/atom_renderer_client.cc +++ b/atom/renderer/atom_renderer_client.cc @@ -415,7 +415,7 @@ void AtomRendererClient::AddSupportedKeySystems( v8::Local AtomRendererClient::GetContext( blink::WebFrame* frame, v8::Isolate* isolate) { - if (isolated_world_) + if (isolated_world()) return frame->worldScriptContext( isolate, World::ISOLATED_WORLD, ExtensionGroup::MAIN_GROUP); else From dcf9a395c241b6a7d0aea3ae48a3d88b8e931945 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Fri, 13 Jan 2017 12:54:23 -0800 Subject: [PATCH 40/44] Only create isolated context in main frame --- atom/renderer/atom_renderer_client.cc | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/atom/renderer/atom_renderer_client.cc b/atom/renderer/atom_renderer_client.cc index 337b1d124c..7d5f2be6e3 100644 --- a/atom/renderer/atom_renderer_client.cc +++ b/atom/renderer/atom_renderer_client.cc @@ -139,7 +139,7 @@ class AtomRenderFrameObserver : public content::RenderFrameObserver { } bool ShouldNotifyClient(int world_id) { - if (renderer_client_->isolated_world()) + if (renderer_client_->isolated_world() && render_frame_->IsMainFrame()) return IsIsolatedWorld(world_id); else return IsMainWorld(world_id); @@ -151,7 +151,8 @@ class AtomRenderFrameObserver : public content::RenderFrameObserver { if (ShouldNotifyClient(world_id)) renderer_client_->DidCreateScriptContext(context, render_frame_); - if (renderer_client_->isolated_world() && IsMainWorld(world_id)) { + if (renderer_client_->isolated_world() && IsMainWorld(world_id) + && render_frame_->IsMainFrame()) { CreateIsolatedWorldContext(); SetupMainWorldOverrides(context); } From 815cb1b31c44b2575a2b31714d528f8b9ccefd43 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 16 Jan 2017 11:31:10 -0800 Subject: [PATCH 41/44] Include atom_natives after builtin includes --- atom/renderer/atom_renderer_client.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/atom/renderer/atom_renderer_client.cc b/atom/renderer/atom_renderer_client.cc index 7d5f2be6e3..214e9d8812 100644 --- a/atom/renderer/atom_renderer_client.cc +++ b/atom/renderer/atom_renderer_client.cc @@ -4,11 +4,11 @@ #include "atom/renderer/atom_renderer_client.h" -#include "atom_natives.h" // NOLINT: This file is generated with js2c - #include #include +#include "atom_natives.h" // NOLINT: This file is generated with js2c + #include "atom/common/api/api_messages.h" #include "atom/common/api/atom_bindings.h" #include "atom/common/api/event_emitter_caller.h" From b26428c43c89219bb24b8eeb2ce72f83712c3245 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 16 Jan 2017 11:36:15 -0800 Subject: [PATCH 42/44] :art: --- atom/renderer/atom_renderer_client.cc | 1 - 1 file changed, 1 deletion(-) diff --git a/atom/renderer/atom_renderer_client.cc b/atom/renderer/atom_renderer_client.cc index 214e9d8812..a5d6f8eea1 100644 --- a/atom/renderer/atom_renderer_client.cc +++ b/atom/renderer/atom_renderer_client.cc @@ -126,7 +126,6 @@ class AtomRenderFrameObserver : public content::RenderFrameObserver { dict.Set("hiddenPage", command_line->HasSwitch(switches::kHiddenPage)); v8::Local args[] = { binding }; - ignore_result(func->Call(context, v8::Null(isolate), 1, args)); } From 1d824d4645057fbbd0e0397a301b32f4bd671498 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 16 Jan 2017 12:56:39 -0800 Subject: [PATCH 43/44] Assert document visibility in main context --- spec/api-browser-window-spec.js | 4 +++- spec/fixtures/api/isolated.html | 4 +++- spec/webview-spec.js | 9 +++++++-- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/spec/api-browser-window-spec.js b/spec/api-browser-window-spec.js index 8fd05a327f..99c8eb289b 100644 --- a/spec/api-browser-window-spec.js +++ b/spec/api-browser-window-spec.js @@ -1853,7 +1853,9 @@ describe('BrowserWindow module', function () { typeofArrayPush: 'number', typeofFunctionApply: 'boolean', typeofPreloadExecuteJavaScriptProperty: 'number', - typeofOpenedWindow: 'object' + typeofOpenedWindow: 'object', + documentHidden: true, + documentVisibilityState: 'hidden' } } diff --git a/spec/fixtures/api/isolated.html b/spec/fixtures/api/isolated.html index 562bf01b7c..25269b35e9 100644 --- a/spec/fixtures/api/isolated.html +++ b/spec/fixtures/api/isolated.html @@ -19,7 +19,9 @@ typeofArrayPush: typeof Array.prototype.push, typeofFunctionApply: typeof Function.prototype.apply, typeofPreloadExecuteJavaScriptProperty: typeof window.preloadExecuteJavaScriptProperty, - typeofOpenedWindow: typeof opened + typeofOpenedWindow: typeof opened, + documentHidden: document.hidden, + documentVisibilityState: document.visibilityState }, '*') diff --git a/spec/webview-spec.js b/spec/webview-spec.js index 616fc45934..7f72931a99 100644 --- a/spec/webview-spec.js +++ b/spec/webview-spec.js @@ -2,9 +2,12 @@ const assert = require('assert') const path = require('path') const http = require('http') const url = require('url') -const {app, session, getGuestWebContents, ipcMain, BrowserWindow, webContents} = require('electron').remote +const {remote} = require('electron') +const {app, session, getGuestWebContents, ipcMain, BrowserWindow, webContents} = remote const {closeWindow} = require('./window-helpers') +const isCI = remote.getGlobal('isCi') + describe(' tag', function () { this.timeout(3 * 60 * 1000) @@ -449,7 +452,9 @@ describe(' tag', function () { typeofArrayPush: 'number', typeofFunctionApply: 'boolean', typeofPreloadExecuteJavaScriptProperty: 'number', - typeofOpenedWindow: 'object' + typeofOpenedWindow: 'object', + documentHidden: isCI, + documentVisibilityState: isCI ? 'hidden' : 'visible' } }) done() From 9ccc78c62f8bb719d7333f67f48fac6b39c94192 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Mon, 16 Jan 2017 13:09:38 -0800 Subject: [PATCH 44/44] Use options constants for keys --- atom/renderer/atom_renderer_client.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/atom/renderer/atom_renderer_client.cc b/atom/renderer/atom_renderer_client.cc index a5d6f8eea1..fdbbdf9e36 100644 --- a/atom/renderer/atom_renderer_client.cc +++ b/atom/renderer/atom_renderer_client.cc @@ -118,10 +118,10 @@ class AtomRenderFrameObserver : public content::RenderFrameObserver { base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); mate::Dictionary dict(isolate, binding); if (command_line->HasSwitch(switches::kGuestInstanceID)) - dict.Set("guestInstanceId", + dict.Set(options::kGuestInstanceID, command_line->GetSwitchValueASCII(switches::kGuestInstanceID)); if (command_line->HasSwitch(switches::kOpenerID)) - dict.Set("openerId", + dict.Set(options::kOpenerID, command_line->GetSwitchValueASCII(switches::kOpenerID)); dict.Set("hiddenPage", command_line->HasSwitch(switches::kHiddenPage));