From adfc9d3f548b8d3c7aac837bd53c68b16b14b137 Mon Sep 17 00:00:00 2001 From: Solly Ross Date: Tue, 28 Feb 2017 20:47:02 -0500 Subject: [PATCH] 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) --- app/error-handler.js | 61 +++++++++++++++++++ app/styles/base.css | 5 ++ app/ui.js | 43 +------------ utils/use_require.js | 26 +++++++- .../dist/browser-es-module-loader.js | 34 ++++++++++- .../src/browser-es-module-loader.js | 34 ++++++++++- vnc.html | 3 + 7 files changed, 157 insertions(+), 49 deletions(-) create mode 100644 app/error-handler.js diff --git a/app/error-handler.js b/app/error-handler.js new file mode 100644 index 00000000..30f77068 --- /dev/null +++ b/app/error-handler.js @@ -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); }); +})(); diff --git a/app/styles/base.css b/app/styles/base.css index 4cec1735..30146963 100644 --- a/app/styles/base.css +++ b/app/styles/base.css @@ -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; diff --git a/app/ui.js b/app/ui.js index d59204c8..46826221 100644 --- a/app/ui.js +++ b/app/ui.js @@ -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; diff --git a/utils/use_require.js b/utils/use_require.js index bfa14f6f..f0383d09 100755 --- a/utils/use_require.js +++ b/utils/use_require.js @@ -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) { diff --git a/vendor/browser-es-module-loader/dist/browser-es-module-loader.js b/vendor/browser-es-module-loader/dist/browser-es-module-loader.js index 3d123da6..0d22bab2 100644 --- a/vendor/browser-es-module-loader/dist/browser-es-module-loader.js +++ b/vendor/browser-es-module-loader/dist/browser-es-module-loader.js @@ -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; + }); } } } diff --git a/vendor/browser-es-module-loader/src/browser-es-module-loader.js b/vendor/browser-es-module-loader/src/browser-es-module-loader.js index 9ba7c36e..a9d72097 100644 --- a/vendor/browser-es-module-loader/src/browser-es-module-loader.js +++ b/vendor/browser-es-module-loader/src/browser-es-module-loader.js @@ -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; + }); } } } diff --git a/vnc.html b/vnc.html index b4f43e73..10cc1430 100644 --- a/vnc.html +++ b/vnc.html @@ -59,6 +59,9 @@ src='http://getfirebug.com/releases/lite/1.2/firebug-lite-compressed.js'> --> + + +