frame.js 9.05 KiB
/* @flow */
import { enableTabClick } from '../utils/dom/enableTabClick';
import { createCode } from './code';
import { isInternalFile } from '../utils/isInternalFile';
import type { StackFrame } from '../utils/stack-frame';
import type { FrameSetting, OmitsObject } from './frames';
import { applyStyles } from '../utils/dom/css';
import {
  omittedFramesExpandedStyle,
  omittedFramesCollapsedStyle,
  functionNameStyle,
  depStyle,
  linkStyle,
  anchorStyle,
  hiddenStyle,
} from '../styles';
function getGroupToggle(
  document: Document,
  omitsCount: number,
  omitBundle: number
) {
  const omittedFrames = document.createElement('div');
  enableTabClick(omittedFrames);
  const text1 = document.createTextNode(
    '\u25B6 ' + omitsCount + ' stack frames were collapsed.'
  omittedFrames.appendChild(text1);
  omittedFrames.addEventListener('click', function() {
    const hide = text1.textContent.match(/▲/);
    const list = document.getElementsByName('bundle-' + omitBundle);
    for (let index = 0; index < list.length; ++index) {
      const n = list[index];
      if (hide) {
        n.style.display = 'none';
      } else {
        n.style.display = '';
    if (hide) {
      text1.textContent = text1.textContent.replace(/▲/, '▶');
      text1.textContent = text1.textContent.replace(/expanded/, 'collapsed');
      applyStyles(omittedFrames, omittedFramesCollapsedStyle);
    } else {
      text1.textContent = text1.textContent.replace(/▶/, '▲');
      text1.textContent = text1.textContent.replace(/collapsed/, 'expanded');
      applyStyles(omittedFrames, omittedFramesExpandedStyle);
  });
  applyStyles(omittedFrames, omittedFramesCollapsedStyle);
  return omittedFrames;
function insertBeforeBundle(
  document: Document,
  parent: Node,
  omitsCount: number,
  omitBundle: number,
  actionElement
) {
  const children = document.getElementsByName('bundle-' + omitBundle);
  if (children.length < 1) {
    return;
  let first: ?Node = children[0];
  while (first != null && first.parentNode !== parent) {
    first = first.parentNode;
  const div = document.createElement('div');
  enableTabClick(div);
7172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
div.setAttribute('name', 'bundle-' + omitBundle); const text = document.createTextNode( '\u25BC ' + omitsCount + ' stack frames were expanded.' ); div.appendChild(text); div.addEventListener('click', function() { return actionElement.click(); }); applyStyles(div, omittedFramesExpandedStyle); div.style.display = 'none'; parent.insertBefore(div, first); } function frameDiv( document: Document, functionName, url, internalUrl, onSourceClick: ?Function ) { const frame = document.createElement('div'); const frameFunctionName = document.createElement('div'); let cleanedFunctionName; if (!functionName || functionName === 'Object.<anonymous>') { cleanedFunctionName = '(anonymous function)'; } else { cleanedFunctionName = functionName; } const cleanedUrl = url.replace('webpack://', '.'); if (internalUrl) { applyStyles( frameFunctionName, Object.assign({}, functionNameStyle, depStyle) ); } else { applyStyles(frameFunctionName, functionNameStyle); } frameFunctionName.appendChild(document.createTextNode(cleanedFunctionName)); frame.appendChild(frameFunctionName); const frameLink = document.createElement('div'); applyStyles(frameLink, linkStyle); const frameAnchor = document.createElement('a'); applyStyles(frameAnchor, anchorStyle); frameAnchor.appendChild(document.createTextNode(cleanedUrl)); frameLink.appendChild(frameAnchor); frame.appendChild(frameLink); if (typeof onSourceClick === 'function') { let handler = onSourceClick; enableTabClick(frameAnchor); frameAnchor.style.cursor = 'pointer'; frameAnchor.addEventListener('click', function() { handler(); }); } return frame; } function isBultinErrorName(errorName: ?string) { switch (errorName) { case 'EvalError': case 'InternalError': case 'RangeError':
141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
case 'ReferenceError': case 'SyntaxError': case 'TypeError': case 'URIError': return true; default: return false; } } function getPrettyURL( sourceFileName: ?string, sourceLineNumber: ?number, sourceColumnNumber: ?number, fileName: ?string, lineNumber: ?number, columnNumber: ?number, compiled: boolean ): string { let prettyURL; if (!compiled && sourceFileName && typeof sourceLineNumber === 'number') { // Remove everything up to the first /src/ or /node_modules/ const trimMatch = /^[/|\\].*?[/|\\]((src|node_modules)[/|\\].*)/.exec( sourceFileName ); if (trimMatch && trimMatch[1]) { prettyURL = trimMatch[1]; } else { prettyURL = sourceFileName; } prettyURL += ':' + sourceLineNumber; // Note: we intentionally skip 0's because they're produced by cheap Webpack maps if (sourceColumnNumber) { prettyURL += ':' + sourceColumnNumber; } } else if (fileName && typeof lineNumber === 'number') { prettyURL = fileName + ':' + lineNumber; // Note: we intentionally skip 0's because they're produced by cheap Webpack maps if (columnNumber) { prettyURL += ':' + columnNumber; } } else { prettyURL = 'unknown'; } return prettyURL; } function createFrame( document: Document, frameSetting: FrameSetting, frame: StackFrame, contextSize: number, critical: boolean, omits: OmitsObject, omitBundle: number, parentContainer: HTMLDivElement, lastElement: boolean, errorName: ?string ) { const { compiled } = frameSetting; let { functionName, _originalFileName: sourceFileName } = frame; const { fileName, lineNumber, columnNumber, _scriptCode: scriptLines, _originalLineNumber: sourceLineNumber, _originalColumnNumber: sourceColumnNumber, _originalScriptCode: sourceLines, } = frame;
211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
// TODO: find a better place for this. // Chrome has a bug with inferring function.name: // https://github.com/facebookincubator/create-react-app/issues/2097 // Let's ignore a meaningless name we get for top-level modules. if ( functionName === 'Object.friendlySyntaxErrorLabel' || functionName === 'Object.exports.__esModule' ) { functionName = '(anonymous function)'; } const prettyURL = getPrettyURL( sourceFileName, sourceLineNumber, sourceColumnNumber, fileName, lineNumber, columnNumber, compiled ); let needsHidden = false; const isInternalUrl = isInternalFile(sourceFileName, fileName); const isThrownIntentionally = !isBultinErrorName(errorName); const shouldCollapse = isInternalUrl && (isThrownIntentionally || omits.hasReachedAppCode); if (!isInternalUrl) { omits.hasReachedAppCode = true; } if (shouldCollapse) { ++omits.value; needsHidden = true; } let collapseElement = null; if (!shouldCollapse || lastElement) { if (omits.value > 0) { const capV = omits.value; const omittedFrames = getGroupToggle(document, capV, omitBundle); window.requestAnimationFrame(() => { insertBeforeBundle( document, parentContainer, capV, omitBundle, omittedFrames ); }); if (lastElement && shouldCollapse) { collapseElement = omittedFrames; } else { parentContainer.appendChild(omittedFrames); } ++omits.bundle; } omits.value = 0; } let onSourceClick = null; if (sourceFileName) { // e.g. "/path-to-my-app/webpack/bootstrap eaddeb46b67d75e4dfc1" const isInternalWebpackBootstrapCode = sourceFileName .trim() .indexOf(' ') !== -1; if (!isInternalWebpackBootstrapCode) { onSourceClick = () => { // Keep this in sync with react-error-overlay/middleware.js
281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345
fetch( '/__open-stack-frame-in-editor?fileName=' + window.encodeURIComponent(sourceFileName) + '&lineNumber=' + window.encodeURIComponent(sourceLineNumber || 1) ).then(() => {}, () => {}); }; } } const elem = frameDiv( document, functionName, prettyURL, shouldCollapse, onSourceClick ); if (needsHidden) { applyStyles(elem, hiddenStyle); elem.setAttribute('name', 'bundle-' + omitBundle); } let hasSource = false; if (!shouldCollapse) { if ( compiled && scriptLines && scriptLines.length !== 0 && lineNumber != null ) { elem.appendChild( createCode( document, scriptLines, lineNumber, columnNumber, contextSize, critical, onSourceClick ) ); hasSource = true; } else if ( !compiled && sourceLines && sourceLines.length !== 0 && sourceLineNumber != null ) { elem.appendChild( createCode( document, sourceLines, sourceLineNumber, sourceColumnNumber, contextSize, critical, onSourceClick ) ); hasSource = true; } } return { elem: elem, hasSource: hasSource, collapseElement: collapseElement }; } export { createFrame };