diff --git a/atom/browser/web_contents_preferences.cc b/atom/browser/web_contents_preferences.cc index d5bde07250e..23f834e8e39 100644 --- a/atom/browser/web_contents_preferences.cc +++ b/atom/browser/web_contents_preferences.cc @@ -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. diff --git a/atom/common/options_switches.cc b/atom/common/options_switches.cc index f1eecdbd47c..f247025f36d 100644 --- a/atom/common/options_switches.cc +++ b/atom/common/options_switches.cc @@ -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. diff --git a/atom/common/options_switches.h b/atom/common/options_switches.h index 2742d0c8259..e368b0a5fc1 100644 --- a/atom/common/options_switches.h +++ b/atom/common/options_switches.h @@ -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[]; diff --git a/atom/renderer/atom_renderer_client.cc b/atom/renderer/atom_renderer_client.cc index e785e26c155..7ef04732c4b 100644 --- a/atom/renderer/atom_renderer_client.cc +++ b/atom/renderer/atom_renderer_client.cc @@ -4,6 +4,8 @@ #include "atom/renderer/atom_renderer_client.h" +#include "atom_natives.h" // NOLINT: This file is generated with js2c + #include #include @@ -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 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::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 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 context, diff --git a/electron.gyp b/electron.gyp index 495d0fd0878..3ca6717b987 100644 --- a/electron.gyp +++ b/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', diff --git a/filenames.gypi b/filenames.gypi index b7a53a48405..6fe7612a55d 100644 --- a/filenames.gypi +++ b/filenames.gypi @@ -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', diff --git a/lib/browser/guest-window-manager.js b/lib/browser/guest-window-manager.js index 62abc8663c7..e5bfa741238 100644 --- a/lib/browser/guest-window-manager.js +++ b/lib/browser/guest-window-manager.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 diff --git a/lib/isolated_renderer/init.js b/lib/isolated_renderer/init.js new file mode 100644 index 00000000000..01e3cac3013 --- /dev/null +++ b/lib/isolated_renderer/init.js @@ -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) diff --git a/lib/renderer/override.js b/lib/renderer/override.js index a05eb898bbc..f31e9c0e8cd 100644 --- a/lib/renderer/override.js +++ b/lib/renderer/override.js @@ -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) diff --git a/lib/renderer/window-setup.js b/lib/renderer/window-setup.js new file mode 100644 index 00000000000..3aa451d1646 --- /dev/null +++ b/lib/renderer/window-setup.js @@ -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 + } + }) +} diff --git a/spec/api-browser-window-spec.js b/spec/api-browser-window-spec.js index b777c684d1a..8fd05a327f1 100644 --- a/spec/api-browser-window-spec.js +++ b/spec/api-browser-window-spec.js @@ -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') }) }) diff --git a/spec/fixtures/api/isolated.html b/spec/fixtures/api/isolated.html index a0cc84770e9..562bf01b7c1 100644 --- a/spec/fixtures/api/isolated.html +++ b/spec/fixtures/api/isolated.html @@ -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 }, '*') diff --git a/spec/webview-spec.js b/spec/webview-spec.js index 2d3ce3c06e3..616fc459346 100644 --- a/spec/webview-spec.js +++ b/spec/webview-spec.js @@ -448,13 +448,15 @@ describe(' 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)