feat: preloads and nodeIntegration in iframes (#16425)
* feat: add support for node / preloads in subframes This feature has delibrately been built / implemented in such a way that it has minimum impact on existing apps / code-paths. Without enabling the new "nodeSupportInSubFrames" option basically none of this new code will be hit. The things that I believe need extra scrutiny are: * Introduction of `event.reply` for IPC events and usage of `event.reply` instead of `event.sender.send()` * Usage of `node::FreeEnvironment(env)` when the new option is enabled in order to avoid memory leaks. I have tested this quite a bit and haven't managed to cause a crash but it is still feature flagged behind the "nodeSupportInSubFrames" flag to avoid potential impact. Closes #10569 Closes #10401 Closes #11868 Closes #12505 Closes #14035 * feat: add support preloads in subframes for sandboxed renderers * spec: add tests for new nodeSupportInSubFrames option * spec: fix specs for .reply and ._replyInternal for internal messages * chore: revert change to use flag instead of environment set size * chore: clean up subframe impl * chore: apply suggestions from code review Co-Authored-By: MarshallOfSound <samuel.r.attard@gmail.com> * chore: clean up reply usage * chore: fix TS docs generation * chore: cleanup after rebase * chore: rename wrap to add in event fns
This commit is contained in:
Родитель
92b9525cfd
Коммит
58a6fe13d6
|
@ -57,9 +57,10 @@ v8::Local<v8::Object> CreateJSEvent(v8::Isolate* isolate,
|
|||
} else {
|
||||
event = CreateEventObject(isolate);
|
||||
}
|
||||
mate::Dictionary(isolate, event).Set("sender", object);
|
||||
mate::Dictionary dict(isolate, event);
|
||||
dict.Set("sender", object);
|
||||
if (sender)
|
||||
mate::Dictionary(isolate, event).Set("frameId", sender->GetRoutingID());
|
||||
dict.Set("frameId", sender->GetRoutingID());
|
||||
return event;
|
||||
}
|
||||
|
||||
|
|
|
@ -123,6 +123,7 @@ WebContentsPreferences::WebContentsPreferences(
|
|||
SetDefaultBoolIfUndefined(options::kPlugins, false);
|
||||
SetDefaultBoolIfUndefined(options::kExperimentalFeatures, false);
|
||||
SetDefaultBoolIfUndefined(options::kNodeIntegration, false);
|
||||
SetDefaultBoolIfUndefined(options::kNodeIntegrationInSubFrames, false);
|
||||
SetDefaultBoolIfUndefined(options::kNodeIntegrationInWorker, false);
|
||||
SetDefaultBoolIfUndefined(options::kWebviewTag, false);
|
||||
SetDefaultBoolIfUndefined(options::kSandbox, false);
|
||||
|
@ -369,6 +370,9 @@ void WebContentsPreferences::AppendCommandLineSwitches(
|
|||
}
|
||||
}
|
||||
|
||||
if (IsEnabled(options::kNodeIntegrationInSubFrames))
|
||||
command_line->AppendSwitch(switches::kNodeIntegrationInSubFrames);
|
||||
|
||||
// We are appending args to a webContents so let's save the current state
|
||||
// of our preferences object so that during the lifetime of the WebContents
|
||||
// we can fetch the options used to initally configure the WebContents
|
||||
|
|
|
@ -154,6 +154,8 @@ const char kAllowRunningInsecureContent[] = "allowRunningInsecureContent";
|
|||
|
||||
const char kOffscreen[] = "offscreen";
|
||||
|
||||
const char kNodeIntegrationInSubFrames[] = "nodeIntegrationInSubFrames";
|
||||
|
||||
} // namespace options
|
||||
|
||||
namespace switches {
|
||||
|
@ -205,6 +207,10 @@ const char kWebviewTag[] = "webview-tag";
|
|||
// Command switch passed to renderer process to control nodeIntegration.
|
||||
const char kNodeIntegrationInWorker[] = "node-integration-in-worker";
|
||||
|
||||
// Command switch passed to renderer process to control whether node
|
||||
// environments will be created in sub-frames.
|
||||
const char kNodeIntegrationInSubFrames[] = "node-integration-in-subframes";
|
||||
|
||||
// Widevine options
|
||||
// Path to Widevine CDM binaries.
|
||||
const char kWidevineCdmPath[] = "widevine-cdm-path";
|
||||
|
|
|
@ -75,6 +75,7 @@ extern const char kSandbox[];
|
|||
extern const char kWebSecurity[];
|
||||
extern const char kAllowRunningInsecureContent[];
|
||||
extern const char kOffscreen[];
|
||||
extern const char kNodeIntegrationInSubFrames[];
|
||||
|
||||
} // namespace options
|
||||
|
||||
|
@ -106,6 +107,7 @@ extern const char kHiddenPage[];
|
|||
extern const char kNativeWindowOpen[];
|
||||
extern const char kNodeIntegrationInWorker[];
|
||||
extern const char kWebviewTag[];
|
||||
extern const char kNodeIntegrationInSubFrames[];
|
||||
|
||||
extern const char kWidevineCdmPath[];
|
||||
extern const char kWidevineCdmVersion[];
|
||||
|
|
|
@ -187,7 +187,7 @@ void AtomRenderFrameObserver::OnBrowserMessage(bool internal,
|
|||
return;
|
||||
|
||||
blink::WebLocalFrame* frame = render_frame_->GetWebFrame();
|
||||
if (!frame || !render_frame_->IsMainFrame())
|
||||
if (!frame)
|
||||
return;
|
||||
|
||||
EmitIPCEvent(frame, internal, channel, args, sender_id);
|
||||
|
|
|
@ -79,25 +79,27 @@ void AtomRendererClient::DidCreateScriptContext(
|
|||
content::RenderFrame* render_frame) {
|
||||
RendererClientBase::DidCreateScriptContext(context, render_frame);
|
||||
|
||||
// Only allow node integration for the main frame of the top window, unless it
|
||||
// is a devtools extension page. Allowing child frames or child windows to
|
||||
// have node integration would result in memory leak, since we don't destroy
|
||||
// node environment when script context is destroyed.
|
||||
//
|
||||
// DevTools extensions do not follow this rule because our implementation
|
||||
// requires node integration in iframes to work. And usually DevTools
|
||||
// extensions do not dynamically add/remove iframes.
|
||||
//
|
||||
// TODO(zcbenz): Do not create Node environment if node integration is not
|
||||
// enabled.
|
||||
if (!(render_frame->IsMainFrame() &&
|
||||
!render_frame->GetWebFrame()->Opener()) &&
|
||||
!IsDevToolsExtension(render_frame))
|
||||
|
||||
// Do not load node if we're aren't a main frame or a devtools extension
|
||||
// unless node support has been explicitly enabled for sub frames
|
||||
bool is_main_frame =
|
||||
render_frame->IsMainFrame() && !render_frame->GetWebFrame()->Opener();
|
||||
bool is_devtools = IsDevToolsExtension(render_frame);
|
||||
bool allow_node_in_subframes =
|
||||
base::CommandLine::ForCurrentProcess()->HasSwitch(
|
||||
switches::kNodeIntegrationInSubFrames);
|
||||
bool should_load_node =
|
||||
is_main_frame || is_devtools || allow_node_in_subframes;
|
||||
if (!should_load_node) {
|
||||
return;
|
||||
}
|
||||
|
||||
injected_frames_.insert(render_frame);
|
||||
|
||||
// Prepare the node bindings.
|
||||
// If this is the first environment we are creating, prepare the node
|
||||
// bindings.
|
||||
if (!node_integration_initialized_) {
|
||||
node_integration_initialized_ = true;
|
||||
node_bindings_->Initialize();
|
||||
|
@ -115,6 +117,8 @@ void AtomRendererClient::DidCreateScriptContext(
|
|||
// Add Electron extended APIs.
|
||||
atom_bindings_->BindTo(env->isolate(), env->process_object());
|
||||
AddRenderBindings(env->isolate(), env->process_object());
|
||||
mate::Dictionary process_dict(env->isolate(), env->process_object());
|
||||
process_dict.SetReadOnly("isMainFrame", render_frame->IsMainFrame());
|
||||
|
||||
// Load everything.
|
||||
node_bindings_->LoadEnvironment(env);
|
||||
|
@ -146,11 +150,13 @@ void AtomRendererClient::WillReleaseScriptContext(
|
|||
if (env == node_bindings_->uv_env())
|
||||
node_bindings_->set_uv_env(nullptr);
|
||||
|
||||
// Destroy the node environment.
|
||||
// This is disabled because pending async tasks may still use the environment
|
||||
// and would cause crashes later. Node does not seem to clear all async tasks
|
||||
// when the environment is destroyed.
|
||||
// node::FreeEnvironment(env);
|
||||
// Destroy the node environment. We only do this if node support has been
|
||||
// enabled for sub-frames to avoid a change-of-behavior / introduce crashes
|
||||
// for existing users.
|
||||
// TODO(MarshallOfSOund): Free the environment regardless of this switch
|
||||
if (base::CommandLine::ForCurrentProcess()->HasSwitch(
|
||||
switches::kNodeIntegrationInSubFrames))
|
||||
node::FreeEnvironment(env);
|
||||
|
||||
// AtomBindings is tracking node environments.
|
||||
atom_bindings_->EnvironmentDestroyed(env);
|
||||
|
|
|
@ -139,7 +139,8 @@ AtomSandboxedRendererClient::~AtomSandboxedRendererClient() {}
|
|||
|
||||
void AtomSandboxedRendererClient::InitializeBindings(
|
||||
v8::Local<v8::Object> binding,
|
||||
v8::Local<v8::Context> context) {
|
||||
v8::Local<v8::Context> context,
|
||||
bool is_main_frame) {
|
||||
auto* isolate = context->GetIsolate();
|
||||
mate::Dictionary b(isolate, binding);
|
||||
b.SetMethod("get", GetBinding);
|
||||
|
@ -154,6 +155,7 @@ void AtomSandboxedRendererClient::InitializeBindings(
|
|||
process.SetReadOnly("pid", base::GetCurrentProcId());
|
||||
process.SetReadOnly("sandboxed", true);
|
||||
process.SetReadOnly("type", "renderer");
|
||||
process.SetReadOnly("isMainFrame", is_main_frame);
|
||||
|
||||
// Pass in CLI flags needed to setup the renderer
|
||||
base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
|
||||
|
@ -180,15 +182,23 @@ void AtomSandboxedRendererClient::DidCreateScriptContext(
|
|||
|
||||
// Only allow preload for the main frame or
|
||||
// For devtools we still want to run the preload_bundle script
|
||||
if (!render_frame->IsMainFrame() && !IsDevTools(render_frame) &&
|
||||
!IsDevToolsExtension(render_frame))
|
||||
// Or when nodeSupport is explicitly enabled in sub frames
|
||||
bool is_main_frame = render_frame->IsMainFrame();
|
||||
bool is_devtools =
|
||||
IsDevTools(render_frame) || IsDevToolsExtension(render_frame);
|
||||
bool allow_node_in_sub_frames =
|
||||
base::CommandLine::ForCurrentProcess()->HasSwitch(
|
||||
switches::kNodeIntegrationInSubFrames);
|
||||
bool should_load_preload =
|
||||
is_main_frame || is_devtools || allow_node_in_sub_frames;
|
||||
if (!should_load_preload)
|
||||
return;
|
||||
|
||||
// Wrap the bundle into a function that receives the binding object as
|
||||
// argument.
|
||||
auto* isolate = context->GetIsolate();
|
||||
auto binding = v8::Object::New(isolate);
|
||||
InitializeBindings(binding, context);
|
||||
InitializeBindings(binding, context, render_frame->IsMainFrame());
|
||||
AddRenderBindings(isolate, binding);
|
||||
|
||||
std::vector<v8::Local<v8::String>> preload_bundle_params = {
|
||||
|
@ -229,7 +239,10 @@ void AtomSandboxedRendererClient::WillReleaseScriptContext(
|
|||
v8::Handle<v8::Context> context,
|
||||
content::RenderFrame* render_frame) {
|
||||
// Only allow preload for the main frame
|
||||
if (!render_frame->IsMainFrame())
|
||||
// Or for sub frames when explicitly enabled
|
||||
if (!render_frame->IsMainFrame() &&
|
||||
!base::CommandLine::ForCurrentProcess()->HasSwitch(
|
||||
switches::kNodeIntegrationInSubFrames))
|
||||
return;
|
||||
|
||||
auto* isolate = context->GetIsolate();
|
||||
|
|
|
@ -19,7 +19,8 @@ class AtomSandboxedRendererClient : public RendererClientBase {
|
|||
~AtomSandboxedRendererClient() override;
|
||||
|
||||
void InitializeBindings(v8::Local<v8::Object> binding,
|
||||
v8::Local<v8::Context> context);
|
||||
v8::Local<v8::Context> context,
|
||||
bool is_main_frame);
|
||||
void InvokeIpcCallback(v8::Handle<v8::Context> context,
|
||||
const std::string& callback_name,
|
||||
std::vector<v8::Handle<v8::Value>> args);
|
||||
|
|
|
@ -255,6 +255,10 @@ It creates a new `BrowserWindow` with native properties as set by the `options`.
|
|||
* `nodeIntegrationInWorker` Boolean (optional) - Whether node integration is
|
||||
enabled in web workers. Default is `false`. More about this can be found
|
||||
in [Multithreading](../tutorial/multithreading.md).
|
||||
* `nodeIntegrationInSubFrames` Boolean (optional) - Experimental option for
|
||||
enabling NodeJS support in sub-frames such as iframes. All your preloads will load for
|
||||
every iframe, you can use `process.isMainFrame` to determine if you are
|
||||
in the main frame or not.
|
||||
* `preload` String (optional) - Specifies a script that will be loaded before other
|
||||
scripts run in the page. This script will always have access to node APIs
|
||||
no matter whether node integration is turned on or off. The value should
|
||||
|
|
|
@ -18,7 +18,9 @@ process, see [webContents.send][web-contents-send] for more information.
|
|||
* When sending a message, the event name is the `channel`.
|
||||
* To reply to a synchronous message, you need to set `event.returnValue`.
|
||||
* To send an asynchronous message back to the sender, you can use
|
||||
`event.sender.send(...)`.
|
||||
`event.reply(...)`. This helper method will automatically handle messages
|
||||
coming from frames that aren't the main frame (e.g. iframes) whereas
|
||||
`event.sender.send(...)` will always send to the main frame.
|
||||
|
||||
An example of sending and handling messages between the render and main
|
||||
processes:
|
||||
|
@ -28,7 +30,7 @@ processes:
|
|||
const { ipcMain } = require('electron')
|
||||
ipcMain.on('asynchronous-message', (event, arg) => {
|
||||
console.log(arg) // prints "ping"
|
||||
event.sender.send('asynchronous-reply', 'pong')
|
||||
event.reply('asynchronous-reply', 'pong')
|
||||
})
|
||||
|
||||
ipcMain.on('synchronous-message', (event, arg) => {
|
||||
|
@ -86,6 +88,10 @@ Removes listeners of the specified `channel`.
|
|||
|
||||
The `event` object passed to the `callback` has the following methods:
|
||||
|
||||
### `event.frameId`
|
||||
|
||||
An `Integer` representing the ID of the renderer frame that sent this message.
|
||||
|
||||
### `event.returnValue`
|
||||
|
||||
Set this to the value to be returned in a synchronous message.
|
||||
|
@ -97,3 +103,10 @@ Returns the `webContents` that sent the message, you can call
|
|||
[webContents.send][web-contents-send] for more information.
|
||||
|
||||
[web-contents-send]: web-contents.md#contentssendchannel-arg1-arg2-
|
||||
|
||||
### `event.reply`
|
||||
|
||||
A function that will send an IPC message to the renderer frane that sent
|
||||
the original message that you are currently handling. You should use this
|
||||
method to "reply" to the sent message in order to guaruntee the reply will go
|
||||
to the correct process and frame.
|
||||
|
|
|
@ -59,6 +59,11 @@ process.once('loaded', () => {
|
|||
A `Boolean`. When app is started by being passed as parameter to the default app, this
|
||||
property is `true` in the main process, otherwise it is `undefined`.
|
||||
|
||||
### `process.isMainFrame`
|
||||
|
||||
A `Boolean`, `true` when the current renderer context is the "main" renderer
|
||||
frame. If you want the ID of the current frame you should use `webFrame.routingId`.
|
||||
|
||||
### `process.mas`
|
||||
|
||||
A `Boolean`. For Mac App Store build, this property is `true`, for other builds it is
|
||||
|
|
|
@ -1420,6 +1420,36 @@ app.on('ready', () => {
|
|||
</html>
|
||||
```
|
||||
|
||||
#### `contents.sendToFrame(frameId, channel[, arg1][, arg2][, ...])`
|
||||
|
||||
* `frameId` Integer
|
||||
* `channel` String
|
||||
* `...args` any[]
|
||||
|
||||
Send an asynchronous message to a specific frame in a renderer process via
|
||||
`channel`. Arguments will be serialized
|
||||
as JSON internally and as such no functions or prototype chains will be included.
|
||||
|
||||
The renderer process can handle the message by listening to `channel` with the
|
||||
[`ipcRenderer`](ipc-renderer.md) module.
|
||||
|
||||
If you want to get the `frameId` of a given renderer context you should use
|
||||
the `webFrame.routingId` value. E.g.
|
||||
|
||||
```js
|
||||
// In a renderer process
|
||||
console.log('My frameId is:', require('electron').webFrame.routingId)
|
||||
```
|
||||
|
||||
You can also read `frameId` from all incoming IPC messages in the main process.
|
||||
|
||||
```js
|
||||
// In the main process
|
||||
ipcMain.on('ping', (event) => {
|
||||
console.info('Message came from frameId:', event.frameId)
|
||||
})
|
||||
```
|
||||
|
||||
#### `contents.enableDeviceEmulation(parameters)`
|
||||
|
||||
* `parameters` Object
|
||||
|
|
|
@ -143,6 +143,18 @@ WebContents.prototype._sendInternalToAll = function (channel, ...args) {
|
|||
|
||||
return this._send(internal, sendToAll, channel, args)
|
||||
}
|
||||
WebContents.prototype.sendToFrame = function (frameId, channel, ...args) {
|
||||
if (typeof channel !== 'string') {
|
||||
throw new Error('Missing required channel argument')
|
||||
} else if (typeof frameId !== 'number') {
|
||||
throw new Error('Missing required frameId argument')
|
||||
}
|
||||
|
||||
const internal = false
|
||||
const sendToAll = false
|
||||
|
||||
return this._sendToFrame(internal, sendToAll, frameId, channel, args)
|
||||
}
|
||||
WebContents.prototype._sendToFrameInternal = function (frameId, channel, ...args) {
|
||||
if (typeof channel !== 'string') {
|
||||
throw new Error('Missing required channel argument')
|
||||
|
@ -330,6 +342,22 @@ WebContents.prototype.loadFile = function (filePath, options = {}) {
|
|||
}))
|
||||
}
|
||||
|
||||
const addReplyToEvent = (event) => {
|
||||
event.reply = (...args) => {
|
||||
event.sender.sendToFrame(event.frameId, ...args)
|
||||
}
|
||||
}
|
||||
|
||||
const addReplyInternalToEvent = (event) => {
|
||||
Object.defineProperty(event, '_replyInternal', {
|
||||
configurable: false,
|
||||
enumerable: false,
|
||||
value: (...args) => {
|
||||
event.sender._sendToFrameInternal(event.frameId, ...args)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Add JavaScript wrappers for WebContents class.
|
||||
WebContents.prototype._init = function () {
|
||||
// The navigation controller.
|
||||
|
@ -343,6 +371,7 @@ WebContents.prototype._init = function () {
|
|||
|
||||
// Dispatch IPC messages to the ipc module.
|
||||
this.on('-ipc-message', function (event, [channel, ...args]) {
|
||||
addReplyToEvent(event)
|
||||
this.emit('ipc-message', event, channel, ...args)
|
||||
ipcMain.emit(channel, event, ...args)
|
||||
})
|
||||
|
@ -354,11 +383,13 @@ WebContents.prototype._init = function () {
|
|||
},
|
||||
get: function () {}
|
||||
})
|
||||
addReplyToEvent(event)
|
||||
this.emit('ipc-message-sync', event, channel, ...args)
|
||||
ipcMain.emit(channel, event, ...args)
|
||||
})
|
||||
|
||||
this.on('ipc-internal-message', function (event, [channel, ...args]) {
|
||||
addReplyInternalToEvent(event)
|
||||
ipcMainInternal.emit(channel, event, ...args)
|
||||
})
|
||||
|
||||
|
@ -369,6 +400,7 @@ WebContents.prototype._init = function () {
|
|||
},
|
||||
get: function () {}
|
||||
})
|
||||
addReplyInternalToEvent(event)
|
||||
ipcMainInternal.emit(channel, event, ...args)
|
||||
})
|
||||
|
||||
|
|
|
@ -180,7 +180,7 @@ ipcMain.on('CHROME_RUNTIME_SENDMESSAGE', function (event, extensionId, message,
|
|||
|
||||
page.webContents._sendInternalToAll(`CHROME_RUNTIME_ONMESSAGE_${extensionId}`, event.sender.id, message, resultID)
|
||||
ipcMain.once(`CHROME_RUNTIME_ONMESSAGE_RESULT_${resultID}`, (event, result) => {
|
||||
event.sender._sendInternal(`CHROME_RUNTIME_SENDMESSAGE_RESULT_${originResultID}`, result)
|
||||
event._replyInternal(`CHROME_RUNTIME_SENDMESSAGE_RESULT_${originResultID}`, result)
|
||||
})
|
||||
resultID++
|
||||
})
|
||||
|
@ -196,7 +196,7 @@ ipcMain.on('CHROME_TABS_SEND_MESSAGE', function (event, tabId, extensionId, isBa
|
|||
|
||||
contents._sendInternalToAll(`CHROME_RUNTIME_ONMESSAGE_${extensionId}`, senderTabId, message, resultID)
|
||||
ipcMain.once(`CHROME_RUNTIME_ONMESSAGE_RESULT_${resultID}`, (event, result) => {
|
||||
event.sender._sendInternal(`CHROME_TABS_SEND_MESSAGE_RESULT_${originResultID}`, result)
|
||||
event._replyInternal(`CHROME_TABS_SEND_MESSAGE_RESULT_${originResultID}`, result)
|
||||
})
|
||||
resultID++
|
||||
})
|
||||
|
|
|
@ -18,7 +18,7 @@ ipcMain.on(electronSources, (event, captureWindow, captureScreen, thumbnailSize,
|
|||
event.sender.emit('desktop-capturer-get-sources', customEvent)
|
||||
|
||||
if (customEvent.defaultPrevented) {
|
||||
event.sender._sendInternal(capturerResult(id), [])
|
||||
event._replyInternal(capturerResult(id), [])
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -30,7 +30,7 @@ ipcMain.on(electronSources, (event, captureWindow, captureScreen, thumbnailSize,
|
|||
thumbnailSize,
|
||||
fetchWindowIcons
|
||||
},
|
||||
webContents: event.sender
|
||||
event
|
||||
}
|
||||
requestsQueue.push(request)
|
||||
if (requestsQueue.length === 1) {
|
||||
|
@ -40,14 +40,13 @@ ipcMain.on(electronSources, (event, captureWindow, captureScreen, thumbnailSize,
|
|||
// If the WebContents is destroyed before receiving result, just remove the
|
||||
// reference from requestsQueue to make the module not send the result to it.
|
||||
event.sender.once('destroyed', () => {
|
||||
request.webContents = null
|
||||
request.event = null
|
||||
})
|
||||
})
|
||||
|
||||
desktopCapturer.emit = (event, name, sources, fetchWindowIcons) => {
|
||||
// Receiving sources result from main process, now send them back to renderer.
|
||||
const handledRequest = requestsQueue.shift()
|
||||
const handledWebContents = handledRequest.webContents
|
||||
const unhandledRequestsQueue = []
|
||||
|
||||
const result = sources.map(source => {
|
||||
|
@ -60,16 +59,16 @@ desktopCapturer.emit = (event, name, sources, fetchWindowIcons) => {
|
|||
}
|
||||
})
|
||||
|
||||
if (handledWebContents) {
|
||||
handledWebContents._sendInternal(capturerResult(handledRequest.id), result)
|
||||
if (handledRequest.event) {
|
||||
handledRequest.event._replyInternal(capturerResult(handledRequest.id), result)
|
||||
}
|
||||
|
||||
// Check the queue to see whether there is another identical request & handle
|
||||
requestsQueue.forEach(request => {
|
||||
const webContents = request.webContents
|
||||
const event = request.event
|
||||
if (deepEqual(handledRequest.options, request.options)) {
|
||||
if (webContents) {
|
||||
webContents._sendInternal(capturerResult(request.id), result)
|
||||
if (event) {
|
||||
event._replyInternal(capturerResult(request.id), result)
|
||||
}
|
||||
} else {
|
||||
unhandledRequestsQueue.push(request)
|
||||
|
|
|
@ -246,7 +246,8 @@ const attachGuest = function (event, embedderFrameId, elementInstanceId, guestIn
|
|||
['nativeWindowOpen', true],
|
||||
['nodeIntegration', false],
|
||||
['enableRemoteModule', false],
|
||||
['sandbox', true]
|
||||
['sandbox', true],
|
||||
['nodeIntegrationInSubFrames', false]
|
||||
])
|
||||
|
||||
// Inherit certain option values from embedder
|
||||
|
@ -350,7 +351,7 @@ const handleMessage = function (channel, handler) {
|
|||
}
|
||||
|
||||
handleMessage('ELECTRON_GUEST_VIEW_MANAGER_CREATE_GUEST', function (event, params, requestId) {
|
||||
event.sender._sendInternal(`ELECTRON_RESPONSE_${requestId}`, createGuest(event.sender, params))
|
||||
event._replyInternal(`ELECTRON_RESPONSE_${requestId}`, createGuest(event.sender, params))
|
||||
})
|
||||
|
||||
handleMessage('ELECTRON_GUEST_VIEW_MANAGER_CREATE_GUEST_SYNC', function (event, params) {
|
||||
|
@ -400,7 +401,7 @@ handleMessage('ELECTRON_GUEST_VIEW_MANAGER_ASYNC_CALL', function (event, request
|
|||
}, error => {
|
||||
return [errorUtils.serialize(error)]
|
||||
}).then(responseArgs => {
|
||||
event.sender._sendInternal(`ELECTRON_GUEST_VIEW_MANAGER_ASYNC_CALL_RESPONSE_${requestId}`, ...responseArgs)
|
||||
event._replyInternal(`ELECTRON_GUEST_VIEW_MANAGER_ASYNC_CALL_RESPONSE_${requestId}`, ...responseArgs)
|
||||
})
|
||||
})
|
||||
|
||||
|
|
|
@ -16,7 +16,8 @@ const inheritedWebPreferences = new Map([
|
|||
['nodeIntegration', false],
|
||||
['enableRemoteModule', false],
|
||||
['sandbox', true],
|
||||
['webviewTag', false]
|
||||
['webviewTag', false],
|
||||
['nodeIntegrationInSubFrames', false]
|
||||
])
|
||||
|
||||
// Copy attribute of |parent| to |child| if it is not defined in |child|.
|
||||
|
|
|
@ -76,12 +76,16 @@ switch (window.location.protocol) {
|
|||
require('@electron/internal/renderer/window-setup')(ipcRenderer, guestInstanceId, openerId, isHiddenPage, usesNativeWindowOpen)
|
||||
|
||||
// Inject content scripts.
|
||||
if (process.isMainFrame) {
|
||||
require('@electron/internal/renderer/content-scripts-injector')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Load webview tag implementation.
|
||||
require('@electron/internal/renderer/web-view/web-view-init')(contextIsolation, webviewTag, guestInstanceId)
|
||||
if (process.isMainFrame) {
|
||||
require('@electron/internal/renderer/web-view/web-view-init')(contextIsolation, webviewTag, guestInstanceId)
|
||||
}
|
||||
|
||||
// Pass the arguments to isolatedWorld.
|
||||
if (contextIsolation) {
|
||||
|
@ -160,4 +164,6 @@ for (const preloadScript of preloadScripts) {
|
|||
}
|
||||
|
||||
// Warn about security issues
|
||||
require('@electron/internal/renderer/security-warnings')(nodeIntegration)
|
||||
if (process.isMainFrame) {
|
||||
require('@electron/internal/renderer/security-warnings')(nodeIntegration)
|
||||
}
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
const { defineProperty, defineProperties } = Object
|
||||
|
||||
// Helper function to resolve relative url.
|
||||
const a = window.top.document.createElement('a')
|
||||
const a = window.document.createElement('a')
|
||||
const resolveURL = function (url) {
|
||||
a.href = url
|
||||
return a.href
|
||||
|
|
|
@ -0,0 +1,88 @@
|
|||
const { expect } = require('chai')
|
||||
const { remote } = require('electron')
|
||||
const path = require('path')
|
||||
|
||||
const { emittedNTimes, emittedOnce } = require('./events-helpers')
|
||||
const { closeWindow } = require('./window-helpers')
|
||||
|
||||
const { BrowserWindow } = remote
|
||||
|
||||
describe('renderer nodeIntegrationInSubFrames', () => {
|
||||
const generateTests = (sandboxEnabled) => {
|
||||
describe(`with sandbox ${sandboxEnabled ? 'enabled' : 'disabled'}`, () => {
|
||||
let w
|
||||
|
||||
beforeEach(async () => {
|
||||
await closeWindow(w)
|
||||
w = new BrowserWindow({
|
||||
show: false,
|
||||
width: 400,
|
||||
height: 400,
|
||||
webPreferences: {
|
||||
sandbox: sandboxEnabled,
|
||||
preload: path.resolve(__dirname, 'fixtures/sub-frames/preload.js'),
|
||||
nodeIntegrationInSubFrames: true
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
return closeWindow(w).then(() => { w = null })
|
||||
})
|
||||
|
||||
it('should load preload scripts in top level iframes', async () => {
|
||||
const detailsPromise = emittedNTimes(remote.ipcMain, 'preload-ran', 2)
|
||||
w.loadFile(path.resolve(__dirname, 'fixtures/sub-frames/frame-container.html'))
|
||||
const [event1, event2] = await detailsPromise
|
||||
expect(event1[0].frameId).to.not.equal(event2[0].frameId)
|
||||
expect(event1[0].frameId).to.equal(event1[2])
|
||||
expect(event2[0].frameId).to.equal(event2[2])
|
||||
})
|
||||
|
||||
it('should load preload scripts in nested iframes', async () => {
|
||||
const detailsPromise = emittedNTimes(remote.ipcMain, 'preload-ran', 3)
|
||||
w.loadFile(path.resolve(__dirname, 'fixtures/sub-frames/frame-with-frame-container.html'))
|
||||
const [event1, event2, event3] = await detailsPromise
|
||||
expect(event1[0].frameId).to.not.equal(event2[0].frameId)
|
||||
expect(event1[0].frameId).to.not.equal(event3[0].frameId)
|
||||
expect(event2[0].frameId).to.not.equal(event3[0].frameId)
|
||||
expect(event1[0].frameId).to.equal(event1[2])
|
||||
expect(event2[0].frameId).to.equal(event2[2])
|
||||
expect(event3[0].frameId).to.equal(event3[2])
|
||||
})
|
||||
|
||||
it('should correctly reply to the main frame with using event.reply', async () => {
|
||||
const detailsPromise = emittedNTimes(remote.ipcMain, 'preload-ran', 2)
|
||||
w.loadFile(path.resolve(__dirname, 'fixtures/sub-frames/frame-container.html'))
|
||||
const [event1] = await detailsPromise
|
||||
const pongPromise = emittedOnce(remote.ipcMain, 'preload-pong')
|
||||
event1[0].reply('preload-ping')
|
||||
const details = await pongPromise
|
||||
expect(details[1]).to.equal(event1[0].frameId)
|
||||
})
|
||||
|
||||
it('should correctly reply to the sub-frames with using event.reply', async () => {
|
||||
const detailsPromise = emittedNTimes(remote.ipcMain, 'preload-ran', 2)
|
||||
w.loadFile(path.resolve(__dirname, 'fixtures/sub-frames/frame-container.html'))
|
||||
const [, event2] = await detailsPromise
|
||||
const pongPromise = emittedOnce(remote.ipcMain, 'preload-pong')
|
||||
event2[0].reply('preload-ping')
|
||||
const details = await pongPromise
|
||||
expect(details[1]).to.equal(event2[0].frameId)
|
||||
})
|
||||
|
||||
it('should correctly reply to the nested sub-frames with using event.reply', async () => {
|
||||
const detailsPromise = emittedNTimes(remote.ipcMain, 'preload-ran', 3)
|
||||
w.loadFile(path.resolve(__dirname, 'fixtures/sub-frames/frame-with-frame-container.html'))
|
||||
const [,, event3] = await detailsPromise
|
||||
const pongPromise = emittedOnce(remote.ipcMain, 'preload-pong')
|
||||
event3[0].reply('preload-ping')
|
||||
const details = await pongPromise
|
||||
expect(details[1]).to.equal(event3[0].frameId)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
generateTests(false)
|
||||
generateTests(true)
|
||||
})
|
|
@ -20,10 +20,23 @@ const waitForEvent = (target, eventName) => {
|
|||
* @return {!Promise<!Array>} With Event as the first item.
|
||||
*/
|
||||
const emittedOnce = (emitter, eventName) => {
|
||||
return emittedNTimes(emitter, eventName, 1).then(([result]) => result)
|
||||
}
|
||||
|
||||
const emittedNTimes = (emitter, eventName, times) => {
|
||||
const events = []
|
||||
return new Promise(resolve => {
|
||||
emitter.once(eventName, (...args) => resolve(args))
|
||||
const handler = (...args) => {
|
||||
events.push(args)
|
||||
if (events.length === times) {
|
||||
emitter.removeListener(eventName, handler)
|
||||
resolve(events)
|
||||
}
|
||||
}
|
||||
emitter.on(eventName, handler)
|
||||
})
|
||||
}
|
||||
|
||||
exports.emittedOnce = emittedOnce
|
||||
exports.emittedNTimes = emittedNTimes
|
||||
exports.waitForEvent = waitForEvent
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<title>Document</title>
|
||||
</head>
|
||||
<body>
|
||||
This is the root page
|
||||
<iframe src="./frame.html"></iframe>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,13 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<title>Document</title>
|
||||
</head>
|
||||
<body>
|
||||
This is the root page
|
||||
<iframe src="./frame-with-frame.html"></iframe>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,13 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<title>Document</title>
|
||||
</head>
|
||||
<body>
|
||||
This is a frame, is has one child
|
||||
<iframe src="./frame.html"></iframe>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,12 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<title>Document</title>
|
||||
</head>
|
||||
<body>
|
||||
This is a frame, it has no children
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,7 @@
|
|||
const { ipcRenderer, webFrame } = require('electron')
|
||||
|
||||
ipcRenderer.send('preload-ran', window.location.href, webFrame.routingId)
|
||||
|
||||
ipcRenderer.on('preload-ping', () => {
|
||||
ipcRenderer.send('preload-pong', webFrame.routingId)
|
||||
})
|
Загрузка…
Ссылка в новой задаче