From 58c49fb6b5346eee1cc7f755c7c274eafc0024b9 Mon Sep 17 00:00:00 2001 From: Joe Haddad Date: Mon, 13 Mar 2017 23:59:19 -0400 Subject: [PATCH 1/4] Modularize and extract crashOverlay to iframe --- packages/react-dev-utils/crashOverlay.js | 1114 +++++++++++++--------- packages/react-dev-utils/package.json | 4 +- 2 files changed, 679 insertions(+), 439 deletions(-) diff --git a/packages/react-dev-utils/crashOverlay.js b/packages/react-dev-utils/crashOverlay.js index 4f58e927f3c..561a3a57e0f 100644 --- a/packages/react-dev-utils/crashOverlay.js +++ b/packages/react-dev-utils/crashOverlay.js @@ -9,71 +9,205 @@ 'use strict'; -var codeFrame = require('babel-code-frame'); +function _interopDefault(ex) { + return ex && typeof ex === 'object' && 'default' in ex ? ex['default'] : ex; +} + +var stackFrameParser = require('stack-frame-parser'); +var stackFrameMapper = require('stack-frame-mapper'); +var stackFrameUnmapper = require('stack-frame-unmapper'); +var codeFrame = _interopDefault(require('babel-code-frame')); var ansiHTML = require('./ansiHTML'); -var StackTraceResolve = require('stack-frame-resolver').default; -var CONTEXT_SIZE = 3; +var boundErrorHandler = null; -var black = '#293238'; -var darkGray = '#878e91'; -var lightGray = '#fafafa'; -var red = '#ce1126'; -var lightRed = '#fccfcf'; -var yellow = '#fbf5b4'; +function errorHandler(callback, e) { + if (!e.error) return; + // $FlowFixMe + var error = e.error; -function getHead() { - return document.head || document.getElementsByTagName('head')[0]; + if (error instanceof Error) { + callback(error); + } else { + // A non-error was thrown, we don't have a trace. :( + // Look in your browser's devtools for more information + callback(new Error(error)); + } } -var injectedCss = []; +function registerUnhandledError(target, callback) { + if (boundErrorHandler !== null) return; + boundErrorHandler = errorHandler.bind(undefined, callback); + target.addEventListener('error', boundErrorHandler); +} -// From: http://stackoverflow.com/a/524721/127629 -function injectCss(css) { - var head = getHead(); - var style = document.createElement('style'); +function unregisterUnhandledError(target) { + if (boundErrorHandler === null) return; + target.removeEventListener('error', boundErrorHandler); + boundErrorHandler = null; +} - style.type = 'text/css'; - if (style.styleSheet) { - style.styleSheet.cssText = css; +var boundRejectionHandler = null; + +function rejectionHandler(callback, e) { + if (e == null || e.reason == null) { + return callback(new Error('Unknown')); + } + var reason = e.reason; + + if (reason instanceof Error) { + return callback(reason); + } + // A non-error was rejected, we don't have a trace :( + // Look in your browser's devtools for more information + return callback(new Error(reason)); +} + +function registerUnhandledRejection(target, callback) { + if (boundRejectionHandler !== null) return; + boundRejectionHandler = rejectionHandler.bind(undefined, callback); + // $FlowFixMe + target.addEventListener('unhandledrejection', boundRejectionHandler); +} + +function unregisterUnhandledRejection(target) { + if (boundRejectionHandler === null) return; + // $FlowFixMe + target.removeEventListener('unhandledrejection', boundRejectionHandler); + boundRejectionHandler = null; +} + +var SHORTCUT_ESCAPE = 'SHORTCUT_ESCAPE'; +var SHORTCUT_LEFT = 'SHORTCUT_LEFT'; +var SHORTCUT_RIGHT = 'SHORTCUT_RIGHT'; + +var boundKeyHandler = null; + +function keyHandler(callback, e) { + var key = e.key, keyCode = e.keyCode, which = e.which; + + if (key === 'Escape' || keyCode === 27 || which === 27) { + callback(SHORTCUT_ESCAPE); + } else if (key === 'ArrowLeft' || keyCode === 37 || which === 37) { + callback(SHORTCUT_LEFT); + } else if (key === 'ArrowRight' || keyCode === 39 || which === 39) { + callback(SHORTCUT_RIGHT); + } +} + +function registerShortcuts(target, callback) { + if (boundKeyHandler !== null) return; + boundKeyHandler = keyHandler.bind(undefined, callback); + target.addEventListener('keydown', boundKeyHandler); +} + +function unregisterShortcuts(target) { + if (boundKeyHandler === null) return; + target.removeEventListener('keydown', boundKeyHandler); + boundKeyHandler = null; +} + +var stackTraceRegistered = false; +// Default: https://docs.microsoft.com/en-us/scripting/javascript/reference/stacktracelimit-property-error-javascript +var restoreStackTraceValue = 10; + +var MAX_STACK_LENGTH = 50; + +function registerStackTraceLimit() { + var limit = arguments.length > 0 && arguments[0] !== undefined + ? arguments[0] + : MAX_STACK_LENGTH; + + if (stackTraceRegistered) return; + try { + restoreStackTraceValue = Error.stackTraceLimit; + Error.stackTraceLimit = limit; + stackTraceRegistered = true; + } catch (e) { + // Not all browsers support this so we don't care if it errors + } +} + +function unregisterStackTraceLimit() { + if (!stackTraceRegistered) return; + try { + Error.stackTraceLimit = restoreStackTraceValue; + stackTraceRegistered = false; + } catch (e) { + // Not all browsers support this so we don't care if it errors + } +} + +var permanentRegister = function proxyConsole(type, callback) { + var orig = console[type]; + console[type] = function __stack_frame_overlay_proxy_console__() { + var message = [].slice.call(arguments).join(' '); + callback(message); + return orig.apply(this, arguments); + }; +}; + +var recorded = []; + +var errorsConsumed = 0; + +function consume(error) { + var unhandledRejection = arguments.length > 1 && arguments[1] !== undefined + ? arguments[1] + : false; + var contextSize = arguments.length > 2 && arguments[2] !== undefined + ? arguments[2] + : 3; + + var parsedFrames = stackFrameParser.parse(error); + var enhancedFramesPromise = void 0; + if (error.__unmap_source) { + enhancedFramesPromise = stackFrameUnmapper.unmap( + error.__unmap_source, + parsedFrames, + contextSize + ); } else { - style.appendChild(document.createTextNode(css)); + enhancedFramesPromise = stackFrameMapper.map(parsedFrames, contextSize); } + return enhancedFramesPromise.then(function(enhancedFrames) { + enhancedFrames = enhancedFrames.filter(function(_ref) { + var functionName = _ref.functionName; + return functionName == null || + functionName.indexOf('__stack_frame_overlay_proxy_console__') === -1; + }); + recorded[++errorsConsumed] = { + error: error, + unhandledRejection: unhandledRejection, + contextSize: contextSize, + enhancedFrames: enhancedFrames, + }; + return errorsConsumed; + }); +} - head.appendChild(style); - injectedCss.push(style); +function getErrorRecord(ref) { + return recorded[ref]; } -var css = [ - '.cra-container {', - ' padding-right: 15px;', - ' padding-left: 15px;', - ' margin-right: auto;', - ' margin-left: auto;', - '}', - '', - '@media (min-width: 768px) {', - ' .cra-container {', - ' width: calc(750px - 6em);', - ' }', - '}', - '', - '@media (min-width: 992px) {', - ' .cra-container {', - ' width: calc(970px - 6em);', - ' }', - '}', - '', - '@media (min-width: 1200px) {', - ' .cra-container {', - ' width: calc(1170px - 6em);', - ' }', - '}', -].join('\n'); +function drain() { + // $FlowFixMe + var keys = Object.keys(recorded); + for (var index = 0; index < keys.length; ++index) { + delete recorded[keys[index]]; + } +} -var overlayStyle = { +var black = '#293238'; +var darkGray = '#878e91'; +var lightGray = '#fafafa'; +var red = '#ce1126'; +var lightRed = '#fccfcf'; +var yellow = '#fbf5b4'; + +var iframeStyle = { + 'background-color': lightGray, position: 'fixed', - 'box-sizing': 'border-box', top: '1em', left: '1em', bottom: '1em', @@ -81,16 +215,19 @@ var overlayStyle = { width: 'calc(100% - 2em)', height: 'calc(100% - 2em)', 'border-radius': '3px', - 'background-color': lightGray, - padding: '4rem', + 'box-shadow': '0 0 6px 0 rgba(0, 0, 0, 0.5)', 'z-index': 1337, +}; + +var overlayStyle = { + 'box-sizing': 'border-box', + padding: '4rem', 'font-family': 'Consolas, Menlo, monospace', color: black, 'white-space': 'pre-wrap', overflow: 'auto', 'overflow-x': 'hidden', 'word-break': 'break-all', - 'box-shadow': '0 0 6px 0 rgba(0, 0, 0, 0.5)', 'line-height': 1.5, }; @@ -220,153 +357,163 @@ var footerStyle = { color: darkGray, }; +var injectedCount = 0; +var injectedCache = {}; + +function getHead(document) { + return document.head || document.getElementsByTagName('head')[0]; +} + +function injectCss(document, css) { + var head = getHead(document); + var style = document.createElement('style'); + style.type = 'text/css'; + style.appendChild(document.createTextNode(css)); + head.appendChild(style); + + injectedCache[++injectedCount] = style; + return injectedCount; +} + function applyStyles(element, styles) { element.setAttribute('style', ''); - // Firefox can't handle const due to non-compliant implementation - // Revisit Jan 2016 - // https://developer.mozilla.org/en-US/Firefox/Releases/51#JavaScript - // https://bugzilla.mozilla.org/show_bug.cgi?id=1101653 for (var key in styles) { - if (!styles.hasOwnProperty(key)) continue; - var val = styles[key]; - if (typeof val === 'function') val = val(); - element.style[key] = val.toString(); + if (!styles.hasOwnProperty(key)) { + continue; + } + // $FlowFixMe + element.style[key] = styles[key]; } } -var overlayReference = null; -var additionalReference = null; -var capturedErrors = []; -var viewIndex = -1; -var frameSettings = []; +function createHint(document, hint) { + var span = document.createElement('span'); + span.appendChild(document.createTextNode(hint)); + applyStyles(span, hintStyle); + return span; +} -function consumeEvent(e) { - e.preventDefault(); - e.target.blur(); +function createClose(document, callback) { + var hints = document.createElement('div'); + applyStyles(hints, hintsStyle); + + var close = createHint(document, '×'); + close.addEventListener('click', function() { + return callback(); + }); + applyStyles(close, closeButtonStyle); + hints.appendChild(close); + return hints; } -function accessify(node) { - node.setAttribute('tabindex', 0); +function enableTabClick(node) { + node.setAttribute('tabindex', '0'); node.addEventListener('keydown', function(e) { var key = e.key, which = e.which, keyCode = e.keyCode; + if (key === 'Enter' || which === 13 || keyCode === 13) { e.preventDefault(); - e.target.click(); + if (typeof e.target.click === 'function') { + e.target.click(); + } } }); } -function renderAdditional() { - if (additionalReference.lastChild) { - additionalReference.removeChild(additionalReference.lastChild); - } - - var text = ' '; - if (capturedErrors.length <= 1) { - additionalReference.appendChild(document.createTextNode(text)); - return; - } - text = 'Errors ' + (viewIndex + 1) + ' of ' + capturedErrors.length; - var span = document.createElement('span'); - span.appendChild(document.createTextNode(text)); - var group = document.createElement('span'); - applyStyles(group, groupStyle); - var left = document.createElement('button'); - applyStyles(left, groupElemLeft); - left.addEventListener('click', function(e) { - consumeEvent(e); - switchError(-1); - }); - left.appendChild(document.createTextNode('←')); - accessify(left); - var right = document.createElement('button'); - applyStyles(right, groupElemRight); - right.addEventListener('click', function(e) { - consumeEvent(e); - switchError(1); - }); - right.appendChild(document.createTextNode('→')); - accessify(right); - group.appendChild(left); - group.appendChild(right); - span.appendChild(group); - additionalReference.appendChild(span); -} - function removeNextBr(parent, component) { while (component != null && component.tagName.toLowerCase() !== 'br') { - component = component.nextSibling; + component = component.nextElementSibling; } if (component != null) { parent.removeChild(component); } } -function absolutifyCode(component) { +function absolutifyCaret(component) { var ccn = component.childNodes; for (var index = 0; index < ccn.length; ++index) { var c = ccn[index]; - if (c.tagName.toLowerCase() !== 'span') continue; - var text = c.innerText.replace(/\s/g, ''); - if (text !== '|^') continue; + // $FlowFixMe + if (c.tagName.toLowerCase() !== 'span') { + continue; + } + var _text = c.innerText; + if (_text == null) { + continue; + } + var text = _text.replace(/\s/g, ''); + if (text !== '|^') { + continue; + } + // $FlowFixMe c.style.position = 'absolute'; + // $FlowFixMe removeNextBr(component, c); } } -function sourceCodePre(sourceLines, lineNum, columnNum) { - var main = arguments.length > 3 && arguments[3] !== undefined - ? arguments[3] +function createCode(document, sourceLines, lineNum, columnNum, contextSize) { + var main = arguments.length > 5 && arguments[5] !== undefined + ? arguments[5] : false; var sourceCode = []; var whiteSpace = Infinity; - sourceLines.forEach(function(_ref2) { - var text = _ref2.text; + sourceLines.forEach(function(e) { + var text = e.content; var m = text.match(/^\s*/); - if (text === '') return; + if (text === '') { + return; + } if (m && m[0]) { whiteSpace = Math.min(whiteSpace, m[0].length); } else { whiteSpace = 0; } }); - sourceLines.forEach(function(_ref3) { - var text = _ref3.text, line = _ref3.line; + sourceLines.forEach(function(e) { + var text = e.content; + var line = e.lineNumber; - if (isFinite(whiteSpace)) text = text.substring(whiteSpace); + if (isFinite(whiteSpace)) { + text = text.substring(whiteSpace); + } sourceCode[line - 1] = text; }); - sourceCode = sourceCode.join('\n'); var ansiHighlight = codeFrame( - sourceCode, + sourceCode.join('\n'), lineNum, columnNum - (isFinite(whiteSpace) ? whiteSpace : 0), { forceColor: true, - linesAbove: CONTEXT_SIZE, - linesBelow: CONTEXT_SIZE, + linesAbove: contextSize, + linesBelow: contextSize, } ); var htmlHighlight = ansiHTML(ansiHighlight); var code = document.createElement('code'); code.innerHTML = htmlHighlight; - absolutifyCode(code); + absolutifyCaret(code); applyStyles(code, codeStyle); var ccn = code.childNodes; - for (var index = 0; index < ccn.length; ++index) { + oLoop: for (var index = 0; index < ccn.length; ++index) { var node = ccn[index]; - var breakOut = false; var ccn2 = node.childNodes; for (var index2 = 0; index2 < ccn2.length; ++index2) { var lineNode = ccn2[index2]; - if (lineNode.innerText.indexOf(' ' + lineNum + ' |') === -1) continue; + var text = lineNode.innerText; + if (text == null) { + continue; + } + if (text.indexOf(' ' + lineNum + ' |') === -1) { + continue; + } + // $FlowFixMe applyStyles(node, main ? primaryErrorStyle : secondaryErrorStyle); - breakOut = true; + break oLoop; } - if (breakOut) break; } var pre = document.createElement('pre'); applyStyles(pre, preStyle); @@ -374,66 +521,17 @@ function sourceCodePre(sourceLines, lineNum, columnNum) { return pre; } -function createHint(hint) { - var span = document.createElement('span'); - span.appendChild(document.createTextNode(hint)); - applyStyles(span, hintStyle); - return span; -} - -function hintsDiv() { - var hints = document.createElement('div'); - applyStyles(hints, hintsStyle); - - var close = createHint('×'); - close.addEventListener('click', function() { - unmount(); - }); - applyStyles(close, closeButtonStyle); - hints.appendChild(close); - return hints; -} - -function frameDiv(functionName, url, internalUrl) { - var frame = document.createElement('div'); - var frameFunctionName = document.createElement('div'); - - var cleanedFunctionName = void 0; - if (!functionName || functionName === 'Object.') { - cleanedFunctionName = '(anonymous function)'; - } else { - cleanedFunctionName = functionName; - } - - var cleanedUrl = url.replace('webpack://', '.'); - - if (internalUrl) { - applyStyles( - frameFunctionName, - Object.assign({}, functionNameStyle, depStyle) - ); - } else { - applyStyles(frameFunctionName, functionNameStyle); - } - - frameFunctionName.appendChild(document.createTextNode(cleanedFunctionName)); - frame.appendChild(frameFunctionName); - - var frameLink = document.createElement('div'); - applyStyles(frameLink, linkStyle); - var frameAnchor = document.createElement('a'); - applyStyles(frameAnchor, anchorStyle); - //frameAnchor.href = url - frameAnchor.appendChild(document.createTextNode(cleanedUrl)); - frameLink.appendChild(frameAnchor); - frame.appendChild(frameLink); - - return frame; +function isInternalFile(url, sourceFileName) { + return url.indexOf('/~/') !== -1 || + url.indexOf('/node_modules/') !== -1 || + url.trim().indexOf(' ') !== -1 || + sourceFileName == null || + sourceFileName.length === 0; } -function getGroupToggle(omitsCount, omitBundle) { +function getGroupToggle(document, omitsCount, omitBundle) { var omittedFrames = document.createElement('div'); - accessify(omittedFrames); + enableTabClick(omittedFrames); var text1 = document.createTextNode( '\u25B6 ' + omitsCount + ' stack frames were collapsed.' ); @@ -461,15 +559,21 @@ function getGroupToggle(omitsCount, omitBundle) { return omittedFrames; } -function insertBeforeBundle(parent, omitsCount, omitBundle, actionElement) { +function insertBeforeBundle( + document, + parent, + omitsCount, + omitBundle, + actionElement +) { var children = document.getElementsByName('bundle-' + omitBundle); if (children.length < 1) return; var first = children[0]; - while (first.parentNode !== parent) { + while (first != null && first.parentNode !== parent) { first = first.parentNode; } var div = document.createElement('div'); - accessify(div); + enableTabClick(div); div.setAttribute('name', 'bundle-' + omitBundle); var text = document.createTextNode( '\u25BC ' + omitsCount + ' stack frames were expanded.' @@ -484,9 +588,48 @@ function insertBeforeBundle(parent, omitsCount, omitBundle, actionElement) { parent.insertBefore(div, first); } -function traceFrame( +function frameDiv(document, functionName, url, internalUrl) { + var frame = document.createElement('div'); + var frameFunctionName = document.createElement('div'); + + var cleanedFunctionName = void 0; + if (!functionName || functionName === 'Object.') { + cleanedFunctionName = '(anonymous function)'; + } else { + cleanedFunctionName = functionName; + } + + var cleanedUrl = url.replace('webpack://', '.'); + + if (internalUrl) { + applyStyles( + frameFunctionName, + Object.assign({}, functionNameStyle, depStyle) + ); + } else { + applyStyles(frameFunctionName, functionNameStyle); + } + + frameFunctionName.appendChild(document.createTextNode(cleanedFunctionName)); + frame.appendChild(frameFunctionName); + + var frameLink = document.createElement('div'); + applyStyles(frameLink, linkStyle); + var frameAnchor = document.createElement('a'); + applyStyles(frameAnchor, anchorStyle); + //frameAnchor.href = url + frameAnchor.appendChild(document.createTextNode(cleanedUrl)); + frameLink.appendChild(frameAnchor); + frame.appendChild(frameLink); + + return frame; +} + +function createFrame( + document, frameSetting, frame, + contextSize, critical, omits, omitBundle, @@ -498,11 +641,11 @@ function traceFrame( fileName = frame.fileName, lineNumber = frame.lineNumber, columnNumber = frame.columnNumber, - scriptLines = frame.scriptLines, - sourceFileName = frame.sourceFileName, - sourceLineNumber = frame.sourceLineNumber, - sourceColumnNumber = frame.sourceColumnNumber, - sourceLines = frame.sourceLines; + scriptLines = frame._scriptCode, + sourceFileName = frame._originalFileName, + sourceLineNumber = frame._originalLineNumber, + sourceColumnNumber = frame._originalColumnNumber, + sourceLines = frame._originalScriptCode; var url = void 0; if (!compiled && sourceFileName) { @@ -522,19 +665,17 @@ function traceFrame( var collapseElement = null; if (!internalUrl || lastElement) { if (omits.value > 0) { - var omittedFrames = getGroupToggle(omits.value, omitBundle); - setTimeout( - (function() { - insertBeforeBundle.apply(undefined, arguments); - }).bind( - undefined, + var capV = omits.value; + var omittedFrames = getGroupToggle(document, capV, omitBundle); + window.requestAnimationFrame(function() { + insertBeforeBundle( + document, parentContainer, - omits.value, + capV, omitBundle, omittedFrames - ), - 1 - ); + ); + }); if (lastElement && internalUrl) { collapseElement = omittedFrames; } else { @@ -545,7 +686,7 @@ function traceFrame( omits.value = 0; } - var elem = frameDiv(functionName, url, internalUrl); + var elem = frameDiv(document, functionName, url, internalUrl); if (needsHidden) { applyStyles(elem, hiddenStyle); elem.setAttribute('name', 'bundle-' + omitBundle); @@ -555,15 +696,24 @@ function traceFrame( if (!internalUrl) { if (compiled && scriptLines.length !== 0) { elem.appendChild( - sourceCodePre(scriptLines, lineNumber, columnNumber, critical) + createCode( + document, + scriptLines, + lineNumber, + columnNumber, + contextSize, + critical + ) ); hasSource = true; } else if (!compiled && sourceLines.length !== 0) { elem.appendChild( - sourceCodePre( + createCode( + document, sourceLines, sourceLineNumber, sourceColumnNumber, + contextSize, critical ) ); @@ -574,9 +724,18 @@ function traceFrame( return { elem: elem, hasSource: hasSource, collapseElement: collapseElement }; } -function lazyFrame(parent, factory, lIndex) { +function createFrameWrapper( + document, + parent, + factory, + lIndex, + frameSettings, + contextSize +) { var fac = factory(); - if (fac == null) return; + if (fac == null) { + return; + } var hasSource = fac.hasSource, elem = fac.elem, collapseElement = fac.collapseElement; @@ -585,27 +744,34 @@ function lazyFrame(parent, factory, lIndex) { elemWrapper.appendChild(elem); if (hasSource) { - (function() { - var compiledDiv = document.createElement('div'); - accessify(compiledDiv); - applyStyles(compiledDiv, toggleStyle); - - var o = frameSettings[lIndex]; - var compiledText = document.createTextNode( - 'View ' + (o && o.compiled ? 'source' : 'compiled') - ); - compiledDiv.addEventListener('click', function() { - if (o) o.compiled = !o.compiled; + var compiledDiv = document.createElement('div'); + enableTabClick(compiledDiv); + applyStyles(compiledDiv, toggleStyle); - var next = lazyFrame(parent, factory, lIndex); - if (next != null) { - parent.insertBefore(next, elemWrapper); - parent.removeChild(elemWrapper); - } - }); - compiledDiv.appendChild(compiledText); - elemWrapper.appendChild(compiledDiv); - })(); + var o = frameSettings[lIndex]; + var compiledText = document.createTextNode( + 'View ' + (o && o.compiled ? 'source' : 'compiled') + ); + compiledDiv.addEventListener('click', function() { + if (o) { + o.compiled = !o.compiled; + } + + var next = createFrameWrapper( + document, + parent, + factory, + lIndex, + frameSettings, + contextSize + ); + if (next != null) { + parent.insertBefore(next, elemWrapper); + parent.removeChild(elemWrapper); + } + }); + compiledDiv.appendChild(compiledText); + elemWrapper.appendChild(compiledDiv); } if (collapseElement != null) { @@ -615,7 +781,12 @@ function lazyFrame(parent, factory, lIndex) { return elemWrapper; } -function traceDiv(resolvedFrames) { +function createFrames(document, resolvedFrames, frameSettings, contextSize) { + if (resolvedFrames.length !== frameSettings.length) { + throw new Error( + 'You must give a frame settings array of identical length to resolved frames.' + ); + } var trace = document.createElement('div'); applyStyles(trace, traceStyle); @@ -624,21 +795,28 @@ function traceDiv(resolvedFrames) { var omits = { value: 0, bundle: 1 }; resolvedFrames.forEach(function(frame) { var lIndex = index++; - var elem = lazyFrame( + var elem = createFrameWrapper( + document, trace, - traceFrame.bind( + createFrame.bind( undefined, + document, frameSettings[lIndex], frame, + contextSize, critical, omits, omits.bundle, trace, index === resolvedFrames.length ), - lIndex + lIndex, + frameSettings, + contextSize ); - if (elem == null) return; + if (elem == null) { + return; + } critical = false; trace.appendChild(elem); }); @@ -648,7 +826,7 @@ function traceDiv(resolvedFrames) { return trace; } -function footer() { +function createFooter(document) { var div = document.createElement('div'); applyStyles(div, footerStyle); div.appendChild( @@ -665,19 +843,74 @@ function footer() { return div; } -function render(error, name, message, resolvedFrames) { - dispose(); +function consumeEvent(e) { + e.preventDefault(); + if (typeof e.target.blur === 'function') { + e.target.blur(); + } +} - frameSettings = resolvedFrames.map(function() { - return { compiled: false }; - }); +function updateAdditional( + document, + additionalReference, + currentError, + totalErrors, + switchCallback +) { + if (additionalReference.lastChild) { + additionalReference.removeChild(additionalReference.lastChild); + } - injectCss(css); + var text = ' '; + if (totalErrors <= 1) { + additionalReference.appendChild(document.createTextNode(text)); + return; + } + text = 'Errors ' + currentError + ' of ' + totalErrors; + var span = document.createElement('span'); + span.appendChild(document.createTextNode(text)); + var group = document.createElement('span'); + applyStyles(group, groupStyle); + var left = document.createElement('button'); + applyStyles(left, groupElemLeft); + left.addEventListener('click', function(e) { + consumeEvent(e); + switchCallback(-1); + }); + left.appendChild(document.createTextNode('←')); + enableTabClick(left); + var right = document.createElement('button'); + applyStyles(right, groupElemRight); + right.addEventListener('click', function(e) { + consumeEvent(e); + switchCallback(1); + }); + right.appendChild(document.createTextNode('→')); + enableTabClick(right); + group.appendChild(left); + group.appendChild(right); + span.appendChild(group); + additionalReference.appendChild(span); +} +function createOverlay( + document, + name, + message, + frames, + contextSize, + currentError, + totalErrors, + switchCallback, + closeCallback +) { + var frameSettings = frames.map(function() { + return { compiled: false }; + }); // Create overlay var overlay = document.createElement('div'); applyStyles(overlay, overlayStyle); - overlay.appendChild(hintsDiv()); + overlay.appendChild(createClose(document, closeCallback)); // Create container var container = document.createElement('div'); @@ -685,10 +918,16 @@ function render(error, name, message, resolvedFrames) { overlay.appendChild(container); // Create additional - additionalReference = document.createElement('div'); - applyStyles(additionalReference, additionalStyle); - container.appendChild(additionalReference); - renderAdditional(); + var additional = document.createElement('div'); + applyStyles(additional, additionalStyle); + container.appendChild(additional); + updateAdditional( + document, + additional, + currentError, + totalErrors, + switchCallback + ); // Create header var header = document.createElement('div'); @@ -701,235 +940,234 @@ function render(error, name, message, resolvedFrames) { container.appendChild(header); // Create trace - container.appendChild(traceDiv(resolvedFrames)); + container.appendChild( + createFrames(document, frames, frameSettings, contextSize) + ); // Show message - container.appendChild(footer()); + container.appendChild(createFooter(document)); - // Mount - document.body.appendChild((overlayReference = overlay)); + return { + overlay: overlay, + additional: additional, + }; } -function dispose() { - if (overlayReference === null) return; - document.body.removeChild(overlayReference); - overlayReference = null; - var head = getHead(); - injectedCss.forEach(function(node) { - head.removeChild(node); - }); - injectedCss = []; -} +var CONTEXT_SIZE = 3; +var iframeReference = null; +var additionalReference = null; +var errorReferences = []; +var currReferenceIndex = -1; -function unmount() { - dispose(); - capturedErrors = []; - viewIndex = -1; -} +var css = [ + '.cra-container {', + ' padding-right: 15px;', + ' padding-left: 15px;', + ' margin-right: auto;', + ' margin-left: auto;', + '}', + '', + '@media (min-width: 768px) {', + ' .cra-container {', + ' width: calc(750px - 6em);', + ' }', + '}', + '', + '@media (min-width: 992px) {', + ' .cra-container {', + ' width: calc(970px - 6em);', + ' }', + '}', + '', + '@media (min-width: 1200px) {', + ' .cra-container {', + ' width: calc(1170px - 6em);', + ' }', + '}', +].join('\n'); -function isInternalFile(url, sourceFileName) { - return url.indexOf('/~/') !== -1 || - url.trim().indexOf(' ') !== -1 || - !sourceFileName; +function render(name, message, resolvedFrames) { + disposeCurrentView(); + + var iframe = window.document.createElement('iframe'); + applyStyles(iframe, iframeStyle); + iframeReference = iframe; + iframe.onload = function() { + if (iframeReference == null) return; + var w = iframeReference.contentWindow; + var document = iframeReference.contentDocument; + + var _createOverlay = createOverlay( + document, + name, + message, + resolvedFrames, + CONTEXT_SIZE, + currReferenceIndex + 1, + errorReferences.length, + function(offset) { + switchError(offset); + }, + function() { + unmount(); + } + ), + overlay = _createOverlay.overlay, + additional = _createOverlay.additional; + + if (w != null) { + w.onkeydown = function(event) { + keyHandler( + function(type) { + return shortcutHandler(type); + }, + event + ); + }; + } + injectCss(iframeReference.contentDocument, css); + if (document.body != null) { + document.body.appendChild(overlay); + } + additionalReference = additional; + }; + window.document.body.appendChild(iframe); } -function renderError(index) { - viewIndex = index; - var _capturedErrors$index = capturedErrors[index], - error = _capturedErrors$index.error, - unhandledRejection = _capturedErrors$index.unhandledRejection, - resolvedFrames = _capturedErrors$index.resolvedFrames; +function renderErrorByIndex(index) { + currReferenceIndex = index; + + var _getErrorRecord = getErrorRecord(errorReferences[index]), + error = _getErrorRecord.error, + unhandledRejection = _getErrorRecord.unhandledRejection, + enhancedFrames = _getErrorRecord.enhancedFrames; if (unhandledRejection) { render( - error, 'Unhandled Rejection (' + error.name + ')', error.message, - resolvedFrames + enhancedFrames ); } else { - render(error, error.name, error.message, resolvedFrames); + render(error.name, error.message, enhancedFrames); + } +} + +function switchError(offset) { + var nextView = currReferenceIndex + offset; + if (nextView < 0 || nextView >= errorReferences.length) { + return; } + renderErrorByIndex(nextView); +} + +function disposeCurrentView() { + if (iframeReference === null) { + return; + } + window.document.body.removeChild(iframeReference); + iframeReference = null; + additionalReference = null; +} + +function unmount() { + disposeCurrentView(); + drain(); + errorReferences = []; + currReferenceIndex = -1; } function crash(error) { var unhandledRejection = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; - var sourceOverrides = arguments.length > 2 && arguments[2] !== undefined - ? arguments[2] - : []; - - if (module.hot) module.hot.decline(); - StackTraceResolve(error, CONTEXT_SIZE) - .then(function(resolvedFrames) { - resolvedFrames = resolvedFrames.filter(function(_ref) { - var functionName = _ref.functionName; - return functionName.indexOf('__cra_proxy_console__') === -1; - }); - var overrideCount = sourceOverrides.length, - frameCount = resolvedFrames.length; - var frameIndex = 0; - for ( - var overrideIndex = 0; - overrideIndex < overrideCount; - ++overrideIndex - ) { - var tag = sourceOverrides[overrideIndex]; - var shouldContinue = false; - for (; frameIndex < frameCount; ++frameIndex) { - var sourceFileName = resolvedFrames[frameIndex].sourceFileName; - - if (sourceFileName == null) continue; - if (sourceFileName.indexOf('/' + tag.file) !== -1) { - var prevLineNumber = resolvedFrames[frameIndex].sourceLineNumber; - if (Math.abs(prevLineNumber - tag.lineNum) < CONTEXT_SIZE) { - resolvedFrames[frameIndex].sourceLineNumber = tag.lineNum; - } - shouldContinue = true; - break; + if (module.hot && typeof module.hot.decline === 'function') { + module.hot.decline(); + } + consume(error, unhandledRejection, CONTEXT_SIZE) + .then(function(ref) { + errorReferences.push(ref); + if (iframeReference !== null && additionalReference !== null) { + updateAdditional( + iframeReference.contentDocument, + additionalReference, + currReferenceIndex + 1, + errorReferences.length, + function(offset) { + switchError(offset); } + ); + } else { + if (errorReferences.length !== 1) { + throw new Error('Something is *really* wrong.'); } - if (shouldContinue) continue; - break; - } - capturedErrors.push({ - error: error, - unhandledRejection: unhandledRejection, - resolvedFrames: resolvedFrames, - }); - if (overlayReference !== null) - renderAdditional(); - else { - renderError((viewIndex = 0)); + renderErrorByIndex((currReferenceIndex = 0)); } }) .catch(function(e) { - // This is another fail case (unlikely to happen) - // e.g. render(...) throws an error with provided arguments - console.log('Red box renderer error:', e); - unmount(); - render( - null, - 'Error', - 'There is an error with red box. *Please* report this (see console).', - [] - ); + console.log('Could not consume error:', e); }); } -function switchError(offset) { - try { - var nextView = viewIndex + offset; - if (nextView < 0 || nextView >= capturedErrors.length) return; - renderError(nextView); - } catch (e) { - console.log('Red box renderer error:', e); - unmount(); - render( - null, - 'Error', - 'There is an error with red box. *Please* report this (see console).', - [] - ); - } -} - -window.onerror = function(messageOrEvent, source, lineno, colno, error) { - if ( - error == null || - !(error instanceof Error) || - messageOrEvent.indexOf('Script error') !== -1 - ) { - crash(new Error(error || messageOrEvent)); // TODO: more helpful message - } else { - crash(error); - } -}; - -var promiseHandler = function promiseHandler(event) { - if (event != null && event.reason != null) { - var reason = event.reason; - - if (reason == null || !(reason instanceof Error)) { - crash(new Error(reason), true); - } else { - crash(reason, true); +function shortcutHandler(type) { + switch (type) { + case SHORTCUT_ESCAPE: { + unmount(); + break; + } + case SHORTCUT_LEFT: { + switchError(-1); + break; + } + case SHORTCUT_RIGHT: { + switchError(1); + break; } - } else { - crash(new Error('Unknown event'), true); } -}; - -window.addEventListener('unhandledrejection', promiseHandler); - -var escapeHandler = function escapeHandler(event) { - var key = event.key, keyCode = event.keyCode, which = event.which; - - if (key === 'Escape' || keyCode === 27 || which === 27) unmount(); - else if (key === 'ArrowLeft' || keyCode === 37 || which === 37) - switchError(-1); - else if (key === 'ArrowRight' || keyCode === 39 || which === 39) - switchError(1); -}; - -window.addEventListener('keydown', escapeHandler); - -try { - Error.stackTraceLimit = 50; -} catch (e) { - // Browser may not support this, we don't care. } -// eslint-disable-next-line -var proxyConsole = function proxyConsole(type) { - var orig = console[type]; - console[type] = function __cra_proxy_console__() { - var warning = [].slice.call(arguments).join(' '); +function inject() { + registerUnhandledError(window, function(error) { + return crash(error); + }); + registerUnhandledRejection(window, function(error) { + return crash(error, true); + }); + registerShortcuts(window, shortcutHandler); + registerStackTraceLimit(); + + permanentRegister('error', function(warning) { var nIndex = warning.indexOf('\n'); var message = warning; - if (nIndex !== -1) message = message.substring(0, nIndex); - var stack = warning - .substring(nIndex + 1) - .split('\n') - .filter(function(line) { - return line.indexOf('(at ') !== -1; - }) - .map(function(line) { - var prefix = '(at '; - var suffix = ')'; - line = line.substring(line.indexOf(prefix) + prefix.length); - line = line.substring(0, line.indexOf(suffix)); - var parts = line.split(/[:]/g); - if (parts.length !== 2) return null; - var file = parts[0]; - var lineNum = Number(parts[1]); - if (isNaN(lineNum)) return null; - return { file: file, lineNum: lineNum }; - }) - .filter(function(obj) { - return obj !== null; - }); - var error = void 0; - try { - throw new Error(message); - } catch (e) { - error = e; + if (nIndex !== -1) { + message = message.substring(0, nIndex); } - setTimeout(function() { - return crash(error, false, stack); + var stack = warning.substring(nIndex + 1); + window.requestAnimationFrame(function() { + return crash( + // $FlowFixMe + { + message: message, + stack: stack, + __unmap_source: '/static/js/bundle.js', + }, + false + ); }); - return orig.apply(this, arguments); - }; -}; + }); +} -// proxyConsole('error'); +function uninject() { + unregisterStackTraceLimit(); + unregisterShortcuts(window); + unregisterUnhandledRejection(window); + unregisterUnhandledError(window); +} -if (module.hot) { +inject(); +if (module.hot && typeof module.hot.dispose === 'function') { module.hot.dispose(function() { - unmount(); - window.removeEventListener('unhandledrejection', promiseHandler); - window.removeEventListener('keydown', escapeHandler); + uninject(); }); } diff --git a/packages/react-dev-utils/package.json b/packages/react-dev-utils/package.json index 64037886a0e..f27a12a1309 100644 --- a/packages/react-dev-utils/package.json +++ b/packages/react-dev-utils/package.json @@ -36,7 +36,9 @@ "opn": "4.0.2", "recursive-readdir": "2.1.1", "sockjs-client": "1.1.2", - "stack-frame-resolver": "0.1.3", + "stack-frame-mapper": "0.4.0", + "stack-frame-parser": "0.4.0", + "stack-frame-unmapper": "0.4.0", "strip-ansi": "3.0.1" } } From c56519826029cf0251f1b1a91986265008417ce1 Mon Sep 17 00:00:00 2001 From: Joe Haddad Date: Sun, 19 Mar 2017 21:18:39 -0400 Subject: [PATCH 2/4] Do not break words --- packages/react-dev-utils/crashOverlay.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-dev-utils/crashOverlay.js b/packages/react-dev-utils/crashOverlay.js index 561a3a57e0f..6ba884b5244 100644 --- a/packages/react-dev-utils/crashOverlay.js +++ b/packages/react-dev-utils/crashOverlay.js @@ -227,7 +227,7 @@ var overlayStyle = { 'white-space': 'pre-wrap', overflow: 'auto', 'overflow-x': 'hidden', - 'word-break': 'break-all', + 'word-break': 'break-word', 'line-height': 1.5, }; From 04dcef1a50e9843e333419a2abd6ab19621a7b83 Mon Sep 17 00:00:00 2001 From: Joe Haddad Date: Wed, 19 Apr 2017 19:43:58 -0400 Subject: [PATCH 3/4] Disable warning overlay --- packages/react-dev-utils/crashOverlay.js | 99 +++++++++++------------- 1 file changed, 46 insertions(+), 53 deletions(-) diff --git a/packages/react-dev-utils/crashOverlay.js b/packages/react-dev-utils/crashOverlay.js index 6ba884b5244..d93b67dbb44 100644 --- a/packages/react-dev-utils/crashOverlay.js +++ b/packages/react-dev-utils/crashOverlay.js @@ -9,20 +9,18 @@ 'use strict'; -function _interopDefault(ex) { - return ex && typeof ex === 'object' && 'default' in ex ? ex['default'] : ex; -} - -var stackFrameParser = require('stack-frame-parser'); -var stackFrameMapper = require('stack-frame-mapper'); -var stackFrameUnmapper = require('stack-frame-unmapper'); -var codeFrame = _interopDefault(require('babel-code-frame')); +import { parse } from 'stack-frame-parser'; +import { map } from 'stack-frame-mapper'; +import { unmap } from 'stack-frame-unmapper'; +import codeFrame from 'babel-code-frame'; var ansiHTML = require('./ansiHTML'); var boundErrorHandler = null; function errorHandler(callback, e) { - if (!e.error) return; + if (!e.error) { + return; + } // $FlowFixMe var error = e.error; @@ -36,13 +34,17 @@ function errorHandler(callback, e) { } function registerUnhandledError(target, callback) { - if (boundErrorHandler !== null) return; + if (boundErrorHandler !== null) { + return; + } boundErrorHandler = errorHandler.bind(undefined, callback); target.addEventListener('error', boundErrorHandler); } function unregisterUnhandledError(target) { - if (boundErrorHandler === null) return; + if (boundErrorHandler === null) { + return; + } target.removeEventListener('error', boundErrorHandler); boundErrorHandler = null; } @@ -64,14 +66,18 @@ function rejectionHandler(callback, e) { } function registerUnhandledRejection(target, callback) { - if (boundRejectionHandler !== null) return; + if (boundRejectionHandler !== null) { + return; + } boundRejectionHandler = rejectionHandler.bind(undefined, callback); // $FlowFixMe target.addEventListener('unhandledrejection', boundRejectionHandler); } function unregisterUnhandledRejection(target) { - if (boundRejectionHandler === null) return; + if (boundRejectionHandler === null) { + return; + } // $FlowFixMe target.removeEventListener('unhandledrejection', boundRejectionHandler); boundRejectionHandler = null; @@ -96,13 +102,17 @@ function keyHandler(callback, e) { } function registerShortcuts(target, callback) { - if (boundKeyHandler !== null) return; + if (boundKeyHandler !== null) { + return; + } boundKeyHandler = keyHandler.bind(undefined, callback); target.addEventListener('keydown', boundKeyHandler); } function unregisterShortcuts(target) { - if (boundKeyHandler === null) return; + if (boundKeyHandler === null) { + return; + } target.removeEventListener('keydown', boundKeyHandler); boundKeyHandler = null; } @@ -118,7 +128,9 @@ function registerStackTraceLimit() { ? arguments[0] : MAX_STACK_LENGTH; - if (stackTraceRegistered) return; + if (stackTraceRegistered) { + return; + } try { restoreStackTraceValue = Error.stackTraceLimit; Error.stackTraceLimit = limit; @@ -129,7 +141,9 @@ function registerStackTraceLimit() { } function unregisterStackTraceLimit() { - if (!stackTraceRegistered) return; + if (!stackTraceRegistered) { + return; + } try { Error.stackTraceLimit = restoreStackTraceValue; stackTraceRegistered = false; @@ -138,15 +152,6 @@ function unregisterStackTraceLimit() { } } -var permanentRegister = function proxyConsole(type, callback) { - var orig = console[type]; - console[type] = function __stack_frame_overlay_proxy_console__() { - var message = [].slice.call(arguments).join(' '); - callback(message); - return orig.apply(this, arguments); - }; -}; - var recorded = []; var errorsConsumed = 0; @@ -159,16 +164,16 @@ function consume(error) { ? arguments[2] : 3; - var parsedFrames = stackFrameParser.parse(error); + var parsedFrames = parse(error); var enhancedFramesPromise = void 0; if (error.__unmap_source) { - enhancedFramesPromise = stackFrameUnmapper.unmap( + enhancedFramesPromise = unmap( error.__unmap_source, parsedFrames, contextSize ); } else { - enhancedFramesPromise = stackFrameMapper.map(parsedFrames, contextSize); + enhancedFramesPromise = map(parsedFrames, contextSize); } return enhancedFramesPromise.then(function(enhancedFrames) { enhancedFrames = enhancedFrames.filter(function(_ref) { @@ -567,7 +572,9 @@ function insertBeforeBundle( actionElement ) { var children = document.getElementsByName('bundle-' + omitBundle); - if (children.length < 1) return; + if (children.length < 1) { + return; + } var first = children[0]; while (first != null && first.parentNode !== parent) { first = first.parentNode; @@ -650,10 +657,14 @@ function createFrame( var url = void 0; if (!compiled && sourceFileName) { url = sourceFileName + ':' + sourceLineNumber; - if (sourceColumnNumber) url += ':' + sourceColumnNumber; + if (sourceColumnNumber) { + url += ':' + sourceColumnNumber; + } } else { url = fileName + ':' + lineNumber; - if (columnNumber) url += ':' + columnNumber; + if (columnNumber) { + url += ':' + columnNumber; + } } var needsHidden = false; @@ -993,7 +1004,9 @@ function render(name, message, resolvedFrames) { applyStyles(iframe, iframeStyle); iframeReference = iframe; iframe.onload = function() { - if (iframeReference == null) return; + if (iframeReference == null) { + return; + } var w = iframeReference.contentWindow; var document = iframeReference.contentDocument; @@ -1136,26 +1149,6 @@ function inject() { }); registerShortcuts(window, shortcutHandler); registerStackTraceLimit(); - - permanentRegister('error', function(warning) { - var nIndex = warning.indexOf('\n'); - var message = warning; - if (nIndex !== -1) { - message = message.substring(0, nIndex); - } - var stack = warning.substring(nIndex + 1); - window.requestAnimationFrame(function() { - return crash( - // $FlowFixMe - { - message: message, - stack: stack, - __unmap_source: '/static/js/bundle.js', - }, - false - ); - }); - }); } function uninject() { From adb88551863cb80596b9ed3e26b28b08e875fba1 Mon Sep 17 00:00:00 2001 From: Joe Haddad Date: Fri, 21 Apr 2017 13:35:25 -0400 Subject: [PATCH 4/4] Use CJS imports --- packages/react-dev-utils/crashOverlay.js | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/packages/react-dev-utils/crashOverlay.js b/packages/react-dev-utils/crashOverlay.js index d93b67dbb44..5330af17478 100644 --- a/packages/react-dev-utils/crashOverlay.js +++ b/packages/react-dev-utils/crashOverlay.js @@ -9,10 +9,14 @@ 'use strict'; -import { parse } from 'stack-frame-parser'; -import { map } from 'stack-frame-mapper'; -import { unmap } from 'stack-frame-unmapper'; -import codeFrame from 'babel-code-frame'; +function _interopDefault(ex) { + return ex && typeof ex === 'object' && 'default' in ex ? ex['default'] : ex; +} + +var stackFrameParser = require('stack-frame-parser'); +var stackFrameMapper = require('stack-frame-mapper'); +var stackFrameUnmapper = require('stack-frame-unmapper'); +var codeFrame = _interopDefault(require('babel-code-frame')); var ansiHTML = require('./ansiHTML'); var boundErrorHandler = null; @@ -164,16 +168,16 @@ function consume(error) { ? arguments[2] : 3; - var parsedFrames = parse(error); + var parsedFrames = stackFrameParser.parse(error); var enhancedFramesPromise = void 0; if (error.__unmap_source) { - enhancedFramesPromise = unmap( + enhancedFramesPromise = stackFrameUnmapper.unmap( error.__unmap_source, parsedFrames, contextSize ); } else { - enhancedFramesPromise = map(parsedFrames, contextSize); + enhancedFramesPromise = stackFrameMapper.map(parsedFrames, contextSize); } return enhancedFramesPromise.then(function(enhancedFrames) { enhancedFrames = enhancedFrames.filter(function(_ref) {