Implement window overrides in main context

This commit is contained in:
Kevin Sawicki 2017-01-11 16:36:59 -08:00
parent 95054f443f
commit 3f7b3c4bd7
13 changed files with 357 additions and 289 deletions

View file

@ -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.

View file

@ -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.

View file

@ -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[];

View file

@ -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,

View file

@ -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',

View file

@ -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',

View file

@ -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

View 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)

View file

@ -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)

View 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
}
})
}

View file

@ -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')
})
})

View file

@ -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>

View file

@ -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)