/* @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 { omittedFramesStyle, 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'); } else { text1.textContent = text1.textContent.replace(/▶/, '▲'); text1.textContent = text1.textContent.replace(/collapsed/, 'expanded'); } }); applyStyles(omittedFrames, omittedFramesStyle); 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); 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, omittedFramesStyle); div.style.display = 'none'; parent.insertBefore(div, first); } function frameDiv(document: Document, functionName, url, internalUrl) { 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); return frame; } function createFrame( document: Document, frameSetting: FrameSetting, frame: StackFrame, contextSize: number, critical: boolean, omits: OmitsObject, omitBundle: number, parentContainer: HTMLDivElement, lastElement: boolean ) { const { compiled } = frameSetting; let { functionName, _originalFileName: sourceFileName } = frame; const { fileName, lineNumber, columnNumber, _scriptCode: scriptLines, _originalLineNumber: sourceLineNumber, _originalColumnNumber: sourceColumnNumber, _originalScriptCode: sourceLines, } = frame; // 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 = '(anonymous function)'; } let url; if (!compiled && sourceFileName && sourceLineNumber) { // Remove everything up to the first /src/ const trimMatch = /^[/|\\].*?[/|\\](src[/|\\].*)/.exec(sourceFileName); if (trimMatch && trimMatch[1]) { sourceFileName = trimMatch[1]; } url = sourceFileName + ':' + sourceLineNumber; if (sourceColumnNumber) { url += ':' + sourceColumnNumber; } } else if (fileName && lineNumber) { url = fileName + ':' + lineNumber; if (columnNumber) { url += ':' + columnNumber; } } else { url = 'unknown'; } let needsHidden = false; const internalUrl = isInternalFile(url, sourceFileName); if (internalUrl) { ++omits.value; needsHidden = true; } let collapseElement = null; if (!internalUrl || 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 && internalUrl) { collapseElement = omittedFrames; } else { parentContainer.appendChild(omittedFrames); } ++omits.bundle; } omits.value = 0; } const elem = frameDiv(document, functionName, url, internalUrl); if (needsHidden) { applyStyles(elem, hiddenStyle); elem.setAttribute('name', 'bundle-' + omitBundle); } let hasSource = false; if (!internalUrl) { if ( compiled && scriptLines && scriptLines.length !== 0 && lineNumber != null ) { elem.appendChild( createCode( document, scriptLines, lineNumber, columnNumber, contextSize, critical ) ); hasSource = true; } else if ( !compiled && sourceLines && sourceLines.length !== 0 && sourceLineNumber != null ) { elem.appendChild( createCode( document, sourceLines, sourceLineNumber, sourceColumnNumber, contextSize, critical ) ); hasSource = true; } } return { elem: elem, hasSource: hasSource, collapseElement: collapseElement }; } export { createFrame };