feat: add worldSafe flag for executeJS results (#24114)
* feat: add worldSafe flag for executeJS results * chore: do not log warning for webContents.executeJS * Apply suggestions from code review Co-authored-by: Jeremy Rose <jeremya@chromium.org> * chore: apply PR feedback * chore: split logic a bit * chore: allow primitives through the world safe checl * chore: clean up per PR feedback * chore: flip boolean logic * chore: update per PR feedback * chore: fix typo * chore: fix spec Co-authored-by: Jeremy Rose <jeremya@chromium.org>
This commit is contained in:
parent
3b250b649b
commit
b500294c1d
10 changed files with 160 additions and 7 deletions
|
@ -348,6 +348,9 @@ It creates a new `BrowserWindow` with native properties as set by the `options`.
|
||||||
You can access this context in the dev tools by selecting the
|
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
|
'Electron Isolated Context' entry in the combo box at the top of the
|
||||||
Console tab.
|
Console tab.
|
||||||
|
* `worldSafeExecuteJavaScript` Boolean (optional) - If true, values returned from `webFrame.executeJavaScript` will be sanitized to ensure JS values
|
||||||
|
can't unsafely cross between worlds when using `contextIsolation`. The default
|
||||||
|
is `false`. In Electron 12, the default will be changed to `true`. _Deprecated_
|
||||||
* `nativeWindowOpen` Boolean (optional) - Whether to use native
|
* `nativeWindowOpen` Boolean (optional) - Whether to use native
|
||||||
`window.open()`. Defaults to `false`. Child windows will always have node
|
`window.open()`. Defaults to `false`. Child windows will always have node
|
||||||
integration disabled unless `nodeIntegrationInSubFrames` is true. **Note:** This option is currently
|
integration disabled unless `nodeIntegrationInSubFrames` is true. **Note:** This option is currently
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { EventEmitter } from 'events';
|
import { EventEmitter } from 'events';
|
||||||
|
import deprecate from '@electron/internal/common/api/deprecate';
|
||||||
|
|
||||||
const binding = process._linkedBinding('electron_renderer_web_frame');
|
const binding = process._linkedBinding('electron_renderer_web_frame');
|
||||||
|
|
||||||
|
@ -47,14 +48,26 @@ class WebFrame extends EventEmitter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { hasSwitch } = process._linkedBinding('electron_common_command_line');
|
||||||
|
const worldSafeJS = hasSwitch('world-safe-execute-javascript') && hasSwitch('context-isolation');
|
||||||
|
|
||||||
// Populate the methods.
|
// Populate the methods.
|
||||||
for (const name in binding) {
|
for (const name in binding) {
|
||||||
if (!name.startsWith('_')) { // some methods are manually populated above
|
if (!name.startsWith('_')) { // some methods are manually populated above
|
||||||
// TODO(felixrieseberg): Once we can type web_frame natives, we could
|
// TODO(felixrieseberg): Once we can type web_frame natives, we could
|
||||||
// use a neat `keyof` here
|
// use a neat `keyof` here
|
||||||
(WebFrame as any).prototype[name] = function (...args: Array<any>) {
|
(WebFrame as any).prototype[name] = function (...args: Array<any>) {
|
||||||
|
if (!worldSafeJS && name.startsWith('executeJavaScript')) {
|
||||||
|
deprecate.log(`Security Warning: webFrame.${name} was called without worldSafeExecuteJavaScript enabled. This is considered unsafe. worldSafeExecuteJavaScript will be enabled by default in Electron 12.`);
|
||||||
|
}
|
||||||
return binding[name](this.context, ...args);
|
return binding[name](this.context, ...args);
|
||||||
};
|
};
|
||||||
|
// TODO(MarshallOfSound): Remove once the above deprecation is removed
|
||||||
|
if (name.startsWith('executeJavaScript')) {
|
||||||
|
(WebFrame as any).prototype[`_${name}`] = function (...args: Array<any>) {
|
||||||
|
return binding[name](this.context, ...args);
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,11 @@ export const webFrameInit = () => {
|
||||||
ipcRendererUtils.handle('ELECTRON_INTERNAL_RENDERER_WEB_FRAME_METHOD', (
|
ipcRendererUtils.handle('ELECTRON_INTERNAL_RENDERER_WEB_FRAME_METHOD', (
|
||||||
event, method: keyof WebFrameMethod, ...args: any[]
|
event, method: keyof WebFrameMethod, ...args: any[]
|
||||||
) => {
|
) => {
|
||||||
|
// TODO(MarshallOfSound): Remove once the world-safe-execute-javascript deprecation warning is removed
|
||||||
|
if (method.startsWith('executeJavaScript')) {
|
||||||
|
return (webFrame as any)[`_${method}`](...args);
|
||||||
|
}
|
||||||
|
|
||||||
// The TypeScript compiler cannot handle the sheer number of
|
// The TypeScript compiler cannot handle the sheer number of
|
||||||
// call signatures here and simply gives up. Incorrect invocations
|
// call signatures here and simply gives up. Incorrect invocations
|
||||||
// will be caught by "keyof WebFrameMethod" though.
|
// will be caught by "keyof WebFrameMethod" though.
|
||||||
|
|
|
@ -137,6 +137,7 @@ WebContentsPreferences::WebContentsPreferences(
|
||||||
"electron");
|
"electron");
|
||||||
}
|
}
|
||||||
SetDefaultBoolIfUndefined(options::kContextIsolation, false);
|
SetDefaultBoolIfUndefined(options::kContextIsolation, false);
|
||||||
|
SetDefaultBoolIfUndefined(options::kWorldSafeExecuteJavaScript, false);
|
||||||
SetDefaultBoolIfUndefined(options::kJavaScript, true);
|
SetDefaultBoolIfUndefined(options::kJavaScript, true);
|
||||||
SetDefaultBoolIfUndefined(options::kImages, true);
|
SetDefaultBoolIfUndefined(options::kImages, true);
|
||||||
SetDefaultBoolIfUndefined(options::kTextAreasAreResizable, true);
|
SetDefaultBoolIfUndefined(options::kTextAreasAreResizable, true);
|
||||||
|
@ -351,6 +352,9 @@ void WebContentsPreferences::AppendCommandLineSwitches(
|
||||||
if (IsEnabled(options::kContextIsolation))
|
if (IsEnabled(options::kContextIsolation))
|
||||||
command_line->AppendSwitch(switches::kContextIsolation);
|
command_line->AppendSwitch(switches::kContextIsolation);
|
||||||
|
|
||||||
|
if (IsEnabled(options::kWorldSafeExecuteJavaScript))
|
||||||
|
command_line->AppendSwitch(switches::kWorldSafeExecuteJavaScript);
|
||||||
|
|
||||||
// --background-color.
|
// --background-color.
|
||||||
std::string s;
|
std::string s;
|
||||||
if (GetAsString(&preference_, options::kBackgroundColor, &s)) {
|
if (GetAsString(&preference_, options::kBackgroundColor, &s)) {
|
||||||
|
|
|
@ -114,6 +114,9 @@ const char kNodeIntegration[] = "nodeIntegration";
|
||||||
// Enable context isolation of Electron APIs and preload script
|
// Enable context isolation of Electron APIs and preload script
|
||||||
const char kContextIsolation[] = "contextIsolation";
|
const char kContextIsolation[] = "contextIsolation";
|
||||||
|
|
||||||
|
// Enable world safe passing of values when using "executeJavaScript"
|
||||||
|
const char kWorldSafeExecuteJavaScript[] = "worldSafeExecuteJavaScript";
|
||||||
|
|
||||||
// Instance ID of guest WebContents.
|
// Instance ID of guest WebContents.
|
||||||
const char kGuestInstanceID[] = "guestInstanceId";
|
const char kGuestInstanceID[] = "guestInstanceId";
|
||||||
|
|
||||||
|
@ -238,6 +241,7 @@ const char kPreloadScript[] = "preload";
|
||||||
const char kPreloadScripts[] = "preload-scripts";
|
const char kPreloadScripts[] = "preload-scripts";
|
||||||
const char kNodeIntegration[] = "node-integration";
|
const char kNodeIntegration[] = "node-integration";
|
||||||
const char kContextIsolation[] = "context-isolation";
|
const char kContextIsolation[] = "context-isolation";
|
||||||
|
const char kWorldSafeExecuteJavaScript[] = "world-safe-execute-javascript";
|
||||||
const char kGuestInstanceID[] = "guest-instance-id";
|
const char kGuestInstanceID[] = "guest-instance-id";
|
||||||
const char kOpenerID[] = "opener-id";
|
const char kOpenerID[] = "opener-id";
|
||||||
const char kScrollBounce[] = "scroll-bounce";
|
const char kScrollBounce[] = "scroll-bounce";
|
||||||
|
|
|
@ -62,6 +62,7 @@ extern const char kPreloadScript[];
|
||||||
extern const char kPreloadURL[];
|
extern const char kPreloadURL[];
|
||||||
extern const char kNodeIntegration[];
|
extern const char kNodeIntegration[];
|
||||||
extern const char kContextIsolation[];
|
extern const char kContextIsolation[];
|
||||||
|
extern const char kWorldSafeExecuteJavaScript[];
|
||||||
extern const char kGuestInstanceID[];
|
extern const char kGuestInstanceID[];
|
||||||
extern const char kExperimentalFeatures[];
|
extern const char kExperimentalFeatures[];
|
||||||
extern const char kOpenerID[];
|
extern const char kOpenerID[];
|
||||||
|
@ -121,6 +122,7 @@ extern const char kPreloadScript[];
|
||||||
extern const char kPreloadScripts[];
|
extern const char kPreloadScripts[];
|
||||||
extern const char kNodeIntegration[];
|
extern const char kNodeIntegration[];
|
||||||
extern const char kContextIsolation[];
|
extern const char kContextIsolation[];
|
||||||
|
extern const char kWorldSafeExecuteJavaScript[];
|
||||||
extern const char kGuestInstanceID[];
|
extern const char kGuestInstanceID[];
|
||||||
extern const char kOpenerID[];
|
extern const char kOpenerID[];
|
||||||
extern const char kScrollBounce[];
|
extern const char kScrollBounce[];
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
#include "shell/common/gin_helper/error_thrower.h"
|
#include "shell/common/gin_helper/error_thrower.h"
|
||||||
#include "shell/common/gin_helper/promise.h"
|
#include "shell/common/gin_helper/promise.h"
|
||||||
#include "shell/common/node_includes.h"
|
#include "shell/common/node_includes.h"
|
||||||
|
#include "shell/common/options_switches.h"
|
||||||
#include "shell/renderer/api/electron_api_spell_check_client.h"
|
#include "shell/renderer/api/electron_api_spell_check_client.h"
|
||||||
#include "third_party/blink/public/common/page/page_zoom.h"
|
#include "third_party/blink/public/common/page/page_zoom.h"
|
||||||
#include "third_party/blink/public/common/web_cache/web_cache_resource_type_stats.h"
|
#include "third_party/blink/public/common/web_cache/web_cache_resource_type_stats.h"
|
||||||
|
@ -120,25 +121,83 @@ class ScriptExecutionCallback : public blink::WebScriptExecutionCallback {
|
||||||
|
|
||||||
explicit ScriptExecutionCallback(
|
explicit ScriptExecutionCallback(
|
||||||
gin_helper::Promise<v8::Local<v8::Value>> promise,
|
gin_helper::Promise<v8::Local<v8::Value>> promise,
|
||||||
|
bool world_safe_result,
|
||||||
CompletionCallback callback)
|
CompletionCallback callback)
|
||||||
: promise_(std::move(promise)), callback_(std::move(callback)) {}
|
: promise_(std::move(promise)),
|
||||||
|
world_safe_result_(world_safe_result),
|
||||||
|
callback_(std::move(callback)) {}
|
||||||
|
|
||||||
~ScriptExecutionCallback() override = default;
|
~ScriptExecutionCallback() override = default;
|
||||||
|
|
||||||
|
void CopyResultToCallingContextAndFinalize(
|
||||||
|
v8::Isolate* isolate,
|
||||||
|
const v8::Local<v8::Value>& result) {
|
||||||
|
blink::CloneableMessage ret;
|
||||||
|
bool success;
|
||||||
|
std::string error_message;
|
||||||
|
{
|
||||||
|
v8::TryCatch try_catch(isolate);
|
||||||
|
success = gin::ConvertFromV8(isolate, result, &ret);
|
||||||
|
if (try_catch.HasCaught()) {
|
||||||
|
auto message = try_catch.Message();
|
||||||
|
|
||||||
|
if (message.IsEmpty() ||
|
||||||
|
!gin::ConvertFromV8(isolate, message->Get(), &error_message)) {
|
||||||
|
error_message =
|
||||||
|
"An unknown exception occurred while getting the result of "
|
||||||
|
"the script";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!success) {
|
||||||
|
// Failed convert so we send undefined everywhere
|
||||||
|
if (callback_)
|
||||||
|
std::move(callback_).Run(
|
||||||
|
v8::Undefined(isolate),
|
||||||
|
v8::Exception::Error(
|
||||||
|
v8::String::NewFromUtf8(isolate, error_message.c_str())
|
||||||
|
.ToLocalChecked()));
|
||||||
|
promise_.RejectWithErrorMessage(error_message);
|
||||||
|
} else {
|
||||||
|
v8::Local<v8::Context> context = promise_.GetContext();
|
||||||
|
v8::Context::Scope context_scope(context);
|
||||||
|
v8::Local<v8::Value> cloned_value = gin::ConvertToV8(isolate, ret);
|
||||||
|
if (callback_)
|
||||||
|
std::move(callback_).Run(cloned_value, v8::Undefined(isolate));
|
||||||
|
promise_.Resolve(cloned_value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Completed(
|
void Completed(
|
||||||
const blink::WebVector<v8::Local<v8::Value>>& result) override {
|
const blink::WebVector<v8::Local<v8::Value>>& result) override {
|
||||||
v8::Isolate* isolate = v8::Isolate::GetCurrent();
|
v8::Isolate* isolate = v8::Isolate::GetCurrent();
|
||||||
if (!result.empty()) {
|
if (!result.empty()) {
|
||||||
if (!result[0].IsEmpty()) {
|
if (!result[0].IsEmpty()) {
|
||||||
// Right now only single results per frame is supported.
|
v8::Local<v8::Value> value = result[0];
|
||||||
if (!callback_.is_null())
|
// Either world safe results are disabled or the result was created in
|
||||||
std::move(callback_).Run(result[0], v8::Undefined(isolate));
|
// the same world as the caller or the result is not an object and
|
||||||
promise_.Resolve(result[0]);
|
// therefore does not have a prototype chain to protect
|
||||||
|
bool should_clone_value =
|
||||||
|
world_safe_result_ &&
|
||||||
|
!(value->IsObject() &&
|
||||||
|
promise_.GetContext() ==
|
||||||
|
value.As<v8::Object>()->CreationContext()) &&
|
||||||
|
value->IsObject();
|
||||||
|
if (should_clone_value) {
|
||||||
|
CopyResultToCallingContextAndFinalize(isolate, value);
|
||||||
|
} else {
|
||||||
|
// Right now only single results per frame is supported.
|
||||||
|
if (callback_)
|
||||||
|
std::move(callback_).Run(value, v8::Undefined(isolate));
|
||||||
|
promise_.Resolve(value);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
const char* error_message =
|
const char* error_message =
|
||||||
"Script failed to execute, this normally means an error "
|
"Script failed to execute, this normally means an error "
|
||||||
"was thrown. Check the renderer console for the error.";
|
"was thrown. Check the renderer console for the error.";
|
||||||
if (!callback_.is_null()) {
|
if (!callback_.is_null()) {
|
||||||
|
v8::Local<v8::Context> context = promise_.GetContext();
|
||||||
|
v8::Context::Scope context_scope(context);
|
||||||
std::move(callback_).Run(
|
std::move(callback_).Run(
|
||||||
v8::Undefined(isolate),
|
v8::Undefined(isolate),
|
||||||
v8::Exception::Error(
|
v8::Exception::Error(
|
||||||
|
@ -152,6 +211,8 @@ class ScriptExecutionCallback : public blink::WebScriptExecutionCallback {
|
||||||
"WebFrame was removed before script could run. This normally means "
|
"WebFrame was removed before script could run. This normally means "
|
||||||
"the underlying frame was destroyed";
|
"the underlying frame was destroyed";
|
||||||
if (!callback_.is_null()) {
|
if (!callback_.is_null()) {
|
||||||
|
v8::Local<v8::Context> context = promise_.GetContext();
|
||||||
|
v8::Context::Scope context_scope(context);
|
||||||
std::move(callback_).Run(
|
std::move(callback_).Run(
|
||||||
v8::Undefined(isolate),
|
v8::Undefined(isolate),
|
||||||
v8::Exception::Error(v8::String::NewFromUtf8(isolate, error_message)
|
v8::Exception::Error(v8::String::NewFromUtf8(isolate, error_message)
|
||||||
|
@ -164,6 +225,7 @@ class ScriptExecutionCallback : public blink::WebScriptExecutionCallback {
|
||||||
|
|
||||||
private:
|
private:
|
||||||
gin_helper::Promise<v8::Local<v8::Value>> promise_;
|
gin_helper::Promise<v8::Local<v8::Value>> promise_;
|
||||||
|
bool world_safe_result_;
|
||||||
CompletionCallback callback_;
|
CompletionCallback callback_;
|
||||||
|
|
||||||
DISALLOW_COPY_AND_ASSIGN(ScriptExecutionCallback);
|
DISALLOW_COPY_AND_ASSIGN(ScriptExecutionCallback);
|
||||||
|
@ -491,10 +553,13 @@ v8::Local<v8::Promise> ExecuteJavaScript(gin_helper::Arguments* args,
|
||||||
ScriptExecutionCallback::CompletionCallback completion_callback;
|
ScriptExecutionCallback::CompletionCallback completion_callback;
|
||||||
args->GetNext(&completion_callback);
|
args->GetNext(&completion_callback);
|
||||||
|
|
||||||
|
bool world_safe_exec_js = base::CommandLine::ForCurrentProcess()->HasSwitch(
|
||||||
|
switches::kWorldSafeExecuteJavaScript);
|
||||||
|
|
||||||
render_frame->GetWebFrame()->RequestExecuteScriptAndReturnValue(
|
render_frame->GetWebFrame()->RequestExecuteScriptAndReturnValue(
|
||||||
blink::WebScriptSource(blink::WebString::FromUTF16(code)),
|
blink::WebScriptSource(blink::WebString::FromUTF16(code)),
|
||||||
has_user_gesture,
|
has_user_gesture,
|
||||||
new ScriptExecutionCallback(std::move(promise),
|
new ScriptExecutionCallback(std::move(promise), world_safe_exec_js,
|
||||||
std::move(completion_callback)));
|
std::move(completion_callback)));
|
||||||
|
|
||||||
return handle;
|
return handle;
|
||||||
|
@ -554,13 +619,16 @@ v8::Local<v8::Promise> ExecuteJavaScriptInIsolatedWorld(
|
||||||
blink::WebURL(GURL(url)), start_line));
|
blink::WebURL(GURL(url)), start_line));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool world_safe_exec_js = base::CommandLine::ForCurrentProcess()->HasSwitch(
|
||||||
|
switches::kWorldSafeExecuteJavaScript);
|
||||||
|
|
||||||
// Debugging tip: if you see a crash stack trace beginning from this call,
|
// Debugging tip: if you see a crash stack trace beginning from this call,
|
||||||
// then it is very likely that some exception happened when executing the
|
// then it is very likely that some exception happened when executing the
|
||||||
// "content_script/init.js" script.
|
// "content_script/init.js" script.
|
||||||
render_frame->GetWebFrame()->RequestExecuteScriptInIsolatedWorld(
|
render_frame->GetWebFrame()->RequestExecuteScriptInIsolatedWorld(
|
||||||
world_id, &sources.front(), sources.size(), has_user_gesture,
|
world_id, &sources.front(), sources.size(), has_user_gesture,
|
||||||
scriptExecutionType,
|
scriptExecutionType,
|
||||||
new ScriptExecutionCallback(std::move(promise),
|
new ScriptExecutionCallback(std::move(promise), world_safe_exec_js,
|
||||||
std::move(completion_callback)));
|
std::move(completion_callback)));
|
||||||
|
|
||||||
return handle;
|
return handle;
|
||||||
|
|
|
@ -2,12 +2,48 @@ import { expect } from 'chai';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import { BrowserWindow, ipcMain } from 'electron/main';
|
import { BrowserWindow, ipcMain } from 'electron/main';
|
||||||
import { closeAllWindows } from './window-helpers';
|
import { closeAllWindows } from './window-helpers';
|
||||||
|
import { emittedOnce } from './events-helpers';
|
||||||
|
|
||||||
describe('webFrame module', () => {
|
describe('webFrame module', () => {
|
||||||
const fixtures = path.resolve(__dirname, '..', 'spec', 'fixtures');
|
const fixtures = path.resolve(__dirname, '..', 'spec', 'fixtures');
|
||||||
|
|
||||||
afterEach(closeAllWindows);
|
afterEach(closeAllWindows);
|
||||||
|
|
||||||
|
for (const worldSafe of [true, false]) {
|
||||||
|
it(`can use executeJavaScript with world safe mode ${worldSafe ? 'enabled' : 'disabled'}`, async () => {
|
||||||
|
const w = new BrowserWindow({
|
||||||
|
show: true,
|
||||||
|
webPreferences: {
|
||||||
|
nodeIntegration: true,
|
||||||
|
contextIsolation: true,
|
||||||
|
worldSafeExecuteJavaScript: worldSafe,
|
||||||
|
preload: path.join(fixtures, 'pages', 'world-safe-preload.js')
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const isSafe = emittedOnce(ipcMain, 'executejs-safe');
|
||||||
|
w.loadURL('about:blank');
|
||||||
|
const [, wasSafe] = await isSafe;
|
||||||
|
expect(wasSafe).to.equal(worldSafe);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
it('can use executeJavaScript with world safe mode enabled and catch conversion errors', async () => {
|
||||||
|
const w = new BrowserWindow({
|
||||||
|
show: true,
|
||||||
|
webPreferences: {
|
||||||
|
nodeIntegration: true,
|
||||||
|
contextIsolation: true,
|
||||||
|
worldSafeExecuteJavaScript: true,
|
||||||
|
preload: path.join(fixtures, 'pages', 'world-safe-preload-error.js')
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const execError = emittedOnce(ipcMain, 'executejs-safe');
|
||||||
|
w.loadURL('about:blank');
|
||||||
|
const [, error] = await execError;
|
||||||
|
expect(error).to.not.equal(null, 'Error should not be null');
|
||||||
|
expect(error).to.have.property('message', 'Uncaught Error: An object could not be cloned.');
|
||||||
|
});
|
||||||
|
|
||||||
it('calls a spellcheck provider', async () => {
|
it('calls a spellcheck provider', async () => {
|
||||||
const w = new BrowserWindow({
|
const w = new BrowserWindow({
|
||||||
show: false,
|
show: false,
|
||||||
|
|
10
spec/fixtures/pages/world-safe-preload-error.js
vendored
Normal file
10
spec/fixtures/pages/world-safe-preload-error.js
vendored
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
const { ipcRenderer, webFrame } = require('electron');
|
||||||
|
|
||||||
|
webFrame.executeJavaScript(`(() => {
|
||||||
|
return Object(Symbol('a'));
|
||||||
|
})()`).catch((err) => {
|
||||||
|
// Considered safe if the object is constructed in this world
|
||||||
|
ipcRenderer.send('executejs-safe', err);
|
||||||
|
}).then(() => {
|
||||||
|
ipcRenderer.send('executejs-safe', null);
|
||||||
|
});
|
8
spec/fixtures/pages/world-safe-preload.js
vendored
Normal file
8
spec/fixtures/pages/world-safe-preload.js
vendored
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
const { ipcRenderer, webFrame } = require('electron');
|
||||||
|
|
||||||
|
webFrame.executeJavaScript(`(() => {
|
||||||
|
return {};
|
||||||
|
})()`).then((obj) => {
|
||||||
|
// Considered safe if the object is constructed in this world
|
||||||
|
ipcRenderer.send('executejs-safe', obj.constructor === Object);
|
||||||
|
});
|
Loading…
Reference in a new issue