From 7f6b308bf1afad31e9e614def47fa6f0bb4bf4f7 Mon Sep 17 00:00:00 2001 From: Jeremy Apthorp Date: Mon, 9 Dec 2019 10:27:30 -0800 Subject: [PATCH] test: move remote specs to main process (#21387) --- .../crash_reporter/crash_reporter_linux.cc | 7 +- spec-main/api-remote-spec.ts | 690 ++++++++++++++++-- {spec => spec-main}/fixtures/module/call.js | 0 .../fixtures/module/circular.js | 0 {spec => spec-main}/fixtures/module/class.js | 0 .../fixtures/module/error-properties.js | 0 .../fixtures/module/exception.js | 0 .../module/export-function-with-properties.js | 0 .../fixtures/module/function-with-args.js | 0 .../function-with-missing-properties.js | 0 .../module/function-with-properties.js | 0 .../fixtures/module/function.js | 0 {spec => spec-main}/fixtures/module/id.js | 0 .../fixtures/module/no-prototype.js | 0 .../fixtures/module/promise.js | 0 .../fixtures/module/property.js | 0 .../fixtures/module/rejected-promise.js | 0 .../fixtures/module/remote-object-set.js | 0 .../fixtures/module/remote-static.js | 0 .../fixtures/module/to-string-non-function.js | 0 .../fixtures/module/unhandled-rejection.js | 0 spec-main/package.json | 4 + spec-main/webview-spec.ts | 46 ++ spec-main/yarn.lock | 17 + spec/api-remote-spec.js | 534 -------------- spec/static/main.js | 2 +- spec/webview-spec.js | 35 - 27 files changed, 702 insertions(+), 633 deletions(-) rename {spec => spec-main}/fixtures/module/call.js (100%) rename {spec => spec-main}/fixtures/module/circular.js (100%) rename {spec => spec-main}/fixtures/module/class.js (100%) rename {spec => spec-main}/fixtures/module/error-properties.js (100%) rename {spec => spec-main}/fixtures/module/exception.js (100%) rename {spec => spec-main}/fixtures/module/export-function-with-properties.js (100%) rename {spec => spec-main}/fixtures/module/function-with-args.js (100%) rename {spec => spec-main}/fixtures/module/function-with-missing-properties.js (100%) rename {spec => spec-main}/fixtures/module/function-with-properties.js (100%) rename {spec => spec-main}/fixtures/module/function.js (100%) rename {spec => spec-main}/fixtures/module/id.js (100%) rename {spec => spec-main}/fixtures/module/no-prototype.js (100%) rename {spec => spec-main}/fixtures/module/promise.js (100%) rename {spec => spec-main}/fixtures/module/property.js (100%) rename {spec => spec-main}/fixtures/module/rejected-promise.js (100%) rename {spec => spec-main}/fixtures/module/remote-object-set.js (100%) rename {spec => spec-main}/fixtures/module/remote-static.js (100%) rename {spec => spec-main}/fixtures/module/to-string-non-function.js (100%) rename {spec => spec-main}/fixtures/module/unhandled-rejection.js (100%) delete mode 100644 spec/api-remote-spec.js diff --git a/shell/common/crash_reporter/crash_reporter_linux.cc b/shell/common/crash_reporter/crash_reporter_linux.cc index 8599f33a30..0d336725f0 100644 --- a/shell/common/crash_reporter/crash_reporter_linux.cc +++ b/shell/common/crash_reporter/crash_reporter_linux.cc @@ -47,8 +47,11 @@ CrashReporterLinux::CrashReporterLinux() : pid_(getpid()) { process_start_time_ = ret; } - // Make base::g_linux_distro work. - base::SetLinuxDistro(base::GetLinuxDistro()); + { + base::ThreadRestrictions::ScopedAllowIO allow_io; + // Make base::g_linux_distro work. + base::SetLinuxDistro(base::GetLinuxDistro()); + } } CrashReporterLinux::~CrashReporterLinux() = default; diff --git a/spec-main/api-remote-spec.ts b/spec-main/api-remote-spec.ts index 4dcf7ce608..f6b8bad17a 100644 --- a/spec-main/api-remote-spec.ts +++ b/spec-main/api-remote-spec.ts @@ -1,6 +1,6 @@ import * as path from 'path' import { expect } from 'chai' -import { closeWindow, closeAllWindows } from './window-helpers' +import { closeAllWindows } from './window-helpers' import { ifdescribe } from './spec-helpers' import { ipcMain, BrowserWindow } from 'electron' @@ -8,134 +8,176 @@ import { emittedOnce } from './events-helpers' const features = process.electronBinding('features') +const expectPathsEqual = (path1: string, path2: string) => { + if (process.platform === 'win32') { + path1 = path1.toLowerCase() + path2 = path2.toLowerCase() + } + expect(path1).to.equal(path2) +} + +function makeRemotely (windowGetter: () => BrowserWindow) { + async function remotely (script: Function, ...args: any[]) { + // executeJavaScript obfuscates the error if the script throws, so catch any + // errors manually. + const assembledScript = `(async function() { + try { + return { result: await Promise.resolve((${script})(...${JSON.stringify(args)})) } + } catch (e) { + return { error: e.message, stack: e.stack } + } + })()` + const { result, error, stack } = await windowGetter().webContents.executeJavaScript(assembledScript) + if (error) { + const e = new Error(error) + e.stack = stack + throw e + } + return result + } + remotely.it = (...vars: any[]) => (name: string, fn: Function) => { + it(name, async () => { + await remotely(fn, ...vars) + }) + } + return remotely +} + +function makeWindow () { + let w: BrowserWindow + before(async () => { + w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, enableRemoteModule: true } }) + await w.loadURL('about:blank') + await w.webContents.executeJavaScript(` + const chai_1 = window.chai_1 = require('chai') + chai_1.use(require('chai-as-promised')) + chai_1.use(require('dirty-chai')) + `) + }) + after(closeAllWindows) + return () => w +} + +function makeEachWindow () { + let w: BrowserWindow + beforeEach(async () => { + w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, enableRemoteModule: true } }) + await w.loadURL('about:blank') + await w.webContents.executeJavaScript(` + const chai_1 = window.chai_1 = require('chai') + chai_1.use(require('chai-as-promised')) + chai_1.use(require('dirty-chai')) + `) + }) + afterEach(closeAllWindows) + return () => w +} + ifdescribe(features.isRemoteModuleEnabled())('remote module', () => { const fixtures = path.join(__dirname, 'fixtures') describe('', () => { - let w = null as unknown as BrowserWindow - beforeEach(async () => { - w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true } }) - w.loadURL('about:blank') - }) - afterEach(async () => { - await closeWindow(w) - }) - - async function remotely (script: string) { - // executeJavaScript never returns if the script throws an error, so catch - // any errors manually. - const assembledScript = `(function() { - try { - return { result: ${script} } - } catch (e) { - return { error: e.message } - } - })()` - const { result, error } = await w.webContents.executeJavaScript(assembledScript) - if (error) { - throw new Error(error) - } - return result - } + const w = makeWindow() + const remotely = makeRemotely(w) describe('remote.getGlobal filtering', () => { it('can return custom values', async () => { - w.webContents.once('remote-get-global', (event, name) => { + w().webContents.once('remote-get-global', (event, name) => { event.returnValue = name }) - expect(await remotely(`require('electron').remote.getGlobal('test')`)).to.equal('test') + expect(await remotely(() => require('electron').remote.getGlobal('test'))).to.equal('test') }) it('throws when no returnValue set', async () => { - w.webContents.once('remote-get-global', (event) => { + w().webContents.once('remote-get-global', (event) => { event.preventDefault() }) - await expect(remotely(`require('electron').remote.getGlobal('test')`)).to.eventually.be.rejected(`Blocked remote.getGlobal('test')`) + await expect(remotely(() => require('electron').remote.getGlobal('test'))).to.eventually.be.rejected(`Blocked remote.getGlobal('test')`) }) }) describe('remote.getBuiltin filtering', () => { it('can return custom values', async () => { - w.webContents.once('remote-get-builtin', (event, name) => { + w().webContents.once('remote-get-builtin', (event, name) => { event.returnValue = name }) - expect(await remotely(`require('electron').remote.getBuiltin('test')`)).to.equal('test') + expect(await remotely(() => (require('electron').remote as any).getBuiltin('test'))).to.equal('test') }) it('throws when no returnValue set', async () => { - w.webContents.once('remote-get-builtin', (event) => { + w().webContents.once('remote-get-builtin', (event) => { event.preventDefault() }) - await expect(remotely(`require('electron').remote.getBuiltin('test')`)).to.eventually.be.rejected(`Blocked remote.getGlobal('test')`) + await expect(remotely(() => (require('electron').remote as any).getBuiltin('test'))).to.eventually.be.rejected(`Blocked remote.getGlobal('test')`) }) }) describe('remote.require filtering', () => { it('can return custom values', async () => { - w.webContents.once('remote-require', (event, name) => { + w().webContents.once('remote-require', (event, name) => { event.returnValue = name }) - expect(await remotely(`require('electron').remote.require('test')`)).to.equal('test') + expect(await remotely(() => require('electron').remote.require('test'))).to.equal('test') }) it('throws when no returnValue set', async () => { - w.webContents.once('remote-require', (event) => { + w().webContents.once('remote-require', (event) => { event.preventDefault() }) - await expect(remotely(`require('electron').remote.require('test')`)).to.eventually.be.rejected(`Blocked remote.require('test')`) + await expect(remotely(() => require('electron').remote.require('test'))).to.eventually.be.rejected(`Blocked remote.require('test')`) }) }) describe('remote.getCurrentWindow filtering', () => { it('can return custom value', async () => { - w.webContents.once('remote-get-current-window', (e) => { + w().webContents.once('remote-get-current-window', (e) => { e.returnValue = 'some window' }) - expect(await remotely(`require('electron').remote.getCurrentWindow()`)).to.equal('some window') + expect(await remotely(() => require('electron').remote.getCurrentWindow())).to.equal('some window') }) it('throws when no returnValue set', async () => { - w.webContents.once('remote-get-current-window', (event) => { + w().webContents.once('remote-get-current-window', (event) => { event.preventDefault() }) - await expect(remotely(`require('electron').remote.getCurrentWindow()`)).to.eventually.be.rejected(`Blocked remote.getCurrentWindow()`) + await expect(remotely(() => require('electron').remote.getCurrentWindow())).to.eventually.be.rejected(`Blocked remote.getCurrentWindow()`) }) }) describe('remote.getCurrentWebContents filtering', () => { it('can return custom value', async () => { - w.webContents.once('remote-get-current-web-contents', (event) => { + w().webContents.once('remote-get-current-web-contents', (event) => { event.returnValue = 'some web contents' }) - expect(await remotely(`require('electron').remote.getCurrentWebContents()`)).to.equal('some web contents') + expect(await remotely(() => require('electron').remote.getCurrentWebContents())).to.equal('some web contents') }) it('throws when no returnValue set', async () => { - w.webContents.once('remote-get-current-web-contents', (event) => { + w().webContents.once('remote-get-current-web-contents', (event) => { event.preventDefault() }) - await expect(remotely(`require('electron').remote.getCurrentWebContents()`)).to.eventually.be.rejected(`Blocked remote.getCurrentWebContents()`) + await expect(remotely(() => require('electron').remote.getCurrentWebContents())).to.eventually.be.rejected(`Blocked remote.getCurrentWebContents()`) }) }) + }) - describe('remote references', () => { - it('render-view-deleted is sent when page is destroyed', (done) => { - w.webContents.once('render-view-deleted' as any, () => { - done() - }) - w.destroy() + describe('remote references', () => { + const w = makeEachWindow() + it('render-view-deleted is sent when page is destroyed', (done) => { + w().webContents.once('render-view-deleted' as any, () => { + done() }) + w().destroy() + }) - // The ELECTRON_BROWSER_CONTEXT_RELEASE message relies on this to work. - it('message can be sent on exit when page is being navigated', async () => { - after(() => { ipcMain.removeAllListeners('SENT_ON_EXIT') }) - await emittedOnce(w.webContents, 'did-finish-load') - w.webContents.once('did-finish-load', () => { - w.webContents.loadURL('about:blank') - }) - w.loadFile(path.join(fixtures, 'api', 'send-on-exit.html')) - await emittedOnce(ipcMain, 'SENT_ON_EXIT') + // The ELECTRON_BROWSER_CONTEXT_RELEASE message relies on this to work. + it('message can be sent on exit when page is being navigated', async () => { + after(() => { ipcMain.removeAllListeners('SENT_ON_EXIT') }) + w().webContents.once('did-finish-load', () => { + w().webContents.loadURL('about:blank') }) + w().loadFile(path.join(fixtures, 'api', 'send-on-exit.html')) + await emittedOnce(ipcMain, 'SENT_ON_EXIT') }) }) @@ -209,4 +251,530 @@ ifdescribe(features.isRemoteModuleEnabled())('remote module', () => { expect(warnMessage).to.equal(expectedMessage) }) }) + + describe('remote.require', () => { + const w = makeWindow() + const remotely = makeRemotely(w) + + remotely.it()('should returns same object for the same module', () => { + const { remote } = require('electron') + const a = remote.require('electron') + const b = remote.require('electron') + expect(a).to.equal(b) + }) + + remotely.it(path.join(fixtures, 'module', 'id.js'))('should work when object contains id property', (module: string) => { + const { id } = require('electron').remote.require(module) + expect(id).to.equal(1127) + }) + + remotely.it(path.join(fixtures, 'module', 'no-prototype.js'))('should work when object has no prototype', (module: string) => { + const a = require('electron').remote.require(module) + expect(a.foo.constructor.name).to.equal('') + expect(a.foo.bar).to.equal('baz') + expect(a.foo.baz).to.equal(false) + expect(a.bar).to.equal(1234) + expect(a.anonymous.constructor.name).to.equal('') + expect(a.getConstructorName(Object.create(null))).to.equal('') + expect(a.getConstructorName(new (class {})())).to.equal('') + }) + + it('should search module from the user app', async () => { + expectPathsEqual( + path.normalize(await remotely(() => require('electron').remote.process.mainModule!.filename)), + path.resolve(__dirname, 'index.js') + ) + expectPathsEqual( + path.normalize(await remotely(() => require('electron').remote.process.mainModule!.paths[0])), + path.resolve(__dirname, 'node_modules') + ) + }) + + remotely.it(fixtures)('should work with function properties', (fixtures: string) => { + const path = require('path') + + { + const a = require('electron').remote.require(path.join(fixtures, 'module', 'export-function-with-properties.js')) + expect(typeof a).to.equal('function') + expect(a.bar).to.equal('baz') + } + + { + const a = require('electron').remote.require(path.join(fixtures, 'module', 'function-with-properties.js')) + expect(typeof a).to.equal('object') + expect(a.foo()).to.equal('hello') + expect(a.foo.bar).to.equal('baz') + expect(a.foo.nested.prop).to.equal('yes') + expect(a.foo.method1()).to.equal('world') + expect(a.foo.method1.prop1()).to.equal(123) + } + + { + const a = require('electron').remote.require(path.join(fixtures, 'module', 'function-with-missing-properties.js')).setup() + expect(a.bar()).to.equal(true) + expect(a.bar.baz).to.be.undefined() + } + }) + + remotely.it(fixtures)('should work with static class members', (fixtures: string) => { + const path = require('path') + const a = require('electron').remote.require(path.join(fixtures, 'module', 'remote-static.js')) + expect(typeof a.Foo).to.equal('function') + expect(a.Foo.foo()).to.equal(3) + expect(a.Foo.bar).to.equal('baz') + expect(new a.Foo().baz()).to.equal(123) + }) + + remotely.it(fixtures)('includes the length of functions specified as arguments', (fixtures: string) => { + const path = require('path') + const a = require('electron').remote.require(path.join(fixtures, 'module', 'function-with-args.js')) + /* eslint-disable @typescript-eslint/no-unused-vars */ + expect(a((a: any, b: any, c: any) => {})).to.equal(3) + expect(a((a: any) => {})).to.equal(1) + expect(a((...args: any[]) => {})).to.equal(0) + /* eslint-enable @typescript-eslint/no-unused-vars */ + }) + + remotely.it(fixtures)('handles circular references in arrays and objects', (fixtures: string) => { + const path = require('path') + const a = require('electron').remote.require(path.join(fixtures, 'module', 'circular.js')) + + let arrayA: any[] = ['foo'] + const arrayB = [arrayA, 'bar'] + arrayA.push(arrayB) + expect(a.returnArgs(arrayA, arrayB)).to.deep.equal([ + ['foo', [null, 'bar']], + [['foo', null], 'bar'] + ]) + + let objectA: any = { foo: 'bar' } + const objectB = { baz: objectA } + objectA.objectB = objectB + expect(a.returnArgs(objectA, objectB)).to.deep.equal([ + { foo: 'bar', objectB: { baz: null } }, + { baz: { foo: 'bar', objectB: null } } + ]) + + arrayA = [1, 2, 3] + expect(a.returnArgs({ foo: arrayA }, { bar: arrayA })).to.deep.equal([ + { foo: [1, 2, 3] }, + { bar: [1, 2, 3] } + ]) + + objectA = { foo: 'bar' } + expect(a.returnArgs({ foo: objectA }, { bar: objectA })).to.deep.equal([ + { foo: { foo: 'bar' } }, + { bar: { foo: 'bar' } } + ]) + + arrayA = [] + arrayA.push(arrayA) + expect(a.returnArgs(arrayA)).to.deep.equal([ + [null] + ]) + + objectA = {} + objectA.foo = objectA + objectA.bar = 'baz' + expect(a.returnArgs(objectA)).to.deep.equal([ + { foo: null, bar: 'baz' } + ]) + + objectA = {} + objectA.foo = { bar: objectA } + objectA.bar = 'baz' + expect(a.returnArgs(objectA)).to.deep.equal([ + { foo: { bar: null }, bar: 'baz' } + ]) + }) + }) + + describe('remote.createFunctionWithReturnValue', () => { + const remotely = makeRemotely(makeWindow()) + + remotely.it(fixtures)('should be called in browser synchronously', async (fixtures: string) => { + const { remote } = require('electron') + const path = require('path') + const buf = Buffer.from('test') + const call = remote.require(path.join(fixtures, 'module', 'call.js')) + const result = call.call((remote as any).createFunctionWithReturnValue(buf)) + expect(result).to.be.an.instanceOf(Uint8Array) + }) + }) + + describe('remote modules', () => { + const remotely = makeRemotely(makeWindow()) + + const mainModules = Object.keys(require('electron')) + remotely.it(mainModules)('includes browser process modules as properties', (mainModules: string[]) => { + const { remote } = require('electron') + const remoteModules = mainModules.filter(name => (remote as any)[name]) + expect(remoteModules).to.be.deep.equal(mainModules) + }) + + remotely.it(fixtures)('returns toString() of original function via toString()', (fixtures: string) => { + const path = require('path') + const { readText } = require('electron').remote.clipboard + expect(readText.toString().startsWith('function')).to.be.true() + + const { functionWithToStringProperty } = require('electron').remote.require(path.join(fixtures, 'module', 'to-string-non-function.js')) + expect(functionWithToStringProperty.toString).to.equal('hello') + }) + }) + + describe('remote object in renderer', () => { + const remotely = makeRemotely(makeWindow()) + + remotely.it(fixtures)('can change its properties', (fixtures: string) => { + const module = require('path').join(fixtures, 'module', 'property.js') + const property = require('electron').remote.require(module) + expect(property.property).to.equal(1127) + property.property = null + expect(property.property).to.equal(null) + property.property = undefined + expect(property.property).to.equal(undefined) + property.property = 1007 + expect(property.property).to.equal(1007) + + expect(property.getFunctionProperty()).to.equal('foo-browser') + property.func.property = 'bar' + expect(property.getFunctionProperty()).to.equal('bar-browser') + property.func.property = 'foo' // revert back + + const property2 = require('electron').remote.require(module) + expect(property2.property).to.equal(1007) + + property.property = 1127 // revert back + }) + + remotely.it(fixtures)('rethrows errors getting/setting properties', (fixtures: string) => { + const foo = require('electron').remote.require(require('path').join(fixtures, 'module', 'error-properties.js')) + + expect(() => { + // eslint-disable-next-line + foo.bar + }).to.throw('getting error') + + expect(() => { + foo.bar = 'test' + }).to.throw('setting error') + }) + + remotely.it(fixtures)('can set a remote property with a remote object', (fixtures: string) => { + const { remote } = require('electron') + const foo = remote.require(require('path').join(fixtures, 'module', 'remote-object-set.js')) + foo.bar = remote.getCurrentWindow() + }) + + remotely.it(fixtures)('can construct an object from its member', (fixtures: string) => { + const call = require('electron').remote.require(require('path').join(fixtures, 'module', 'call.js')) + const obj = new call.constructor() + expect(obj.test).to.equal('test') + }) + + remotely.it(fixtures)('can reassign and delete its member functions', (fixtures: string) => { + const remoteFunctions = require('electron').remote.require(require('path').join(fixtures, 'module', 'function.js')) + expect(remoteFunctions.aFunction()).to.equal(1127) + + remoteFunctions.aFunction = () => { return 1234 } + expect(remoteFunctions.aFunction()).to.equal(1234) + + expect(delete remoteFunctions.aFunction).to.equal(true) + }) + + remotely.it('is referenced by its members', () => { + const stringify = require('electron').remote.getGlobal('JSON').stringify + global.gc() + stringify({}) + }) + }) + + describe('remote value in browser', () => { + const remotely = makeRemotely(makeWindow()) + const print = path.join(fixtures, 'module', 'print_name.js') + + remotely.it(print)('preserves NaN', (print: string) => { + const printName = require('electron').remote.require(print) + expect(printName.getNaN()).to.be.NaN() + expect(printName.echo(NaN)).to.be.NaN() + }) + + remotely.it(print)('preserves Infinity', (print: string) => { + const printName = require('electron').remote.require(print) + expect(printName.getInfinity()).to.equal(Infinity) + expect(printName.echo(Infinity)).to.equal(Infinity) + }) + + remotely.it(print)('keeps its constructor name for objects', (print: string) => { + const printName = require('electron').remote.require(print) + const buf = Buffer.from('test') + expect(printName.print(buf)).to.equal('Buffer') + }) + + remotely.it(print)('supports instanceof Boolean', (print: string) => { + const printName = require('electron').remote.require(print) + const obj = Boolean(true) + expect(printName.print(obj)).to.equal('Boolean') + expect(printName.echo(obj)).to.deep.equal(obj) + }) + + remotely.it(print)('supports instanceof Number', (print: string) => { + const printName = require('electron').remote.require(print) + const obj = Number(42) + expect(printName.print(obj)).to.equal('Number') + expect(printName.echo(obj)).to.deep.equal(obj) + }) + + remotely.it(print)('supports instanceof String', (print: string) => { + const printName = require('electron').remote.require(print) + const obj = String('Hello World!') + expect(printName.print(obj)).to.equal('String') + expect(printName.echo(obj)).to.deep.equal(obj) + }) + + remotely.it(print)('supports instanceof Date', (print: string) => { + const printName = require('electron').remote.require(print) + const now = new Date() + expect(printName.print(now)).to.equal('Date') + expect(printName.echo(now)).to.deep.equal(now) + }) + + remotely.it(print)('supports instanceof RegExp', (print: string) => { + const printName = require('electron').remote.require(print) + const regexp = RegExp('.*') + expect(printName.print(regexp)).to.equal('RegExp') + expect(printName.echo(regexp)).to.deep.equal(regexp) + }) + + remotely.it(print)('supports instanceof Buffer', (print: string) => { + const printName = require('electron').remote.require(print) + const buffer = Buffer.from('test') + expect(buffer.equals(printName.echo(buffer))).to.be.true() + + const objectWithBuffer = { a: 'foo', b: Buffer.from('bar') } + expect(objectWithBuffer.b.equals(printName.echo(objectWithBuffer).b)).to.be.true() + + const arrayWithBuffer = [1, 2, Buffer.from('baz')] + expect((arrayWithBuffer[2] as Buffer).equals(printName.echo(arrayWithBuffer)[2])).to.be.true() + }) + + remotely.it(print)('supports instanceof ArrayBuffer', (print: string) => { + const printName = require('electron').remote.require(print) + const buffer = new ArrayBuffer(8) + const view = new DataView(buffer) + + view.setFloat64(0, Math.PI) + expect(printName.echo(buffer)).to.deep.equal(buffer) + expect(printName.print(buffer)).to.equal('ArrayBuffer') + }) + + const arrayTests: [string, number[]][] = [ + ['Int8Array', [1, 2, 3, 4]], + ['Uint8Array', [1, 2, 3, 4]], + ['Uint8ClampedArray', [1, 2, 3, 4]], + ['Int16Array', [0x1234, 0x2345, 0x3456, 0x4567]], + ['Uint16Array', [0x1234, 0x2345, 0x3456, 0x4567]], + ['Int32Array', [0x12345678, 0x23456789]], + ['Uint32Array', [0x12345678, 0x23456789]], + ['Float32Array', [0.5, 1.0, 1.5]], + ['Float64Array', [0.5, 1.0, 1.5]] + ] + + arrayTests.forEach(([arrayType, values]) => { + remotely.it(print, arrayType, values)(`supports instanceof ${arrayType}`, (print: string, arrayType: string, values: number[]) => { + const printName = require('electron').remote.require(print) + expect([...printName.typedArray(arrayType, values)]).to.deep.equal(values) + + const int8values = new ((window as any)[arrayType])(values) + expect(printName.typedArray(arrayType, int8values)).to.deep.equal(int8values) + expect(printName.print(int8values)).to.equal(arrayType) + }) + }) + + describe('constructing a Uint8Array', () => { + remotely.it()('does not crash', () => { + const RUint8Array = require('electron').remote.getGlobal('Uint8Array') + new RUint8Array() // eslint-disable-line + }) + }) + }) + + describe('remote promise', () => { + const remotely = makeRemotely(makeWindow()) + + remotely.it(fixtures)('can be used as promise in each side', async (fixtures: string) => { + const promise = require('electron').remote.require(require('path').join(fixtures, 'module', 'promise.js')) + const value = await promise.twicePromise(Promise.resolve(1234)) + expect(value).to.equal(2468) + }) + + remotely.it(fixtures)('handles rejections via catch(onRejected)', async (fixtures: string) => { + const promise = require('electron').remote.require(require('path').join(fixtures, 'module', 'rejected-promise.js')) + const error = await new Promise(resolve => { + promise.reject(Promise.resolve(1234)).catch(resolve) + }) + expect(error.message).to.equal('rejected') + }) + + remotely.it(fixtures)('handles rejections via then(onFulfilled, onRejected)', async (fixtures: string) => { + const promise = require('electron').remote.require(require('path').join(fixtures, 'module', 'rejected-promise.js')) + const error = await new Promise(resolve => { + promise.reject(Promise.resolve(1234)).then(() => {}, resolve) + }) + expect(error.message).to.equal('rejected') + }) + + it('does not emit unhandled rejection events in the main process', (done) => { + function onUnhandledRejection () { + done(new Error('Unexpected unhandledRejection event')) + } + process.once('unhandledRejection', onUnhandledRejection) + + remotely(async (fixtures: string) => { + const promise = require('electron').remote.require(require('path').join(fixtures, 'module', 'unhandled-rejection.js')) + return new Promise((resolve, reject) => { + promise.reject().then(() => { + reject(new Error('Promise was not rejected')) + }).catch((error: Error) => { + resolve(error) + }) + }) + }, fixtures).then(error => { + try { + expect(error.message).to.equal('rejected') + done() + } catch (e) { + done(e) + } finally { + process.off('unhandledRejection', onUnhandledRejection) + } + }) + }) + + it('emits unhandled rejection events in the renderer process', (done) => { + remotely((module: string) => new Promise((resolve, reject) => { + const promise = require('electron').remote.require(module) + + window.addEventListener('unhandledrejection', function handler (event) { + event.preventDefault() + window.removeEventListener('unhandledrejection', handler) + resolve(event.reason.message) + }) + + promise.reject().then(() => { + reject(new Error('Promise was not rejected')) + }) + }), path.join(fixtures, 'module', 'unhandled-rejection.js')).then( + (message) => { + try { + expect(message).to.equal('rejected') + done() + } catch (e) { + done(e) + } + }, + done + ) + }) + + before(() => { + (global as any).returnAPromise = (value: any) => new Promise((resolve) => setTimeout(() => resolve(value), 100)) + }) + after(() => { + delete (global as any).returnAPromise + }) + remotely.it()('using a promise based method resolves correctly when global Promise is overridden', async () => { + const { remote } = require('electron') + const original = global.Promise + try { + expect(await remote.getGlobal('returnAPromise')(123)).to.equal(123) + global.Promise = { resolve: () => ({}) } as any + expect(await remote.getGlobal('returnAPromise')(456)).to.equal(456) + } finally { + global.Promise = original + } + }) + }) + + describe('remote webContents', () => { + const remotely = makeRemotely(makeWindow()) + + it('can return same object with different getters', async () => { + const equal = await remotely(() => { + const { remote } = require('electron') + const contents1 = remote.getCurrentWindow().webContents + const contents2 = remote.getCurrentWebContents() + return contents1 === contents2 + }) + expect(equal).to.be.true() + }) + }) + + describe('remote class', () => { + const remotely = makeRemotely(makeWindow()) + + remotely.it(fixtures)('can get methods', (fixtures: string) => { + const { base } = require('electron').remote.require(require('path').join(fixtures, 'module', 'class.js')) + expect(base.method()).to.equal('method') + }) + + remotely.it(fixtures)('can get properties', (fixtures: string) => { + const { base } = require('electron').remote.require(require('path').join(fixtures, 'module', 'class.js')) + expect(base.readonly).to.equal('readonly') + }) + + remotely.it(fixtures)('can change properties', (fixtures: string) => { + const { base } = require('electron').remote.require(require('path').join(fixtures, 'module', 'class.js')) + expect(base.value).to.equal('old') + base.value = 'new' + expect(base.value).to.equal('new') + base.value = 'old' + }) + + remotely.it(fixtures)('has unenumerable methods', (fixtures: string) => { + const { base } = require('electron').remote.require(require('path').join(fixtures, 'module', 'class.js')) + expect(base).to.not.have.ownProperty('method') + expect(Object.getPrototypeOf(base)).to.have.ownProperty('method') + }) + + remotely.it(fixtures)('keeps prototype chain in derived class', (fixtures: string) => { + const { derived } = require('electron').remote.require(require('path').join(fixtures, 'module', 'class.js')) + expect(derived.method()).to.equal('method') + expect(derived.readonly).to.equal('readonly') + expect(derived).to.not.have.ownProperty('method') + const proto = Object.getPrototypeOf(derived) + expect(proto).to.not.have.ownProperty('method') + expect(Object.getPrototypeOf(proto)).to.have.ownProperty('method') + }) + + remotely.it(fixtures)('is referenced by methods in prototype chain', (fixtures: string) => { + let { derived } = require('electron').remote.require(require('path').join(fixtures, 'module', 'class.js')) + const method = derived.method + derived = null + global.gc() + expect(method()).to.equal('method') + }) + }) + + describe('remote exception', () => { + const remotely = makeRemotely(makeWindow()) + + remotely.it(fixtures)('throws errors from the main process', (fixtures: string) => { + const throwFunction = require('electron').remote.require(require('path').join(fixtures, 'module', 'exception.js')) + expect(() => { + throwFunction() + }).to.throw(/undefined/) + }) + + remotely.it(fixtures)('tracks error cause', (fixtures: string) => { + const throwFunction = require('electron').remote.require(require('path').join(fixtures, 'module', 'exception.js')) + try { + throwFunction(new Error('error from main')) + expect.fail() + } catch (e) { + expect(e.message).to.match(/Could not call remote function/) + expect(e.cause.message).to.equal('error from main') + } + }) + }) }) diff --git a/spec/fixtures/module/call.js b/spec-main/fixtures/module/call.js similarity index 100% rename from spec/fixtures/module/call.js rename to spec-main/fixtures/module/call.js diff --git a/spec/fixtures/module/circular.js b/spec-main/fixtures/module/circular.js similarity index 100% rename from spec/fixtures/module/circular.js rename to spec-main/fixtures/module/circular.js diff --git a/spec/fixtures/module/class.js b/spec-main/fixtures/module/class.js similarity index 100% rename from spec/fixtures/module/class.js rename to spec-main/fixtures/module/class.js diff --git a/spec/fixtures/module/error-properties.js b/spec-main/fixtures/module/error-properties.js similarity index 100% rename from spec/fixtures/module/error-properties.js rename to spec-main/fixtures/module/error-properties.js diff --git a/spec/fixtures/module/exception.js b/spec-main/fixtures/module/exception.js similarity index 100% rename from spec/fixtures/module/exception.js rename to spec-main/fixtures/module/exception.js diff --git a/spec/fixtures/module/export-function-with-properties.js b/spec-main/fixtures/module/export-function-with-properties.js similarity index 100% rename from spec/fixtures/module/export-function-with-properties.js rename to spec-main/fixtures/module/export-function-with-properties.js diff --git a/spec/fixtures/module/function-with-args.js b/spec-main/fixtures/module/function-with-args.js similarity index 100% rename from spec/fixtures/module/function-with-args.js rename to spec-main/fixtures/module/function-with-args.js diff --git a/spec/fixtures/module/function-with-missing-properties.js b/spec-main/fixtures/module/function-with-missing-properties.js similarity index 100% rename from spec/fixtures/module/function-with-missing-properties.js rename to spec-main/fixtures/module/function-with-missing-properties.js diff --git a/spec/fixtures/module/function-with-properties.js b/spec-main/fixtures/module/function-with-properties.js similarity index 100% rename from spec/fixtures/module/function-with-properties.js rename to spec-main/fixtures/module/function-with-properties.js diff --git a/spec/fixtures/module/function.js b/spec-main/fixtures/module/function.js similarity index 100% rename from spec/fixtures/module/function.js rename to spec-main/fixtures/module/function.js diff --git a/spec/fixtures/module/id.js b/spec-main/fixtures/module/id.js similarity index 100% rename from spec/fixtures/module/id.js rename to spec-main/fixtures/module/id.js diff --git a/spec/fixtures/module/no-prototype.js b/spec-main/fixtures/module/no-prototype.js similarity index 100% rename from spec/fixtures/module/no-prototype.js rename to spec-main/fixtures/module/no-prototype.js diff --git a/spec/fixtures/module/promise.js b/spec-main/fixtures/module/promise.js similarity index 100% rename from spec/fixtures/module/promise.js rename to spec-main/fixtures/module/promise.js diff --git a/spec/fixtures/module/property.js b/spec-main/fixtures/module/property.js similarity index 100% rename from spec/fixtures/module/property.js rename to spec-main/fixtures/module/property.js diff --git a/spec/fixtures/module/rejected-promise.js b/spec-main/fixtures/module/rejected-promise.js similarity index 100% rename from spec/fixtures/module/rejected-promise.js rename to spec-main/fixtures/module/rejected-promise.js diff --git a/spec/fixtures/module/remote-object-set.js b/spec-main/fixtures/module/remote-object-set.js similarity index 100% rename from spec/fixtures/module/remote-object-set.js rename to spec-main/fixtures/module/remote-object-set.js diff --git a/spec/fixtures/module/remote-static.js b/spec-main/fixtures/module/remote-static.js similarity index 100% rename from spec/fixtures/module/remote-static.js rename to spec-main/fixtures/module/remote-static.js diff --git a/spec/fixtures/module/to-string-non-function.js b/spec-main/fixtures/module/to-string-non-function.js similarity index 100% rename from spec/fixtures/module/to-string-non-function.js rename to spec-main/fixtures/module/to-string-non-function.js diff --git a/spec/fixtures/module/unhandled-rejection.js b/spec-main/fixtures/module/unhandled-rejection.js similarity index 100% rename from spec/fixtures/module/unhandled-rejection.js rename to spec-main/fixtures/module/unhandled-rejection.js diff --git a/spec-main/package.json b/spec-main/package.json index eb5a7e949a..696a3713fe 100644 --- a/spec-main/package.json +++ b/spec-main/package.json @@ -6,5 +6,9 @@ "devDependencies": { "echo": "file:fixtures/native-addon/echo", "q": "^1.5.1" + }, + "dependencies": { + "chai-as-promised": "^7.1.1", + "dirty-chai": "^2.0.1" } } diff --git a/spec-main/webview-spec.ts b/spec-main/webview-spec.ts index 28b4d2526c..7c8cf27b37 100644 --- a/spec-main/webview-spec.ts +++ b/spec-main/webview-spec.ts @@ -558,4 +558,50 @@ describe(' tag', function () { expect(error).to.equal('denied') }) }) + + describe('enableremotemodule attribute', () => { + let w: BrowserWindow + beforeEach(async () => { + w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, webviewTag: true } }) + await w.loadURL('about:blank') + }) + afterEach(closeAllWindows) + + const generateSpecs = (description: string, sandbox: boolean) => { + describe(description, () => { + const preload = `file://${fixtures}/module/preload-disable-remote.js` + const src = `file://${fixtures}/api/blank.html` + + it('enables the remote module by default', async () => { + loadWebView(w.webContents, { + preload, + src, + sandbox: sandbox.toString() + }) + const [, webViewContents] = await emittedOnce(app, 'web-contents-created') + const [, , message] = await emittedOnce(webViewContents, 'console-message') + + const typeOfRemote = JSON.parse(message) + expect(typeOfRemote).to.equal('object') + }) + + it('disables the remote module when false', async () => { + loadWebView(w.webContents, { + preload, + src, + sandbox: sandbox.toString(), + enableremotemodule: 'false' + }) + const [, webViewContents] = await emittedOnce(app, 'web-contents-created') + const [, , message] = await emittedOnce(webViewContents, 'console-message') + + const typeOfRemote = JSON.parse(message) + expect(typeOfRemote).to.equal('undefined') + }) + }) + } + + generateSpecs('without sandbox', false) + generateSpecs('with sandbox', true) + }) }) diff --git a/spec-main/yarn.lock b/spec-main/yarn.lock index 79ec47695e..20e1741ae1 100644 --- a/spec-main/yarn.lock +++ b/spec-main/yarn.lock @@ -2,6 +2,23 @@ # yarn lockfile v1 +chai-as-promised@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/chai-as-promised/-/chai-as-promised-7.1.1.tgz#08645d825deb8696ee61725dbf590c012eb00ca0" + integrity sha512-azL6xMoi+uxu6z4rhWQ1jbdUhOMhis2PvscD/xjLqNMkv3BPPp2JyyuTHOrf9BOosGpNQ11v6BKv/g57RXbiaA== + dependencies: + check-error "^1.0.2" + +check-error@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.2.tgz#574d312edd88bb5dd8912e9286dd6c0aed4aac82" + integrity sha1-V00xLt2Iu13YkS6Sht1sCu1KrII= + +dirty-chai@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/dirty-chai/-/dirty-chai-2.0.1.tgz#6b2162ef17f7943589da840abc96e75bda01aff3" + integrity sha512-ys79pWKvDMowIDEPC6Fig8d5THiC0DJ2gmTeGzVAoEH18J8OzLud0Jh7I9IWg3NSk8x2UocznUuFmfHCXYZx9w== + "echo@file:fixtures/native-addon/echo": version "0.0.1" diff --git a/spec/api-remote-spec.js b/spec/api-remote-spec.js deleted file mode 100644 index 38256d0c1d..0000000000 --- a/spec/api-remote-spec.js +++ /dev/null @@ -1,534 +0,0 @@ -'use strict' - -const { expect } = require('chai') -const path = require('path') -const { resolveGetters } = require('./expect-helpers') -const { ifdescribe } = require('./spec-helpers') - -const { remote, ipcRenderer } = require('electron') -const { ipcMain, BrowserWindow } = remote - -const features = process.electronBinding('features') - -const comparePaths = (path1, path2) => { - if (process.platform === 'win32') { - path1 = path1.toLowerCase() - path2 = path2.toLowerCase() - } - expect(path1).to.equal(path2) -} - -ifdescribe(features.isRemoteModuleEnabled())('remote module', () => { - const fixtures = path.join(__dirname, 'fixtures') - - describe('remote.require', () => { - it('should returns same object for the same module', () => { - const dialog1 = remote.require('electron') - const dialog2 = remote.require('electron') - expect(dialog1).to.equal(dialog2) - }) - - it('should work when object contains id property', () => { - const a = remote.require(path.join(fixtures, 'module', 'id.js')) - expect(a.id).to.equal(1127) - }) - - it('should work when object has no prototype', () => { - const a = remote.require(path.join(fixtures, 'module', 'no-prototype.js')) - expect(a.foo.constructor.name).to.equal('') - expect(a.foo.bar).to.equal('baz') - expect(a.foo.baz).to.equal(false) - expect(a.bar).to.equal(1234) - expect(a.anonymous.constructor.name).to.equal('') - expect(a.getConstructorName(Object.create(null))).to.equal('') - expect(a.getConstructorName(new (class {})())).to.equal('') - }) - - it('should search module from the user app', () => { - comparePaths(path.normalize(remote.process.mainModule.filename), path.resolve(__dirname, 'static', 'main.js')) - comparePaths(path.normalize(remote.process.mainModule.paths[0]), path.resolve(__dirname, 'static', 'node_modules')) - }) - - it('should work with function properties', () => { - let a = remote.require(path.join(fixtures, 'module', 'export-function-with-properties.js')) - expect(a).to.be.a('function') - expect(a.bar).to.equal('baz') - - a = remote.require(path.join(fixtures, 'module', 'function-with-properties.js')) - expect(a).to.be.an('object') - expect(a.foo()).to.equal('hello') - expect(a.foo.bar).to.equal('baz') - expect(a.foo.nested.prop).to.equal('yes') - expect(a.foo.method1()).to.equal('world') - expect(a.foo.method1.prop1()).to.equal(123) - - expect(a.foo).to.have.a.property('bar') - expect(a.foo).to.have.a.property('nested') - expect(a.foo).to.have.a.property('method1') - - a = remote.require(path.join(fixtures, 'module', 'function-with-missing-properties.js')).setup() - expect(a.bar()).to.equal(true) - expect(a.bar.baz).to.be.undefined() - }) - - it('should work with static class members', () => { - const a = remote.require(path.join(fixtures, 'module', 'remote-static.js')) - expect(a.Foo).to.be.a('function') - expect(a.Foo.foo()).to.equal(3) - expect(a.Foo.bar).to.equal('baz') - - const foo = new a.Foo() - expect(foo.baz()).to.equal(123) - }) - - it('includes the length of functions specified as arguments', () => { - const a = remote.require(path.join(fixtures, 'module', 'function-with-args.js')) - expect(a((a, b, c, d, f) => {})).to.equal(5) - expect(a((a) => {})).to.equal(1) - expect(a((...args) => {})).to.equal(0) - }) - - it('handles circular references in arrays and objects', () => { - const a = remote.require(path.join(fixtures, 'module', 'circular.js')) - - let arrayA = ['foo'] - const arrayB = [arrayA, 'bar'] - arrayA.push(arrayB) - expect(a.returnArgs(arrayA, arrayB)).to.deep.equal([ - ['foo', [null, 'bar']], - [['foo', null], 'bar'] - ]) - - let objectA = { foo: 'bar' } - const objectB = { baz: objectA } - objectA.objectB = objectB - expect(a.returnArgs(objectA, objectB)).to.deep.equal([ - { foo: 'bar', objectB: { baz: null } }, - { baz: { foo: 'bar', objectB: null } } - ]) - - arrayA = [1, 2, 3] - expect(a.returnArgs({ foo: arrayA }, { bar: arrayA })).to.deep.equal([ - { foo: [1, 2, 3] }, - { bar: [1, 2, 3] } - ]) - - objectA = { foo: 'bar' } - expect(a.returnArgs({ foo: objectA }, { bar: objectA })).to.deep.equal([ - { foo: { foo: 'bar' } }, - { bar: { foo: 'bar' } } - ]) - - arrayA = [] - arrayA.push(arrayA) - expect(a.returnArgs(arrayA)).to.deep.equal([ - [null] - ]) - - objectA = {} - objectA.foo = objectA - objectA.bar = 'baz' - expect(a.returnArgs(objectA)).to.deep.equal([ - { foo: null, bar: 'baz' } - ]) - - objectA = {} - objectA.foo = { bar: objectA } - objectA.bar = 'baz' - expect(a.returnArgs(objectA)).to.deep.equal([ - { foo: { bar: null }, bar: 'baz' } - ]) - }) - }) - - describe('remote.createFunctionWithReturnValue', () => { - it('should be called in browser synchronously', () => { - const buf = Buffer.from('test') - const call = remote.require(path.join(fixtures, 'module', 'call.js')) - const result = call.call(remote.createFunctionWithReturnValue(buf)) - expect(result).to.be.an.instanceOf(Buffer) - }) - }) - - describe('remote modules', () => { - it('includes browser process modules as properties', async () => { - const mainModules = await ipcRenderer.invoke('get-modules') - const remoteModules = mainModules.filter(name => remote[name]) - expect(remoteModules).to.be.deep.equal(mainModules) - }) - - it('returns toString() of original function via toString()', () => { - const { readText } = remote.clipboard - expect(readText.toString().startsWith('function')).to.be.true() - - const { functionWithToStringProperty } = remote.require(path.join(fixtures, 'module', 'to-string-non-function.js')) - expect(functionWithToStringProperty.toString).to.equal('hello') - }) - }) - - describe('remote object in renderer', () => { - it('can change its properties', () => { - const property = remote.require(path.join(fixtures, 'module', 'property.js')) - expect(property).to.have.a.property('property').that.is.equal(1127) - - property.property = null - expect(property).to.have.a.property('property').that.is.null() - property.property = undefined - expect(property).to.have.a.property('property').that.is.undefined() - property.property = 1007 - expect(property).to.have.a.property('property').that.is.equal(1007) - - expect(property.getFunctionProperty()).to.equal('foo-browser') - property.func.property = 'bar' - expect(property.getFunctionProperty()).to.equal('bar-browser') - property.func.property = 'foo' // revert back - - const property2 = remote.require(path.join(fixtures, 'module', 'property.js')) - expect(property2.property).to.equal(1007) - property.property = 1127 - }) - - it('rethrows errors getting/setting properties', () => { - const foo = remote.require(path.join(fixtures, 'module', 'error-properties.js')) - - expect(() => { - // eslint-disable-next-line - foo.bar - }).to.throw('getting error') - - expect(() => { - foo.bar = 'test' - }).to.throw('setting error') - }) - - it('can set a remote property with a remote object', () => { - const foo = remote.require(path.join(fixtures, 'module', 'remote-object-set.js')) - - expect(() => { - foo.bar = remote.getCurrentWindow() - }).to.not.throw() - }) - - it('can construct an object from its member', () => { - const call = remote.require(path.join(fixtures, 'module', 'call.js')) - const obj = new call.constructor() - expect(obj.test).to.equal('test') - }) - - it('can reassign and delete its member functions', () => { - const remoteFunctions = remote.require(path.join(fixtures, 'module', 'function.js')) - expect(remoteFunctions.aFunction()).to.equal(1127) - - remoteFunctions.aFunction = () => { return 1234 } - expect(remoteFunctions.aFunction()).to.equal(1234) - - expect(delete remoteFunctions.aFunction).to.equal(true) - }) - - it('is referenced by its members', () => { - const stringify = remote.getGlobal('JSON').stringify - global.gc() - stringify({}) - }) - }) - - describe('remote value in browser', () => { - const print = path.join(__dirname, '..', 'spec-main', 'fixtures', 'module', 'print_name.js') - const printName = remote.require(print) - - it('preserves NaN', () => { - expect(printName.getNaN()).to.be.NaN() - expect(printName.echo(NaN)).to.be.NaN() - }) - - it('preserves Infinity', () => { - expect(printName.getInfinity()).to.equal(Infinity) - expect(printName.echo(Infinity)).to.equal(Infinity) - }) - - it('keeps its constructor name for objects', () => { - const buf = Buffer.from('test') - expect(printName.print(buf)).to.equal('Buffer') - }) - - it('supports instanceof Boolean', () => { - const obj = Boolean(true) - expect(printName.print(obj)).to.equal('Boolean') - expect(printName.echo(obj)).to.deep.equal(obj) - }) - - it('supports instanceof Number', () => { - const obj = Number(42) - expect(printName.print(obj)).to.equal('Number') - expect(printName.echo(obj)).to.deep.equal(obj) - }) - - it('supports instanceof String', () => { - const obj = String('Hello World!') - expect(printName.print(obj)).to.equal('String') - expect(printName.echo(obj)).to.deep.equal(obj) - }) - - it('supports instanceof Date', () => { - const now = new Date() - expect(printName.print(now)).to.equal('Date') - expect(printName.echo(now)).to.deep.equal(now) - }) - - it('supports instanceof RegExp', () => { - const regexp = RegExp('.*') - expect(printName.print(regexp)).to.equal('RegExp') - expect(printName.echo(regexp)).to.deep.equal(regexp) - }) - - it('supports instanceof Buffer', () => { - const buffer = Buffer.from('test') - expect(buffer.equals(printName.echo(buffer))).to.be.true() - - const objectWithBuffer = { a: 'foo', b: Buffer.from('bar') } - expect(objectWithBuffer.b.equals(printName.echo(objectWithBuffer).b)).to.be.true() - - const arrayWithBuffer = [1, 2, Buffer.from('baz')] - expect(arrayWithBuffer[2].equals(printName.echo(arrayWithBuffer)[2])).to.be.true() - }) - - it('supports instanceof ArrayBuffer', () => { - const buffer = new ArrayBuffer(8) - const view = new DataView(buffer) - - view.setFloat64(0, Math.PI) - expect(printName.echo(buffer)).to.deep.equal(buffer) - expect(printName.print(buffer)).to.equal('ArrayBuffer') - }) - - it('supports instanceof Int8Array', () => { - const values = [1, 2, 3, 4] - expect([...printName.typedArray('Int8Array', values)]).to.deep.equal(values) - - const int8values = new Int8Array(values) - expect(printName.typedArray('Int8Array', int8values)).to.deep.equal(int8values) - expect(printName.print(int8values)).to.equal('Int8Array') - }) - - it('supports instanceof Uint8Array', () => { - const values = [1, 2, 3, 4] - expect([...printName.typedArray('Uint8Array', values)]).to.deep.equal(values) - - const uint8values = new Uint8Array(values) - expect(printName.typedArray('Uint8Array', uint8values)).to.deep.equal(uint8values) - expect(printName.print(uint8values)).to.equal('Uint8Array') - }) - - it('supports instanceof Uint8ClampedArray', () => { - const values = [1, 2, 3, 4] - expect([...printName.typedArray('Uint8ClampedArray', values)]).to.deep.equal(values) - - const uint8values = new Uint8ClampedArray(values) - expect(printName.typedArray('Uint8ClampedArray', uint8values)).to.deep.equal(uint8values) - expect(printName.print(uint8values)).to.equal('Uint8ClampedArray') - }) - - it('supports instanceof Int16Array', () => { - const values = [0x1234, 0x2345, 0x3456, 0x4567] - expect([...printName.typedArray('Int16Array', values)]).to.deep.equal(values) - - const int16values = new Int16Array(values) - expect(printName.typedArray('Int16Array', int16values)).to.deep.equal(int16values) - expect(printName.print(int16values)).to.equal('Int16Array') - }) - - it('supports instanceof Uint16Array', () => { - const values = [0x1234, 0x2345, 0x3456, 0x4567] - expect([...printName.typedArray('Uint16Array', values)]).to.deep.equal(values) - - const uint16values = new Uint16Array(values) - expect(printName.typedArray('Uint16Array', uint16values)).to.deep.equal(uint16values) - expect(printName.print(uint16values)).to.equal('Uint16Array') - }) - - it('supports instanceof Int32Array', () => { - const values = [0x12345678, 0x23456789] - expect([...printName.typedArray('Int32Array', values)]).to.deep.equal(values) - - const int32values = new Int32Array(values) - expect(printName.typedArray('Int32Array', int32values)).to.deep.equal(int32values) - expect(printName.print(int32values)).to.equal('Int32Array') - }) - - it('supports instanceof Uint32Array', () => { - const values = [0x12345678, 0x23456789] - expect([...printName.typedArray('Uint32Array', values)]).to.deep.equal(values) - - const uint32values = new Uint32Array(values) - expect(printName.typedArray('Uint32Array', uint32values)).to.deep.equal(uint32values) - expect(printName.print(uint32values)).to.equal('Uint32Array') - }) - - it('supports instanceof Float32Array', () => { - const values = [0.5, 1.0, 1.5] - expect([...printName.typedArray('Float32Array', values)]).to.deep.equal(values) - - const float32values = new Float32Array() - expect(printName.typedArray('Float32Array', float32values)).to.deep.equal(float32values) - expect(printName.print(float32values)).to.equal('Float32Array') - }) - - it('supports instanceof Float64Array', () => { - const values = [0.5, 1.0, 1.5] - expect([...printName.typedArray('Float64Array', values)]).to.deep.equal(values) - - const float64values = new Float64Array([0.5, 1.0, 1.5]) - expect(printName.typedArray('Float64Array', float64values)).to.deep.equal(float64values) - expect(printName.print(float64values)).to.equal('Float64Array') - }) - }) - - describe('remote promise', () => { - it('can be used as promise in each side', (done) => { - const promise = remote.require(path.join(fixtures, 'module', 'promise.js')) - promise.twicePromise(Promise.resolve(1234)).then((value) => { - expect(value).to.equal(2468) - done() - }) - }) - - it('handles rejections via catch(onRejected)', (done) => { - const promise = remote.require(path.join(fixtures, 'module', 'rejected-promise.js')) - promise.reject(Promise.resolve(1234)).catch((error) => { - expect(error.message).to.equal('rejected') - done() - }) - }) - - it('handles rejections via then(onFulfilled, onRejected)', (done) => { - const promise = remote.require(path.join(fixtures, 'module', 'rejected-promise.js')) - promise.reject(Promise.resolve(1234)).then(() => {}, (error) => { - expect(error.message).to.equal('rejected') - done() - }) - }) - - it('does not emit unhandled rejection events in the main process', (done) => { - remote.process.once('unhandledRejection', function (reason) { - done(reason) - }) - - const promise = remote.require(path.join(fixtures, 'module', 'unhandled-rejection.js')) - promise.reject().then(() => { - done(new Error('Promise was not rejected')) - }).catch((error) => { - expect(error.message).to.equal('rejected') - done() - }) - }) - - it('emits unhandled rejection events in the renderer process', (done) => { - window.addEventListener('unhandledrejection', function handler (event) { - event.preventDefault() - expect(event.reason.message).to.equal('rejected') - window.removeEventListener('unhandledrejection', handler) - done() - }) - - const promise = remote.require(path.join(fixtures, 'module', 'unhandled-rejection.js')) - promise.reject().then(() => { - done(new Error('Promise was not rejected')) - }) - }) - }) - - describe('remote webContents', () => { - it('can return same object with different getters', () => { - const contents1 = remote.getCurrentWindow().webContents - const contents2 = remote.getCurrentWebContents() - expect(contents1).to.equal(contents2) - }) - }) - - describe('remote class', () => { - const cl = remote.require(path.join(fixtures, 'module', 'class.js')) - const base = cl.base - let derived = cl.derived - - it('can get methods', () => { - expect(base.method()).to.equal('method') - }) - - it('can get properties', () => { - expect(base.readonly).to.equal('readonly') - }) - - it('can change properties', () => { - expect(base.value).to.equal('old') - base.value = 'new' - expect(base.value).to.equal('new') - base.value = 'old' - }) - - it('has unenumerable methods', () => { - expect(base).to.not.have.own.property('method') - expect(Object.getPrototypeOf(base)).to.have.own.property('method') - }) - - it('keeps prototype chain in derived class', () => { - expect(derived.method()).to.equal('method') - expect(derived.readonly).to.equal('readonly') - expect(derived).to.not.have.own.property('method') - const proto = Object.getPrototypeOf(derived) - expect(proto).to.not.have.own.property('method') - expect(Object.getPrototypeOf(proto)).to.have.own.property('method') - }) - - it('is referenced by methods in prototype chain', () => { - const method = derived.method - derived = null - global.gc() - expect(method()).to.equal('method') - }) - }) - - describe('remote exception', () => { - const throwFunction = remote.require(path.join(fixtures, 'module', 'exception.js')) - - it('throws errors from the main process', () => { - expect(() => { - throwFunction() - }).to.throw(/undefined/) - }) - - it('tracks error cause', () => { - try { - throwFunction(new Error('error from main')) - expect.fail() - } catch (e) { - expect(e.message).to.match(/Could not call remote function/) - expect(e.cause.message).to.equal('error from main') - } - }) - }) - - describe('constructing a Uint8Array', () => { - it('does not crash', () => { - const RUint8Array = remote.getGlobal('Uint8Array') - const arr = new RUint8Array() - }) - }) - - describe('with an overriden global Promise constrctor', () => { - let original - - before(() => { - original = Promise - }) - - it('using a promise based method resolves correctly', async () => { - expect(await remote.getGlobal('returnAPromise')(123)).to.equal(123) - global.Promise = { resolve: () => ({}) } - expect(await remote.getGlobal('returnAPromise')(456)).to.equal(456) - }) - - after(() => { - global.Promise = original - }) - }) -}) diff --git a/spec/static/main.js b/spec/static/main.js index b112078475..306845be2c 100644 --- a/spec/static/main.js +++ b/spec/static/main.js @@ -77,7 +77,6 @@ ipcMain.on('echo', function (event, msg) { }) global.setTimeoutPromisified = util.promisify(setTimeout) -global.returnAPromise = (value) => new Promise((resolve) => setTimeout(() => resolve(value), 100)) process.removeAllListeners('uncaughtException') process.on('uncaughtException', function (error) { @@ -128,6 +127,7 @@ app.on('ready', function () { webPreferences: { backgroundThrottling: false, nodeIntegration: true, + enableRemoteModule: false, webviewTag: true } }) diff --git a/spec/webview-spec.js b/spec/webview-spec.js index d628a00a1c..0ada6bd187 100644 --- a/spec/webview-spec.js +++ b/spec/webview-spec.js @@ -172,41 +172,6 @@ describe(' tag', function () { }) }) - describe('enableremotemodule attribute', () => { - const generateSpecs = (description, sandbox) => { - describe(description, () => { - const preload = `${fixtures}/module/preload-disable-remote.js` - const src = `file://${fixtures}/api/blank.html` - - it('enables the remote module by default', async () => { - const message = await startLoadingWebViewAndWaitForMessage(webview, { - preload, - src, - sandbox - }) - - const typeOfRemote = JSON.parse(message) - expect(typeOfRemote).to.equal('object') - }) - - it('disables the remote module when false', async () => { - const message = await startLoadingWebViewAndWaitForMessage(webview, { - preload, - src, - sandbox, - enableremotemodule: false - }) - - const typeOfRemote = JSON.parse(message) - expect(typeOfRemote).to.equal('undefined') - }) - }) - } - - generateSpecs('without sandbox', false) - generateSpecs('with sandbox', true) - }) - describe('preload attribute', () => { it('loads the script before other scripts in window', async () => { const message = await startLoadingWebViewAndWaitForMessage(webview, {