Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add preload-error event to webContents #16411

Merged
merged 1 commit into from Jan 18, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
10 changes: 10 additions & 0 deletions docs/api/web-contents.md
Expand Up @@ -663,6 +663,16 @@ Returns:
Emitted when the associated window logs a console message. Will not be emitted
for windows with *offscreen rendering* enabled.

#### Event: 'preload-error'

Returns:

* `event` Event
* `preloadPath` String
* `error` Error

Emitted when the preload script `preloadPath` throws an unhandled exception `error`.

#### Event: 'desktop-capturer-get-sources'

Returns:
Expand Down
7 changes: 6 additions & 1 deletion lib/browser/rpc-server.js
Expand Up @@ -542,10 +542,11 @@ ipcMain.on('ELECTRON_BROWSER_SANDBOX_LOAD', function (event) {
try {
preloadSrc = fs.readFileSync(preloadPath).toString()
} catch (err) {
preloadError = { stack: err ? err.stack : (new Error(`Failed to load "${preloadPath}"`)).stack }
preloadError = errorUtils.serialize(err)
}
}
event.returnValue = {
preloadPath,
preloadSrc,
preloadError,
isRemoteModuleEnabled: event.sender._isRemoteModuleEnabled(),
Expand All @@ -560,3 +561,7 @@ ipcMain.on('ELECTRON_BROWSER_SANDBOX_LOAD', function (event) {
}
}
})

ipcMain.on('ELECTRON_BROWSER_PRELOAD_ERROR', function (event, preloadPath, error) {
event.sender.emit('preload-error', event, preloadPath, errorUtils.deserialize(error))
})
8 changes: 6 additions & 2 deletions lib/renderer/init.js
Expand Up @@ -147,13 +147,17 @@ if (nodeIntegration) {
})
}

const errorUtils = require('@electron/internal/common/error-utils')

// Load the preload scripts.
for (const preloadScript of preloadScripts) {
try {
require(preloadScript)
} catch (error) {
console.error('Unable to load preload script: ' + preloadScript)
console.error(error.stack || error.message)
console.error(`Unable to load preload script: ${preloadScript}`)
console.error(`${error}`)

ipcRenderer.send('ELECTRON_BROWSER_PRELOAD_ERROR', preloadScript, errorUtils.serialize(error))
}
}

Expand Down
22 changes: 18 additions & 4 deletions lib/sandboxed_renderer/init.js
Expand Up @@ -29,7 +29,7 @@ Object.setPrototypeOf(process, EventEmitter.prototype)
const ipcRenderer = require('@electron/internal/renderer/ipc-renderer-internal')

const {
preloadSrc, preloadError, isRemoteModuleEnabled, isWebViewTagEnabled, process: processProps
preloadPath, preloadSrc, preloadError, isRemoteModuleEnabled, isWebViewTagEnabled, process: processProps
} = ipcRenderer.sendSync('ELECTRON_BROWSER_SANDBOX_LOAD')

process.isRemoteModuleEnabled = isRemoteModuleEnabled
Expand Down Expand Up @@ -132,6 +132,8 @@ if (!process.guestInstanceId && isWebViewTagEnabled) {
setupWebView(v8Util, webViewImpl)
}

const errorUtils = require('@electron/internal/common/error-utils')

// Wrap the script into a function executed in global scope. It won't have
// access to the current scope, so we'll expose a few objects as arguments:
//
Expand All @@ -151,17 +153,29 @@ if (!process.guestInstanceId && isWebViewTagEnabled) {
// and any `require('electron')` calls in `preload.js` will work as expected
// since browserify won't try to include `electron` in the bundle, falling back
// to the `preloadRequire` function above.
if (preloadSrc) {
function runPreloadScript (preloadSrc) {
const preloadWrapperSrc = `(function(require, process, Buffer, global, setImmediate, clearImmediate) {
${preloadSrc}
})`

// eval in window scope
const preloadFn = binding.createPreloadScript(preloadWrapperSrc)
const { setImmediate, clearImmediate } = require('timers')

preloadFn(preloadRequire, preloadProcess, Buffer, global, setImmediate, clearImmediate)
} else if (preloadError) {
console.error(preloadError.stack)
}

try {
if (preloadSrc) {
runPreloadScript(preloadSrc)
} else if (preloadError) {
throw errorUtils.deserialize(preloadError)
}
} catch (error) {
console.error(`Unable to load preload script: ${preloadPath}`)
console.error(`${error}`)

ipcRenderer.send('ELECTRON_BROWSER_PRELOAD_ERROR', preloadPath, errorUtils.serialize(error))
}

// Warn about security issues
Expand Down
69 changes: 69 additions & 0 deletions spec/api-web-contents-spec.js
Expand Up @@ -1050,6 +1050,75 @@ describe('webContents module', () => {
})
})

describe('preload-error event', () => {
const generateSpecs = (description, sandbox) => {
describe(description, () => {
it('is triggered when unhandled exception is thrown', async () => {
const preload = path.join(fixtures, 'module', 'preload-error-exception.js')

w.destroy()
w = new BrowserWindow({
show: false,
webPreferences: {
sandbox,
preload
}
})

const promise = emittedOnce(w.webContents, 'preload-error')
w.loadURL('about:blank')

const [, preloadPath, error] = await promise
expect(preloadPath).to.equal(preload)
expect(error.message).to.equal('Hello World!')
})

it('is triggered on syntax errors', async () => {
const preload = path.join(fixtures, 'module', 'preload-error-syntax.js')

w.destroy()
w = new BrowserWindow({
show: false,
webPreferences: {
sandbox,
preload
}
})

const promise = emittedOnce(w.webContents, 'preload-error')
w.loadURL('about:blank')

const [, preloadPath, error] = await promise
expect(preloadPath).to.equal(preload)
expect(error.message).to.equal('foobar is not defined')
})

it('is triggered when preload script loading fails', async () => {
const preload = path.join(fixtures, 'module', 'preload-invalid.js')

w.destroy()
w = new BrowserWindow({
show: false,
webPreferences: {
sandbox,
preload
}
})

const promise = emittedOnce(w.webContents, 'preload-error')
w.loadURL('about:blank')

const [, preloadPath, error] = await promise
expect(preloadPath).to.equal(preload)
expect(error.message).to.contain('preload-invalid.js')
})
})
}

generateSpecs('without sandbox', false)
generateSpecs('with sandbox', true)
})

describe('takeHeapSnapshot()', () => {
it('works with sandboxed renderers', async () => {
w.destroy()
Expand Down
1 change: 1 addition & 0 deletions spec/fixtures/module/preload-error-exception.js
@@ -0,0 +1 @@
throw new Error('Hello World!')
2 changes: 2 additions & 0 deletions spec/fixtures/module/preload-error-syntax.js
@@ -0,0 +1,2 @@
// eslint-disable-next-line
foobar