/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ "use strict"; const { LocalizationHelper } = require("devtools/shared/l10n"); const STRINGS_URI = "devtools/client/locales/jit-optimizations.properties"; const L10N = new LocalizationHelper(STRINGS_URI); const { assert } = require("devtools/shared/DevToolsUtils"); const { DOM: dom, createClass, createFactory, PropTypes } = require("devtools/client/shared/vendor/react"); const Tree = createFactory(require("../../shared/components/tree")); const OptimizationsItem = createFactory(require("./jit-optimizations-item")); const FrameView = createFactory(require("../../shared/components/frame")); const JIT_TITLE = L10N.getStr("jit.title"); // If TREE_ROW_HEIGHT changes, be sure to change `var(--jit-tree-row-height)` // in `devtools/client/themes/jit-optimizations.css` const TREE_ROW_HEIGHT = 14; /* eslint-disable no-unused-vars */ /** * TODO - Re-enable this eslint rule. The JIT tool is a work in progress, and isn't fully * integrated as of yet, and this may represent intended functionality. */ const onClickTooltipString = frame => L10N.getFormatStr("viewsourceindebugger", `${frame.source}:${frame.line}:${frame.column}`); /* eslint-enable no-unused-vars */ const optimizationAttemptModel = { id: PropTypes.number.isRequired, strategy: PropTypes.string.isRequired, outcome: PropTypes.string.isRequired, }; const optimizationObservedTypeModel = { keyedBy: PropTypes.string.isRequired, name: PropTypes.string, location: PropTypes.string, line: PropTypes.string, }; const optimizationIonTypeModel = { id: PropTypes.number.isRequired, typeset: PropTypes.arrayOf(optimizationObservedTypeModel), site: PropTypes.number.isRequired, mirType: PropTypes.number.isRequired, }; const optimizationSiteModel = { id: PropTypes.number.isRequired, propertyName: PropTypes.string, line: PropTypes.number.isRequired, column: PropTypes.number.isRequired, data: PropTypes.shape({ attempts: PropTypes.arrayOf(optimizationAttemptModel).isRequired, types: PropTypes.arrayOf(optimizationIonTypeModel).isRequired, }).isRequired, }; const JITOptimizations = createClass({ displayName: "JITOptimizations", propTypes: { onViewSourceInDebugger: PropTypes.func.isRequired, frameData: PropTypes.object.isRequired, optimizationSites: PropTypes.arrayOf(optimizationSiteModel).isRequired, autoExpandDepth: PropTypes.number, }, getDefaultProps() { return { autoExpandDepth: 0 }; }, getInitialState() { return { expanded: new Set() }; }, /** * Frame data generated from `frameNode.getInfo()`, or an empty * object, as well as a handler for clicking on the frame component. * * @param {?Object} .frameData * @param {Function} .onViewSourceInDebugger * @return {ReactElement} */ _createHeader: function ({ frameData, onViewSourceInDebugger }) { let { isMetaCategory, url, line } = frameData; let name = isMetaCategory ? frameData.categoryData.label : frameData.functionName || ""; // Simulate `SavedFrame`s interface let frame = { source: url, line: +line, functionDisplayName: name }; // Neither Meta Category nodes, or the lack of a selected frame node, // renders out a frame source, like "file.js:123"; so just use // an empty span. let frameComponent; if (isMetaCategory || !name) { frameComponent = dom.span(); } else { frameComponent = FrameView({ frame, onClick: () => onViewSourceInDebugger(frame), }); } return dom.div({ className: "optimization-header" }, dom.span({ className: "header-title" }, JIT_TITLE), dom.span({ className: "header-function-name" }, name), frameComponent ); }, _createTree(props) { let { autoExpandDepth, frameData, onViewSourceInDebugger, optimizationSites: sites } = this.props; let getSite = id => sites.find(site => site.id === id); let getIonTypeForObserved = type => { return getSite(type.id).data.types .find(iontype => (iontype.typeset || []) .indexOf(type) !== -1); }; let isSite = site => getSite(site.id) === site; let isAttempts = attempts => getSite(attempts.id).data.attempts === attempts; let isAttempt = attempt => getSite(attempt.id).data.attempts.indexOf(attempt) !== -1; let isTypes = types => getSite(types.id).data.types === types; let isType = type => getSite(type.id).data.types.indexOf(type) !== -1; let isObservedType = type => getIonTypeForObserved(type); let getRowType = node => { if (isSite(node)) { return "site"; } if (isAttempts(node)) { return "attempts"; } if (isTypes(node)) { return "types"; } if (isAttempt(node)) { return "attempt"; } if (isType(node)) { return "type"; } if (isObservedType(node)) { return "observedtype"; } return null; }; // Creates a unique key for each node in the // optimizations data let getKey = node => { let site = getSite(node.id); if (isSite(node)) { return node.id; } else if (isAttempts(node)) { return `${node.id}-A`; } else if (isTypes(node)) { return `${node.id}-T`; } else if (isType(node)) { return `${node.id}-T-${site.data.types.indexOf(node)}`; } else if (isAttempt(node)) { return `${node.id}-A-${site.data.attempts.indexOf(node)}`; } else if (isObservedType(node)) { let iontype = getIonTypeForObserved(node); return `${getKey(iontype)}-O-${iontype.typeset.indexOf(node)}`; } return ""; }; return Tree({ autoExpandDepth, getParent: node => { let site = getSite(node.id); let parent; if (isAttempts(node) || isTypes(node)) { parent = site; } else if (isType(node)) { parent = site.data.types; } else if (isAttempt(node)) { parent = site.data.attempts; } else if (isObservedType(node)) { parent = getIonTypeForObserved(node); } assert(parent, "Could not find a parent for optimization data node"); return parent; }, getChildren: node => { if (isSite(node)) { return [node.data.types, node.data.attempts]; } else if (isAttempts(node) || isTypes(node)) { return node; } else if (isType(node)) { return node.typeset || []; } return []; }, isExpanded: node => this.state.expanded.has(node), onExpand: node => this.setState(state => { let expanded = new Set(state.expanded); expanded.add(node); return { expanded }; }), onCollapse: node => this.setState(state => { let expanded = new Set(state.expanded); expanded.delete(node); return { expanded }; }), onFocus: function () {}, getKey, getRoots: () => sites || [], itemHeight: TREE_ROW_HEIGHT, renderItem: (item, depth, focused, arrow, expanded) => new OptimizationsItem({ onViewSourceInDebugger, item, depth, focused, arrow, expanded, type: getRowType(item), frameData, }), }); }, render() { let header = this._createHeader(this.props); let tree = this._createTree(this.props); return dom.div({}, header, tree); } }); module.exports = JITOptimizations;