/* 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 { createClass, createFactory, PropTypes } = require("devtools/client/shared/vendor/react"); const Tree = createFactory(require("devtools/client/shared/components/tree")); const WaterfallTreeRow = createFactory(require("./waterfall-tree-row")); // px - keep in sync with var(--waterfall-tree-row-height) in performance.css const WATERFALL_TREE_ROW_HEIGHT = 15; /** * Checks if a given marker is in the specified time range. * * @param object e * The marker containing the { start, end } timestamps. * @param number start * The earliest allowed time. * @param number end * The latest allowed time. * @return boolean * True if the marker fits inside the specified time range. */ function isMarkerInRange(e, start, end) { let mStart = e.start | 0; let mEnd = e.end | 0; return ( // bounds inside (mStart >= start && mEnd <= end) || // bounds outside (mStart < start && mEnd > end) || // overlap start (mStart < start && mEnd >= start && mEnd <= end) || // overlap end (mEnd > end && mStart >= start && mStart <= end) ); } const WaterfallTree = createClass({ displayName: "WaterfallTree", propTypes: { marker: PropTypes.object.isRequired, startTime: PropTypes.number.isRequired, endTime: PropTypes.number.isRequired, dataScale: PropTypes.number.isRequired, sidebarWidth: PropTypes.number.isRequired, waterfallWidth: PropTypes.number.isRequired, onFocus: PropTypes.func, }, getInitialState() { return { focused: null, expanded: new Set() }; }, _getRoots(node) { let roots = this.props.marker.submarkers || []; return roots.filter(this._filter); }, /** * Find the parent node of 'node' with a depth-first search of the marker tree */ _getParent(node) { function findParent(marker) { if (marker.submarkers) { for (let submarker of marker.submarkers) { if (submarker === node) { return marker; } let parent = findParent(submarker); if (parent) { return parent; } } } return null; } let rootMarker = this.props.marker; let parent = findParent(rootMarker); // We are interested only in parent markers that are rendered, // which rootMarker is not. Return null if the parent is rootMarker. return parent !== rootMarker ? parent : null; }, _getChildren(node) { let submarkers = node.submarkers || []; return submarkers.filter(this._filter); }, _getKey(node) { return `marker-${node.index}`; }, _isExpanded(node) { return 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(node) { this.setState({ focused: node }); if (this.props.onFocus) { this.props.onFocus(node); } }, _filter(node) { let { startTime, endTime } = this.props; return isMarkerInRange(node, startTime, endTime); }, _renderItem(marker, level, focused, arrow, expanded) { let { startTime, dataScale, sidebarWidth } = this.props; return WaterfallTreeRow({ marker, level, arrow, expanded, focused, startTime, dataScale, sidebarWidth }); }, render() { return Tree({ getRoots: this._getRoots, getParent: this._getParent, getChildren: this._getChildren, getKey: this._getKey, isExpanded: this._isExpanded, onExpand: this._onExpand, onCollapse: this._onCollapse, onFocus: this._onFocus, renderItem: this._renderItem, focused: this.state.focused, itemHeight: WATERFALL_TREE_ROW_HEIGHT }); } }); module.exports = WaterfallTree;