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

Show uncaughtException as an internal error and quit the app #626

Merged
merged 8 commits into from
Nov 6, 2017
27 changes: 15 additions & 12 deletions src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,18 @@ const {
systemPreferences,
session
} = require('electron');
const os = require('os');
const path = require('path');
const isDev = require('electron-is-dev');
const installExtension = require('electron-devtools-installer');
const squirrelStartup = require('./main/squirrelStartup');
const CriticalErrorHandler = require('./main/CriticalErrorHandler');

const protocols = require('../electron-builder.json').protocols;

process.on('uncaughtException', (error) => {
console.error(error);
});
const criticalErrorHandler = new CriticalErrorHandler();

process.on('uncaughtException', criticalErrorHandler.processUncaughtExceptionHandler.bind(criticalErrorHandler));

global.willAppQuit = false;

Expand All @@ -27,9 +30,6 @@ if (squirrelStartup()) {
global.willAppQuit = true;
}

const os = require('os');
const path = require('path');

var settings = require('./common/settings');
var certificateStore = require('./main/certificateStore').load(path.resolve(app.getPath('userData'), 'certificate.json'));
const {createMainWindow} = require('./main/mainWindow');
Expand Down Expand Up @@ -172,7 +172,7 @@ if (app.makeSingleInstance((commandLine/*, workingDirectory*/) => {
}
}
})) {
app.quit();
app.exit();
}

function shouldShowTrayIcon() {
Expand Down Expand Up @@ -304,6 +304,10 @@ app.on('certificate-error', (event, webContents, url, error, certificate, callba
}
});

app.on('gpu-process-crashed', () => {
throw new Error('The GPU process has crached');
});

const loginCallbackMap = new Map();

ipcMain.on('login-credentials', (event, request, user, password) => {
Expand Down Expand Up @@ -392,11 +396,10 @@ app.on('ready', () => {
// when you should delete the corresponding element.
mainWindow = null;
});
mainWindow.on('unresponsive', () => {
console.log('The application has become unresponsive.');
});
criticalErrorHandler.setMainWindow(mainWindow);
mainWindow.on('unresponsive', criticalErrorHandler.windowUnresponsiveHandler.bind(criticalErrorHandler));
mainWindow.webContents.on('crashed', () => {
console.log('The application has crashed.');
throw new Error('webContents \'crashed\' event has been emitted');
});

ipcMain.on('notified', () => {
Expand Down Expand Up @@ -468,7 +471,7 @@ app.on('ready', () => {
}
}

if (trayIcon) {
if (trayIcon && !trayIcon.isDestroyed()) {
if (arg.mentionCount > 0) {
trayIcon.setImage(trayImages.mention);
if (process.platform === 'darwin') {
Expand Down
100 changes: 100 additions & 0 deletions src/main/CriticalErrorHandler.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
const {app, dialog} = require('electron');
const {spawn} = require('child_process');
const fs = require('fs');
const os = require('os');
const path = require('path');

const BUTTON_OK = 'OK';
const BUTTON_SHOW_DETAILS = 'Show Details';
const BUTTON_REOPEN = 'Reopen';

function createErrorReport(err) {
return `Application: ${app.getName()} ${app.getVersion()}\n` +
`Platform: ${os.type()} ${os.release()} ${os.arch()}\n` +
`${err.stack}`;
}

function openDetachedExternal(url) {
const spawnOption = {detached: true, stdio: 'ignore'};
switch (process.platform) {
case 'win32':
return spawn('cmd', ['/C', 'start', url], spawnOption);
case 'darwin':
return spawn('open', [url], spawnOption);
case 'linux':
return spawn('xdg-open', [url], spawnOption);
default:
return null;
}
}

function bindWindowToShowMessageBox(win) {
if (win && win.isVisible()) {
return dialog.showMessageBox.bind(null, win);
}
return dialog.showMessageBox;
}

class CriticalErrorHandler {
constructor() {
this.mainWindow = null;
}

setMainWindow(mainWindow) {
this.mainWindow = mainWindow;
}

windowUnresponsiveHandler() {
const result = dialog.showMessageBox(this.mainWindow, {
type: 'warning',
title: app.getName(),
message: 'The window is no longer responsive.\nDo you wait until the window becomes responsive again?',
buttons: ['No', 'Yes'],
defaultId: 0
});
if (result === 0) {
throw new Error('BrowserWindow \'unresponsive\' event has been emitted');
}
}

processUncaughtExceptionHandler(err) {
const file = path.join(app.getPath('userData'), `uncaughtException-${Date.now()}.txt`);
const report = createErrorReport(err);
fs.writeFileSync(file, report.replace(new RegExp('\\n', 'g'), os.EOL));

if (app.isReady()) {
const buttons = [BUTTON_SHOW_DETAILS, BUTTON_OK, BUTTON_REOPEN];
if (process.platform === 'darwin') {
buttons.reverse();
}
const showMessageBox = bindWindowToShowMessageBox(this.mainWindow);
const result = showMessageBox({
type: 'error',
title: app.getName(),
message: `The ${app.getName()} app quit unexpectedly. Click "Show Details" to learn more or "Reopen" to open the application again.\n\n Internal error: ${err.message}`,
buttons,
defaultId: buttons.indexOf(BUTTON_REOPEN),
noLink: true
});
switch (result) {
case buttons.indexOf(BUTTON_SHOW_DETAILS):
{
const child = openDetachedExternal(file);
if (child) {
child.on('error', (spawnError) => {
console.log(spawnError);
});
child.unref();
}
}
break;
case buttons.indexOf(BUTTON_REOPEN):
app.relaunch();
break;
}
}
throw err;
}
}

module.exports = CriticalErrorHandler;