Skip to content

Commit

Permalink
fix: capture the promise global to avoid userland mutation
Browse files Browse the repository at this point in the history
  • Loading branch information
MarshallOfSound committed Nov 2, 2019
1 parent 1fed1ed commit ae1cef9
Show file tree
Hide file tree
Showing 18 changed files with 93 additions and 2 deletions.
7 changes: 5 additions & 2 deletions build/webpack/webpack.config.base.js
Expand Up @@ -74,7 +74,10 @@ module.exports = ({
global: ['@electron/internal/renderer/webpack-provider', '_global'],
Buffer: ['@electron/internal/renderer/webpack-provider', 'Buffer'],
})
] : [])
] : []),
new webpack.ProvidePlugin({
capturedGlobals: ['@electron/internal/common/webpack-globals-provider', 'globals'],
}),
]
})
}
}
8 changes: 8 additions & 0 deletions lib/.eslintrc
@@ -0,0 +1,8 @@
{
"rules": {
"no-restricted-globals": ["error", "primordials", "Promise"]
},
"globals": {
"capturedGlobals": true
}
}
4 changes: 4 additions & 0 deletions lib/browser/api/web-contents.js
Expand Up @@ -11,6 +11,10 @@ const NavigationController = require('@electron/internal/browser/navigation-cont
const { ipcMainInternal } = require('@electron/internal/browser/ipc-main-internal')
const ipcMainUtils = require('@electron/internal/browser/ipc-main-internal-utils')

const {
Promise
} = capturedGlobals

// session is not used here, the purpose is to make sure session is initalized
// before the webContents module.
// eslint-disable-next-line
Expand Down
4 changes: 4 additions & 0 deletions lib/browser/desktop-capturer.ts
Expand Up @@ -2,6 +2,10 @@ import { EventEmitter } from 'events'

const { createDesktopCapturer } = process.electronBinding('desktop_capturer')

const {
Promise
} = capturedGlobals

const deepEqual = (a: ElectronInternal.GetSourcesOptions, b: ElectronInternal.GetSourcesOptions) => JSON.stringify(a) === JSON.stringify(b)

let currentlyRunning: {
Expand Down
4 changes: 4 additions & 0 deletions lib/browser/devtools.ts
Expand Up @@ -5,6 +5,10 @@ import * as url from 'url'
import { ipcMainInternal } from '@electron/internal/browser/ipc-main-internal'
import * as ipcMainUtils from '@electron/internal/browser/ipc-main-internal-utils'

const {
Promise
} = capturedGlobals

const convertToMenuTemplate = function (items: ContextMenuItem[], handler: (id: number) => void) {
return items.map(function (item) {
const transformed: Electron.MenuItemConstructorOptions = item.type === 'subMenu' ? {
Expand Down
4 changes: 4 additions & 0 deletions lib/browser/init.ts
Expand Up @@ -5,6 +5,10 @@ import * as util from 'util'

const Module = require('module')

const {
Promise
} = capturedGlobals

// We modified the original process.argv to let node.js load the init.js,
// we need to restore it here.
process.argv.splice(1, 1)
Expand Down
4 changes: 4 additions & 0 deletions lib/browser/ipc-main-impl.ts
@@ -1,6 +1,10 @@
import { EventEmitter } from 'events'
import { IpcMainInvokeEvent } from 'electron'

const {
Promise
} = capturedGlobals

export class IpcMainImpl extends EventEmitter {
private _invokeHandlers: Map<string, (e: IpcMainInvokeEvent, ...args: any[]) => void> = new Map();

Expand Down
4 changes: 4 additions & 0 deletions lib/browser/ipc-main-internal-utils.ts
@@ -1,5 +1,9 @@
import { ipcMainInternal } from '@electron/internal/browser/ipc-main-internal'

const {
Promise
} = capturedGlobals

type IPCHandler = (event: Electron.IpcMainInvokeEvent, ...args: any[]) => any

export const handleSync = function <T extends IPCHandler> (channel: string, handler: T) {
Expand Down
4 changes: 4 additions & 0 deletions lib/browser/navigation-controller.js
Expand Up @@ -2,6 +2,10 @@

const { ipcMainInternal } = require('@electron/internal/browser/ipc-main-internal')

const {
Promise
} = capturedGlobals

// The history operation in renderer is redirected to browser.
ipcMainInternal.on('ELECTRON_NAVIGATION_CONTROLLER_GO_BACK', function (event) {
event.sender.goBack()
Expand Down
4 changes: 4 additions & 0 deletions lib/browser/remote/server.ts
Expand Up @@ -11,6 +11,10 @@ const v8Util = process.electronBinding('v8_util')
const eventBinding = process.electronBinding('event')
const features = process.electronBinding('features')

const {
Promise
} = capturedGlobals

if (!features.isRemoteModuleEnabled()) {
throw new Error('remote module is disabled')
}
Expand Down
4 changes: 4 additions & 0 deletions lib/browser/rpc-server.js
Expand Up @@ -13,6 +13,10 @@ const ipcMainUtils = require('@electron/internal/browser/ipc-main-internal-utils
const guestViewManager = require('@electron/internal/browser/guest-view-manager')
const clipboardUtils = require('@electron/internal/common/clipboard-utils')

const {
Promise
} = capturedGlobals

const emitCustomEvent = function (contents, eventName, ...args) {
const event = eventBinding.createWithSender(contents)

Expand Down
2 changes: 2 additions & 0 deletions lib/common/asar.js
Expand Up @@ -8,6 +8,8 @@
const path = require('path')
const util = require('util')

const Promise = global.Promise // eslint-disable-line no-restricted-globals

const envNoAsar = process.env.ELECTRON_NO_ASAR &&
process.type !== 'browser' &&
process.type !== 'renderer'
Expand Down
10 changes: 10 additions & 0 deletions lib/common/webpack-globals-provider.ts
@@ -0,0 +1,10 @@
// Captures original globals into a scope to ensure that userland modifications do
// not impact Electron. Note that users doing:
//
// global.Promise.resolve = myFn
//
// Will mutate this captured one as well and that is OK.

export const globals = {
Promise: global.Promise
}
4 changes: 4 additions & 0 deletions lib/renderer/api/remote.js
Expand Up @@ -7,6 +7,10 @@ const { CallbacksRegistry } = require('@electron/internal/renderer/remote/callba
const { isPromise, isSerializableObject } = require('@electron/internal/common/remote/type-utils')
const { ipcRendererInternal } = require('@electron/internal/renderer/ipc-renderer-internal')

const {
Promise
} = capturedGlobals

const callbacksRegistry = new CallbacksRegistry()
const remoteObjectCache = v8Util.createIDWeakMap()

Expand Down
4 changes: 4 additions & 0 deletions lib/renderer/chrome-api.ts
Expand Up @@ -4,6 +4,10 @@ import * as url from 'url'

import { Event } from '@electron/internal/renderer/extensions/event'

const {
Promise
} = capturedGlobals

class Tab {
public id: number

Expand Down
18 changes: 18 additions & 0 deletions spec/api-remote-spec.js
Expand Up @@ -517,4 +517,22 @@ ifdescribe(features.isRemoteModuleEnabled())('remote module', () => {
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
})
})
})
1 change: 1 addition & 0 deletions spec/static/main.js
Expand Up @@ -75,6 +75,7 @@ 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) {
Expand Down
5 changes: 5 additions & 0 deletions typings/internal-ambient.d.ts
@@ -1,4 +1,9 @@
interface CapturedGlobals {
Promise: typeof Promise;
}

declare var internalBinding: any;
declare var capturedGlobals: CapturedGlobals;

declare namespace NodeJS {
interface FeaturesBinding {
Expand Down

0 comments on commit ae1cef9

Please sign in to comment.