Merge branch 'support-chromium-sandbox' of https://github.com/tarruda/electron into tarruda-support-chromium-sandbox

This commit is contained in:
Cheng Zhao 2016-09-27 20:02:23 +08:00
commit 458c4dd129
35 changed files with 1055 additions and 100 deletions

View file

@ -11,7 +11,9 @@
#include "atom/browser/atom_browser_client.h" #include "atom/browser/atom_browser_client.h"
#include "atom/browser/relauncher.h" #include "atom/browser/relauncher.h"
#include "atom/common/google_api_key.h" #include "atom/common/google_api_key.h"
#include "atom/common/options_switches.h"
#include "atom/renderer/atom_renderer_client.h" #include "atom/renderer/atom_renderer_client.h"
#include "atom/renderer/atom_sandboxed_renderer_client.h"
#include "atom/utility/atom_content_utility_client.h" #include "atom/utility/atom_content_utility_client.h"
#include "base/command_line.h" #include "base/command_line.h"
#include "base/debug/stack_trace.h" #include "base/debug/stack_trace.h"
@ -29,7 +31,7 @@ namespace {
const char* kRelauncherProcess = "relauncher"; const char* kRelauncherProcess = "relauncher";
bool IsBrowserProcess(base::CommandLine* cmd) { bool IsBrowserProcess(base::CommandLine* cmd) {
std::string process_type = cmd->GetSwitchValueASCII(switches::kProcessType); std::string process_type = cmd->GetSwitchValueASCII(::switches::kProcessType);
return process_type.empty(); return process_type.empty();
} }
@ -72,7 +74,7 @@ bool AtomMainDelegate::BasicStartupComplete(int* exit_code) {
// Only enable logging when --enable-logging is specified. // Only enable logging when --enable-logging is specified.
std::unique_ptr<base::Environment> env(base::Environment::Create()); std::unique_ptr<base::Environment> env(base::Environment::Create());
if (!command_line->HasSwitch(switches::kEnableLogging) && if (!command_line->HasSwitch(::switches::kEnableLogging) &&
!env->HasVar("ELECTRON_ENABLE_LOGGING")) { !env->HasVar("ELECTRON_ENABLE_LOGGING")) {
settings.logging_dest = logging::LOG_NONE; settings.logging_dest = logging::LOG_NONE;
logging::SetMinLogLevel(logging::LOG_NUM_SEVERITIES); logging::SetMinLogLevel(logging::LOG_NUM_SEVERITIES);
@ -115,17 +117,23 @@ void AtomMainDelegate::PreSandboxStartup() {
auto command_line = base::CommandLine::ForCurrentProcess(); auto command_line = base::CommandLine::ForCurrentProcess();
std::string process_type = command_line->GetSwitchValueASCII( std::string process_type = command_line->GetSwitchValueASCII(
switches::kProcessType); ::switches::kProcessType);
// Only append arguments for browser process. // Only append arguments for browser process.
if (!IsBrowserProcess(command_line)) if (!IsBrowserProcess(command_line))
return; return;
if (command_line->HasSwitch(switches::kEnableSandbox)) {
// Disable setuid sandbox since it is not longer required on linux(namespace
// sandbox is available on most distros).
command_line->AppendSwitch(::switches::kDisableSetuidSandbox);
} else {
// Disable renderer sandbox for most of node's functions. // Disable renderer sandbox for most of node's functions.
command_line->AppendSwitch(switches::kNoSandbox); command_line->AppendSwitch(::switches::kNoSandbox);
}
// Allow file:// URIs to read other file:// URIs by default. // Allow file:// URIs to read other file:// URIs by default.
command_line->AppendSwitch(switches::kAllowFileAccessFromFiles); command_line->AppendSwitch(::switches::kAllowFileAccessFromFiles);
#if defined(OS_MACOSX) #if defined(OS_MACOSX)
// Enable AVFoundation. // Enable AVFoundation.
@ -140,7 +148,13 @@ content::ContentBrowserClient* AtomMainDelegate::CreateContentBrowserClient() {
content::ContentRendererClient* content::ContentRendererClient*
AtomMainDelegate::CreateContentRendererClient() { AtomMainDelegate::CreateContentRendererClient() {
if (base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kEnableSandbox)) {
renderer_client_.reset(new AtomSandboxedRendererClient);
} else {
renderer_client_.reset(new AtomRendererClient); renderer_client_.reset(new AtomRendererClient);
}
return renderer_client_.get(); return renderer_client_.get();
} }

View file

@ -258,17 +258,25 @@ void OnCapturePageDone(base::Callback<void(const gfx::Image&)> callback,
} // namespace } // namespace
WebContents::WebContents(v8::Isolate* isolate, WebContents::WebContents(v8::Isolate* isolate,
content::WebContents* web_contents) content::WebContents* web_contents,
Type type)
: content::WebContentsObserver(web_contents), : content::WebContentsObserver(web_contents),
embedder_(nullptr), embedder_(nullptr),
type_(REMOTE), type_(type),
request_id_(0), request_id_(0),
background_throttling_(true), background_throttling_(true),
enable_devtools_(true) { enable_devtools_(true) {
web_contents->SetUserAgentOverride(GetBrowserContext()->GetUserAgent());
if (type == REMOTE) {
web_contents->SetUserAgentOverride(GetBrowserContext()->GetUserAgent());
Init(isolate); Init(isolate);
AttachAsUserData(web_contents); AttachAsUserData(web_contents);
} else {
const mate::Dictionary options = mate::Dictionary::CreateEmpty(isolate);
auto session = Session::CreateFrom(isolate, GetBrowserContext());
session_.Reset(isolate, session.ToV8());
InitWithSessionAndOptions(isolate, web_contents, session, options);
}
} }
WebContents::WebContents(v8::Isolate* isolate, WebContents::WebContents(v8::Isolate* isolate,
@ -336,6 +344,13 @@ WebContents::WebContents(v8::Isolate* isolate,
web_contents = content::WebContents::Create(params); web_contents = content::WebContents::Create(params);
} }
InitWithSessionAndOptions(isolate, web_contents, session, options);
}
void WebContents::InitWithSessionAndOptions(v8::Isolate* isolate,
content::WebContents *web_contents,
mate::Handle<api::Session> session,
const mate::Dictionary& options) {
Observe(web_contents); Observe(web_contents);
InitWithWebContents(web_contents, session->browser_context()); InitWithWebContents(web_contents, session->browser_context());
@ -408,6 +423,31 @@ void WebContents::OnCreateWindow(const GURL& target_url,
Emit("new-window", target_url, frame_name, disposition); Emit("new-window", target_url, frame_name, disposition);
} }
void WebContents::WebContentsCreated(content::WebContents* source_contents,
int opener_render_frame_id,
const std::string& frame_name,
const GURL& target_url,
content::WebContents* new_contents) {
v8::Locker locker(isolate());
v8::HandleScope handle_scope(isolate());
auto api_web_contents = CreateFrom(isolate(), new_contents, BROWSER_WINDOW);
Emit("-web-contents-created", api_web_contents, target_url, frame_name);
}
void WebContents::AddNewContents(content::WebContents* source,
content::WebContents* new_contents,
WindowOpenDisposition disposition,
const gfx::Rect& initial_rect,
bool user_gesture,
bool* was_blocked) {
v8::Locker locker(isolate());
v8::HandleScope handle_scope(isolate());
auto api_web_contents = CreateFrom(isolate(), new_contents);
Emit("-add-new-contents", api_web_contents, disposition, user_gesture,
initial_rect.x(), initial_rect.y(), initial_rect.width(),
initial_rect.height());
}
content::WebContents* WebContents::OpenURLFromTab( content::WebContents* WebContents::OpenURLFromTab(
content::WebContents* source, content::WebContents* source,
const content::OpenURLParams& params) { const content::OpenURLParams& params) {
@ -801,7 +841,14 @@ void WebContents::NavigationEntryCommitted(
details.is_in_page, details.did_replace_entry); details.is_in_page, details.did_replace_entry);
} }
int WebContents::GetID() const { int64_t WebContents::GetID() const {
int64_t process_id = web_contents()->GetRenderProcessHost()->GetID();
int64_t routing_id = web_contents()->GetRoutingID();
int64_t rv = (process_id << 32) + routing_id;
return rv;
}
int WebContents::GetProcessID() const {
return web_contents()->GetRenderProcessHost()->GetID(); return web_contents()->GetRenderProcessHost()->GetID();
} }
@ -1496,6 +1543,7 @@ void WebContents::BuildPrototype(v8::Isolate* isolate,
mate::ObjectTemplateBuilder(isolate, prototype->PrototypeTemplate()) mate::ObjectTemplateBuilder(isolate, prototype->PrototypeTemplate())
.MakeDestroyable() .MakeDestroyable()
.SetMethod("getId", &WebContents::GetID) .SetMethod("getId", &WebContents::GetID)
.SetMethod("getProcessId", &WebContents::GetProcessID)
.SetMethod("equal", &WebContents::Equal) .SetMethod("equal", &WebContents::Equal)
.SetMethod("_loadURL", &WebContents::LoadURL) .SetMethod("_loadURL", &WebContents::LoadURL)
.SetMethod("downloadURL", &WebContents::DownloadURL) .SetMethod("downloadURL", &WebContents::DownloadURL)
@ -1605,7 +1653,15 @@ mate::Handle<WebContents> WebContents::CreateFrom(
return mate::CreateHandle(isolate, static_cast<WebContents*>(existing)); return mate::CreateHandle(isolate, static_cast<WebContents*>(existing));
// Otherwise create a new WebContents wrapper object. // Otherwise create a new WebContents wrapper object.
return mate::CreateHandle(isolate, new WebContents(isolate, web_contents)); return mate::CreateHandle(isolate, new WebContents(isolate, web_contents,
REMOTE));
}
mate::Handle<WebContents> WebContents::CreateFrom(
v8::Isolate* isolate, content::WebContents* web_contents, Type type) {
// Otherwise create a new WebContents wrapper object.
return mate::CreateHandle(isolate, new WebContents(isolate, web_contents,
type));
} }
// static // static

View file

@ -58,6 +58,8 @@ class WebContents : public mate::TrackableObject<WebContents>,
// Create from an existing WebContents. // Create from an existing WebContents.
static mate::Handle<WebContents> CreateFrom( static mate::Handle<WebContents> CreateFrom(
v8::Isolate* isolate, content::WebContents* web_contents); v8::Isolate* isolate, content::WebContents* web_contents);
static mate::Handle<WebContents> CreateFrom(
v8::Isolate* isolate, content::WebContents* web_contents, Type type);
// Create a new WebContents. // Create a new WebContents.
static mate::Handle<WebContents> Create( static mate::Handle<WebContents> Create(
@ -66,7 +68,8 @@ class WebContents : public mate::TrackableObject<WebContents>,
static void BuildPrototype(v8::Isolate* isolate, static void BuildPrototype(v8::Isolate* isolate,
v8::Local<v8::FunctionTemplate> prototype); v8::Local<v8::FunctionTemplate> prototype);
int GetID() const; int64_t GetID() const;
int GetProcessID() const;
Type GetType() const; Type GetType() const;
bool Equal(const WebContents* web_contents) const; bool Equal(const WebContents* web_contents) const;
void LoadURL(const GURL& url, const mate::Dictionary& options); void LoadURL(const GURL& url, const mate::Dictionary& options);
@ -191,16 +194,34 @@ class WebContents : public mate::TrackableObject<WebContents>,
v8::Local<v8::Value> Debugger(v8::Isolate* isolate); v8::Local<v8::Value> Debugger(v8::Isolate* isolate);
protected: protected:
WebContents(v8::Isolate* isolate, content::WebContents* web_contents); WebContents(v8::Isolate* isolate,
content::WebContents* web_contents,
Type type);
WebContents(v8::Isolate* isolate, const mate::Dictionary& options); WebContents(v8::Isolate* isolate, const mate::Dictionary& options);
~WebContents(); ~WebContents();
void InitWithSessionAndOptions(v8::Isolate* isolate,
content::WebContents *web_contents,
mate::Handle<class Session> session,
const mate::Dictionary& options);
// content::WebContentsDelegate: // content::WebContentsDelegate:
bool AddMessageToConsole(content::WebContents* source, bool AddMessageToConsole(content::WebContents* source,
int32_t level, int32_t level,
const base::string16& message, const base::string16& message,
int32_t line_no, int32_t line_no,
const base::string16& source_id) override; const base::string16& source_id) override;
void WebContentsCreated(content::WebContents* source_contents,
int opener_render_frame_id,
const std::string& frame_name,
const GURL& target_url,
content::WebContents* new_contents) override;
void AddNewContents(content::WebContents* source,
content::WebContents* new_contents,
WindowOpenDisposition disposition,
const gfx::Rect& initial_rect,
bool user_gesture,
bool* was_blocked) override;
content::WebContents* OpenURLFromTab( content::WebContents* OpenURLFromTab(
content::WebContents* source, content::WebContents* source,
const content::OpenURLParams& params) override; const content::OpenURLParams& params) override;

View file

@ -72,6 +72,9 @@ v8::Local<v8::Value> ToBuffer(v8::Isolate* isolate, void* val, int size) {
Window::Window(v8::Isolate* isolate, v8::Local<v8::Object> wrapper, Window::Window(v8::Isolate* isolate, v8::Local<v8::Object> wrapper,
const mate::Dictionary& options) { const mate::Dictionary& options) {
mate::Handle<class WebContents> web_contents;
// If no WebContents was passed to the constructor, create it from options.
if (!options.Get("webContents", &web_contents)) {
// Use options.webPreferences to create WebContents. // Use options.webPreferences to create WebContents.
mate::Dictionary web_preferences = mate::Dictionary::CreateEmpty(isolate); mate::Dictionary web_preferences = mate::Dictionary::CreateEmpty(isolate);
options.Get(options::kWebPreferences, &web_preferences); options.Get(options::kWebPreferences, &web_preferences);
@ -84,8 +87,18 @@ Window::Window(v8::Isolate* isolate, v8::Local<v8::Object> wrapper,
v8::Local<v8::Value> transparent; v8::Local<v8::Value> transparent;
if (options.Get("transparent", &transparent)) if (options.Get("transparent", &transparent))
web_preferences.Set("transparent", transparent); web_preferences.Set("transparent", transparent);
// Creates the WebContents used by BrowserWindow. // Creates the WebContents used by BrowserWindow.
auto web_contents = WebContents::Create(isolate, web_preferences); web_contents = WebContents::Create(isolate, web_preferences);
}
Init(isolate, wrapper, options, web_contents);
}
void Window::Init(v8::Isolate* isolate,
v8::Local<v8::Object> wrapper,
const mate::Dictionary& options,
mate::Handle<class WebContents> web_contents) {
web_contents_.Reset(isolate, web_contents.ToV8()); web_contents_.Reset(isolate, web_contents.ToV8());
api_web_contents_ = web_contents.get(); api_web_contents_ = web_contents.get();

View file

@ -89,6 +89,10 @@ class Window : public mate::TrackableObject<Window>,
#endif #endif
private: private:
void Init(v8::Isolate* isolate,
v8::Local<v8::Object> wrapper,
const mate::Dictionary& options,
mate::Handle<class WebContents> web_contents);
// APIs for NativeWindow. // APIs for NativeWindow.
void Close(); void Close();
void Focus(); void Focus();

View file

@ -99,6 +99,44 @@ content::WebContents* AtomBrowserClient::GetWebContentsFromProcessID(
return WebContentsPreferences::GetWebContentsFromProcessID(process_id); return WebContentsPreferences::GetWebContentsFromProcessID(process_id);
} }
bool AtomBrowserClient::ShouldCreateNewSiteInstance(
content::BrowserContext* browser_context,
content::SiteInstance* current_instance,
const GURL& url) {
if (url.SchemeIs(url::kJavaScriptScheme))
// "javacript:" scheme should always use same SiteInstance
return false;
if (!IsRendererSandboxed(current_instance->GetProcess()->GetID()))
// non-sandboxed renderers should always create a new SiteInstance
return true;
// Create new a SiteInstance if navigating to a different site.
auto src_url = current_instance->GetSiteURL();
return
!content::SiteInstance::IsSameWebSite(browser_context, src_url, url) &&
// `IsSameWebSite` doesn't seem to work for some URIs such as `file:`,
// handle these scenarios by comparing only the site as defined by
// `GetSiteForURL`.
content::SiteInstance::GetSiteForURL(browser_context, url) != src_url;
}
void AtomBrowserClient::AddSandboxedRendererId(int process_id) {
base::AutoLock auto_lock(sandboxed_renderers_lock_);
sandboxed_renderers_.insert(process_id);
}
void AtomBrowserClient::RemoveSandboxedRendererId(int process_id) {
base::AutoLock auto_lock(sandboxed_renderers_lock_);
sandboxed_renderers_.erase(process_id);
}
bool AtomBrowserClient::IsRendererSandboxed(int process_id) {
base::AutoLock auto_lock(sandboxed_renderers_lock_);
return sandboxed_renderers_.count(process_id);
}
void AtomBrowserClient::RenderProcessWillLaunch( void AtomBrowserClient::RenderProcessWillLaunch(
content::RenderProcessHost* host) { content::RenderProcessHost* host) {
int process_id = host->GetID(); int process_id = host->GetID();
@ -106,6 +144,13 @@ void AtomBrowserClient::RenderProcessWillLaunch(
host->AddFilter(new TtsMessageFilter(process_id, host->GetBrowserContext())); host->AddFilter(new TtsMessageFilter(process_id, host->GetBrowserContext()));
host->AddFilter( host->AddFilter(
new WidevineCdmMessageFilter(process_id, host->GetBrowserContext())); new WidevineCdmMessageFilter(process_id, host->GetBrowserContext()));
content::WebContents* web_contents = GetWebContentsFromProcessID(process_id);
if (WebContentsPreferences::IsSandboxed(web_contents)) {
AddSandboxedRendererId(host->GetID());
// ensure the sandboxed renderer id is removed later
host->AddObserver(this);
}
} }
content::SpeechRecognitionManagerDelegate* content::SpeechRecognitionManagerDelegate*
@ -155,8 +200,7 @@ void AtomBrowserClient::OverrideSiteInstanceForNavigation(
return; return;
} }
// Restart renderer process for all navigations except "javacript:" scheme. if (!ShouldCreateNewSiteInstance(browser_context, current_instance, url))
if (url.SchemeIs(url::kJavaScriptScheme))
return; return;
scoped_refptr<content::SiteInstance> site_instance = scoped_refptr<content::SiteInstance> site_instance =
@ -188,6 +232,7 @@ void AtomBrowserClient::AppendExtraCommandLineSwitches(
// Copy following switches to child process. // Copy following switches to child process.
static const char* const kCommonSwitchNames[] = { static const char* const kCommonSwitchNames[] = {
switches::kStandardSchemes, switches::kStandardSchemes,
switches::kEnableSandbox
}; };
command_line->CopySwitchesFrom( command_line->CopySwitchesFrom(
*base::CommandLine::ForCurrentProcess(), *base::CommandLine::ForCurrentProcess(),
@ -281,6 +326,11 @@ bool AtomBrowserClient::CanCreateWindow(
bool* no_javascript_access) { bool* no_javascript_access) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO); DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
if (IsRendererSandboxed(render_process_id)) {
*no_javascript_access = false;
return true;
}
if (delegate_) { if (delegate_) {
content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE, content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE,
base::Bind(&api::App::OnCreateWindow, base::Bind(&api::App::OnCreateWindow,
@ -338,6 +388,7 @@ void AtomBrowserClient::RenderProcessHostDestroyed(
break; break;
} }
} }
RemoveSandboxedRendererId(process_id);
} }
} // namespace atom } // namespace atom

View file

@ -6,6 +6,7 @@
#define ATOM_BROWSER_ATOM_BROWSER_CLIENT_H_ #define ATOM_BROWSER_ATOM_BROWSER_CLIENT_H_
#include <map> #include <map>
#include <set>
#include <string> #include <string>
#include <vector> #include <vector>
@ -108,8 +109,19 @@ class AtomBrowserClient : public brightray::BrowserClient,
void RenderProcessHostDestroyed(content::RenderProcessHost* host) override; void RenderProcessHostDestroyed(content::RenderProcessHost* host) override;
private: private:
bool ShouldCreateNewSiteInstance(content::BrowserContext* browser_context,
content::SiteInstance* current_instance,
const GURL& dest_url);
// Add/remove a process id to `sandboxed_renderers_`.
void AddSandboxedRendererId(int process_id);
void RemoveSandboxedRendererId(int process_id);
bool IsRendererSandboxed(int process_id);
// pending_render_process => current_render_process. // pending_render_process => current_render_process.
std::map<int, int> pending_processes_; std::map<int, int> pending_processes_;
// Set that contains the process ids of all sandboxed renderers
std::set<int> sandboxed_renderers_;
base::Lock sandboxed_renderers_lock_;
std::unique_ptr<AtomResourceDispatcherHostDelegate> std::unique_ptr<AtomResourceDispatcherHostDelegate>
resource_dispatcher_host_delegate_; resource_dispatcher_host_delegate_;

View file

@ -391,6 +391,10 @@ void NativeWindow::RequestToClosePage() {
if (window_unresposive_closure_.IsCancelled()) if (window_unresposive_closure_.IsCancelled())
ScheduleUnresponsiveEvent(5000); ScheduleUnresponsiveEvent(5000);
if (!web_contents())
// Already closed by renderer
return;
if (web_contents()->NeedToFireBeforeUnload()) if (web_contents()->NeedToFireBeforeUnload())
web_contents()->DispatchBeforeUnload(); web_contents()->DispatchBeforeUnload();
else else

View file

@ -97,6 +97,12 @@ void WebContentsPreferences::AppendExtraCommandLineSwitches(
command_line->AppendSwitchASCII(switches::kNodeIntegration, command_line->AppendSwitchASCII(switches::kNodeIntegration,
node_integration ? "true" : "false"); node_integration ? "true" : "false");
// If the `sandbox` option was passed to the BrowserWindow's webPreferences,
// pass `--enable-sandbox` to the renderer so it won't have any node.js
// integration.
if (IsSandboxed(web_contents))
command_line->AppendSwitch(switches::kEnableSandbox);
// The preload script. // The preload script.
base::FilePath::StringType preload; base::FilePath::StringType preload;
if (web_preferences.GetString(options::kPreloadScript, &preload)) { if (web_preferences.GetString(options::kPreloadScript, &preload)) {
@ -194,6 +200,21 @@ void WebContentsPreferences::AppendExtraCommandLineSwitches(
command_line->AppendSwitch(cc::switches::kEnableBeginFrameScheduling); command_line->AppendSwitch(cc::switches::kEnableBeginFrameScheduling);
} }
bool WebContentsPreferences::IsSandboxed(content::WebContents* web_contents) {
WebContentsPreferences* self;
if (!web_contents)
return false;
self = FromWebContents(web_contents);
if (!self)
return false;
base::DictionaryValue& web_preferences = self->web_preferences_;
bool sandboxed = false;
web_preferences.GetBoolean("sandbox", &sandboxed);
return sandboxed;
}
// static // static
void WebContentsPreferences::OverrideWebkitPrefs( void WebContentsPreferences::OverrideWebkitPrefs(
content::WebContents* web_contents, content::WebPreferences* prefs) { content::WebContents* web_contents, content::WebPreferences* prefs) {

View file

@ -36,6 +36,8 @@ class WebContentsPreferences
static void AppendExtraCommandLineSwitches( static void AppendExtraCommandLineSwitches(
content::WebContents* web_contents, base::CommandLine* command_line); content::WebContents* web_contents, base::CommandLine* command_line);
static bool IsSandboxed(content::WebContents* web_contents);
// Modify the WebPreferences according to |web_contents|'s preferences. // Modify the WebPreferences according to |web_contents|'s preferences.
static void OverrideWebkitPrefs( static void OverrideWebkitPrefs(
content::WebContents* web_contents, content::WebPreferences* prefs); content::WebContents* web_contents, content::WebPreferences* prefs);

View file

@ -104,7 +104,7 @@ void Initialize(v8::Local<v8::Object> exports, v8::Local<v8::Value> unused,
dict.SetMethod("setRemoteObjectFreer", &atom::RemoteObjectFreer::BindTo); dict.SetMethod("setRemoteObjectFreer", &atom::RemoteObjectFreer::BindTo);
dict.SetMethod("createIDWeakMap", &atom::api::KeyWeakMap<int32_t>::Create); dict.SetMethod("createIDWeakMap", &atom::api::KeyWeakMap<int32_t>::Create);
dict.SetMethod("createDoubleIDWeakMap", dict.SetMethod("createDoubleIDWeakMap",
&atom::api::KeyWeakMap<std::pair<int32_t, int32_t>>::Create); &atom::api::KeyWeakMap<std::pair<int64_t, int32_t>>::Create);
} }
} // namespace } // namespace

View file

@ -117,6 +117,9 @@ const char kDisableBlinkFeatures[] = "disableBlinkFeatures";
namespace switches { namespace switches {
// Enable chromium sandbox.
const char kEnableSandbox[] = "enable-sandbox";
// Enable plugins. // Enable plugins.
const char kEnablePlugins[] = "enable-plugins"; const char kEnablePlugins[] = "enable-plugins";

View file

@ -67,6 +67,7 @@ extern const char kDisableBlinkFeatures[];
namespace switches { namespace switches {
extern const char kEnableSandbox[];
extern const char kEnablePlugins[]; extern const char kEnablePlugins[];
extern const char kPpapiFlashPath[]; extern const char kPpapiFlashPath[];
extern const char kPpapiFlashVersion[]; extern const char kPpapiFlashVersion[];

View file

@ -2,6 +2,7 @@
// Use of this source code is governed by the MIT license that can be // Use of this source code is governed by the MIT license that can be
// found in the LICENSE file. // found in the LICENSE file.
#include "atom/renderer/api/atom_api_renderer_ipc.h"
#include "atom/common/api/api_messages.h" #include "atom/common/api/api_messages.h"
#include "atom/common/native_mate_converters/string16_converter.h" #include "atom/common/native_mate_converters/string16_converter.h"
#include "atom/common/native_mate_converters/value_converter.h" #include "atom/common/native_mate_converters/value_converter.h"
@ -15,7 +16,9 @@ using content::RenderView;
using blink::WebLocalFrame; using blink::WebLocalFrame;
using blink::WebView; using blink::WebView;
namespace { namespace atom {
namespace api {
RenderView* GetCurrentRenderView() { RenderView* GetCurrentRenderView() {
WebLocalFrame* frame = WebLocalFrame::frameForCurrentContext(); WebLocalFrame* frame = WebLocalFrame::frameForCurrentContext();
@ -69,6 +72,8 @@ void Initialize(v8::Local<v8::Object> exports, v8::Local<v8::Value> unused,
dict.SetMethod("sendSync", &SendSync); dict.SetMethod("sendSync", &SendSync);
} }
} // namespace } // namespace api
NODE_MODULE_CONTEXT_AWARE_BUILTIN(atom_renderer_ipc, Initialize) } // namespace atom
NODE_MODULE_CONTEXT_AWARE_BUILTIN(atom_renderer_ipc, atom::api::Initialize)

View file

@ -0,0 +1,30 @@
// 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_API_ATOM_API_RENDERER_IPC_H_
#define ATOM_RENDERER_API_ATOM_API_RENDERER_IPC_H_
#include "base/values.h"
#include "native_mate/arguments.h"
namespace atom {
namespace api {
void Send(mate::Arguments* args,
const base::string16& channel,
const base::ListValue& arguments);
base::string16 SendSync(mate::Arguments* args,
const base::string16& channel,
const base::ListValue& arguments);
void Initialize(v8::Local<v8::Object> exports, v8::Local<v8::Value> unused,
v8::Local<v8::Context> context, void* priv);
} // namespace api
} // namespace atom
#endif // ATOM_RENDERER_API_ATOM_API_RENDERER_IPC_H_

View file

@ -59,7 +59,31 @@ std::vector<v8::Local<v8::Value>> ListValueToVector(
return result; return result;
} }
void EmitIPCEvent(blink::WebFrame* frame, base::StringPiece NetResourceProvider(int key) {
if (key == IDR_DIR_HEADER_HTML) {
base::StringPiece html_data =
ui::ResourceBundle::GetSharedInstance().GetRawDataResource(
IDR_DIR_HEADER_HTML);
return html_data;
}
return base::StringPiece();
}
} // namespace
AtomRenderViewObserver::AtomRenderViewObserver(
content::RenderView* render_view,
AtomRendererClient* renderer_client)
: content::RenderViewObserver(render_view),
document_created_(false) {
// Initialise resource for directory listing.
net::NetModule::SetResourceProvider(NetResourceProvider);
}
AtomRenderViewObserver::~AtomRenderViewObserver() {
}
void AtomRenderViewObserver::EmitIPCEvent(blink::WebFrame* frame,
const base::string16& channel, const base::string16& channel,
const base::ListValue& args) { const base::ListValue& args) {
if (!frame || frame->isWebRemoteFrame()) if (!frame || frame->isWebRemoteFrame())
@ -87,30 +111,6 @@ void EmitIPCEvent(blink::WebFrame* frame,
} }
} }
base::StringPiece NetResourceProvider(int key) {
if (key == IDR_DIR_HEADER_HTML) {
base::StringPiece html_data =
ui::ResourceBundle::GetSharedInstance().GetRawDataResource(
IDR_DIR_HEADER_HTML);
return html_data;
}
return base::StringPiece();
}
} // namespace
AtomRenderViewObserver::AtomRenderViewObserver(
content::RenderView* render_view,
AtomRendererClient* renderer_client)
: content::RenderViewObserver(render_view),
document_created_(false) {
// Initialise resource for directory listing.
net::NetModule::SetResourceProvider(NetResourceProvider);
}
AtomRenderViewObserver::~AtomRenderViewObserver() {
}
void AtomRenderViewObserver::DidCreateDocumentElement( void AtomRenderViewObserver::DidCreateDocumentElement(
blink::WebLocalFrame* frame) { blink::WebLocalFrame* frame) {
document_created_ = true; document_created_ = true;

View file

@ -7,6 +7,7 @@
#include "base/strings/string16.h" #include "base/strings/string16.h"
#include "content/public/renderer/render_view_observer.h" #include "content/public/renderer/render_view_observer.h"
#include "third_party/WebKit/public/web/WebFrame.h"
namespace base { namespace base {
class ListValue; class ListValue;
@ -24,6 +25,10 @@ class AtomRenderViewObserver : public content::RenderViewObserver {
protected: protected:
virtual ~AtomRenderViewObserver(); virtual ~AtomRenderViewObserver();
virtual void EmitIPCEvent(blink::WebFrame* frame,
const base::string16& channel,
const base::ListValue& args);
private: private:
// content::RenderViewObserver implementation. // content::RenderViewObserver implementation.
void DidCreateDocumentElement(blink::WebLocalFrame* frame) override; void DidCreateDocumentElement(blink::WebLocalFrame* frame) override;

View file

@ -0,0 +1,201 @@
// 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_sandboxed_renderer_client.h"
#include <string>
#include "atom_natives.h" // NOLINT: This file is generated with js2c
#include "atom/common/api/api_messages.h"
#include "atom/common/native_mate_converters/string16_converter.h"
#include "atom/common/native_mate_converters/value_converter.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 "base/command_line.h"
#include "content/public/renderer/render_frame.h"
#include "content/public/renderer/render_frame_observer.h"
#include "content/public/renderer/render_view.h"
#include "content/public/renderer/render_view_observer.h"
#include "ipc/ipc_message_macros.h"
#include "third_party/WebKit/public/web/WebFrame.h"
#include "third_party/WebKit/public/web/WebKit.h"
#include "third_party/WebKit/public/web/WebLocalFrame.h"
#include "third_party/WebKit/public/web/WebScriptSource.h"
#include "third_party/WebKit/public/web/WebView.h"
namespace atom {
namespace {
const std::string kBindingKey = "binding";
class AtomSandboxedRenderFrameObserver : public content::RenderFrameObserver {
public:
AtomSandboxedRenderFrameObserver(content::RenderFrame* frame,
AtomSandboxedRendererClient* renderer_client)
: content::RenderFrameObserver(frame),
render_frame_(frame),
world_id_(-1),
renderer_client_(renderer_client) {}
// content::RenderFrameObserver:
void DidClearWindowObject() override {
// Make sure every page will get a script context created.
render_frame_->GetWebFrame()->executeScript(
blink::WebScriptSource("void 0"));
}
void DidCreateScriptContext(v8::Handle<v8::Context> 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_);
}
void WillReleaseScriptContext(v8::Local<v8::Context> context,
int world_id) override {
if (world_id_ != world_id)
return;
renderer_client_->WillReleaseScriptContext(context, render_frame_);
}
void OnDestruct() override {
delete this;
}
private:
content::RenderFrame* render_frame_;
int world_id_;
AtomSandboxedRendererClient* renderer_client_;
DISALLOW_COPY_AND_ASSIGN(AtomSandboxedRenderFrameObserver);
};
class AtomSandboxedRenderViewObserver : public AtomRenderViewObserver {
public:
AtomSandboxedRenderViewObserver(content::RenderView* render_view,
AtomSandboxedRendererClient* renderer_client)
: AtomRenderViewObserver(render_view, nullptr),
renderer_client_(renderer_client) {
}
protected:
void EmitIPCEvent(blink::WebFrame* frame,
const base::string16& channel,
const base::ListValue& args) override {
if (!frame || frame->isWebRemoteFrame())
return;
auto isolate = blink::mainThreadIsolate();
v8::HandleScope handle_scope(isolate);
auto context = frame->mainWorldScriptContext();
v8::Context::Scope context_scope(context);
v8::Local<v8::Value> argv[] = {
mate::ConvertToV8(isolate, channel),
mate::ConvertToV8(isolate, args)
};
renderer_client_->InvokeBindingCallback(
context,
"onMessage",
std::vector<v8::Local<v8::Value>>(argv, argv + 2));
}
private:
AtomSandboxedRendererClient* renderer_client_;
DISALLOW_COPY_AND_ASSIGN(AtomSandboxedRenderViewObserver);
};
} // namespace
AtomSandboxedRendererClient::AtomSandboxedRendererClient() {
}
AtomSandboxedRendererClient::~AtomSandboxedRendererClient() {
}
void AtomSandboxedRendererClient::RenderFrameCreated(
content::RenderFrame* render_frame) {
new AtomSandboxedRenderFrameObserver(render_frame, this);
}
void AtomSandboxedRendererClient::RenderViewCreated(
content::RenderView* render_view) {
new AtomSandboxedRenderViewObserver(render_view, this);
}
void AtomSandboxedRendererClient::DidCreateScriptContext(
v8::Handle<v8::Context> context, content::RenderFrame* render_frame) {
base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
std::string preload_script = command_line->GetSwitchValueASCII(
switches::kPreloadScript);
if (preload_script.empty())
return;
auto isolate = context->GetIsolate();
v8::HandleScope handle_scope(isolate);
v8::Context::Scope context_scope(context);
// Wrap the bundle into a function that receives the binding object and the
// preload script path as arguments.
std::string preload_bundle_native(node::preload_bundle_native,
node::preload_bundle_native + sizeof(node::preload_bundle_native));
std::stringstream ss;
ss << "(function(binding, preloadPath) {\n";
ss << preload_bundle_native << "\n";
ss << "})";
std::string preload_wrapper = ss.str();
// Compile the wrapper and run it to get the function object
auto script = v8::Script::Compile(
mate::ConvertToV8(isolate, preload_wrapper)->ToString());
auto func = v8::Handle<v8::Function>::Cast(
script->Run(context).ToLocalChecked());
// Create and initialize the binding object
auto binding = v8::Object::New(isolate);
api::Initialize(binding, v8::Null(isolate), context, nullptr);
v8::Local<v8::Value> args[] = {
binding,
mate::ConvertToV8(isolate, preload_script)
};
// Execute the function with proper arguments
ignore_result(func->Call(context, v8::Null(isolate), 2, args));
// Store the bindingt privately for handling messages from the main process.
auto binding_key = mate::ConvertToV8(isolate, kBindingKey)->ToString();
auto private_binding_key = v8::Private::ForApi(isolate, binding_key);
context->Global()->SetPrivate(context, private_binding_key, binding);
}
void AtomSandboxedRendererClient::WillReleaseScriptContext(
v8::Handle<v8::Context> context, content::RenderFrame* render_frame) {
auto isolate = context->GetIsolate();
v8::HandleScope handle_scope(isolate);
v8::Context::Scope context_scope(context);
InvokeBindingCallback(context, "onExit", std::vector<v8::Local<v8::Value>>());
}
void AtomSandboxedRendererClient::InvokeBindingCallback(
v8::Handle<v8::Context> context,
std::string callback_name,
std::vector<v8::Handle<v8::Value>> args) {
auto isolate = context->GetIsolate();
auto binding_key = mate::ConvertToV8(isolate, kBindingKey)->ToString();
auto private_binding_key = v8::Private::ForApi(isolate, binding_key);
auto global_object = context->Global();
v8::Local<v8::Value> value;
if (!global_object->GetPrivate(context, private_binding_key).ToLocal(&value))
return;
if (value.IsEmpty() || !value->IsObject())
return;
auto binding = value->ToObject();
auto callback_key = mate::ConvertToV8(isolate, callback_name)->ToString();
auto callback_value = binding->Get(callback_key);
DCHECK(callback_value->IsFunction()); // set by sandboxed_renderer/init.js
auto callback = v8::Handle<v8::Function>::Cast(callback_value);
ignore_result(callback->Call(context, binding, args.size(), &args[0]));
}
} // namespace atom

View file

@ -0,0 +1,37 @@
// 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_SANDBOXED_RENDERER_CLIENT_H_
#define ATOM_RENDERER_ATOM_SANDBOXED_RENDERER_CLIENT_H_
#include <string>
#include <vector>
#include "content/public/renderer/content_renderer_client.h"
#include "content/public/renderer/render_frame.h"
namespace atom {
class AtomSandboxedRendererClient : public content::ContentRendererClient {
public:
AtomSandboxedRendererClient();
virtual ~AtomSandboxedRendererClient();
void DidCreateScriptContext(
v8::Handle<v8::Context> context, content::RenderFrame* render_frame);
void WillReleaseScriptContext(
v8::Handle<v8::Context> context, content::RenderFrame* render_frame);
void InvokeBindingCallback(v8::Handle<v8::Context> context,
std::string callback_name,
std::vector<v8::Handle<v8::Value>> args);
// content::ContentRendererClient:
void RenderFrameCreated(content::RenderFrame*) override;
void RenderViewCreated(content::RenderView*) override;
private:
DISALLOW_COPY_AND_ASSIGN(AtomSandboxedRendererClient);
};
} // namespace atom
#endif // ATOM_RENDERER_ATOM_SANDBOXED_RENDERER_CLIENT_H_

View file

@ -5,6 +5,7 @@
'company_name%': 'GitHub, Inc', 'company_name%': 'GitHub, Inc',
'company_abbr%': 'github', 'company_abbr%': 'github',
'version%': '1.4.1', 'version%': '1.4.1',
'js2c_input_dir': '<(SHARED_INTERMEDIATE_DIR)/js2c',
}, },
'includes': [ 'includes': [
'filenames.gypi', 'filenames.gypi',
@ -410,14 +411,64 @@
} }
], ],
}, # target app2asar }, # target app2asar
{
'target_name': 'atom_js2c_copy',
'type': 'none',
'copies': [
{
'destination': '<(js2c_input_dir)',
'files': [
'<@(js2c_sources)',
],
},
],
}, # target atom_js2c_copy
{
'target_name': 'atom_browserify',
'type': 'none',
'dependencies': [
# depend on this target to ensure the '<(js2c_input_dir)' is created
'atom_js2c_copy',
],
'actions': [
{
'action_name': 'atom_browserify',
'inputs': [
'<@(browserify_entries)',
# Any js file under `lib/` can be included in the preload bundle.
# Add all js sources as dependencies so any change to a js file will
# trigger a rebuild of the bundle(and consequently of js2c).
'<@(js_sources)',
],
'outputs': [
'<(js2c_input_dir)/preload_bundle.js',
],
'action': [
'npm',
'run',
'browserify',
'--',
'<@(browserify_entries)',
'-o',
'<@(_outputs)',
],
}
],
}, # target atom_browserify
{ {
'target_name': 'atom_js2c', 'target_name': 'atom_js2c',
'type': 'none', 'type': 'none',
'dependencies': [
'atom_js2c_copy',
'atom_browserify',
],
'actions': [ 'actions': [
{ {
'action_name': 'atom_js2c', 'action_name': 'atom_js2c',
'inputs': [ 'inputs': [
# List all input files that should trigger a rebuild with js2c
'<@(js2c_sources)', '<@(js2c_sources)',
'<(js2c_input_dir)/preload_bundle.js',
], ],
'outputs': [ 'outputs': [
'<(SHARED_INTERMEDIATE_DIR)/atom_natives.h', '<(SHARED_INTERMEDIATE_DIR)/atom_natives.h',
@ -426,7 +477,7 @@
'python', 'python',
'tools/js2c.py', 'tools/js2c.py',
'<@(_outputs)', '<@(_outputs)',
'<@(_inputs)', '<(js2c_input_dir)',
], ],
} }
], ],

View file

@ -61,6 +61,7 @@
'lib/renderer/api/desktop-capturer.js', 'lib/renderer/api/desktop-capturer.js',
'lib/renderer/api/exports/electron.js', 'lib/renderer/api/exports/electron.js',
'lib/renderer/api/ipc-renderer.js', 'lib/renderer/api/ipc-renderer.js',
'lib/renderer/api/ipc-renderer-setup.js',
'lib/renderer/api/remote.js', 'lib/renderer/api/remote.js',
'lib/renderer/api/screen.js', 'lib/renderer/api/screen.js',
'lib/renderer/api/web-frame.js', 'lib/renderer/api/web-frame.js',
@ -69,6 +70,9 @@
'lib/renderer/extensions/storage.js', 'lib/renderer/extensions/storage.js',
'lib/renderer/extensions/web-navigation.js', 'lib/renderer/extensions/web-navigation.js',
], ],
'browserify_entries': [
'lib/sandboxed_renderer/init.js',
],
'js2c_sources': [ 'js2c_sources': [
'lib/common/asar.js', 'lib/common/asar.js',
'lib/common/asar_init.js', 'lib/common/asar_init.js',
@ -424,6 +428,7 @@
'atom/common/platform_util_linux.cc', 'atom/common/platform_util_linux.cc',
'atom/common/platform_util_mac.mm', 'atom/common/platform_util_mac.mm',
'atom/common/platform_util_win.cc', 'atom/common/platform_util_win.cc',
'atom/renderer/api/atom_api_renderer_ipc.h',
'atom/renderer/api/atom_api_renderer_ipc.cc', 'atom/renderer/api/atom_api_renderer_ipc.cc',
'atom/renderer/api/atom_api_spell_check_client.cc', 'atom/renderer/api/atom_api_spell_check_client.cc',
'atom/renderer/api/atom_api_spell_check_client.h', 'atom/renderer/api/atom_api_spell_check_client.h',
@ -435,6 +440,8 @@
'atom/renderer/atom_renderer_client.h', 'atom/renderer/atom_renderer_client.h',
'atom/renderer/content_settings_observer.cc', 'atom/renderer/content_settings_observer.cc',
'atom/renderer/content_settings_observer.h', 'atom/renderer/content_settings_observer.h',
'atom/renderer/atom_sandboxed_renderer_client.cc',
'atom/renderer/atom_sandboxed_renderer_client.h',
'atom/renderer/guest_view_container.cc', 'atom/renderer/guest_view_container.cc',
'atom/renderer/guest_view_container.h', 'atom/renderer/guest_view_container.h',
'atom/renderer/node_array_buffer_bridge.cc', 'atom/renderer/node_array_buffer_bridge.cc',

View file

@ -3,6 +3,7 @@
const {ipcMain} = require('electron') const {ipcMain} = require('electron')
const {EventEmitter} = require('events') const {EventEmitter} = require('events')
const {BrowserWindow} = process.atomBinding('window') const {BrowserWindow} = process.atomBinding('window')
const v8Util = process.atomBinding('v8_util')
Object.setPrototypeOf(BrowserWindow.prototype, EventEmitter.prototype) Object.setPrototypeOf(BrowserWindow.prototype, EventEmitter.prototype)
@ -26,6 +27,34 @@ BrowserWindow.prototype._init = function () {
ipcMain.emit('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_OPEN', event, url, frameName, disposition, options) ipcMain.emit('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_OPEN', event, url, frameName, disposition, options)
}) })
this.webContents.on('-web-contents-created', (event, webContents, url,
frameName) => {
v8Util.setHiddenValue(webContents, 'url-framename', {url, frameName})
})
// Create a new browser window for the native implementation of
// "window.open"(sandbox mode only)
this.webContents.on('-add-new-contents', (event, webContents, disposition,
userGesture, left, top, width,
height) => {
let urlFrameName = v8Util.getHiddenValue(webContents, 'url-framename')
if ((disposition !== 'foreground-tab' && disposition !== 'new-window') ||
!urlFrameName) {
return
}
let {url, frameName} = urlFrameName
v8Util.deleteHiddenValue(webContents, 'url-framename')
const options = {
show: true,
x: left,
y: top,
width: width || 800,
height: height || 600,
webContents: webContents
}
ipcMain.emit('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_OPEN', event, url, frameName, disposition, options)
})
// window.resizeTo(...) // window.resizeTo(...)
// window.moveTo(...) // window.moveTo(...)
this.webContents.on('move', (event, size) => { this.webContents.on('move', (event, size) => {

View file

@ -123,7 +123,7 @@ const hookWebContentsEvents = function (webContents) {
sendToBackgroundPages('CHROME_WEBNAVIGATION_ONBEFORENAVIGATE', { sendToBackgroundPages('CHROME_WEBNAVIGATION_ONBEFORENAVIGATE', {
frameId: 0, frameId: 0,
parentFrameId: -1, parentFrameId: -1,
processId: webContents.getId(), processId: webContents.getProcessId(),
tabId: tabId, tabId: tabId,
timeStamp: Date.now(), timeStamp: Date.now(),
url: url url: url
@ -134,7 +134,7 @@ const hookWebContentsEvents = function (webContents) {
sendToBackgroundPages('CHROME_WEBNAVIGATION_ONCOMPLETED', { sendToBackgroundPages('CHROME_WEBNAVIGATION_ONCOMPLETED', {
frameId: 0, frameId: 0,
parentFrameId: -1, parentFrameId: -1,
processId: webContents.getId(), processId: webContents.getProcessId(),
tabId: tabId, tabId: tabId,
timeStamp: Date.now(), timeStamp: Date.now(),
url: url url: url

View file

@ -57,7 +57,30 @@ const createGuest = function (embedder, url, frameName, options) {
} }
options.webPreferences.openerId = embedder.id options.webPreferences.openerId = embedder.id
guest = new BrowserWindow(options) guest = new BrowserWindow(options)
if (!options.webContents || url !== 'about:blank') {
// We should not call `loadURL` if the window was constructed from an
// existing webContents(window.open in a sandboxed renderer) and if the url
// is not 'about:blank'.
//
// Navigating to the url when creating the window from an existing
// webContents would not be necessary(it will navigate there anyway), but
// apparently there's a bug that allows the child window to be scripted by
// the opener, even when the child window is from another origin.
//
// That's why the second condition(url !== "about:blank") is required: to
// force `OverrideSiteInstanceForNavigation` to be called and consequently
// spawn a new renderer if the new window is targeting a different origin.
//
// If the URL is "about:blank", then it is very likely that the opener just
// wants to synchronously script the popup, for example:
//
// let popup = window.open()
// popup.document.body.write('<h1>hello</h1>')
//
// The above code would not work if a navigation to "about:blank" is done
// here, since the window would be cleared of all changes in the next tick.
guest.loadURL(url) guest.loadURL(url)
}
// When |embedder| is destroyed we should also destroy attached guest, and if // When |embedder| is destroyed we should also destroy attached guest, and if
// guest is closed by user then we should prevent |embedder| from double // guest is closed by user then we should prevent |embedder| from double
@ -72,8 +95,19 @@ const createGuest = function (embedder, url, frameName, options) {
embedder.send('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_CLOSED_' + guestId) embedder.send('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_CLOSED_' + guestId)
embedder.removeListener('render-view-deleted', closedByEmbedder) embedder.removeListener('render-view-deleted', closedByEmbedder)
} }
if (!options.webPreferences.sandbox) {
// These events should only be handled when the guest window is opened by a
// non-sandboxed renderer for two reasons:
//
// - `render-view-deleted` is emitted when the popup is closed by the user,
// and that will eventually result in NativeWindow::NotifyWindowClosed
// using a dangling pointer since `destroy()` would have been called by
// `closeByEmbedded`
// - No need to emit `ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_CLOSED_` since
// there's no renderer code listening to it.,
embedder.once('render-view-deleted', closedByEmbedder) embedder.once('render-view-deleted', closedByEmbedder)
guest.once('closed', closedByUser) guest.once('closed', closedByUser)
}
if (frameName) { if (frameName) {
frameToGuest[frameName] = guest frameToGuest[frameName] = guest

View file

@ -5,6 +5,8 @@ const electron = require('electron')
const v8Util = process.atomBinding('v8_util') const v8Util = process.atomBinding('v8_util')
const {ipcMain, isPromise, webContents} = electron const {ipcMain, isPromise, webContents} = electron
const fs = require('fs')
const objectsRegistry = require('./objects-registry') const objectsRegistry = require('./objects-registry')
const hasProp = {}.hasOwnProperty const hasProp = {}.hasOwnProperty
@ -235,6 +237,13 @@ ipcMain.on('ELECTRON_BROWSER_REQUIRE', function (event, module) {
} }
}) })
ipcMain.on('ELECTRON_BROWSER_READ_FILE', function (event, file) {
fs.readFile(file, (err, data) => {
if (err) event.returnValue = {err: err.message}
else event.returnValue = {data: data.toString()}
})
})
ipcMain.on('ELECTRON_BROWSER_GET_BUILTIN', function (event, module) { ipcMain.on('ELECTRON_BROWSER_GET_BUILTIN', function (event, module) {
try { try {
event.returnValue = valueToMeta(event.sender, electron[module]) event.returnValue = valueToMeta(event.sender, electron[module])

View file

@ -0,0 +1,29 @@
module.exports = function (ipcRenderer, binding) {
ipcRenderer.send = function (...args) {
return binding.send('ipc-message', args)
}
ipcRenderer.sendSync = function (...args) {
return JSON.parse(binding.sendSync('ipc-message-sync', args))
}
ipcRenderer.sendToHost = function (...args) {
return binding.send('ipc-message-host', args)
}
ipcRenderer.sendTo = function (webContentsId, channel, ...args) {
if (typeof webContentsId !== 'number') {
throw new TypeError('First argument has to be webContentsId')
}
ipcRenderer.send('ELECTRON_BROWSER_SEND_TO', false, webContentsId, channel, ...args)
}
ipcRenderer.sendToAll = function (webContentsId, channel, ...args) {
if (typeof webContentsId !== 'number') {
throw new TypeError('First argument has to be webContentsId')
}
ipcRenderer.send('ELECTRON_BROWSER_SEND_TO', true, webContentsId, channel, ...args)
}
}

View file

@ -5,33 +5,6 @@ const v8Util = process.atomBinding('v8_util')
// Created by init.js. // Created by init.js.
const ipcRenderer = v8Util.getHiddenValue(global, 'ipc') const ipcRenderer = v8Util.getHiddenValue(global, 'ipc')
require('./ipc-renderer-setup')(ipcRenderer, binding)
ipcRenderer.send = function (...args) {
return binding.send('ipc-message', args)
}
ipcRenderer.sendSync = function (...args) {
return JSON.parse(binding.sendSync('ipc-message-sync', args))
}
ipcRenderer.sendToHost = function (...args) {
return binding.send('ipc-message-host', args)
}
ipcRenderer.sendTo = function (webContentsId, channel, ...args) {
if (typeof webContentsId !== 'number') {
throw new TypeError('First argument has to be webContentsId')
}
ipcRenderer.send('ELECTRON_BROWSER_SEND_TO', false, webContentsId, channel, ...args)
}
ipcRenderer.sendToAll = function (webContentsId, channel, ...args) {
if (typeof webContentsId !== 'number') {
throw new TypeError('First argument has to be webContentsId')
}
ipcRenderer.send('ELECTRON_BROWSER_SEND_TO', true, webContentsId, channel, ...args)
}
module.exports = ipcRenderer module.exports = ipcRenderer

View file

@ -0,0 +1,56 @@
/* eslint no-eval: "off" */
/* global binding, preloadPath, process, Buffer */
const events = require('events')
const ipcRenderer = new events.EventEmitter()
const proc = new events.EventEmitter()
// eval in window scope:
// http://www.ecma-international.org/ecma-262/5.1/#sec-10.4.2
const geval = eval
require('../renderer/api/ipc-renderer-setup')(ipcRenderer, binding)
binding.onMessage = function (channel, args) {
ipcRenderer.emit.apply(ipcRenderer, [channel].concat(args))
}
binding.onExit = function () {
proc.emit('exit')
}
const preloadModules = new Map([
['electron', {
ipcRenderer: ipcRenderer
}]
])
function preloadRequire (module) {
if (preloadModules.has(module)) {
return preloadModules.get(module)
}
throw new Error('module not found')
}
// Fetch the source for the preload
let preloadSrc = ipcRenderer.sendSync('ELECTRON_BROWSER_READ_FILE', preloadPath)
if (preloadSrc.err) {
throw new Error(preloadSrc.err)
}
// Wrap the source into a function receives a `require` function as argument.
// Browserify bundles can make use of this, as explained in:
// https://github.com/substack/node-browserify#multiple-bundles
//
// For example, the user can create a browserify bundle with:
//
// $ browserify -x electron preload.js > renderer.js
//
// and any `require('electron')` calls in `preload.js` will work as expected
// since browserify won't try to include `electron` in the bundle and will fall
// back to the `preloadRequire` function above.
let preloadWrapperSrc = `(function(require, process, Buffer, global) {
${preloadSrc.data}
})`
let preloadFn = geval(preloadWrapperSrc)
preloadFn(preloadRequire, proc, Buffer, global)

View file

@ -3,6 +3,7 @@
"version": "1.4.1", "version": "1.4.1",
"devDependencies": { "devDependencies": {
"asar": "^0.11.0", "asar": "^0.11.0",
"browserify": "^13.1.0",
"electabul": "~0.0.4", "electabul": "~0.0.4",
"electron-docs-linter": "^1.6.2", "electron-docs-linter": "^1.6.2",
"request": "*", "request": "*",
@ -25,6 +26,7 @@
"private": true, "private": true,
"scripts": { "scripts": {
"bootstrap": "python ./script/bootstrap.py", "bootstrap": "python ./script/bootstrap.py",
"browserify": "browserify",
"build": "python ./script/build.py -c D", "build": "python ./script/build.py -c D",
"clean": "python ./script/clean.py", "clean": "python ./script/clean.py",
"coverage": "npm run instrument-code-coverage && npm test -- --use-instrumented-asar", "coverage": "npm run instrument-code-coverage && npm test -- --use-instrumented-asar",

View file

@ -14,10 +14,7 @@ SOURCE_ROOT = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))
VENDOR_DIR = os.path.join(SOURCE_ROOT, 'vendor') VENDOR_DIR = os.path.join(SOURCE_ROOT, 'vendor')
PYTHON_26_URL = 'https://chromium.googlesource.com/chromium/deps/python_26' PYTHON_26_URL = 'https://chromium.googlesource.com/chromium/deps/python_26'
if os.environ.has_key('CI'): NPM = 'npm'
NPM = os.path.join(SOURCE_ROOT, 'node_modules', '.bin', 'npm')
else:
NPM = 'npm'
if sys.platform in ['win32', 'cygwin']: if sys.platform in ['win32', 'cygwin']:
NPM += '.cmd' NPM += '.cmd'

View file

@ -65,6 +65,11 @@ def main():
execute([npm, 'install', 'npm@2.12.1']) execute([npm, 'install', 'npm@2.12.1'])
log_versions() log_versions()
# Add "./node_modules/.bin" to the beginning of $PATH, which will ensure
# future "npm" invocations use the right version.
node_bin_dir = os.path.join(SOURCE_ROOT, 'node_modules', '.bin')
os.environ['PATH'] = os.path.pathsep.join([node_bin_dir,
os.environ.get('PATH', '')])
is_release = os.environ.has_key('ELECTRON_RELEASE') is_release = os.environ.has_key('ELECTRON_RELEASE')
args = ['--target_arch=' + target_arch] args = ['--target_arch=' + target_arch]

View file

@ -16,6 +16,7 @@ const ipcRenderer = require('electron').ipcRenderer
const BrowserWindow = remote.require('electron').BrowserWindow const BrowserWindow = remote.require('electron').BrowserWindow
const isCI = remote.getGlobal('isCi') const isCI = remote.getGlobal('isCi')
const {protocol} = remote
describe('browser-window module', function () { describe('browser-window module', function () {
var fixtures = path.resolve(__dirname, 'fixtures') var fixtures = path.resolve(__dirname, 'fixtures')
@ -572,6 +573,198 @@ describe('browser-window module', function () {
w.loadURL('file://' + path.join(fixtures, 'api', 'blank.html')) w.loadURL('file://' + path.join(fixtures, 'api', 'blank.html'))
}) })
}) })
describe('"sandbox" option', function () {
function waitForEvents (emitter, events, callback) {
let count = events.length
for (let event of events) {
emitter.once(event, () => {
if (!--count) callback()
})
}
}
const preload = path.join(fixtures, 'module', 'preload-sandbox.js')
// http protocol to simulate accessing a another domain. this is required
// because the code paths for cross domain popups is different.
function crossDomainHandler (request, callback) {
callback({
mimeType: 'text/html',
data: `<html><body><h1>${request.url}</h1></body></html>`
})
}
before(function (done) {
protocol.interceptStringProtocol('http', crossDomainHandler, function () {
done()
})
})
after(function (done) {
protocol.uninterceptProtocol('http', function () {
done()
})
})
it('exposes ipcRenderer to preload script', function (done) {
ipcMain.once('answer', function (event, test) {
assert.equal(test, 'preload')
done()
})
w.destroy()
w = new BrowserWindow({
show: false,
webPreferences: {
sandbox: true,
preload: preload
}
})
w.loadURL('file://' + path.join(fixtures, 'api', 'preload.html'))
})
it('exposes "exit" event to preload script', function (done) {
w.destroy()
w = new BrowserWindow({
show: false,
webPreferences: {
sandbox: true,
preload: preload
}
})
let htmlPath = path.join(fixtures, 'api', 'sandbox.html?exit-event')
const pageUrl = 'file://' + htmlPath
w.loadURL(pageUrl)
ipcMain.once('answer', function (event, url) {
let expectedUrl = pageUrl
if (process.platform === 'win32') {
expectedUrl = 'file:///' + htmlPath.replace(/\\/g, '/')
}
assert.equal(url, expectedUrl)
done()
})
})
it('should open windows in same domain with cross-scripting enabled', function (done) {
w.destroy()
w = new BrowserWindow({
show: false,
webPreferences: {
sandbox: true,
preload: preload
}
})
let htmlPath = path.join(fixtures, 'api', 'sandbox.html?window-open')
const pageUrl = 'file://' + htmlPath
w.loadURL(pageUrl)
w.webContents.once('new-window', (e, url, frameName, disposition, options) => {
let expectedUrl = pageUrl
if (process.platform === 'win32') {
expectedUrl = 'file:///' + htmlPath.replace(/\\/g, '/')
}
assert.equal(url, expectedUrl)
assert.equal(frameName, 'popup!')
assert.equal(options.x, 50)
assert.equal(options.y, 60)
assert.equal(options.width, 500)
assert.equal(options.height, 600)
ipcMain.once('answer', function (event, html) {
assert.equal(html, '<h1>scripting from opener</h1>')
done()
})
})
})
it('should open windows in another domain with cross-scripting disabled', function (done) {
w.destroy()
w = new BrowserWindow({
show: false,
webPreferences: {
sandbox: true,
preload: preload
}
})
let htmlPath = path.join(fixtures, 'api', 'sandbox.html?window-open-external')
const pageUrl = 'file://' + htmlPath
w.loadURL(pageUrl)
w.webContents.once('new-window', (e, url, frameName, disposition, options) => {
assert.equal(url, 'http://www.google.com/#q=electron')
assert.equal(options.x, 55)
assert.equal(options.y, 65)
assert.equal(options.width, 505)
assert.equal(options.height, 605)
ipcMain.once('child-loaded', function (event, openerIsNull, html) {
assert(openerIsNull)
assert.equal(html, '<h1>http://www.google.com/#q=electron</h1>')
ipcMain.once('answer', function (event, exceptionMessage) {
assert(/Blocked a frame with origin/.test(exceptionMessage))
done()
})
w.webContents.send('child-loaded')
})
})
})
it('should set ipc event sender correctly', function (done) {
w.destroy()
w = new BrowserWindow({
show: false,
webPreferences: {
sandbox: true,
preload: preload
}
})
let htmlPath = path.join(fixtures, 'api', 'sandbox.html?verify-ipc-sender')
const pageUrl = 'file://' + htmlPath
w.loadURL(pageUrl)
w.webContents.once('new-window', (e, url, frameName, disposition, options) => {
let parentWc = w.webContents
let childWc = options.webContents
assert.notEqual(parentWc, childWc)
ipcMain.once('parent-ready', function (event) {
assert.equal(parentWc, event.sender)
parentWc.send('verified')
})
ipcMain.once('child-ready', function (event) {
assert.equal(childWc, event.sender)
childWc.send('verified')
})
waitForEvents(ipcMain, [
'parent-answer',
'child-answer'
], done)
})
})
describe('event handling', function () {
it('works for window events', function (done) {
waitForEvents(w, [
'page-title-updated'
], done)
w.loadURL('file://' + path.join(fixtures, 'api', 'sandbox.html?window-events'))
})
it('works for web contents events', function (done) {
waitForEvents(w.webContents, [
'did-navigate',
'did-fail-load',
'did-stop-loading'
], done)
w.loadURL('file://' + path.join(fixtures, 'api', 'sandbox.html?webcontents-stop'))
waitForEvents(w.webContents, [
'did-finish-load',
'did-frame-finish-load',
'did-navigate-in-page',
'will-navigate',
'did-start-loading',
'did-stop-loading',
'did-frame-finish-load',
'dom-ready'
], done)
w.loadURL('file://' + path.join(fixtures, 'api', 'sandbox.html?webcontents-events'))
})
})
})
}) })
describe('beforeunload handler', function () { describe('beforeunload handler', function () {

76
spec/fixtures/api/sandbox.html vendored Normal file
View file

@ -0,0 +1,76 @@
<html>
<script type="text/javascript" charset="utf-8">
if (window.opener) {
window.callback = function() {
opener.require('electron').ipcRenderer.send('answer', document.body.innerHTML)
};
} else {
const {ipcRenderer} = require('electron')
const tests = {
'window-events': () => {
document.title = 'changed'
},
'webcontents-stop': () => {
stop()
},
'webcontents-events': () => {
addEventListener('load', () => {
location.hash = 'in-page-navigate'
setTimeout(() => {
location.reload()
}, 50)
})
},
'exit-event': () => {
process.on('exit', () => {
ipcRenderer.send('answer', location.href)
})
location.assign('http://www.google.com')
},
'window-open': () => {
addEventListener('load', () => {
popup = open(window.location.href, 'popup!', 'top=60,left=50,width=500,height=600')
popup.addEventListener('DOMContentLoaded', () => {
popup.document.write('<h1>scripting from opener</h1>')
popup.callback()
}, false)
})
},
'window-open-external': () => {
addEventListener('load', () => {
ipcRenderer.once('child-loaded', (e) => {
try {
let childDoc = popup.document
} catch (e) {
ipcRenderer.send('answer', e.message)
}
})
popup = open('http://www.google.com/#q=electron', '', 'top=65,left=55,width=505,height=605')
})
},
'verify-ipc-sender': () => {
popup = open()
ipcRenderer.once('verified', () => {
ipcRenderer.send('parent-answer')
})
popup.ipcRenderer.once('verified', () => {
popup.ipcRenderer.send('child-answer')
})
setTimeout(() => {
ipcRenderer.send('parent-ready')
popup.ipcRenderer.send('child-ready')
}, 50)
}
}
addEventListener('unload', () => {
if (window.popup)
popup.close()
}, false)
let [,test] = window.location.href.split('?')
if (tests.hasOwnProperty(test))
tests[test]()
}
</script>
</html>

13
spec/fixtures/module/preload-sandbox.js vendored Normal file
View file

@ -0,0 +1,13 @@
(function () {
const {ipcRenderer} = require('electron')
window.ipcRenderer = ipcRenderer
if (location.protocol === 'file:') {
window.test = 'preload'
window.require = require
window.process = process
} else if (location.href !== 'about:blank') {
addEventListener('DOMContentLoaded', () => {
ipcRenderer.send('child-loaded', window.opener == null, document.body.innerHTML)
}, false)
}
})()

View file

@ -1,6 +1,7 @@
#!/usr/bin/env python #!/usr/bin/env python
import contextlib import contextlib
import glob
import os import os
import subprocess import subprocess
import sys import sys
@ -11,7 +12,7 @@ SOURCE_ROOT = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))
def main(): def main():
natives = os.path.abspath(sys.argv[1]) natives = os.path.abspath(sys.argv[1])
js_source_files = sys.argv[2:] js_source_files = glob.glob('{0}/*.js'.format(sys.argv[2]))
call_js2c(natives, js_source_files) call_js2c(natives, js_source_files)