Implement window overrides in main context
This commit is contained in:
parent
95054f443f
commit
3f7b3c4bd7
13 changed files with 357 additions and 289 deletions
|
@ -196,7 +196,7 @@ void WebContentsPreferences::AppendExtraCommandLineSwitches(
|
|||
if (window) {
|
||||
bool visible = window->IsVisible() && !window->IsMinimized();
|
||||
if (!visible) // Default state is visible.
|
||||
command_line->AppendSwitch("hidden-page");
|
||||
command_line->AppendSwitch(switches::kHiddenPage);
|
||||
}
|
||||
|
||||
// Use frame scheduling for offscreen renderers.
|
||||
|
|
|
@ -102,7 +102,7 @@ const char kNodeIntegration[] = "nodeIntegration";
|
|||
// Enable context isolation of Electron APIs and preload script
|
||||
const char kContextIsolation[] = "contextIsolation";
|
||||
|
||||
// Instancd ID of guest WebContents.
|
||||
// Instance ID of guest WebContents.
|
||||
const char kGuestInstanceID[] = "guestInstanceId";
|
||||
|
||||
// Web runtime features.
|
||||
|
@ -170,6 +170,7 @@ const char kContextIsolation[] = "context-isolation";
|
|||
const char kGuestInstanceID[] = "guest-instance-id";
|
||||
const char kOpenerID[] = "opener-id";
|
||||
const char kScrollBounce[] = "scroll-bounce";
|
||||
const char kHiddenPage[] = "hidden-page";
|
||||
|
||||
// Widevine options
|
||||
// Path to Widevine CDM binaries.
|
||||
|
|
|
@ -91,6 +91,7 @@ extern const char kContextIsolation[];
|
|||
extern const char kGuestInstanceID[];
|
||||
extern const char kOpenerID[];
|
||||
extern const char kScrollBounce[];
|
||||
extern const char kHiddenPage[];
|
||||
|
||||
extern const char kWidevineCdmPath[];
|
||||
extern const char kWidevineCdmVersion[];
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
|
||||
#include "atom/renderer/atom_renderer_client.h"
|
||||
|
||||
#include "atom_natives.h" // NOLINT: This file is generated with js2c
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
|
@ -14,6 +16,7 @@
|
|||
#include "atom/common/native_mate_converters/value_converter.h"
|
||||
#include "atom/common/node_bindings.h"
|
||||
#include "atom/common/options_switches.h"
|
||||
#include "atom/renderer/api/atom_api_renderer_ipc.h"
|
||||
#include "atom/renderer/atom_render_view_observer.h"
|
||||
#include "atom/renderer/content_settings_observer.h"
|
||||
#include "atom/renderer/guest_view_container.h"
|
||||
|
@ -90,6 +93,39 @@ class AtomRenderFrameObserver : public content::RenderFrameObserver {
|
|||
World::ISOLATED_WORLD, &source, 1, ExtensionGroup::MAIN_GROUP);
|
||||
}
|
||||
|
||||
void SetupMainWorldOverrides(v8::Handle<v8::Context> context) {
|
||||
// Setup window overrides in the main world context
|
||||
v8::Isolate* isolate = context->GetIsolate();
|
||||
|
||||
// Wrap the bundle into a function that receives the binding object as
|
||||
// an argument.
|
||||
std::string bundle(node::isolated_bundle_native,
|
||||
node::isolated_bundle_native + sizeof(node::isolated_bundle_native));
|
||||
std::string wrapper = "(function (binding) {\n" + bundle + "\n})";
|
||||
auto script = v8::Script::Compile(
|
||||
mate::ConvertToV8(isolate, wrapper)->ToString());
|
||||
auto func = v8::Handle<v8::Function>::Cast(
|
||||
script->Run(context).ToLocalChecked());
|
||||
|
||||
auto binding = v8::Object::New(isolate);
|
||||
api::Initialize(binding, v8::Null(isolate), context, nullptr);
|
||||
|
||||
// Pass in CLI flags needed to setup window
|
||||
base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
|
||||
mate::Dictionary dict(isolate, binding);
|
||||
if (command_line->HasSwitch(switches::kGuestInstanceID))
|
||||
dict.Set("guestInstanceId",
|
||||
command_line->GetSwitchValueASCII(switches::kGuestInstanceID));
|
||||
if (command_line->HasSwitch(switches::kOpenerID))
|
||||
dict.Set("openerId",
|
||||
command_line->GetSwitchValueASCII(switches::kOpenerID));
|
||||
dict.Set("hiddenPage", command_line->HasSwitch(switches::kHiddenPage));
|
||||
|
||||
v8::Local<v8::Value> args[] = { binding };
|
||||
|
||||
ignore_result(func->Call(context, v8::Null(isolate), 1, args));
|
||||
}
|
||||
|
||||
bool IsMainWorld(int world_id) {
|
||||
return world_id == World::MAIN_WORLD;
|
||||
}
|
||||
|
@ -111,8 +147,10 @@ class AtomRenderFrameObserver : public content::RenderFrameObserver {
|
|||
if (NotifyClient(world_id))
|
||||
renderer_client_->DidCreateScriptContext(context, render_frame_);
|
||||
|
||||
if (renderer_client_->isolated_world() && IsMainWorld(world_id))
|
||||
if (renderer_client_->isolated_world() && IsMainWorld(world_id)) {
|
||||
CreateIsolatedWorldContext();
|
||||
SetupMainWorldOverrides(context);
|
||||
}
|
||||
}
|
||||
|
||||
void WillReleaseScriptContext(v8::Local<v8::Context> context,
|
||||
|
|
24
electron.gyp
24
electron.gyp
|
@ -433,7 +433,7 @@
|
|||
],
|
||||
'actions': [
|
||||
{
|
||||
'action_name': 'atom_browserify',
|
||||
'action_name': 'atom_browserify_sandbox',
|
||||
'inputs': [
|
||||
'<@(browserify_entries)',
|
||||
],
|
||||
|
@ -450,7 +450,26 @@
|
|||
'-o',
|
||||
'<@(_outputs)',
|
||||
],
|
||||
}
|
||||
},
|
||||
{
|
||||
'action_name': 'atom_browserify_isolated_context',
|
||||
'inputs': [
|
||||
'<@(isolated_context_browserify_entries)',
|
||||
],
|
||||
'outputs': [
|
||||
'<(js2c_input_dir)/isolated_bundle.js',
|
||||
],
|
||||
'action': [
|
||||
'npm',
|
||||
'run',
|
||||
'--silent',
|
||||
'browserify',
|
||||
'--',
|
||||
'lib/isolated_renderer/init.js',
|
||||
'-o',
|
||||
'<@(_outputs)',
|
||||
],
|
||||
},
|
||||
],
|
||||
}, # target atom_browserify
|
||||
{
|
||||
|
@ -467,6 +486,7 @@
|
|||
# List all input files that should trigger a rebuild with js2c
|
||||
'<@(js2c_sources)',
|
||||
'<(js2c_input_dir)/preload_bundle.js',
|
||||
'<(js2c_input_dir)/isolated_bundle.js',
|
||||
],
|
||||
'outputs': [
|
||||
'<(SHARED_INTERMEDIATE_DIR)/atom_natives.h',
|
||||
|
|
|
@ -56,6 +56,7 @@
|
|||
'lib/renderer/init.js',
|
||||
'lib/renderer/inspector.js',
|
||||
'lib/renderer/override.js',
|
||||
'lib/renderer/window-setup.js',
|
||||
'lib/renderer/web-view/guest-view-internal.js',
|
||||
'lib/renderer/web-view/web-view.js',
|
||||
'lib/renderer/web-view/web-view-attributes.js',
|
||||
|
@ -76,6 +77,10 @@
|
|||
'lib/renderer/api/ipc-renderer-setup.js',
|
||||
'lib/sandboxed_renderer/init.js',
|
||||
],
|
||||
'isolated_context_browserify_entries': [
|
||||
'lib/renderer/window-setup.js',
|
||||
'lib/isolated_renderer/init.js',
|
||||
],
|
||||
'js2c_sources': [
|
||||
'lib/common/asar.js',
|
||||
'lib/common/asar_init.js',
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
const {BrowserWindow, ipcMain, webContents} = require('electron')
|
||||
const {isSameOrigin} = process.atomBinding('v8_util')
|
||||
const parseFeaturesString = require('../common/parse-features-string')
|
||||
|
||||
const hasProp = {}.hasOwnProperty
|
||||
const frameToGuest = {}
|
||||
|
@ -176,8 +177,68 @@ const canAccessWindow = function (sender, target) {
|
|||
isSameOrigin(sender.getURL(), target.getURL())
|
||||
}
|
||||
|
||||
// Routed window.open messages.
|
||||
ipcMain.on('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_OPEN', function (event, url, frameName,
|
||||
// Routed window.open messages with raw options
|
||||
ipcMain.on('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_OPEN', (event, url, frameName, features) => {
|
||||
if (url == null || url === '') url = 'about:blank'
|
||||
if (frameName == null) frameName = ''
|
||||
if (features == null) features = ''
|
||||
|
||||
const options = {}
|
||||
|
||||
const ints = ['x', 'y', 'width', 'height', 'minWidth', 'maxWidth', 'minHeight', 'maxHeight', 'zoomFactor']
|
||||
const webPreferences = ['zoomFactor', 'nodeIntegration', 'preload']
|
||||
const disposition = 'new-window'
|
||||
|
||||
// Used to store additional features
|
||||
const additionalFeatures = []
|
||||
|
||||
// Parse the features
|
||||
parseFeaturesString(features, function (key, value) {
|
||||
if (value === undefined) {
|
||||
additionalFeatures.push(key)
|
||||
} else {
|
||||
if (webPreferences.includes(key)) {
|
||||
if (options.webPreferences == null) {
|
||||
options.webPreferences = {}
|
||||
}
|
||||
options.webPreferences[key] = value
|
||||
} else {
|
||||
options[key] = value
|
||||
}
|
||||
}
|
||||
})
|
||||
if (options.left) {
|
||||
if (options.x == null) {
|
||||
options.x = options.left
|
||||
}
|
||||
}
|
||||
if (options.top) {
|
||||
if (options.y == null) {
|
||||
options.y = options.top
|
||||
}
|
||||
}
|
||||
if (options.title == null) {
|
||||
options.title = frameName
|
||||
}
|
||||
if (options.width == null) {
|
||||
options.width = 800
|
||||
}
|
||||
if (options.height == null) {
|
||||
options.height = 600
|
||||
}
|
||||
|
||||
for (const name of ints) {
|
||||
if (options[name] != null) {
|
||||
options[name] = parseInt(options[name], 10)
|
||||
}
|
||||
}
|
||||
|
||||
ipcMain.emit('ELECTRON_GUEST_WINDOW_MANAGER_INTERNAL_WINDOW_OPEN', event,
|
||||
url, frameName, disposition, options, additionalFeatures)
|
||||
})
|
||||
|
||||
// Routed window.open messages with fully parsed options
|
||||
ipcMain.on('ELECTRON_GUEST_WINDOW_MANAGER_INTERNAL_WINDOW_OPEN', function (event, url, frameName,
|
||||
disposition, options,
|
||||
additionalFeatures, postData) {
|
||||
options = mergeBrowserWindowOptions(event.sender, options)
|
||||
|
@ -229,6 +290,10 @@ ipcMain.on('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_METHOD', function (event, guest
|
|||
})
|
||||
|
||||
ipcMain.on('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_POSTMESSAGE', function (event, guestId, message, targetOrigin, sourceOrigin) {
|
||||
if (targetOrigin == null) {
|
||||
targetOrigin = '*'
|
||||
}
|
||||
|
||||
const guestContents = webContents.fromId(guestId)
|
||||
if (guestContents == null) return
|
||||
|
||||
|
|
22
lib/isolated_renderer/init.js
Normal file
22
lib/isolated_renderer/init.js
Normal file
|
@ -0,0 +1,22 @@
|
|||
/* global binding */
|
||||
|
||||
'use strict'
|
||||
|
||||
const {guestInstanceId, hiddenPage, openerId, send, sendSync} = binding
|
||||
const {parse} = JSON
|
||||
|
||||
const ipcRenderer = {
|
||||
send (...args) {
|
||||
return send('ipc-message', args)
|
||||
},
|
||||
|
||||
sendSync (...args) {
|
||||
return parse(sendSync('ipc-message-sync', args))
|
||||
},
|
||||
|
||||
// No-ops since events aren't received
|
||||
on () {},
|
||||
once () {}
|
||||
}
|
||||
|
||||
require('../renderer/window-setup')(ipcRenderer, guestInstanceId, openerId, hiddenPage)
|
|
@ -1,244 +1,8 @@
|
|||
'use strict'
|
||||
|
||||
const {ipcRenderer} = require('electron')
|
||||
const parseFeaturesString = require('../common/parse-features-string')
|
||||
|
||||
const {defineProperty} = Object
|
||||
const {guestInstanceId, openerId} = process
|
||||
const hiddenPage = process.argv.includes('--hidden-page')
|
||||
|
||||
// Helper function to resolve relative url.
|
||||
const a = window.top.document.createElement('a')
|
||||
const resolveURL = function (url) {
|
||||
a.href = url
|
||||
return a.href
|
||||
}
|
||||
|
||||
// Window object returned by "window.open".
|
||||
const BrowserWindowProxy = (function () {
|
||||
BrowserWindowProxy.proxies = {}
|
||||
|
||||
BrowserWindowProxy.getOrCreate = function (guestId) {
|
||||
let proxy = this.proxies[guestId]
|
||||
if (proxy == null) {
|
||||
proxy = new BrowserWindowProxy(guestId)
|
||||
this.proxies[guestId] = proxy
|
||||
}
|
||||
return proxy
|
||||
}
|
||||
|
||||
BrowserWindowProxy.remove = function (guestId) {
|
||||
delete this.proxies[guestId]
|
||||
}
|
||||
|
||||
function BrowserWindowProxy (guestId1) {
|
||||
defineProperty(this, 'guestId', {
|
||||
configurable: false,
|
||||
enumerable: true,
|
||||
writeable: false,
|
||||
value: guestId1
|
||||
})
|
||||
|
||||
this.closed = false
|
||||
ipcRenderer.once('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_CLOSED_' + this.guestId, () => {
|
||||
BrowserWindowProxy.remove(this.guestId)
|
||||
this.closed = true
|
||||
})
|
||||
}
|
||||
|
||||
BrowserWindowProxy.prototype.close = function () {
|
||||
ipcRenderer.send('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_CLOSE', this.guestId)
|
||||
}
|
||||
|
||||
BrowserWindowProxy.prototype.focus = function () {
|
||||
ipcRenderer.send('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_METHOD', this.guestId, 'focus')
|
||||
}
|
||||
|
||||
BrowserWindowProxy.prototype.blur = function () {
|
||||
ipcRenderer.send('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_METHOD', this.guestId, 'blur')
|
||||
}
|
||||
|
||||
BrowserWindowProxy.prototype.print = function () {
|
||||
ipcRenderer.send('ELECTRON_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD', this.guestId, 'print')
|
||||
}
|
||||
|
||||
defineProperty(BrowserWindowProxy.prototype, 'location', {
|
||||
get: function () {
|
||||
return ipcRenderer.sendSync('ELECTRON_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD_SYNC', this.guestId, 'getURL')
|
||||
},
|
||||
set: function (url) {
|
||||
url = resolveURL(url)
|
||||
return ipcRenderer.sendSync('ELECTRON_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD_SYNC', this.guestId, 'loadURL', url)
|
||||
}
|
||||
})
|
||||
|
||||
BrowserWindowProxy.prototype.postMessage = function (message, targetOrigin) {
|
||||
if (targetOrigin == null) {
|
||||
targetOrigin = '*'
|
||||
}
|
||||
ipcRenderer.send('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_POSTMESSAGE', this.guestId, message, targetOrigin, window.location.origin)
|
||||
}
|
||||
|
||||
BrowserWindowProxy.prototype['eval'] = function (...args) {
|
||||
ipcRenderer.send('ELECTRON_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD', this.guestId, 'executeJavaScript', ...args)
|
||||
}
|
||||
|
||||
return BrowserWindowProxy
|
||||
})()
|
||||
|
||||
if (process.guestInstanceId == null) {
|
||||
// Override default window.close.
|
||||
window.close = function () {
|
||||
ipcRenderer.sendSync('ELECTRON_BROWSER_WINDOW_CLOSE')
|
||||
}
|
||||
}
|
||||
|
||||
// Make the browser window or guest view emit "new-window" event.
|
||||
window.open = function (url, frameName, features) {
|
||||
let guestId, j, len1, name, options, additionalFeatures
|
||||
if (frameName == null) {
|
||||
frameName = ''
|
||||
}
|
||||
if (features == null) {
|
||||
features = ''
|
||||
}
|
||||
options = {}
|
||||
|
||||
const ints = ['x', 'y', 'width', 'height', 'minWidth', 'maxWidth', 'minHeight', 'maxHeight', 'zoomFactor']
|
||||
const webPreferences = ['zoomFactor', 'nodeIntegration', 'preload']
|
||||
const disposition = 'new-window'
|
||||
|
||||
// Used to store additional features
|
||||
additionalFeatures = []
|
||||
|
||||
// Parse the features
|
||||
parseFeaturesString(features, function (key, value) {
|
||||
if (value === undefined) {
|
||||
additionalFeatures.push(key)
|
||||
} else {
|
||||
if (webPreferences.includes(key)) {
|
||||
if (options.webPreferences == null) {
|
||||
options.webPreferences = {}
|
||||
}
|
||||
options.webPreferences[key] = value
|
||||
} else {
|
||||
options[key] = value
|
||||
}
|
||||
}
|
||||
})
|
||||
if (options.left) {
|
||||
if (options.x == null) {
|
||||
options.x = options.left
|
||||
}
|
||||
}
|
||||
if (options.top) {
|
||||
if (options.y == null) {
|
||||
options.y = options.top
|
||||
}
|
||||
}
|
||||
if (options.title == null) {
|
||||
options.title = frameName
|
||||
}
|
||||
if (options.width == null) {
|
||||
options.width = 800
|
||||
}
|
||||
if (options.height == null) {
|
||||
options.height = 600
|
||||
}
|
||||
|
||||
// Resolve relative urls.
|
||||
if (url == null || url === '') {
|
||||
url = 'about:blank'
|
||||
} else {
|
||||
url = resolveURL(url)
|
||||
}
|
||||
for (j = 0, len1 = ints.length; j < len1; j++) {
|
||||
name = ints[j]
|
||||
if (options[name] != null) {
|
||||
options[name] = parseInt(options[name], 10)
|
||||
}
|
||||
}
|
||||
guestId = ipcRenderer.sendSync('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_OPEN', url, frameName, disposition, options, additionalFeatures)
|
||||
if (guestId) {
|
||||
return BrowserWindowProxy.getOrCreate(guestId)
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
window.alert = function (message, title) {
|
||||
ipcRenderer.sendSync('ELECTRON_BROWSER_WINDOW_ALERT', message, title)
|
||||
}
|
||||
|
||||
window.confirm = function (message, title) {
|
||||
return ipcRenderer.sendSync('ELECTRON_BROWSER_WINDOW_CONFIRM', message, title)
|
||||
}
|
||||
|
||||
// But we do not support prompt().
|
||||
window.prompt = function () {
|
||||
throw new Error('prompt() is and will not be supported.')
|
||||
}
|
||||
|
||||
if (process.openerId != null) {
|
||||
window.opener = BrowserWindowProxy.getOrCreate(process.openerId)
|
||||
}
|
||||
|
||||
ipcRenderer.on('ELECTRON_GUEST_WINDOW_POSTMESSAGE', function (event, sourceId, message, sourceOrigin) {
|
||||
// Manually dispatch event instead of using postMessage because we also need to
|
||||
// set event.source.
|
||||
event = document.createEvent('Event')
|
||||
event.initEvent('message', false, false)
|
||||
event.data = message
|
||||
event.origin = sourceOrigin
|
||||
event.source = BrowserWindowProxy.getOrCreate(sourceId)
|
||||
window.dispatchEvent(event)
|
||||
})
|
||||
|
||||
// Forward history operations to browser.
|
||||
const sendHistoryOperation = function (...args) {
|
||||
ipcRenderer.send('ELECTRON_NAVIGATION_CONTROLLER', ...args)
|
||||
}
|
||||
|
||||
const getHistoryOperation = function (...args) {
|
||||
return ipcRenderer.sendSync('ELECTRON_SYNC_NAVIGATION_CONTROLLER', ...args)
|
||||
}
|
||||
|
||||
window.history.back = function () {
|
||||
sendHistoryOperation('goBack')
|
||||
}
|
||||
|
||||
window.history.forward = function () {
|
||||
sendHistoryOperation('goForward')
|
||||
}
|
||||
|
||||
window.history.go = function (offset) {
|
||||
sendHistoryOperation('goToOffset', offset)
|
||||
}
|
||||
|
||||
defineProperty(window.history, 'length', {
|
||||
get: function () {
|
||||
return getHistoryOperation('length')
|
||||
}
|
||||
})
|
||||
|
||||
// The initial visibilityState.
|
||||
let cachedVisibilityState = process.argv.includes('--hidden-page') ? 'hidden' : 'visible'
|
||||
|
||||
// Subscribe to visibilityState changes.
|
||||
ipcRenderer.on('ELECTRON_RENDERER_WINDOW_VISIBILITY_CHANGE', function (event, visibilityState) {
|
||||
if (cachedVisibilityState !== visibilityState) {
|
||||
cachedVisibilityState = visibilityState
|
||||
document.dispatchEvent(new Event('visibilitychange'))
|
||||
}
|
||||
})
|
||||
|
||||
// Make document.hidden and document.visibilityState return the correct value.
|
||||
defineProperty(document, 'hidden', {
|
||||
get: function () {
|
||||
return cachedVisibilityState !== 'visible'
|
||||
}
|
||||
})
|
||||
|
||||
defineProperty(document, 'visibilityState', {
|
||||
get: function () {
|
||||
return cachedVisibilityState
|
||||
}
|
||||
})
|
||||
require('./window-setup')(ipcRenderer, guestInstanceId, openerId, hiddenPage)
|
||||
|
|
163
lib/renderer/window-setup.js
Normal file
163
lib/renderer/window-setup.js
Normal file
|
@ -0,0 +1,163 @@
|
|||
// This file should have no requires since it is used by the isolated context
|
||||
// preload bundle. Instead arguments should be passed in for everything it
|
||||
// needs.
|
||||
|
||||
'use strict'
|
||||
|
||||
const {defineProperty} = Object
|
||||
|
||||
// Helper function to resolve relative url.
|
||||
const a = window.top.document.createElement('a')
|
||||
const resolveURL = function (url) {
|
||||
a.href = url
|
||||
return a.href
|
||||
}
|
||||
|
||||
const windowProxies = {}
|
||||
|
||||
module.exports = (ipcRenderer, guestInstanceId, openerId, hiddenPage) => {
|
||||
const getOrCreateProxy = (guestId) => {
|
||||
let proxy = windowProxies[guestId]
|
||||
if (proxy == null) {
|
||||
proxy = new BrowserWindowProxy(guestId)
|
||||
windowProxies[guestId] = proxy
|
||||
}
|
||||
return proxy
|
||||
}
|
||||
|
||||
const removeProxy = (guestId) => {
|
||||
delete windowProxies[guestId]
|
||||
}
|
||||
|
||||
function BrowserWindowProxy (guestId) {
|
||||
this.closed = false
|
||||
|
||||
ipcRenderer.once(`ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_CLOSED_${guestId}`, () => {
|
||||
removeProxy(this.guestId)
|
||||
this.closed = true
|
||||
})
|
||||
|
||||
this.close = () => {
|
||||
ipcRenderer.send('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_CLOSE', guestId)
|
||||
}
|
||||
|
||||
this.focus = () => {
|
||||
ipcRenderer.send('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_METHOD', guestId, 'focus')
|
||||
}
|
||||
|
||||
this.blur = () => {
|
||||
ipcRenderer.send('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_METHOD', guestId, 'blur')
|
||||
}
|
||||
|
||||
this.print = () => {
|
||||
ipcRenderer.send('ELECTRON_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD', guestId, 'print')
|
||||
}
|
||||
|
||||
this.postMessage = (message, targetOrigin) => {
|
||||
ipcRenderer.send('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_POSTMESSAGE', guestId, message, targetOrigin, window.location.origin)
|
||||
}
|
||||
|
||||
this.eval = (...args) => {
|
||||
ipcRenderer.send('ELECTRON_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD', guestId, 'executeJavaScript', ...args)
|
||||
}
|
||||
}
|
||||
|
||||
if (guestInstanceId == null) {
|
||||
// Override default window.close.
|
||||
window.close = function () {
|
||||
ipcRenderer.sendSync('ELECTRON_BROWSER_WINDOW_CLOSE')
|
||||
}
|
||||
}
|
||||
|
||||
// Make the browser window or guest view emit "new-window" event.
|
||||
window.open = function (url, frameName, features) {
|
||||
if (url != null && url.length > 0) {
|
||||
url = resolveURL(url)
|
||||
}
|
||||
const guestId = ipcRenderer.sendSync('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_OPEN', url, frameName, features)
|
||||
if (guestId != null) {
|
||||
return getOrCreateProxy(guestId)
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
window.alert = function (message, title) {
|
||||
ipcRenderer.sendSync('ELECTRON_BROWSER_WINDOW_ALERT', message, title)
|
||||
}
|
||||
|
||||
window.confirm = function (message, title) {
|
||||
return ipcRenderer.sendSync('ELECTRON_BROWSER_WINDOW_CONFIRM', message, title)
|
||||
}
|
||||
|
||||
// But we do not support prompt().
|
||||
window.prompt = function () {
|
||||
throw new Error('prompt() is and will not be supported.')
|
||||
}
|
||||
|
||||
if (openerId != null) {
|
||||
window.opener = getOrCreateProxy(openerId)
|
||||
}
|
||||
|
||||
ipcRenderer.on('ELECTRON_GUEST_WINDOW_POSTMESSAGE', function (event, sourceId, message, sourceOrigin) {
|
||||
// Manually dispatch event instead of using postMessage because we also need to
|
||||
// set event.source.
|
||||
event = document.createEvent('Event')
|
||||
event.initEvent('message', false, false)
|
||||
event.data = message
|
||||
event.origin = sourceOrigin
|
||||
event.source = BrowserWindowProxy.getOrCreate(sourceId)
|
||||
window.dispatchEvent(event)
|
||||
})
|
||||
|
||||
// Forward history operations to browser.
|
||||
const sendHistoryOperation = function (...args) {
|
||||
ipcRenderer.send('ELECTRON_NAVIGATION_CONTROLLER', ...args)
|
||||
}
|
||||
|
||||
const getHistoryOperation = function (...args) {
|
||||
return ipcRenderer.sendSync('ELECTRON_SYNC_NAVIGATION_CONTROLLER', ...args)
|
||||
}
|
||||
|
||||
window.history.back = function () {
|
||||
sendHistoryOperation('goBack')
|
||||
}
|
||||
|
||||
window.history.forward = function () {
|
||||
sendHistoryOperation('goForward')
|
||||
}
|
||||
|
||||
window.history.go = function (offset) {
|
||||
sendHistoryOperation('goToOffset', offset)
|
||||
}
|
||||
|
||||
defineProperty(window.history, 'length', {
|
||||
get: function () {
|
||||
return getHistoryOperation('length')
|
||||
}
|
||||
})
|
||||
|
||||
// The initial visibilityState.
|
||||
let cachedVisibilityState = hiddenPage ? 'hidden' : 'visible'
|
||||
|
||||
// Subscribe to visibilityState changes.
|
||||
ipcRenderer.on('ELECTRON_RENDERER_WINDOW_VISIBILITY_CHANGE', function (event, visibilityState) {
|
||||
if (cachedVisibilityState !== visibilityState) {
|
||||
cachedVisibilityState = visibilityState
|
||||
document.dispatchEvent(new Event('visibilitychange'))
|
||||
}
|
||||
})
|
||||
|
||||
// Make document.hidden and document.visibilityState return the correct value.
|
||||
defineProperty(document, 'hidden', {
|
||||
get: function () {
|
||||
return cachedVisibilityState !== 'visible'
|
||||
}
|
||||
})
|
||||
|
||||
defineProperty(document, 'visibilityState', {
|
||||
get: function () {
|
||||
return cachedVisibilityState
|
||||
}
|
||||
})
|
||||
}
|
|
@ -1836,6 +1836,27 @@ describe('BrowserWindow module', function () {
|
|||
})
|
||||
|
||||
describe('contextIsolation option', () => {
|
||||
const expectedContextData = {
|
||||
preloadContext: {
|
||||
preloadProperty: 'number',
|
||||
pageProperty: 'undefined',
|
||||
typeofRequire: 'function',
|
||||
typeofProcess: 'object',
|
||||
typeofArrayPush: 'function',
|
||||
typeofFunctionApply: 'function'
|
||||
},
|
||||
pageContext: {
|
||||
preloadProperty: 'undefined',
|
||||
pageProperty: 'string',
|
||||
typeofRequire: 'undefined',
|
||||
typeofProcess: 'undefined',
|
||||
typeofArrayPush: 'number',
|
||||
typeofFunctionApply: 'boolean',
|
||||
typeofPreloadExecuteJavaScriptProperty: 'number',
|
||||
typeofOpenedWindow: 'object'
|
||||
}
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
if (w != null) w.destroy()
|
||||
w = new BrowserWindow({
|
||||
|
@ -1849,56 +1870,18 @@ describe('BrowserWindow module', function () {
|
|||
|
||||
it('separates the page context from the Electron/preload context', (done) => {
|
||||
ipcMain.once('isolated-world', (event, data) => {
|
||||
assert.deepEqual(data, {
|
||||
preloadContext: {
|
||||
preloadProperty: 'number',
|
||||
pageProperty: 'undefined',
|
||||
typeofRequire: 'function',
|
||||
typeofProcess: 'object',
|
||||
typeofArrayPush: 'function',
|
||||
typeofFunctionApply: 'function'
|
||||
},
|
||||
pageContext: {
|
||||
preloadProperty: 'undefined',
|
||||
pageProperty: 'string',
|
||||
typeofRequire: 'undefined',
|
||||
typeofProcess: 'undefined',
|
||||
typeofArrayPush: 'number',
|
||||
typeofFunctionApply: 'boolean',
|
||||
typeofPreloadExecuteJavaScriptProperty: 'number'
|
||||
}
|
||||
})
|
||||
assert.deepEqual(data, expectedContextData)
|
||||
done()
|
||||
})
|
||||
|
||||
w.loadURL('file://' + fixtures + '/api/isolated.html')
|
||||
})
|
||||
|
||||
it('recreates the contexts on reload', (done) => {
|
||||
w.webContents.once('did-finish-load', () => {
|
||||
ipcMain.once('isolated-world', (event, data) => {
|
||||
assert.deepEqual(data, {
|
||||
preloadContext: {
|
||||
preloadProperty: 'number',
|
||||
pageProperty: 'undefined',
|
||||
typeofRequire: 'function',
|
||||
typeofProcess: 'object',
|
||||
typeofArrayPush: 'function',
|
||||
typeofFunctionApply: 'function'
|
||||
},
|
||||
pageContext: {
|
||||
preloadProperty: 'undefined',
|
||||
pageProperty: 'string',
|
||||
typeofRequire: 'undefined',
|
||||
typeofProcess: 'undefined',
|
||||
typeofArrayPush: 'number',
|
||||
typeofFunctionApply: 'boolean',
|
||||
typeofPreloadExecuteJavaScriptProperty: 'number'
|
||||
}
|
||||
})
|
||||
assert.deepEqual(data, expectedContextData)
|
||||
done()
|
||||
})
|
||||
|
||||
w.webContents.reload()
|
||||
})
|
||||
w.loadURL('file://' + fixtures + '/api/isolated.html')
|
||||
|
@ -1909,7 +1892,6 @@ describe('BrowserWindow module', function () {
|
|||
assert.equal(window.webContents.getWebPreferences().contextIsolation, true)
|
||||
done()
|
||||
})
|
||||
|
||||
w.loadURL('file://' + fixtures + '/pages/window-open.html')
|
||||
})
|
||||
})
|
||||
|
|
7
spec/fixtures/api/isolated.html
vendored
7
spec/fixtures/api/isolated.html
vendored
|
@ -7,6 +7,10 @@
|
|||
window.hello = 'world'
|
||||
Array.prototype.push = 3
|
||||
Function.prototype.apply = true
|
||||
|
||||
const opened = window.open()
|
||||
opened.close()
|
||||
|
||||
window.postMessage({
|
||||
preloadProperty: typeof window.foo,
|
||||
pageProperty: typeof window.hello,
|
||||
|
@ -14,7 +18,8 @@
|
|||
typeofProcess: typeof process,
|
||||
typeofArrayPush: typeof Array.prototype.push,
|
||||
typeofFunctionApply: typeof Function.prototype.apply,
|
||||
typeofPreloadExecuteJavaScriptProperty: typeof window.preloadExecuteJavaScriptProperty
|
||||
typeofPreloadExecuteJavaScriptProperty: typeof window.preloadExecuteJavaScriptProperty,
|
||||
typeofOpenedWindow: typeof opened
|
||||
}, '*')
|
||||
</script>
|
||||
</head>
|
||||
|
|
|
@ -448,13 +448,15 @@ describe('<webview> tag', function () {
|
|||
typeofProcess: 'undefined',
|
||||
typeofArrayPush: 'number',
|
||||
typeofFunctionApply: 'boolean',
|
||||
typeofPreloadExecuteJavaScriptProperty: 'number'
|
||||
typeofPreloadExecuteJavaScriptProperty: 'number',
|
||||
typeofOpenedWindow: 'object'
|
||||
}
|
||||
})
|
||||
done()
|
||||
})
|
||||
|
||||
webview.setAttribute('preload', path.join(fixtures, 'api', 'isolated-preload.js'))
|
||||
webview.setAttribute('allowpopups', 'yes')
|
||||
webview.setAttribute('webpreferences', 'contextIsolation=yes')
|
||||
webview.src = 'file://' + fixtures + '/api/isolated.html'
|
||||
document.body.appendChild(webview)
|
||||
|
|
Loading…
Reference in a new issue