Move error handler into separate file

This commit moves the global error handler into a separate file,
so that it can catch module loading errors.

This also adds support for properly displaying error messages with
newlines in them (since the module loader may throw those)
This commit is contained in:
Solly Ross 2017-02-28 20:47:02 -05:00
parent e6a8eb15ca
commit adfc9d3f54
7 changed files with 157 additions and 49 deletions

61
app/error-handler.js Normal file
View File

@ -0,0 +1,61 @@
// NB: this should *not* be included as a module until we have
// native support in the browsers, so that our error handler
// can catch script-loading errors.
(function(){
"use strict";
function convertNewlines(msg, parentElem) {
const lines = msg.split("\n");
lines.forEach(function (line) {
parentElem.appendChild(document.createElement("br"));
parentElem.appendChild(document.createTextNode(line));
});
parentElem.removeChild(parentElem.firstChild);
return parentElem;
}
// Fallback for all uncought errors
function handleError (event, err) {
try {
const msg = document.getElementById('noVNC_fallback_errormsg');
// Only show the initial error
if (msg.hasChildNodes()) {
return false;
}
var div = document.createElement("div");
div.classList.add('noVNC_message');
convertNewlines(event.message, div);
msg.appendChild(div);
if (event.filename !== undefined && event.lineno !== undefined && event.colno !== undefined) {
div = document.createElement("div");
div.className = 'noVNC_location';
const text = event.filename + ":" + event.lineno + ":" + event.colno;
div.appendChild(document.createTextNode(text));
msg.appendChild(div);
}
if ((err !== undefined) &&
(err.stack !== undefined)) {
div = document.createElement("div");
div.className = 'noVNC_stack';
div.appendChild(document.createTextNode(err.stack));
msg.appendChild(div);
}
document.getElementById('noVNC_fallback_error')
.classList.add("noVNC_open");
} catch (exc) {
document.write("noVNC encountered an error.");
}
// Don't return true since this would prevent the error
// from being printed to the browser console.
return false;
}
window.addEventListener('error', function (evt) { handleError(evt, evt.error); });
window.addEventListener('unhandledrejection', function (evt) { handleError(evt.reason, evt.reason); });
})();

View File

@ -247,6 +247,11 @@ select:active {
font-weight: normal;
}
#noVNC_fallback_errormsg .noVNC_message {
display: inline-block;
text-align: left;
}
#noVNC_fallback_error .noVNC_location {
font-style: italic;
font-size: 0.8em;

View File

@ -21,46 +21,6 @@ import RFB from "../core/rfb.js";
import Display from "../core/display.js";
import * as WebUtil from "./webutil.js";
// Fallback for all uncought errors
window.addEventListener('error', function(event) {
try {
var msg, div, text;
msg = document.getElementById('noVNC_fallback_errormsg');
// Only show the initial error
if (msg.hasChildNodes()) {
return false;
}
div = document.createElement("div");
div.appendChild(document.createTextNode(event.message));
msg.appendChild(div);
div = document.createElement("div");
div.className = 'noVNC_location';
text = event.filename + ":" + event.lineno + ":" + event.colno;
div.appendChild(document.createTextNode(text));
msg.appendChild(div);
if ((event.error !== undefined) &&
(event.error.stack !== undefined)) {
div = document.createElement("div");
div.className = 'noVNC_stack';
div.appendChild(document.createTextNode(event.error.stack));
msg.appendChild(div);
}
document.getElementById('noVNC_fallback_error')
.classList.add("noVNC_open");
} catch (exc) {
document.write("noVNC encountered an error.");
}
// Don't return true since this would prevent the error
// from being printed to the browser console.
return false;
});
const UI = {
connected: false,
@ -1751,11 +1711,10 @@ if (l10n.language !== "en" && l10n.dictionary === undefined) {
// wait for translations to load before loading the UI
UI.prime();
}, function (err) {
throw err;
throw err;
});
} else {
UI.prime();
}
export default UI;

View File

@ -21,6 +21,17 @@ var vendor_path = path.resolve(__dirname, '..', 'vendor');
var out_dir_base = path.resolve(__dirname, '..', 'build');
var lib_dir_base = path.resolve(__dirname, '..', 'lib');
const no_copy_files = new Set([
// skip these -- they don't belong in the processed application
path.join(vendor_path, 'sinon.js'),
path.join(vendor_path, 'browser-es-module-loader'),
]);
const no_transform_files = new Set([
// don't transform this -- we want it imported as-is to properly catch loading errors
path.join(app_path, 'error-handler.js'),
]);
// walkDir *recursively* walks directories trees,
// calling the callback for all normal files found.
var walkDir = function (base_path, cb, filter) {
@ -93,6 +104,8 @@ var make_lib_files = function (import_format, source_maps, with_app_dir) {
const helper = helpers[import_format];
var handleDir = (js_only, in_path_base, filename) => {
if (no_copy_files.has(filename)) return;
const out_path = path.join(out_path_base, path.relative(in_path_base, filename));
if(path.extname(filename) !== '.js') {
if (!js_only) {
@ -103,10 +116,17 @@ var make_lib_files = function (import_format, source_maps, with_app_dir) {
}
fse.ensureDir(path.dirname(out_path), () => {
if (no_transform_files.has(filename)) {
console.log(`Writing ${out_path}`);
fse.copy(filename, out_path, (err) => { if (err) throw err; });
return;
}
const opts = babel_opts();
if (helper && helpers.optionsOverride) {
helper.optionsOverride(opts);
}
babel.transformFile(filename, babel_opts(), (err, res) => {
console.log(`Writing ${out_path}`);
if (err) throw err;
@ -124,11 +144,11 @@ var make_lib_files = function (import_format, source_maps, with_app_dir) {
});
};
walkDir(core_path, handleDir.bind(null, true, in_path || core_path));
walkDir(vendor_path, handleDir.bind(null, true, in_path || main_path), (filepath, stats) => !((stats.isDirectory() && path.basename(filepath) === 'browser-es-module-loader') || path.basename(filepath) === 'sinon.js'));
walkDir(core_path, handleDir.bind(null, true, in_path || core_path), (filename, stats) => !no_copy_files.has(filename));
walkDir(vendor_path, handleDir.bind(null, true, in_path || main_path), (filename, stats) => !no_copy_files.has(filename));
if (with_app_dir) {
walkDir(app_path, handleDir.bind(null, false, in_path || app_path));
walkDir(app_path, handleDir.bind(null, false, in_path || app_path), (filename, stats) => !no_copy_files.has(filename));
const out_app_path = path.join(out_path_base, 'app.js');
if (helper && helper.appWriter) {

View File

@ -1180,7 +1180,22 @@ if (typeof document != 'undefined' && document.getElementsByTagName) {
if (script.type == 'module' && !script.loaded) {
script.loaded = true;
if (script.src) {
loader.import(script.src);
loader.import(script.src).catch(function(err) {
// dispatch an error event so that we can display in errors in browsers
// that don't yet support unhandledrejection
try {
var evt = new Event('error');
} catch (_eventError) {
var evt = document.createEvent('Event');
evt.initEvent('error', true, true);
}
evt.message = err.message;
evt.error = err;
window.dispatchEvent(evt);
// throw so it still shows up in the console
throw err;
});
}
// anonymous modules supported via a custom naming scheme and registry
else {
@ -1191,7 +1206,22 @@ if (typeof document != 'undefined' && document.getElementsByTagName) {
var anonName = resolveIfNotPlain(uri, baseURI);
anonSources[anonName] = script.innerHTML;
loader.import(anonName);
loader.import(anonName).catch(function(err) {
// dispatch an error event so that we can display in errors in browsers
// that don't yet support unhandledrejection
try {
var evt = new Event('error');
} catch (_eventError) {
var evt = document.createEvent('Event');
evt.initEvent('error', true, true);
}
evt.message = err.message;
evt.error = err;
window.dispatchEvent(evt);
// throw so it still shows up in the console
throw err;
});
}
}
}

View File

@ -20,7 +20,22 @@ if (typeof document != 'undefined' && document.getElementsByTagName) {
if (script.type == 'module' && !script.loaded) {
script.loaded = true;
if (script.src) {
loader.import(script.src);
loader.import(script.src).catch(function(err) {
// dispatch an error event so that we can display in errors in browsers
// that don't yet support unhandledrejection
try {
var evt = new Event('error');
} catch (_eventError) {
var evt = document.createEvent('Event');
evt.initEvent('error', true, true);
}
evt.message = err.message;
evt.error = err;
window.dispatchEvent(evt);
// throw so it still shows up in the console
throw err;
});
}
// anonymous modules supported via a custom naming scheme and registry
else {
@ -31,7 +46,22 @@ if (typeof document != 'undefined' && document.getElementsByTagName) {
var anonName = resolveIfNotPlain(uri, baseURI);
anonSources[anonName] = script.innerHTML;
loader.import(anonName);
loader.import(anonName).catch(function(err) {
// dispatch an error event so that we can display in errors in browsers
// that don't yet support unhandledrejection
try {
var evt = new Event('error');
} catch (_eventError) {
var evt = document.createEvent('Event');
evt.initEvent('error', true, true);
}
evt.message = err.message;
evt.error = err;
window.dispatchEvent(evt);
// throw so it still shows up in the console
throw err;
});
}
}
}

View File

@ -59,6 +59,9 @@
src='http://getfirebug.com/releases/lite/1.2/firebug-lite-compressed.js'></script>
-->
<!-- this is included as a normal file in order to catch script-loading errors as well -->
<script type="text/javascript" src="app/error-handler.js"></script>
<!-- begin scripts -->
<script src="vendor/browser-es-module-loader/dist/browser-es-module-loader.js"></script>
<script type="module" src="app/ui.js"></script>