remove old heap capture in preparation for pull from tracery-prerelease

Reviewed By: bnham

Differential Revision: D4250926

fbshipit-source-id: 7cfbb8d8aae5ef150f0260c92e946d569e41f7fb
This commit is contained in:
Charles Dick 2016-11-30 12:43:43 -08:00 committed by Facebook Github Bot
parent bed9087191
commit 3094c36c81
6 changed files with 0 additions and 3363 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,760 +0,0 @@
/**
* Copyright (c) 2016-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
'use strict';
/*eslint no-bitwise: "off"*/
/*eslint no-console-disallow: "off"*/
// TODO: future features
// put in a module.exports
// filtering / search
// pivot around frames in the middle of a stack by callers / callees
// graphing?
function StringInterner() { // eslint-disable-line no-unused-vars
const strings = [];
const ids = {};
return {
intern: function internString(s) {
const find = ids[s];
if (find === undefined) {
const id = strings.length;
ids[s] = id;
strings.push(s);
return id;
} else {
return find;
}
},
get: function getString(id) {
return strings[id];
},
};
}
function StackRegistry() { // eslint-disable-line no-unused-vars
return {
root: { id: 0 },
nodeCount: 1,
maxDepth: -1,
stackIdMap: null,
insert: function insertNode(parent, frameId) {
if (this.stackIdMap !== null) {
throw 'stacks already flattened';
}
let node = parent[frameId];
if (node === undefined) {
node = { id: this.nodeCount };
this.nodeCount++;
parent[frameId] = node;
}
return node;
},
get: function getStackArray(id) {
return this.stackIdMap[id];
},
flatten: function flattenStacks() {
if (this.stackIdMap !== null) {
return;
}
let stackFrameCount = 0;
function countStacks(tree, depth) {
let leaf = true;
for (const frameId in tree) {
if (frameId !== 'id') {
leaf = countStacks(tree[frameId], depth + 1);
}
}
if (leaf) {
stackFrameCount += depth;
}
return false;
}
countStacks(this.root, 0);
console.log('size needed to store stacks: ' + (stackFrameCount * 4).toString() + 'B');
const stackIdMap = new Array(this.nodeCount);
const stackArray = new Int32Array(stackFrameCount);
let maxStackDepth = 0;
stackFrameCount = 0;
function flattenStacksImpl(tree, stack) {
let childStack;
maxStackDepth = Math.max(maxStackDepth, stack.length);
for (const frameId in tree) {
if (frameId !== 'id') {
stack.push(Number(frameId));
childStack = flattenStacksImpl(tree[frameId], stack);
stack.pop();
}
}
const id = tree.id;
if (id < 0 || id >= stackIdMap.length || stackIdMap[id] !== undefined) {
throw 'invalid stack id!';
}
if (childStack !== undefined) {
// each child must have our stack as a prefix, so just use that
stackIdMap[id] = childStack.subarray(0, stack.length);
} else {
const newStack = stackArray.subarray(stackFrameCount, stackFrameCount + stack.length);
stackFrameCount += stack.length;
for (let i = 0; i < stack.length; i++) {
newStack[i] = stack[i];
}
stackIdMap[id] = newStack;
}
return stackIdMap[id];
}
flattenStacksImpl(this.root, []);
this.root = null;
this.stackIdMap = stackIdMap;
this.maxDepth = maxStackDepth;
},
};
}
function AggrowData(columns) { // eslint-disable-line no-unused-vars
const columnCount = columns.length;
const columnConverter = columns.map(c => {
switch (c.type) {
case 'int': // stores raw value
return (i) => i;
case 'string': // stores interned id of string
return (s) => c.strings.intern(s);
case 'stack': // stores id of stack node
return (s) => s.id;
default:
throw 'unknown AggrowData column type';
}
});
return {
data: new Int32Array(0),
columns: columns,
rowCount: 0,
rowInserter: function rowInserter(numRows) {
console.log(
'increasing row data from ' + (this.data.length * 4).toLocaleString() + ' B to ' +
(this.data.length * 4 + numRows * columnCount * 4).toLocaleString() + ' B'
);
const newData = new Int32Array(this.data.length + numRows * columnCount);
newData.set(this.data);
let currOffset = this.data.length;
const endOffset = newData.length;
this.data = newData;
this.rowCount = newData.length / columnCount;
return {
insertRow: function insertRow() {
if (currOffset >= endOffset) {
throw 'tried to insert data off end of added range';
}
if (arguments.length !== columnCount) {
throw 'expected data for ' + columnCount.toString() + ' columns, got' +
arguments.length.toString() + ' columns';
}
for (let i = 0; i < arguments.length; i++) {
newData[currOffset + i] = columnConverter[i](arguments[i]);
}
currOffset += columnCount;
},
done: function done() {
if (currOffset !== endOffset) {
throw 'unfilled rows';
}
},
};
},
};
}
function Aggrow(aggrowData) {
const columns = aggrowData.columns;
const columnCount = columns.length;
const data = aggrowData.data;
function columnIndex(columnName, columnType) {
const index = columns.findIndex(c => c.name === columnName && c.type === columnType);
if (index < 0) {
throw 'did not find data column ' + columnName + ' with type ' + columnType;
}
return index;
}
for (let i = 0; i < columns.length; i++) {
if (columns[i].type === 'stack') {
columns[i].stacks.flatten();
}
}
return {
expander: new AggrowExpander(aggrowData.rowCount),
addSumAggregator: function addSumAggregator(aggregatorName, columnName) {
const index = columnIndex(columnName, 'int');
return this.expander.addAggregator(
aggregatorName,
function aggregateSize(indices) {
let size = 0;
for (let i = 0; i < indices.length; i++) {
const row = indices[i];
size += data[row * columnCount + index];
}
return size;
},
(value) => value.toLocaleString(),
(a, b) => b - a,
);
},
addCountAggregator: function addCountAggregator(aggregatorName) {
return this.expander.addAggregator(
aggregatorName,
function aggregateCount(indices) {
return indices.length;
},
(value) => value.toLocaleString(),
(a, b) => b - a,
);
},
addStringExpander: function addStringExpander(expanderName, columnName) {
const index = columnIndex(columnName, 'string');
const strings = columns[index].strings;
return this.expander.addFieldExpander(
expanderName,
(row) => strings.get(data[row * columnCount + index]),
(rowA, rowB) => data[rowA * columnCount + index] - data[rowB * columnCount + index],
);
},
addNumberExpander: function addNumberExpander(expanderName, columnName) {
const index = columnIndex(columnName, 'int');
return this.expander.addFieldExpander(
expanderName,
(row) => data[row * columnCount + index].toLocaleString(),
(rowA, rowB) => data[rowA * columnCount + index] - data[rowB * columnCount + index],
);
},
addPointerExpander: function addPointerExpander(expanderName, columnName) {
const index = columnIndex(columnName, 'int');
return this.expander.addFieldExpander(
expanderName,
(row) => '0x' + (data[row * columnCount + index] >>> 0).toString(),
(rowA, rowB) => data[rowA * columnCount + index] - data[rowB * columnCount + index],
);
},
addStackExpander: function addStackExpander(expanderName, columnName, formatter) {
// TODO: options for caller/callee, pivoting
const index = columnIndex(columnName, 'stack');
const stacks = columns[index].stacks;
return this.expander.addCalleeStackExpander(
expanderName,
stacks.maxDepth,
(row) => stacks.get(data[row * columnCount + index]),
formatter,
);
},
};
}
function AggrowExpander(numRows) { // eslint-disable-line no-unused-vars
// expander ID definitions
const FIELD_EXPANDER_ID_MIN = 0x0000;
const FIELD_EXPANDER_ID_MAX = 0x7fff;
const STACK_EXPANDER_ID_MIN = 0x8000;
const STACK_EXPANDER_ID_MAX = 0xffff;
// used for row.expander which reference state.activeExpanders (with frame index masked in)
const INVALID_ACTIVE_EXPANDER = -1;
const ACTIVE_EXPANDER_MASK = 0xffff;
const ACTIVE_EXPANDER_FRAME_SHIFT = 16;
// aggregator ID definitions
const AGGREGATOR_ID_MAX = 0xffff;
// active aggragators can have sort order changed in the reference
const ACTIVE_AGGREGATOR_MASK = 0xffff;
const ACTIVE_AGGREGATOR_ASC_BIT = 0x10000;
// tree node state definitions
const NODE_EXPANDED_BIT = 0x0001; // this row is expanded
const NODE_REAGGREGATE_BIT = 0x0002; // children need aggregates
const NODE_REORDER_BIT = 0x0004; // children need to be sorted
const NODE_REPOSITION_BIT = 0x0008; // children need position
const NODE_INDENT_SHIFT = 16;
function calleeFrameIdGetter(stack, depth) {
return stack[depth];
}
function callerFrameIdGetter(stack, depth) {
return stack[stack.length - depth - 1];
}
function createStackComparers(stackGetter, frameIdGetter, maxStackDepth) {
const comparers = new Array(maxStackDepth);
for (let depth = 0; depth < maxStackDepth; depth++) {
const captureDepth = depth; // NB: to capture depth per loop iteration
comparers[depth] = function calleeStackComparer(rowA, rowB) {
const a = stackGetter(rowA);
const b = stackGetter(rowB);
// NB: we put the stacks that are too short at the top,
// so they can be grouped into the '<exclusive>' bucket
if (a.length <= captureDepth && b.length <= captureDepth) {
return 0;
} else if (a.length <= captureDepth) {
return -1;
} else if (b.length <= captureDepth) {
return 1;
}
return frameIdGetter(a, captureDepth) - frameIdGetter(b, captureDepth);
};
}
return comparers;
}
function createTreeNode(parent, label, indices, expander) {
const indent = parent === null ? 0 : (parent.state >>> NODE_INDENT_SHIFT) + 1;
const state = NODE_REPOSITION_BIT |
NODE_REAGGREGATE_BIT |
NODE_REORDER_BIT |
(indent << NODE_INDENT_SHIFT);
return {
parent: parent, // null if root
children: null, // array of children nodes
label: label, // string to show in UI
indices: indices, // row indices under this node
aggregates: null, // result of aggregate on indices
expander: expander, // index into state.activeExpanders
top: 0, // y position of top row (in rows)
height: 1, // number of rows including children
state: state, // see NODE_* definitions above
};
}
function noSortOrder(a, b) {
return 0;
}
const indices = new Int32Array(numRows);
for (let i = 0; i < numRows; i++) {
indices[i] = i;
}
const state = {
fieldExpanders: [], // tree expanders that expand on simple values
stackExpanders: [], // tree expanders that expand stacks
activeExpanders: [], // index into field or stack expanders, hierarchy of tree
aggregators: [], // all available aggregators, might not be used
activeAggregators: [], // index into aggregators, to actually compute
sorter: noSortOrder, // compare function that uses sortOrder to sort row.children
root: createTreeNode(null, '<root>', indices, INVALID_ACTIVE_EXPANDER),
};
function evaluateAggregate(row) {
const activeAggregators = state.activeAggregators;
const aggregates = new Array(activeAggregators.length);
for (let j = 0; j < activeAggregators.length; j++) {
const aggregator = state.aggregators[activeAggregators[j]];
aggregates[j] = aggregator.aggregator(row.indices);
}
row.aggregates = aggregates;
row.state |= NODE_REAGGREGATE_BIT;
}
function evaluateAggregates(row) {
if ((row.state & NODE_EXPANDED_BIT) !== 0) {
const children = row.children;
for (let i = 0; i < children.length; i++) {
evaluateAggregate(children[i]);
}
row.state |= NODE_REORDER_BIT;
}
row.state ^= NODE_REAGGREGATE_BIT;
}
function evaluateOrder(row) {
if ((row.state & NODE_EXPANDED_BIT) !== 0) {
const children = row.children;
for (let i = 0; i < children.length; i++) {
const child = children[i];
child.state |= NODE_REORDER_BIT;
}
children.sort(state.sorter);
row.state |= NODE_REPOSITION_BIT;
}
row.state ^= NODE_REORDER_BIT;
}
function evaluatePosition(row) {
if ((row.state & NODE_EXPANDED_BIT) !== 0) {
const children = row.children;
let childTop = row.top + 1;
for (let i = 0; i < children.length; i++) {
const child = children[i];
if (child.top !== childTop) {
child.top = childTop;
child.state |= NODE_REPOSITION_BIT;
}
childTop += child.height;
}
}
row.state ^= NODE_REPOSITION_BIT;
}
function getRowsImpl(row, top, height, result) {
if ((row.state & NODE_REAGGREGATE_BIT) !== 0) {
evaluateAggregates(row);
}
if ((row.state & NODE_REORDER_BIT) !== 0) {
evaluateOrder(row);
}
if ((row.state & NODE_REPOSITION_BIT) !== 0) {
evaluatePosition(row);
}
if (row.top >= top && row.top < top + height) {
if (result[row.top - top] != null) {
throw 'getRows put more than one row at position ' + row.top + ' into result';
}
result[row.top - top] = row;
}
if ((row.state & NODE_EXPANDED_BIT) !== 0) {
const children = row.children;
for (let i = 0; i < children.length; i++) {
const child = children[i];
if (child.top < top + height && top < child.top + child.height) {
getRowsImpl(child, top, height, result);
}
}
}
}
function updateHeight(row, heightChange) {
while (row !== null) {
row.height += heightChange;
row.state |= NODE_REPOSITION_BIT;
row = row.parent;
}
}
function addChildrenWithFieldExpander(row, expander, nextActiveIndex) {
const rowIndices = row.indices;
const comparer = expander.comparer;
rowIndices.sort(comparer);
let begin = 0;
let end = 1;
row.children = [];
while (end < rowIndices.length) {
if (comparer(rowIndices[begin], rowIndices[end]) !== 0) {
row.children.push(createTreeNode(
row,
expander.name + ': ' + expander.formatter(rowIndices[begin]),
rowIndices.subarray(begin, end),
nextActiveIndex));
begin = end;
}
end++;
}
row.children.push(createTreeNode(
row,
expander.name + ': ' + expander.formatter(rowIndices[begin]),
rowIndices.subarray(begin, end),
nextActiveIndex));
}
function addChildrenWithStackExpander(row, expander, activeIndex, depth, nextActiveIndex) {
const rowIndices = row.indices;
const stackGetter = expander.stackGetter;
const frameIdGetter = expander.frameIdGetter;
const frameGetter = expander.frameGetter;
const comparer = expander.comparers[depth];
const expandNextFrame = activeIndex | ((depth + 1) << ACTIVE_EXPANDER_FRAME_SHIFT);
rowIndices.sort(comparer);
let columnName = '';
if (depth === 0) {
columnName = expander.name + ': ';
}
// put all the too-short stacks under <exclusive>
let begin = 0;
let beginStack = null;
row.children = [];
while (begin < rowIndices.length) {
beginStack = stackGetter(rowIndices[begin]);
if (beginStack.length > depth) {
break;
}
begin++;
}
if (begin > 0) {
row.children.push(createTreeNode(
row,
columnName + '<exclusive>',
rowIndices.subarray(0, begin),
nextActiveIndex));
}
// aggregate the rest under frames
if (begin < rowIndices.length) {
let end = begin + 1;
while (end < rowIndices.length) {
const endStack = stackGetter(rowIndices[end]);
if (frameIdGetter(beginStack, depth) !== frameIdGetter(endStack, depth)) {
row.children.push(createTreeNode(
row,
columnName + frameGetter(frameIdGetter(beginStack, depth)),
rowIndices.subarray(begin, end),
expandNextFrame));
begin = end;
beginStack = endStack;
}
end++;
}
row.children.push(createTreeNode(
row,
columnName + frameGetter(frameIdGetter(beginStack, depth)),
rowIndices.subarray(begin, end),
expandNextFrame));
}
}
function contractRow(row) {
if ((row.state & NODE_EXPANDED_BIT) === 0) {
throw 'can not contract row, already contracted';
}
row.state ^= NODE_EXPANDED_BIT;
const heightChange = 1 - row.height;
updateHeight(row, heightChange);
}
function pruneExpanders(row, oldExpander, newExpander) {
row.state |= NODE_REPOSITION_BIT;
if (row.expander === oldExpander) {
row.state |= NODE_REAGGREGATE_BIT | NODE_REORDER_BIT | NODE_REPOSITION_BIT;
if ((row.state & NODE_EXPANDED_BIT) !== 0) {
contractRow(row);
}
row.children = null;
row.expander = newExpander;
} else {
row.state |= NODE_REPOSITION_BIT;
const children = row.children;
if (children != null) {
for (let i = 0; i < children.length; i++) {
const child = children[i];
pruneExpanders(child, oldExpander, newExpander);
}
}
}
}
return {
addFieldExpander: function addFieldExpander(name, formatter, comparer) {
if (FIELD_EXPANDER_ID_MIN + state.fieldExpanders.length >= FIELD_EXPANDER_ID_MAX) {
throw 'too many field expanders!';
}
state.fieldExpanders.push({
name: name, // name for column
formatter: formatter, // row index -> display string
comparer: comparer, // compares by two row indices
});
return FIELD_EXPANDER_ID_MIN + state.fieldExpanders.length - 1;
},
addCalleeStackExpander: function addCalleeStackExpander(name, maxStackDepth, stackGetter, frameGetter) {
if (STACK_EXPANDER_ID_MIN + state.fieldExpanders.length >= STACK_EXPANDER_ID_MAX) {
throw 'too many stack expanders!';
}
state.stackExpanders.push({
name: name, // name for column
stackGetter: stackGetter, // row index -> stack array
comparers: createStackComparers(stackGetter, calleeFrameIdGetter, maxStackDepth), // depth -> comparer
frameIdGetter: calleeFrameIdGetter, // (stack, depth) -> string id
frameGetter: frameGetter,
});
return STACK_EXPANDER_ID_MIN + state.stackExpanders.length - 1;
},
addCallerStackExpander: function addCallerStackExpander(name, maxStackDepth, stackGetter, frameGetter) {
if (STACK_EXPANDER_ID_MIN + state.fieldExpanders.length >= STACK_EXPANDER_ID_MAX) {
throw 'too many stack expanders!';
}
state.stackExpanders.push({
name: name,
stackGetter: stackGetter,
comparers: createStackComparers(stackGetter, callerFrameIdGetter, maxStackDepth),
frameIdGetter: callerFrameIdGetter,
frameGetter: frameGetter,
});
return STACK_EXPANDER_ID_MIN + state.stackExpanders.length - 1;
},
getExpanders: function getExpanders() {
const expanders = [];
for (let i = 0; i < state.fieldExpanders.length; i++) {
expanders.push(FIELD_EXPANDER_ID_MIN + i);
}
for (let i = 0; i < state.stackExpanders.length; i++) {
expanders.push(STACK_EXPANDER_ID_MIN + i);
}
return expanders;
},
getExpanderName: function getExpanderName(id) {
if (id >= FIELD_EXPANDER_ID_MIN && id <= FIELD_EXPANDER_ID_MAX) {
return state.fieldExpanders[id - FIELD_EXPANDER_ID_MIN].name;
} else if (id >= STACK_EXPANDER_ID_MIN && id <= STACK_EXPANDER_ID_MAX) {
return state.stackExpanders[id - STACK_EXPANDER_ID_MIN].name;
}
throw 'Unknown expander ID ' + id.toString();
},
setActiveExpanders: function setActiveExpanders(ids) {
for (let i = 0; i < ids.length; i++) {
const id = ids[i];
if (id >= FIELD_EXPANDER_ID_MIN && id <= FIELD_EXPANDER_ID_MAX) {
if (id - FIELD_EXPANDER_ID_MIN >= state.fieldExpanders.length) {
throw 'field expander for id ' + id.toString() + ' does not exist!';
}
} else if (id >= STACK_EXPANDER_ID_MIN && id <= STACK_EXPANDER_ID_MAX) {
if (id - STACK_EXPANDER_ID_MIN >= state.stackExpanders.length) {
throw 'stack expander for id ' + id.toString() + ' does not exist!';
}
}
}
for (let i = 0; i < ids.length; i++) {
if (state.activeExpanders.length <= i) {
pruneExpanders(state.root, INVALID_ACTIVE_EXPANDER, i);
break;
} else if (ids[i] !== state.activeExpanders[i]) {
pruneExpanders(state.root, i, i);
break;
}
}
// TODO: if ids is prefix of activeExpanders, we need to make an expander invalid
state.activeExpanders = ids.slice();
},
getActiveExpanders: function getActiveExpanders() {
return state.activeExpanders.slice();
},
addAggregator: function addAggregator(name, aggregator, formatter, sorter) {
if (state.aggregators.length >= AGGREGATOR_ID_MAX) {
throw 'too many aggregators!';
}
state.aggregators.push({
name: name, // name for column
aggregator: aggregator, // index array -> aggregate value
formatter: formatter, // aggregate value -> display string
sorter: sorter, // compare two aggregate values
});
return state.aggregators.length - 1;
},
getAggregators: function getAggregators() {
const aggregators = [];
for (let i = 0; i < state.aggregators.length; i++) {
aggregators.push(i);
}
return aggregators;
},
getAggregatorName: function getAggregatorName(id) {
return state.aggregators[id & ACTIVE_AGGREGATOR_MASK].name;
},
setActiveAggregators: function setActiveAggregators(ids) {
for (let i = 0; i < ids.length; i++) {
const id = ids[i] & ACTIVE_AGGREGATOR_MASK;
if (id < 0 || id > state.aggregators.length) {
throw 'aggregator id ' + id.toString() + ' not valid';
}
}
state.activeAggregators = ids.slice();
// NB: evaluate root here because dirty bit is for children
// so someone has to start with root, and it might as well be right away
evaluateAggregate(state.root);
let sorter = noSortOrder;
for (let i = ids.length - 1; i >= 0; i--) {
const ascending = (ids[i] & ACTIVE_AGGREGATOR_ASC_BIT) !== 0;
const id = ids[i] & ACTIVE_AGGREGATOR_MASK;
const comparer = state.aggregators[id].sorter;
const captureSorter = sorter;
const captureIndex = i;
sorter = function (a, b) {
const c = comparer(a.aggregates[captureIndex], b.aggregates[captureIndex]);
if (c === 0) {
return captureSorter(a, b);
}
return ascending ? -c : c;
};
}
state.sorter = sorter;
state.root.state |= NODE_REORDER_BIT;
},
getActiveAggregators: function getActiveAggregators() {
return state.activeAggregators.slice();
},
getRows: function getRows(top, height) {
const result = new Array(height);
for (let i = 0; i < height; i++) {
result[i] = null;
}
getRowsImpl(state.root, top, height, result);
return result;
},
getRowLabel: function getRowLabel(row) {
return row.label;
},
getRowIndent: function getRowIndent(row) {
return row.state >>> NODE_INDENT_SHIFT;
},
getRowAggregate: function getRowAggregate(row, index) {
const aggregator = state.aggregators[state.activeAggregators[index]];
return aggregator.formatter(row.aggregates[index]);
},
getHeight: function getHeight() {
return state.root.height;
},
canExpand: function canExpand(row) {
return (row.state & NODE_EXPANDED_BIT) === 0 && (row.expander !== INVALID_ACTIVE_EXPANDER);
},
canContract: function canContract(row) {
return (row.state & NODE_EXPANDED_BIT) !== 0;
},
expand: function expand(row) {
if ((row.state & NODE_EXPANDED_BIT) !== 0) {
throw 'can not expand row, already expanded';
}
if (row.height !== 1) {
throw 'unexpanded row has height ' + row.height.toString() + ' != 1';
}
if (row.children === null) { // first expand, generate children
const activeIndex = row.expander & ACTIVE_EXPANDER_MASK;
let nextActiveIndex = activeIndex + 1; // NB: if next is stack, frame is 0
if (nextActiveIndex >= state.activeExpanders.length) {
nextActiveIndex = INVALID_ACTIVE_EXPANDER;
}
if (activeIndex >= state.activeExpanders.length) {
throw 'invalid active expander index ' + activeIndex.toString();
}
const exId = state.activeExpanders[activeIndex];
if (exId >= FIELD_EXPANDER_ID_MIN &&
exId < FIELD_EXPANDER_ID_MIN + state.fieldExpanders.length) {
const expander = state.fieldExpanders[exId - FIELD_EXPANDER_ID_MIN];
addChildrenWithFieldExpander(row, expander, nextActiveIndex);
} else if (exId >= STACK_EXPANDER_ID_MIN &&
exId < STACK_EXPANDER_ID_MIN + state.stackExpanders.length) {
const depth = row.expander >>> ACTIVE_EXPANDER_FRAME_SHIFT;
const expander = state.stackExpanders[exId - STACK_EXPANDER_ID_MIN];
addChildrenWithStackExpander(row, expander, activeIndex, depth, nextActiveIndex);
} else {
throw 'state.activeIndex ' + activeIndex.toString()
+ ' has invalid expander' + exId.toString();
}
}
row.state |= NODE_EXPANDED_BIT
| NODE_REAGGREGATE_BIT | NODE_REORDER_BIT | NODE_REPOSITION_BIT;
let heightChange = 0;
for (let i = 0; i < row.children.length; i++) {
heightChange += row.children[i].height;
}
updateHeight(row, heightChange);
// if children only contains one node, then expand it as well
if (row.children.length === 1 && this.canExpand(row.children[0])) {
this.expand(row.children[0]);
}
},
contract: function contract(row) {
contractRow(row);
},
};
}

View File

@ -1,379 +0,0 @@
/**
* Copyright (c) 2016-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
'use strict';
/*eslint no-console-disallow: "off"*/
/*global React ReactDOM Table StringInterner StackRegistry AggrowData Aggrow preLoadedCapture:true*/
function RefVisitor(refs, id) {
this.refs = refs;
this.id = id;
}
RefVisitor.prototype = {
moveToEdge: function moveToEdge(name) {
const ref = this.refs[this.id];
if (ref && ref.edges) {
const edges = ref.edges;
for (const edgeId in edges) {
if (edges[edgeId] === name) {
this.id = edgeId;
return this;
}
}
}
this.id = undefined;
return this;
},
moveToFirst: function moveToFirst(callback) {
const ref = this.refs[this.id];
if (ref && ref.edges) {
const edges = ref.edges;
for (const edgeId in edges) {
this.id = edgeId;
if (callback(edges[edgeId], this)) {
return this;
}
}
}
this.id = undefined;
return this;
},
forEachEdge: function forEachEdge(callback) {
const ref = this.refs[this.id];
if (ref && ref.edges) {
const edges = ref.edges;
const visitor = new RefVisitor(this.refs, undefined);
for (const edgeId in edges) {
visitor.id = edgeId;
callback(edges[edgeId], visitor);
}
}
},
getType: function getType() {
const ref = this.refs[this.id];
if (ref) {
return ref.type;
}
return undefined;
},
getRef: function getRef() {
return this.refs[this.id];
},
clone: function clone() {
return new RefVisitor(this.refs, this.id);
},
isDefined: function isDefined() {
return !!this.id;
},
getValue: function getValue() {
const ref = this.refs[this.id];
if (ref) {
if (ref.type === 'string') {
if (ref.value) {
return ref.value;
} else {
const rope = [];
this.forEachEdge((name, visitor) => {
if (name && name.startsWith('[') && name.endsWith(']')) {
const index = parseInt(name.substring(1, name.length - 1), 10);
rope[index] = visitor.getValue();
}
});
return rope.join('');
}
} else if (ref.type === 'ScriptExecutable'
|| ref.type === 'EvalExecutable'
|| ref.type === 'ProgramExecutable') {
return ref.value.url + ':' + ref.value.line + ':' + ref.value.col;
} else if (ref.type === 'FunctionExecutable') {
return ref.value.name + '@' + ref.value.url + ':' + ref.value.line + ':' + ref.value.col;
} else if (ref.type === 'NativeExecutable') {
return ref.value.function + ' ' + ref.value.constructor + ' ' + ref.value.name;
} else if (ref.type === 'Function') {
const executable = this.clone().moveToEdge('@Executable');
if (executable.id) {
return executable.getRef().type + ' ' + executable.getValue();
}
}
}
return '#none';
}
};
function forEachRef(refs, callback) {
const visitor = new RefVisitor(refs, undefined);
for (const id in refs) {
visitor.id = id;
callback(visitor);
}
}
function firstRef(refs, callback) {
for (const id in refs) {
const ref = refs[id];
if (callback(id, ref)) {
return new RefVisitor(refs, id);
}
}
return new RefVisitor(refs, undefined);
}
function getInternalInstanceName(visitor) {
const type = visitor.clone().moveToEdge('_currentElement').moveToEdge('type');
if (type.getType() === 'string') { // element.type is string
return type.getValue();
} else if (type.getType() === 'Function') { // element.type is function
const displayName = type.clone().moveToEdge('displayName');
if (displayName.isDefined()) {
return displayName.getValue(); // element.type.displayName
}
const name = type.clone().moveToEdge('name');
if (name.isDefined()) {
return name.getValue(); // element.type.name
}
type.moveToEdge('@Executable');
if (type.getType() === 'FunctionExecutable') {
return type.getRef().value.name; // element.type symbolicated name
}
}
return '#unknown';
}
function buildReactComponentTree(visitor, registry, strings) {
const ref = visitor.getRef();
if (ref.reactTree || ref.reactParent === undefined) {
return; // has one or doesn't need one
}
const parentVisitor = ref.reactParent;
if (parentVisitor === null) {
ref.reactTree = registry.insert(registry.root, strings.intern(getInternalInstanceName(visitor)));
} else if (parentVisitor) {
const parentRef = parentVisitor.getRef();
buildReactComponentTree(parentVisitor, registry, strings);
let relativeName = getInternalInstanceName(visitor);
if (ref.reactKey) {
relativeName = ref.reactKey + ': ' + relativeName;
}
ref.reactTree = registry.insert(parentRef.reactTree, strings.intern(relativeName));
} else {
throw 'non react instance parent of react instance';
}
}
function markReactComponentTree(refs, registry, strings) {
// annotate all refs that are react internal instances with their parent and name
// ref.reactParent = visitor that points to parent instance,
// null if we know it's an instance, but don't have a parent yet
// ref.reactKey = if a key is used to distinguish siblings
forEachRef(refs, (visitor) => {
const visitorClone = visitor.clone(); // visitor will get stomped on next iteration
const ref = visitor.getRef();
visitor.forEachEdge((edgeName, edgeVisitor) => {
const edgeRef = edgeVisitor.getRef();
if (edgeRef) {
if (edgeName === '_renderedChildren') {
if (ref.reactParent === undefined) {
// ref is react component, even if we don't have a parent yet
ref.reactParent = null;
}
edgeVisitor.forEachEdge((childName, childVisitor) => {
const childRef = childVisitor.getRef();
if (childRef && childName.startsWith('.')) {
childRef.reactParent = visitorClone;
childRef.reactKey = childName;
}
});
} else if (edgeName === '_renderedComponent') {
if (ref.reactParent === undefined) {
ref.reactParent = null;
}
edgeRef.reactParent = visitorClone;
}
}
});
});
// build tree of react internal instances (since that's what has the structure)
// fill in ref.reactTree = path registry node
forEachRef(refs, (visitor) => {
buildReactComponentTree(visitor, registry, strings);
});
// hook in components by looking at their _reactInternalInstance fields
forEachRef(refs, (visitor) => {
const ref = visitor.getRef();
const instanceRef = visitor.moveToEdge('_reactInternalInstance').getRef();
if (instanceRef) {
ref.reactTree = instanceRef.reactTree;
}
});
}
function functionUrlFileName(visitor) {
const executable = visitor.clone().moveToEdge('@Executable');
const ref = executable.getRef();
if (ref && ref.value && ref.value.url) {
const url = ref.value.url;
let file = url.substring(url.lastIndexOf('/') + 1);
if (file.endsWith('.js')) {
file = file.substring(0, file.length - 3);
}
return file;
}
return undefined;
}
function markModules(refs) {
const modules = firstRef(refs, (id, ref) => ref.type === 'CallbackGlobalObject');
modules.moveToEdge('require');
modules.moveToFirst((name, visitor) => visitor.getType() === 'JSActivation');
modules.moveToEdge('modules');
modules.forEachEdge((name, visitor) => {
const ref = visitor.getRef();
visitor.moveToEdge('exports');
if (visitor.getType() === 'Object') {
visitor.moveToFirst((memberName, member) => member.getType() === 'Function');
if (visitor.isDefined()) {
ref.module = functionUrlFileName(visitor);
}
} else if (visitor.getType() === 'Function') {
const displayName = visitor.clone().moveToEdge('displayName');
if (displayName.isDefined()) {
ref.module = displayName.getValue();
}
ref.module = functionUrlFileName(visitor);
}
if (ref && !ref.module) {
ref.module = '#unknown ' + name;
}
});
}
function registerPathToRoot(refs, registry, strings) {
markReactComponentTree(refs, registry, strings);
markModules(refs);
let breadth = [];
forEachRef(refs, (visitor) => {
const ref = visitor.getRef();
if (ref.type === 'CallbackGlobalObject') {
ref.rootPath = registry.insert(registry.root, strings.intern(ref.type));
breadth.push(visitor.clone());
}
});
while (breadth.length > 0) {
const nextBreadth = [];
for (let i = 0; i < breadth.length; i++) {
const visitor = breadth[i];
const ref = visitor.getRef();
visitor.forEachEdge((edgeName, edgeVisitor) => {
const edgeRef = edgeVisitor.getRef();
if (edgeRef && edgeRef.rootPath === undefined) {
let pathName = edgeRef.type;
if (edgeName) {
pathName = edgeName + ': ' + pathName;
}
edgeRef.rootPath = registry.insert(ref.rootPath, strings.intern(pathName));
nextBreadth.push(edgeVisitor.clone());
// copy module and react tree forward
if (edgeRef.module === undefined) {
edgeRef.module = ref.module;
}
if (edgeRef.reactTree === undefined) {
edgeRef.reactTree = ref.reactTree;
}
}
});
}
breadth = nextBreadth;
}
}
function registerCapture(data, captureId, capture, stacks, strings) {
// NB: capture.refs is potentially VERY large, so we try to avoid making
// copies, even if iteration is a bit more annoying.
let rowCount = 0;
for (const id in capture.refs) { // eslint-disable-line no-unused-vars
rowCount++;
}
for (const id in capture.markedBlocks) { // eslint-disable-line no-unused-vars
rowCount++;
}
const inserter = data.rowInserter(rowCount);
registerPathToRoot(capture.refs, stacks, strings);
const noneString = strings.intern('#none');
const noneStack = stacks.insert(stacks.root, noneString);
forEachRef(capture.refs, (visitor) => {
// want to data.append(value, value, value), not IDs
const ref = visitor.getRef();
const id = visitor.id;
inserter.insertRow(
parseInt(id, 16),
ref.type,
ref.size,
captureId,
ref.rootPath === undefined ? noneStack : ref.rootPath,
ref.reactTree === undefined ? noneStack : ref.reactTree,
visitor.getValue(),
ref.module === undefined ? '#none' : ref.module,
);
});
for (const id in capture.markedBlocks) {
const block = capture.markedBlocks[id];
inserter.insertRow(
parseInt(id, 16),
'Marked Block Overhead',
block.capacity - block.size,
captureId,
noneStack,
noneStack,
'capacity: ' + block.capacity + ', size: ' + block.size + ', granularity: ' + block.cellSize,
'#none',
);
}
inserter.done();
}
if (preLoadedCapture) {
const strings = StringInterner();
const stacks = new StackRegistry();
const columns = [
{ name: 'id', type: 'int' },
{ name: 'type', type: 'string', strings: strings },
{ name: 'size', type: 'int' },
{ name: 'trace', type: 'string', strings: strings },
{ name: 'path', type: 'stack', stacks: stacks },
{ name: 'react', type: 'stack', stacks: stacks },
{ name: 'value', type: 'string', strings: strings },
{ name: 'module', type: 'string', strings: strings },
];
const data = new AggrowData(columns);
registerCapture(data, 'trace', preLoadedCapture, stacks, strings);
preLoadedCapture = undefined; // let GG clean up the capture
const aggrow = new Aggrow(data);
aggrow.addPointerExpander('Id', 'id');
const typeExpander = aggrow.addStringExpander('Type', 'type');
aggrow.addNumberExpander('Size', 'size');
aggrow.addStringExpander('Trace', 'trace');
const pathExpander = aggrow.addStackExpander('Path', 'path', strings.get);
const reactExpander = aggrow.addStackExpander('React Tree', 'react', strings.get);
const valueExpander = aggrow.addStringExpander('Value', 'value');
const moduleExpander = aggrow.addStringExpander('Module', 'module');
aggrow.expander.setActiveExpanders([
pathExpander,
reactExpander,
moduleExpander,
typeExpander,
valueExpander,
]);
const sizeAggregator = aggrow.addSumAggregator('Size', 'size');
const countAggregator = aggrow.addCountAggregator('Count');
aggrow.expander.setActiveAggregators([
sizeAggregator,
countAggregator,
]);
ReactDOM.render(<Table aggrow={aggrow.expander} />, document.body);
}

View File

@ -1,540 +0,0 @@
/**
* Copyright (c) 2016-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
'use strict';
/*eslint no-console-disallow: "off"*/
/*global React:true*/
// TODO:
// selection and arrow keys for navigating
const rowHeight = 20;
const treeIndent = 16;
class Draggable extends React.Component { // eslint-disable-line no-unused-vars
constructor(props) {
super(props);
}
render() {
const id = this.props.id;
return React.cloneElement(
this.props.children,
{
draggable: 'true',
onDragStart: (e) => {
e.dataTransfer.setData('text', id);
},
}
);
}
}
Draggable.propTypes = {
children: React.PropTypes.element.isRequired,
id: React.PropTypes.string.isRequired,
};
class DropTarget extends React.Component { // eslint-disable-line no-unused-vars
constructor(props) {
super(props);
}
render() {
const thisId = this.props.id;
const dropAction = this.props.dropAction;
return React.cloneElement(
this.props.children,
{
onDragOver: (e) => e.preventDefault(),
onDrop: (e) => {
const sourceId = e.dataTransfer.getData('text');
e.preventDefault();
dropAction(sourceId, thisId);
},
}
);
}
}
DropTarget.propTypes = {
children: React.PropTypes.element.isRequired,
id: React.PropTypes.string.isRequired,
dropAction: React.PropTypes.func.isRequired,
};
class ExpanderConfiguration extends React.Component {
constructor(props) {
super(props);
}
render() {
const aggrow = this.props.aggrow;
const expander = this.props.expander;
return (
<Draggable id={'expander:add:' + expander.toString()}>
<div
style={{
width: 'auto',
height: '26px',
border: '1px solid darkGray',
margin: '2px',
}}>
{aggrow.getExpanderName(expander)}
</div>
</Draggable>
);
}
}
class TableConfiguration extends React.Component {
constructor(props) {
super(props);
this.state = {
expanded: false,
};
}
renderExpander(ex) {
return (<ExpanderConfiguration aggrow={this.props.aggrow} expander={ex} />);
}
render() {
const expanderText = this.state.expanded ? '>>' : '<<';
const aggrow = this.props.aggrow;
let config = [];
if (this.state.expanded) {
config = aggrow.getExpanders().map((ex) => this.renderExpander(ex));
}
return (
<div style={{
width: this.state.expanded ? '512px' : '26px',
height: '100%',
display: 'flex',
flexDirection: 'column',
overflow: 'hidden',
borderLeft: '2px solid black',
}}>
<div style={{
width: '100%',
height: '26px',
border: '1px solid darkGray',
}}
onClick={ (e) => this.setState({expanded: !this.state.expanded}) }>
{ expanderText }
</div>
{ config }
</div>
);
}
}
TableConfiguration.propTypes = {
aggrow: React.PropTypes.object.isRequired,
};
class TableHeader extends React.Component {
constructor(props) {
super(props);
}
render() {
const aggrow = this.props.aggrow;
const aggregators = aggrow.getActiveAggregators();
const expanders = aggrow.getActiveExpanders();
const headers = [];
for (let i = 0; i < aggregators.length; i++) {
const name = aggrow.getAggregatorName(aggregators[i]);
headers.push((
<DropTarget
id={'aggregate:insert:' + i.toString()}
dropAction={this.props.dropAction}
>
<div style={{
width: '16px',
height: 'inherit',
backgroundColor: 'darkGray',
flexShrink: '0' }}
></div>
</DropTarget>));
headers.push((<Draggable id={'aggregate:active:' + i.toString()}>
<div style={{ width: '128px', textAlign: 'center', flexShrink: '0' }}>{name}</div>
</Draggable>));
}
headers.push((
<DropTarget
id="divider:insert"
dropAction={this.props.dropAction}
>
<div style={{
width: '16px',
height: 'inherit',
backgroundColor: 'gold',
flexShrink: '0'
}}></div>
</DropTarget>));
for (let i = 0; i < expanders.length; i++) {
const name = aggrow.getExpanderName(expanders[i]);
const bg = (i % 2 === 0) ? 'white' : 'lightGray';
headers.push((<Draggable id={'expander:active:' + i.toString()}>
<div style={{
width: '128px',
textAlign: 'center',
backgroundColor: bg,
flexShrink: '0'
}}>
{name}
</div>
</Draggable>));
const sep = i + 1 < expanders.length ? '->' : '...';
headers.push((
<DropTarget
id={'expander:insert:' + (i + 1).toString()}
dropAction={this.props.dropAction}
>
<div style={{
height: 'inherit',
backgroundColor: 'darkGray',
flexShrink: '0'
}}>
{sep}
</div>
</DropTarget>)
);
}
return (
<div style={{
width: '100%',
height: '26px',
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
borderBottom: '2px solid black',
}}>
{headers}
</div>
);
}
}
TableHeader.propTypes = {
aggrow: React.PropTypes.object.isRequired,
dropAction: React.PropTypes.func.isRequired,
};
class Table extends React.Component { // eslint-disable-line no-unused-vars
constructor(props) {
super(props);
this.state = {
aggrow: props.aggrow,
viewport: { top: 0, height: 100 },
cursor: 0,
};
}
scroll(e) {
const viewport = e.target;
const top = Math.floor((viewport.scrollTop - viewport.clientHeight * 1.0) / rowHeight);
const height = Math.ceil(viewport.clientHeight * 3.0 / rowHeight);
if (top !== this.state.viewport.top || height !== this.state.viewport.height) {
this.setState({viewport: {top, height}});
}
}
_contractRow(row) {
let newCursor = this.state.cursor;
if (newCursor > row.top && newCursor < row.top + row.height) { // in contracted section
newCursor = row.top;
} else if (newCursor >= row.top + row.height) { // below contracted section
newCursor -= row.height - 1;
}
this.state.aggrow.contract(row);
this.setState({cursor: newCursor});
console.log('-' + row.top);
}
_expandRow(row) {
let newCursor = this.state.cursor;
this.state.aggrow.expand(row);
if (newCursor > row.top) { // below expanded section
newCursor += row.height - 1;
}
this.setState({cursor: newCursor});
console.log('+' + row.top);
}
_scrollDiv: null;
_keepCursorInViewport() {
if (this._scrollDiv) {
const cursor = this.state.cursor;
const scrollDiv = this._scrollDiv;
if (cursor * rowHeight < scrollDiv.scrollTop + scrollDiv.clientHeight * 0.1) {
scrollDiv.scrollTop = cursor * rowHeight - scrollDiv.clientHeight * 0.1;
} else if ((cursor + 1) * rowHeight > scrollDiv.scrollTop + scrollDiv.clientHeight * 0.9) {
scrollDiv.scrollTop = (cursor + 1) * rowHeight - scrollDiv.clientHeight * 0.9;
}
}
}
keydown(e) {
const aggrow = this.state.aggrow;
let cursor = this.state.cursor;
let row = aggrow.getRows(cursor, 1)[0];
switch (e.keyCode) {
case 38: // up
if (cursor > 0) {
this.setState({cursor: cursor - 1});
this._keepCursorInViewport();
}
e.preventDefault();
break;
case 40: // down
if (cursor < aggrow.getHeight() - 1) {
this.setState({cursor: cursor + 1});
this._keepCursorInViewport();
}
e.preventDefault();
break;
case 37: // left
if (aggrow.canContract(row)) {
this._contractRow(row);
} else if (aggrow.getRowIndent(row) > 0) {
const indent = aggrow.getRowIndent(row) - 1;
while (aggrow.getRowIndent(row) > indent) {
cursor--;
row = aggrow.getRows(cursor, 1)[0];
}
this.setState({cursor: cursor});
this._keepCursorInViewport();
}
e.preventDefault();
break;
case 39: // right
if (aggrow.canExpand(row)) {
this._expandRow(row);
} else if (cursor < aggrow.getHeight() - 1) {
this.setState({cursor: cursor + 1});
this._keepCursorInViewport();
}
e.preventDefault();
break;
}
}
dropAction(s, d) {
const aggrow = this.state.aggrow;
console.log('dropped ' + s + ' to ' + d);
if (s.startsWith('aggregate:active:')) {
const sIndex = parseInt(s.substr(17), 10);
let dIndex = -1;
const active = aggrow.getActiveAggregators();
const dragged = active[sIndex];
if (d.startsWith('aggregate:insert:')) {
dIndex = parseInt(d.substr(17), 10);
} else if (d === 'divider:insert') {
dIndex = active.length;
} else {
throw 'not allowed to drag ' + s + ' to ' + d;
}
if (dIndex > sIndex) {
dIndex--;
}
active.splice(sIndex, 1);
active.splice(dIndex, 0, dragged);
aggrow.setActiveAggregators(active);
this.setState({cursor:0});
} else if (s.startsWith('expander:active:')) {
const sIndex = parseInt(s.substr(16), 10);
let dIndex = -1;
const active = aggrow.getActiveExpanders();
const dragged = active[sIndex];
if (d.startsWith('expander:insert:')) {
dIndex = parseInt(d.substr(16), 10);
} else if (d === 'divider:insert') {
dIndex = 0;
} else {
throw 'not allowed to drag ' + s + ' to ' + d;
}
if (dIndex > sIndex) {
dIndex--;
}
active.splice(sIndex, 1);
active.splice(dIndex, 0, dragged);
aggrow.setActiveExpanders(active);
this.setState({cursor:0});
} else if (s.startsWith('expander:add:')) {
let dIndex = -1;
const sExpander = parseInt(s.substring(13), 10);
if (d.startsWith('expander:insert:')) {
dIndex = parseInt(d.substr(16), 10);
} else if (d === 'divider:insert') {
dIndex = 0;
} else {
throw 'not allowed to drag ' + s + ' to ' + d;
}
const active = aggrow.getActiveExpanders();
active.splice(dIndex, 0, sExpander);
aggrow.setActiveExpanders(active);
this.setState({cursor:0});
}
}
render() {
return (
<div style={{ width: '100%', height: '100%', display: 'flex', flexDirection: 'row' }}>
<div style={{ width: '100%', height: '100%', display: 'flex', flexDirection: 'column', overflow: 'hidden', }}>
<TableHeader aggrow={this.state.aggrow} dropAction={(s, d) => this.dropAction(s, d)} />
<div
style={{
width: '100%',
flexGrow: '1',
overflow: 'scroll'
}}
onScroll={ (e) => this.scroll(e) }
ref={(div) => { this._scrollDiv = div; } }>
<div style={{ position: 'relative' }}>
{ this.renderVirtualizedRows() }
</div>
</div>
</div>
<TableConfiguration aggrow={this.state.aggrow} />
</div>
);
}
renderVirtualizedRows() {
const aggrow = this.state.aggrow;
const viewport = this.state.viewport;
const rows = aggrow.getRows(viewport.top, viewport.height);
return (
<div style={{
position: 'absolute',
width: '100%',
height: (rowHeight * (aggrow.getHeight() + 20)).toString() + 'px'
}}>
{ rows.map(child => this.renderRow(child)) }
</div>
);
}
renderRow(row) {
if (row === null) {
return null;
}
let bg = 'lightGray';
const aggrow = this.state.aggrow;
const columns = [];
let rowText = '';
const indent = 4 + aggrow.getRowIndent(row) * treeIndent;
const aggregates = aggrow.getActiveAggregators();
if (row.parent !== null && (row.parent.expander % 2 === 0)) {
bg = 'white';
}
if (row.top === this.state.cursor) {
bg = 'lightblue';
}
for (let i = 0; i < aggregates.length; i++) {
var aggregate = aggrow.getRowAggregate(row, i);
columns.push((
<div style={{
width: '16px',
height: 'inherit',
backgroundColor: 'darkGray',
flexShrink: '0'
}}></div>
));
columns.push((
<div style={{
width: '128px',
textAlign: 'right',
flexShrink: '0'
}}>
{aggregate}
</div>
));
}
columns.push((
<div style={{
width: '16px',
height: 'inherit',
backgroundColor: 'gold',
flexShrink: '0'
}}></div>
));
if (aggrow.canExpand(row)) {
columns.push((
<div
style={{
marginLeft: indent.toString() + 'px',
flexShrink: '0',
width: '12px',
textAlign: 'center',
border: '1px solid gray',
}}
onClick={ () => this._expandRow(row) }
>+</div>
));
} else if (aggrow.canContract(row)) {
columns.push((
<div
style={{
marginLeft: indent.toString() + 'px',
flexShrink: '0',
width: '12px',
textAlign: 'center',
border: '1px solid gray',
}}
onClick={ () => this._contractRow(row) }
>-</div>
));
} else {
columns.push((
<div
style={{
marginLeft: indent.toString() + 'px',
}}
></div>
));
}
rowText += aggrow.getRowLabel(row);
columns.push((
<div style={{
flexShrink: '0',
whiteSpace: 'nowrap',
marginRight: '20px'
}}>
{rowText}
</div>
));
return (
<div
key={row.top}
style={{
position: 'absolute',
height: (rowHeight - 1).toString() + 'px',
top: (rowHeight * row.top).toString() + 'px',
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
backgroundColor: bg,
borderBottom: '1px solid gray',
}}
onClick={ () => {
this.setState({cursor: row.top});
}}>
{columns}
</div>
);
}
componentDidMount() {
this.keydown = this.keydown.bind(this);
document.body.addEventListener('keydown', this.keydown);
}
componentWillUnmount() {
document.body.removeEventListener('keydown', this.keydown);
}
}