Commit a97a362d authored by Sigurd Schneider's avatar Sigurd Schneider Committed by Commit Bot

[turbolizer] Add support for inlined functions

This CL adds support for inlined functions in Turbolizer. It is also a
refactoring of the Turbolizer code-base. Most importantly, handling of
source positions changed to exact source positions, and not code ranges.
This improves selection interoperability between different phase views.

A separate CL changes the Turbolizer JSON format to include inlining
information. This Turbolizer update, however, is intended to be backwards
compatible with the JSON format Turbolizer generated before the JSON
format change.


Bug: v8:7327
Change-Id: Ic67506a6f3a36fe98c012b1e76994972779c1fd2
Reviewed-on: https://chromium-review.googlesource.com/1032784
Commit-Queue: Sigurd Schneider <sigurds@chromium.org>
Reviewed-by: 's avatarDaniel Clifford <danno@chromium.org>
Cr-Commit-Position: refs/heads/master@{#53213}
parent 4b6263be
This diff is collapsed.
...@@ -11,7 +11,6 @@ var SOURCE_PANE_ID = 'left'; ...@@ -11,7 +11,6 @@ var SOURCE_PANE_ID = 'left';
var SOURCE_COLLAPSE_ID = 'source-shrink'; var SOURCE_COLLAPSE_ID = 'source-shrink';
var SOURCE_EXPAND_ID = 'source-expand'; var SOURCE_EXPAND_ID = 'source-expand';
var INTERMEDIATE_PANE_ID = 'middle'; var INTERMEDIATE_PANE_ID = 'middle';
var EMPTY_PANE_ID = 'empty';
var GRAPH_PANE_ID = 'graph'; var GRAPH_PANE_ID = 'graph';
var SCHEDULE_PANE_ID = 'schedule'; var SCHEDULE_PANE_ID = 'schedule';
var GENERATED_PANE_ID = 'right'; var GENERATED_PANE_ID = 'right';
......
...@@ -5,22 +5,39 @@ ...@@ -5,22 +5,39 @@
"use strict"; "use strict";
class DisassemblyView extends TextView { class DisassemblyView extends TextView {
constructor(id, broker) {
super(id, broker, null, false);
createViewElement() {
const pane = document.createElement('div');
pane.setAttribute('id', "disassembly");
pane.innerHTML =
`<pre id='disassembly-text-pre' class='prettyprint prettyprinted'>
<ul id='disassembly-list' class='nolinenums noindent'>
</ul>
</pre>`;
return pane;
}
constructor(parentId, broker) {
super(parentId, broker, null, false);
let view = this; let view = this;
let ADDRESS_STYLE = { let ADDRESS_STYLE = {
css: 'tag', css: 'tag',
location: function(text) { assignSourcePosition: function (text) {
ADDRESS_STYLE.last_address = text; return SOURCE_POSITION_HEADER_STYLE.currentSourcePosition;
return undefined; },
linkHandler: function (text, fragment) {
if (fragment.sourcePosition === undefined) return undefined;
return (e) => {
e.stopPropagation();
if (!e.shiftKey) {
view.sourcePositionSelectionHandler.clear();
}
view.sourcePositionSelectionHandler.select([fragment.sourcePosition], true);
};
} }
}; };
let ADDRESS_LINK_STYLE = { let ADDRESS_LINK_STYLE = {
css: 'tag', css: 'tag'
link: function(text) {
view.select(function(location) { return location.address == text; }, true, true);
}
}; };
let UNCLASSIFIED_STYLE = { let UNCLASSIFIED_STYLE = {
css: 'com' css: 'com'
...@@ -33,52 +50,47 @@ class DisassemblyView extends TextView { ...@@ -33,52 +50,47 @@ class DisassemblyView extends TextView {
}; };
let POSITION_STYLE = { let POSITION_STYLE = {
css: 'com', css: 'com',
location: function(text) { location: function (text) {
view.pos_start = Number(text); view.pos_start = Number(text);
} }
}; };
let OPCODE_STYLE = { let OPCODE_STYLE = {
css: 'kwd', css: 'kwd',
location: function(text) {
if (BLOCK_HEADER_STYLE.block_id != undefined) {
return {
address: ADDRESS_STYLE.last_address,
block_id: BLOCK_HEADER_STYLE.block_id
};
} else {
return {
address: ADDRESS_STYLE.last_address
};
}
}
}; };
const BLOCK_HEADER_STYLE = { const BLOCK_HEADER_STYLE = {
css: 'com', css: ['com', 'block'],
block_id: -1, blockId: function (text) {
location: function(text) {
let matches = /\d+/.exec(text); let matches = /\d+/.exec(text);
if (!matches) return undefined; if (!matches) return undefined;
BLOCK_HEADER_STYLE.block_id = Number(matches[0]); BLOCK_HEADER_STYLE.block_id = Number(matches[0]);
return { return BLOCK_HEADER_STYLE.block_id;
block_id: BLOCK_HEADER_STYLE.block_id
};
}, },
linkHandler: function (text) {
let matches = /\d+/.exec(text);
if (!matches) return undefined;
const blockId = matches[0];
return function (e) {
e.stopPropagation();
if (!e.shiftKey) {
view.selectionHandler.clear();
}
view.blockSelectionHandler.select([blockId], true);
};
}
}; };
const SOURCE_POSITION_HEADER_STYLE = { const SOURCE_POSITION_HEADER_STYLE = {
css: 'com', css: 'com',
location: function(text) { sourcePosition: function (text) {
let matches = /(\d+):(\d+)/.exec(text); let matches = view.SOURCE_POSITION_HEADER_REGEX.exec(text);
if (!matches) return undefined; if (!matches) return undefined;
let li = Number(matches[1]); const scriptOffset = Number(matches[3]);
if (view.pos_lines === null) return undefined; const inliningId = matches[1] === 'not inlined' ? -1 : Number(matches[2]);
let pos = view.pos_lines[li-1] + Number(matches[2]); const sp = { scriptOffset: scriptOffset, inliningId: inliningId };
return { SOURCE_POSITION_HEADER_STYLE.currentSourcePosition = sp;
pos_start: pos, return sp;
pos_end: pos + 1
};
}, },
}; };
view.SOURCE_POSITION_HEADER_REGEX = /^(\s*-- .+:)(\d+:\d+)( --)/; view.SOURCE_POSITION_HEADER_REGEX = /^\s*--[^<]*<.*(not inlined|inlined\((\d+)\)):(\d+)>\s*--/;
let patterns = [ let patterns = [
[ [
[/^0x[0-9a-f]{8,16}/, ADDRESS_STYLE, 1], [/^0x[0-9a-f]{8,16}/, ADDRESS_STYLE, 1],
...@@ -119,36 +131,6 @@ class DisassemblyView extends TextView { ...@@ -119,36 +131,6 @@ class DisassemblyView extends TextView {
view.setPatterns(patterns); view.setPatterns(patterns);
} }
lineLocation(li) {
let view = this;
let result = undefined;
for (let i = 0; i < li.children.length; ++i) {
let fragment = li.children[i];
let location = fragment.location;
if (location != null) {
if (location.block_id != undefined) {
if (result === undefined) result = {};
result.block_id = location.block_id;
}
if (location.address != undefined) {
if (result === undefined) result = {};
result.address = location.address;
}
if (location.pos_start != undefined && location.pos_end != undefined) {
if (result === undefined) result = {};
result.pos_start = location.pos_start;
result.pos_end = location.pos_end;
}
else if (view.pos_start != -1) {
if (result === undefined) result = {};
result.pos_start = view.pos_start;
result.pos_end = result.pos_start + 1;
}
}
}
return result;
}
initializeContent(data, rememberedSelection) { initializeContent(data, rememberedSelection) {
this.data = data; this.data = data;
super.initializeContent(data, rememberedSelection); super.initializeContent(data, rememberedSelection);
...@@ -164,13 +146,13 @@ class DisassemblyView extends TextView { ...@@ -164,13 +146,13 @@ class DisassemblyView extends TextView {
// Comment lines for line 0 include sourcePosition already, only need to // Comment lines for line 0 include sourcePosition already, only need to
// add sourcePosition for lines > 0. // add sourcePosition for lines > 0.
view.pos_lines[0] = sourcePosition; view.pos_lines[0] = sourcePosition;
if (sourceText != "") { if (sourceText && sourceText != "") {
let base = sourcePosition; let base = sourcePosition;
let current = 0; let current = 0;
let source_lines = sourceText.split("\n"); let source_lines = sourceText.split("\n");
for (let i = 1; i < source_lines.length; i++) { for (let i = 1; i < source_lines.length; i++) {
// Add 1 for newline character that is split off. // Add 1 for newline character that is split off.
current += source_lines[i-1].length + 1; current += source_lines[i - 1].length + 1;
view.pos_lines[i] = base + current; view.pos_lines[i] = base + current;
} }
} }
...@@ -209,16 +191,6 @@ class DisassemblyView extends TextView { ...@@ -209,16 +191,6 @@ class DisassemblyView extends TextView {
processLine(line) { processLine(line) {
let view = this; let view = this;
let func = function(match, p1, p2, p3) {
let nums = p2.split(":");
let li = Number(nums[0]);
let pos = Number(nums[1]);
if(li === 0)
pos -= view.pos_lines[0];
li++;
return p1 + li + ":" + pos + p3;
};
line = line.replace(view.SOURCE_POSITION_HEADER_REGEX, func);
let fragments = super.processLine(line); let fragments = super.processLine(line);
// Add profiling data per instruction if available. // Add profiling data per instruction if available.
...@@ -230,7 +202,7 @@ class DisassemblyView extends TextView { ...@@ -230,7 +202,7 @@ class DisassemblyView extends TextView {
let count = view.addr_event_counts[event][matches[1]]; let count = view.addr_event_counts[event][matches[1]];
let str = " "; let str = " ";
let css_cls = "prof"; let css_cls = "prof";
if(count !== undefined) { if (count !== undefined) {
let perc = count / view.total_event_counts[event] * 100; let perc = count / view.total_event_counts[event] * 100;
let col = { r: 255, g: 255, b: 255 }; let col = { r: 255, g: 255, b: 255 };
......
// Copyright 2015 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
"use strict";
class EmptyView extends View {
constructor(id, broker) {
super(id, broker);
this.svg = this.divElement.append("svg").attr('version','1.1').attr("width", "100%");
}
initializeContent(data, rememberedSelection) {
this.svg.attr("height", document.documentElement.clientHeight + "px");
}
deleteContent() {
}
}
...@@ -447,7 +447,7 @@ function layoutNodeGraph(graph) { ...@@ -447,7 +447,7 @@ function layoutNodeGraph(graph) {
}); });
graph.maxBackEdgeNumber = 0; graph.maxBackEdgeNumber = 0;
graph.visibleEdges.each(function (e) { graph.visibleEdges.selectAll("path").each(function (e) {
if (e.isBackEdge()) { if (e.isBackEdge()) {
e.backEdgeNumber = ++graph.maxBackEdgeNumber; e.backEdgeNumber = ++graph.maxBackEdgeNumber;
} else { } else {
......
This diff is collapsed.
...@@ -3,13 +3,11 @@ ...@@ -3,13 +3,11 @@
<head> <head>
<title>Turbolizer</title> <title>Turbolizer</title>
<link rel="stylesheet" href="turbo-visualizer.css" /> <link rel="stylesheet" href="turbo-visualizer.css" />
<link rel="stylesheet" href="prettify.css" />
<link rel="icon" type="image/png" href="turbolizer.png">
</head> </head>
<body> <body>
<div id="left" class="viewpane"> <div id="left" class="viewpane scrollable"></div>
<div id='source-text'>
<pre id='source-text-pre'\>
</div>
</div>
<div class="resizer-left"></div> <div class="resizer-left"></div>
<div id="middle" class="viewpane"> <div id="middle" class="viewpane">
<div id="graph-toolbox-anchor"> <div id="graph-toolbox-anchor">
...@@ -18,7 +16,7 @@ ...@@ -18,7 +16,7 @@
alt="layout graph" class="button-input"> alt="layout graph" class="button-input">
<input id="show-all" type="image" title="show all nodes" src="expand-all.jpg" <input id="show-all" type="image" title="show all nodes" src="expand-all.jpg"
alt="show all nodes" class="button-input"> alt="show all nodes" class="button-input">
<input id="hide-dead" type="image" title="only live nodes" src="live.png" <input id="toggle-hide-dead" type="image" title="show only live nodes" src="live.png"
alt="only live nodes" class="button-input"> alt="only live nodes" class="button-input">
<input id="hide-unselected" type="image" title="hide unselected nodes" <input id="hide-unselected" type="image" title="hide unselected nodes"
src="hide-unselected.png" alt="hide unselected nodes" class="button-input"> src="hide-unselected.png" alt="hide unselected nodes" class="button-input">
...@@ -36,33 +34,17 @@ ...@@ -36,33 +34,17 @@
</select> </select>
</span> </span>
</div> </div>
<div id="load-file"> <div id="load-file">
<input type="file" id="hidden-file-upload"> <input id="upload-helper" type="file">
<input id="upload" type="image" title="load graph" class="button-input" <input id="upload" type="image" title="load graph" class="button-input"
src="upload-icon.png" alt="upload graph"> src="upload-icon.png" alt="upload graph">
</div> </div>
<div id="empty" width="100%" height="100%"></div>
<div id="graph" width="100%" height="100%"></div>
<div id="schedule" width="100%">
<pre id="schedule-text-pre" class='prettyprint prettyprinted'>
<ul id="schedule-list" class='nolinenums noindent'>
</ul>
</pre>
</div>
<div id='text-placeholder' width="0px" height="0px" style="position: absolute; top:100000px;" ><svg><text text-anchor="right"> <div id='text-placeholder' width="0px" height="0px" style="position: absolute; top:100000px;" ><svg><text text-anchor="right">
<tspan white-space="inherit" id="text-measure"/> <tspan white-space="inherit" id="text-measure"/>
</text></svg></div> </text></svg></div>
</div> </div>
<div class="resizer-right"></div> <div class="resizer-right"></div>
<div id="right" class="viewpane"> <div id="right" class="viewpane scrollable"></div>
<div id='disassembly'>
<pre id='disassembly-text-pre' class='prettyprint prettyprinted'>
<ul id='disassembly-list' class='nolinenums noindent'>
</ul>
</pre>
</div>
</div>
<div id="source-collapse" class="collapse-pane"> <div id="source-collapse" class="collapse-pane">
<input id="source-expand" type="image" title="show source" <input id="source-expand" type="image" title="show source"
src="right-arrow.png" class="button-input invisible"> src="right-arrow.png" class="button-input invisible">
...@@ -76,19 +58,18 @@ ...@@ -76,19 +58,18 @@
src="right-arrow.png" class="button-input"> src="right-arrow.png" class="button-input">
</div> </div>
<script src="https://cdn.rawgit.com/google/code-prettify/master/loader/run_prettify.js"></script> <script src="https://cdn.rawgit.com/google/code-prettify/master/loader/run_prettify.js"></script>
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script> <script src="https://d3js.org/d3.v3.min.js" charset="utf-8"></script>
<script src="https://cdn.jsdelivr.net/filesaver.js/0.1/FileSaver.min.js"></script>
<script src="monkey.js"></script> <script src="monkey.js"></script>
<script src="util.js"></script> <script src="util.js"></script>
<script src="lang-disassembly.js"></script> <script src="lang-disassembly.js"></script>
<script src="node.js"></script> <script src="node.js"></script>
<script src="edge.js"></script> <script src="edge.js"></script>
<script src="source-resolver.js"></script>
<script src="selection.js"></script> <script src="selection.js"></script>
<script src="selection-broker.js"></script> <script src="selection-broker.js"></script>
<script src="constants.js"></script> <script src="constants.js"></script>
<script src="view.js"></script> <script src="view.js"></script>
<script src="text-view.js"></script> <script src="text-view.js"></script>
<script src="empty-view.js"></script>
<script src="code-view.js"></script> <script src="code-view.js"></script>
<script src="graph-layout.js"></script> <script src="graph-layout.js"></script>
<script src="graph-view.js"></script> <script src="graph-view.js"></script>
......
...@@ -5,124 +5,159 @@ ...@@ -5,124 +5,159 @@
"use strict"; "use strict";
class ScheduleView extends TextView { class ScheduleView extends TextView {
constructor(id, broker) {
super(id, broker, null, false); createViewElement() {
let view = this; const pane = document.createElement('div');
let BLOCK_STYLE = { pane.setAttribute('id', "schedule");
css: 'tag' pane.innerHTML =
}; `<pre id='schedule-text-pre' class='prettyprint prettyprinted'>
const BLOCK_HEADER_STYLE = { <ul id='schedule-list' class='nolinenums noindent'>
css: 'com', </ul>
block_id: -1, </pre>`;
location: function(text) { return pane;
let matches = /\d+/.exec(text); }
if (!matches) return undefined;
BLOCK_HEADER_STYLE.block_id = Number(matches[0]); constructor(parentId, broker) {
return { super(parentId, broker, null, false);
block_id: BLOCK_HEADER_STYLE.block_id }
};
}, attachSelection(s) {
}; const view = this;
const BLOCK_LINK_STYLE = { if (!(s instanceof Set)) return;
css: 'tag', view.selectionHandler.clear();
link: function(text) { view.blockSelectionHandler.clear();
let id = Number(text.substr(1)); view.sourcePositionSelectionHandler.clear();
view.select(function(location) { return location.block_id == id; }, true, true); const selected = new Array();
} for (const key of s) selected.push(key);
}; view.selectionHandler.select(selected, true);
const ID_STYLE = { }
css: 'tag',
location: function(text) { createElementFromString(htmlString) {
let matches = /\d+/.exec(text); var div = document.createElement('div');
return { div.innerHTML = htmlString.trim();
node_id: Number(matches[0]), return div.firstChild;
block_id: BLOCK_HEADER_STYLE.block_id }
};
},
}; elementForBlock(block) {
const ID_LINK_STYLE = { const view = this;
css: 'tag', function createElement(tag, cls, content) {
link: function(text) { const el = document.createElement(tag);
let id = Number(text); if (isIterable(cls)) {
view.select(function(location) { return location.node_id == id; }, true, true); for (const c of cls) el.classList.add(c);
} else {
el.classList.add(cls);
} }
}; if (content != undefined) el.innerHTML = content;
const NODE_STYLE = { css: 'kwd' }; return el;
const GOTO_STYLE = { css: 'kwd', }
goto_id: -2,
location: function(text) { function mkNodeLinkHandler(nodeId) {
return { return function (e) {
node_id: GOTO_STYLE.goto_id--, e.stopPropagation();
block_id: BLOCK_HEADER_STYLE.block_id if (!e.shiftKey) {
}; view.selectionHandler.clear();
}
view.selectionHandler.select([nodeId], true);
};
}
function createElementForNode(node) {
const nodeEl = createElement("div", "node");
const node_id = createElement("div", ["node-id", "tag", "clickable"], node.id);
node_id.onclick = mkNodeLinkHandler(node.id);
view.addHtmlElementForNodeId(node.id, node_id);
nodeEl.appendChild(node_id);
const node_label = createElement("div", "node-label", node.label);
nodeEl.appendChild(node_label);
if (node.inputs.length > 0) {
const node_parameters = createElement("div", ["parameter-list", "comma-sep-list"]);
for (const param of node.inputs) {
const paramEl = createElement("div", ["parameter", "tag", "clickable"], param);
node_parameters.appendChild(paramEl);
paramEl.onclick = mkNodeLinkHandler(param);
view.addHtmlElementForNodeId(param, paramEl);
}
nodeEl.appendChild(node_parameters);
} }
return nodeEl;
} }
const ARROW_STYLE = { css: 'kwd' };
let patterns = [ function mkBlockLinkHandler(blockId) {
[ return function (e) {
[/^--- BLOCK B\d+/, BLOCK_HEADER_STYLE, 1], e.stopPropagation();
[/^\s+\d+: /, ID_STYLE, 2], if (!e.shiftKey) {
[/^\s+Goto/, GOTO_STYLE, 6], view.blockSelectionHandler.clear();
[/^.*/, null, -1] }
], view.blockSelectionHandler.select(["" + blockId], true);
[ };
[/^ +/, null], }
[/^\(deferred\)/, BLOCK_HEADER_STYLE],
[/^B\d+/, BLOCK_LINK_STYLE], const schedule_block = createElement("div", "schedule-block");
[/^<-/, ARROW_STYLE], const block_id = createElement("div", ["block-id", "com", "clickable"], block.id);
[/^->/, ARROW_STYLE], block_id.onclick = mkBlockLinkHandler(block.id);
[/^,/, null], schedule_block.appendChild(block_id);
[/^---/, BLOCK_HEADER_STYLE, -1] const block_pred = createElement("div", ["predecessor-list", "block-list", "comma-sep-list"]);
], for (const pred of block.pred) {
// Parse opcode including [] const predEl = createElement("div", ["block-id", "com", "clickable"], pred);
[ predEl.onclick = mkBlockLinkHandler(pred);
[/^[A-Za-z0-9_]+(\[.*\])?$/, NODE_STYLE, -1], block_pred.appendChild(predEl);
[/^[A-Za-z0-9_]+(\[(\[.*?\]|.)*?\])?/, NODE_STYLE, 3] }
], if (block.pred.length) schedule_block.appendChild(block_pred);
// Parse optional parameters const nodes = createElement("div", "nodes");
[ for (const node of block.nodes) {
[/^ /, null, 4], nodes.appendChild(createElementForNode(node, block.id));
[/^\(/, null], }
[/^\d+/, ID_LINK_STYLE], schedule_block.appendChild(nodes);
[/^, /, null], const block_succ = createElement("div", ["successor-list", "block-list", "comma-sep-list"]);
[/^\)$/, null, -1], for (const succ of block.succ) {
[/^\)/, null, 4], const succEl = createElement("div", ["block-id", "com", "clickable"], succ);
], succEl.onclick = mkBlockLinkHandler(succ);
[ block_succ.appendChild(succEl);
[/^ -> /, ARROW_STYLE, 5], }
[/^.*/, null, -1] if (block.succ.length) schedule_block.appendChild(block_succ);
], this.addHtmlElementForBlockId(block.id, schedule_block);
[ return schedule_block;
[/^B\d+$/, BLOCK_LINK_STYLE, -1],
[/^B\d+/, BLOCK_LINK_STYLE],
[/^, /, null]
],
[
[/^ -> /, ARROW_STYLE],
[/^B\d+$/, BLOCK_LINK_STYLE, -1]
]
];
this.setPatterns(patterns);
} }
initializeContent(data, rememberedSelection) { addBlocks(blocks) {
super.initializeContent(data, rememberedSelection); for (const block of blocks) {
var graph = this; const blockEl = this.elementForBlock(block);
var locations = []; this.divNode.appendChild(blockEl);
for (var id of rememberedSelection) {
locations.push({ node_id : id });
} }
this.selectLocations(locations, true, true); }
initializeContent(data, rememberedSelection) {
this.clearText();
this.schedule = data.schedule
this.addBlocks(data.schedule.blocks);
this.attachSelection(rememberedSelection);
} }
detachSelection() { detachSelection() {
var selection = this.selection.detachSelection(); this.blockSelection.clear();
var s = new Set(); this.sourcePositionSelection.clear();
for (var i of selection) { return this.selection.detachSelection();
if (i.location.node_id != undefined && i.location.node_id > 0) { }
s.add(i.location.node_id);
lineString(node) {
return `${node.id}: ${node.label}(${node.inputs.join(", ")})`
}
searchInputAction(view, searchBar) {
d3.event.stopPropagation();
this.selectionHandler.clear();
const query = searchBar.value;
if (query.length == 0) return;
const select = [];
window.sessionStorage.setItem("lastSearch", query);
const reg = new RegExp(query);
for (const node of this.schedule.nodes) {
if (node === undefined) continue;
if (reg.exec(this.lineString(node)) != null) {
select.push(node.id)
} }
}; }
return s; this.selectionHandler.select(select, true);
} }
} }
...@@ -2,98 +2,72 @@ ...@@ -2,98 +2,72 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
var SelectionBroker = function() { class SelectionBroker {
this.brokers = []; constructor(sourceResolver) {
this.dispatching = false; this.sourcePositionHandlers = [];
this.lastDispatchingHandler = null; this.nodeHandlers = [];
this.nodePositionMap = []; this.blockHandlers = [];
this.sortedPositionList = []; this.sourceResolver = sourceResolver;
this.positionNodeMap = []; };
};
SelectionBroker.prototype.addSelectionHandler = function(handler) { addSourcePositionHandler(handler) {
this.brokers.push(handler); this.sourcePositionHandlers.push(handler);
} }
SelectionBroker.prototype.setNodePositionMap = function(map) { addNodeHandler(handler) {
let broker = this; this.nodeHandlers.push(handler);
if (!map) return;
broker.nodePositionMap = map;
broker.positionNodeMap = [];
broker.sortedPositionList = [];
let next = 0;
for (let i in broker.nodePositionMap) {
broker.sortedPositionList[next] = Number(broker.nodePositionMap[i]);
broker.positionNodeMap[next++] = i;
} }
broker.sortedPositionList = sortUnique(broker.sortedPositionList,
function(a,b) { return a - b; });
this.positionNodeMap.sort(function(a,b) {
let result = broker.nodePositionMap[a] - broker.nodePositionMap[b];
if (result != 0) return result;
return a - b;
});
}
SelectionBroker.prototype.select = function(from, locations, selected) { addBlockHandler(handler) {
let broker = this; this.blockHandlers.push(handler);
if (!broker.dispatching) { }
broker.lastDispatchingHandler = from;
try { broadcastSourcePositionSelect(from, sourcePositions, selected) {
broker.dispatching = true; let broker = this;
let enrichLocations = function(locations) { sourcePositions = sourcePositions.filter((l) => {
result = []; if (typeof l.scriptOffset == 'undefined'
for (let location of locations) { || typeof l.inliningId == 'undefined') {
let newLocation = {}; console.log("Warning: invalid source position");
if (location.pos_start != undefined) { return false;
newLocation.pos_start = location.pos_start;
}
if (location.pos_end != undefined) {
newLocation.pos_end = location.pos_end;
}
if (location.node_id != undefined) {
newLocation.node_id = location.node_id;
}
if (location.block_id != undefined) {
newLocation.block_id = location.block_id;
}
if (newLocation.pos_start == undefined &&
newLocation.pos_end == undefined &&
newLocation.node_id != undefined) {
if (broker.nodePositionMap && broker.nodePositionMap[location.node_id]) {
newLocation.pos_start = broker.nodePositionMap[location.node_id];
newLocation.pos_end = location.pos_start + 1;
}
}
result.push(newLocation);
}
return result;
}
locations = enrichLocations(locations);
for (var b of this.brokers) {
if (b != from) {
b.brokeredSelect(locations, selected);
}
} }
return true;
});
for (var b of this.sourcePositionHandlers) {
if (b != from) b.brokeredSourcePositionSelect(sourcePositions, selected);
} }
finally { const nodes = this.sourceResolver.sourcePositionsToNodeIds(sourcePositions);
broker.dispatching = false; for (var b of this.nodeHandlers) {
if (b != from) b.brokeredNodeSelect(nodes, selected);
} }
} }
}
SelectionBroker.prototype.clear = function(from) { broadcastNodeSelect(from, nodes, selected) {
this.lastDispatchingHandler = null; let broker = this;
if (!this.dispatching) { for (var b of this.nodeHandlers) {
try { if (b != from) b.brokeredNodeSelect(nodes, selected);
this.dispatching = true;
this.brokers.forEach(function(b) {
if (b != from) {
b.brokeredClear();
}
});
} finally {
this.dispatching = false;
} }
const sourcePositions = this.sourceResolver.nodeIdsToSourcePositions(nodes);
for (var b of this.sourcePositionHandlers) {
if (b != from) b.brokeredSourcePositionSelect(sourcePositions, selected);
}
}
broadcastBlockSelect(from, blocks, selected) {
let broker = this;
for (var b of this.blockHandlers) {
if (b != from) b.brokeredBlockSelect(blocks, selected);
}
}
broadcastClear(from) {
this.sourcePositionHandlers.forEach(function (b) {
if (b != from) b.brokeredClear();
});
this.nodeHandlers.forEach(function (b) {
if (b != from) b.brokeredClear();
});
this.blockHandlers.forEach(function (b) {
if (b != from) b.brokeredClear();
});
} }
} }
...@@ -2,107 +2,59 @@ ...@@ -2,107 +2,59 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
var Selection = function(handler) { class Selection {
this.handler = handler; constructor(stringKeyFnc) {
this.selectionBase = null; this.selection = new Map();
this.lastSelection = null; this.stringKey = stringKeyFnc;
this.selection = new Set(); }
}
Selection.prototype.isEmpty = function() {
return this.selection.size == 0;
}
Selection.prototype.clear = function() {
var handler = this.handler;
this.selectionBase = null;
this.lastSelection = null;
handler.select(this.selection, false);
handler.clear();
this.selection = new Set();
}
isEmpty() {
return this.selection.size == 0;
}
count = 0; clear() {
this.selection = new Map();
}
Selection.prototype.select = function(s, isSelected) { select(s, isSelected) {
var handler = this.handler; if (!isIterable(s)) { s = [s]; }
if (!(Symbol.iterator in Object(s))) { s = [s]; } for (const i of s) {
if (isSelected) { if (!i) continue;
let first = true; if (isSelected == undefined) {
for (let i of s) { isSelected = !this.selection.has(this.stringKey(i));
if (first) {
this.selectionBase = i;
this.lastSelection = i;
first = false;
} }
this.selection.add(i); if (isSelected) {
} this.selection.set(this.stringKey(i), i);
handler.select(this.selection, true); } else {
} else { this.selection.delete(this.stringKey(i));
let unselectSet = new Set();
for (let i of s) {
if (this.selection.has(i)) {
unselectSet.add(i);
this.selection.delete(i);
} }
} }
handler.select(unselectSet, false);
} }
}
isSelected(i) {
return this.selection.has(this.stringKey(i));
}
Selection.prototype.extendTo = function(pos) { isKeySelected(key) {
if (pos == this.lastSelection || this.lastSelection === null) return; return this.selection.has(key);
}
var handler = this.handler; selectedKeys() {
var pos_diff = handler.selectionDifference(pos, true, this.lastSelection, false); var result = new Set();
var unselect_diff = []; for (var i of this.selection.keys()) {
if (pos_diff.length == 0) { result.add(i);
pos_diff = handler.selectionDifference(this.selectionBase, false, pos, true);
if (pos_diff.length != 0) {
unselect_diff = handler.selectionDifference(this.lastSelection, true, this.selectionBase, false);
this.selection = new Set();
this.selection.add(this.selectionBase);
for (var d of pos_diff) {
this.selection.add(d);
}
} else {
unselect_diff = handler.selectionDifference(this.lastSelection, true, pos, false);
for (var d of unselect_diff) {
this.selection.delete(d);
}
}
} else {
unselect_diff = handler.selectionDifference(this.selectionBase, false, this.lastSelection, true);
if (unselect_diff != 0) {
pos_diff = handler.selectionDifference(pos, true, this.selectionBase, false);
if (pos_diff.length == 0) {
unselect_diff = handler.selectionDifference(pos, false, this.lastSelection, true);
}
for (var d of unselect_diff) {
this.selection.delete(d);
}
}
if (pos_diff.length != 0) {
for (var d of pos_diff) {
this.selection.add(d);
}
} }
return result;
} }
handler.select(unselect_diff, false);
handler.select(pos_diff, true);
this.lastSelection = pos;
}
detachSelection() {
Selection.prototype.detachSelection = function() { var result = new Set();
var result = new Set(); for (var i of this.selection.keys()) {
for (var i of this.selection) { result.add(i);
result.add(i); }
this.clear();
return result;
} }
this.clear();
return result; [Symbol.iterator]() { return this.selection.values() }
} }
// Copyright 2018 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
function sourcePositionLe(a, b) {
if (a.inliningId == b.inliningId) {
return a.scriptOffset - b.scriptOffset;
}
return a.inliningId - b.inliningId;
}
function sourcePositionEq(a, b) {
return a.inliningId == b.inliningId &&
a.scriptOffset == b.scriptOffset;
}
function sourcePositionToStringKey(sourcePosition) {
if (!sourcePosition) return "undefined";
return "" + sourcePosition.inliningId + ":" + sourcePosition.scriptOffset;
}
class SourceResolver {
constructor() {
// Maps node ids to source positions.
this.nodePositionMap = [];
// Maps source ids to source objects.
this.sources = [];
// Maps inlining ids to inlining objects.
this.inlinings = [];
// Maps source position keys to inlinings.
this.inliningsMap = new Map();
// Maps source position keys to node ids.
this.positionToNodes = new Map();
// Maps phase ids to phases.
this.phases = [];
// The disassembly phase is stored separately.
this.disassemblyPhase = undefined;
}
setSources(sources, mainBackup) {
if (sources) {
for (let [sourceId, source] of Object.entries(sources)) {
this.sources[sourceId] = source;
this.sources[sourceId].sourcePositions = [];
}
}
// This is a fallback if the JSON is incomplete (e.g. due to compiler crash).
if (!this.sources[-1]) {
this.sources[-1] = mainBackup;
this.sources[-1].sourcePositions = [];
}
}
setInlinings(inlinings) {
if (inlinings) {
for (const [inliningId, inlining] of Object.entries(inlinings)) {
this.inlinings[inliningId] = inlining;
this.inliningsMap.set(sourcePositionToStringKey(inlining.inliningPosition), inlining);
}
}
// This is a default entry for the script itself that helps
// keep other code more uniform.
this.inlinings[-1] = { sourceId: -1 };
}
setNodePositionMap(map) {
if (!map) return;
if (typeof map[0] != 'object') {
const alternativeMap = {};
for (const [nodeId, scriptOffset] of Object.entries(map)) {
alternativeMap[nodeId] = { scriptOffset: scriptOffset, inliningId: -1 };
}
map = alternativeMap;
};
for (const [nodeId, sourcePosition] of Object.entries(map)) {
if (sourcePosition == undefined) {
console.log("Warning: undefined source position ", sourcePosition, " for nodeId ", nodeId);
}
const inliningId = sourcePosition.inliningId;
const inlining = this.inlinings[inliningId];
if (inlining) {
const sourceId = inlining.sourceId;
this.sources[sourceId].sourcePositions.push(sourcePosition);
}
this.nodePositionMap[nodeId] = sourcePosition;
let key = sourcePositionToStringKey(sourcePosition);
if (!this.positionToNodes.has(key)) {
this.positionToNodes.set(key, []);
}
this.positionToNodes.get(key).push(nodeId);
}
for (const [sourceId, source] of Object.entries(this.sources)) {
source.sourcePositions = sortUnique(source.sourcePositions,
sourcePositionLe, sourcePositionEq);
}
}
sourcePositionsToNodeIds(sourcePositions) {
const nodeIds = new Set();
for (const sp of sourcePositions) {
let key = sourcePositionToStringKey(sp);
let nodeIdsForPosition = this.positionToNodes.get(key);
if (!nodeIdsForPosition) continue;
for (const nodeId of nodeIdsForPosition) {
nodeIds.add(nodeId);
}
}
return nodeIds;
}
nodeIdsToSourcePositions(nodeIds) {
const sourcePositions = new Map();
for (const nodeId of nodeIds) {
let sp = this.nodePositionMap[nodeId];
let key = sourcePositionToStringKey(sp);
sourcePositions.set(key, sp);
}
const sourcePositionArray = [];
for (const sp of sourcePositions.values()) {
sourcePositionArray.push(sp);
}
return sourcePositionArray;
}
forEachSource(f) {
this.sources.forEach(f);
}
translateToSourceId(sourceId, location) {
for (const position of this.getInlineStack(location)) {
let inlining = this.inlinings[position.inliningId];
if (!inlining) continue;
if (inlining.sourceId == sourceId) {
return position;
}
}
return location;
}
addInliningPositions(sourcePosition, locations) {
let inlining = this.inliningsMap.get(sourcePositionToStringKey(sourcePosition));
if (!inlining) return;
let sourceId = inlining.sourceId
const source = this.sources[sourceId];
for (const sp of source.sourcePositions) {
locations.push(sp);
this.addInliningPositions(sp, locations);
}
}
getInliningForPosition(sourcePosition) {
return this.inliningsMap.get(sourcePositionToStringKey(sourcePosition));
}
getSource(sourceId) {
return this.sources[sourceId];
}
getSourceName(sourceId) {
const source = this.sources[sourceId];
return `${source.sourceName}:${source.functionName}`;
}
sourcePositionFor(sourceId, scriptOffset) {
if (!this.sources[sourceId]) {
return null;
}
const list = this.sources[sourceId].sourcePositions;
for (let i = 0; i < list.length; i++) {
const sourcePosition = list[i]
const position = sourcePosition.scriptOffset;
const nextPosition = list[Math.min(i + 1, list.length - 1)].scriptOffset;
if ((position <= scriptOffset && scriptOffset < nextPosition)) {
return sourcePosition;
}
}
return null;
}
sourcePositionsInRange(sourceId, start, end) {
if (!this.sources[sourceId]) return [];
const res = [];
const list = this.sources[sourceId].sourcePositions;
for (let i = 0; i < list.length; i++) {
const sourcePosition = list[i]
if (start <= sourcePosition.scriptOffset && sourcePosition.scriptOffset < end) {
res.push(sourcePosition);
}
}
return res;
}
getInlineStack(sourcePosition) {
if (!sourcePosition) {
return [];
}
let inliningStack = [];
let cur = sourcePosition;
while (cur && cur.inliningId != -1) {
inliningStack.push(cur);
let inlining = this.inlinings[cur.inliningId];
if (!inlining) {
break;
}
cur = inlining.inliningPosition;
}
if (cur && cur.inliningId == -1) {
inliningStack.push(cur);
}
return inliningStack;
}
parsePhases(phases) {
for (const [phaseId, phase] of Object.entries(phases)) {
if (phase.type == 'disassembly') {
this.disassemblyPhase = phase;
} else if (phase.type == 'schedule') {
this.phases.push(this.parseSchedule(phase))
} else {
this.phases.push(phase);
}
}
}
repairPhaseId(anyPhaseId) {
return Math.max(0, Math.min(anyPhaseId, this.phases.length - 1))
}
getPhase(phaseId) {
return this.phases[phaseId];
}
forEachPhase(f) {
this.phases.forEach(f);
}
parseSchedule(phase) {
function createNode(state, match) {
let inputs = [];
if (match.groups.args) {
const nodeIdsString = match.groups.args.replace(/\s/g, '');
const nodeIdStrings = nodeIdsString.split(',');
inputs = nodeIdStrings.map((n) => Number.parseInt(n, 10));
}
const node = {id: Number.parseInt(match.groups.id, 10),
label: match.groups.label,
inputs: inputs};
if (match.groups.blocks) {
const nodeIdsString = match.groups.blocks.replace(/\s/g, '').replace(/B/g,'');
const nodeIdStrings = nodeIdsString.split(',');
const successors = nodeIdStrings.map((n) => Number.parseInt(n, 10));
state.currentBlock.succ = successors;
}
state.nodes[node.id] = node;
state.currentBlock.nodes.push(node);
}
function createBlock(state, match) {
let predecessors = [];
if (match.groups.in) {
const blockIdsString = match.groups.in.replace(/\s/g, '').replace(/B/g, '');
const blockIdStrings = blockIdsString.split(',');
predecessors = blockIdStrings.map((n) => Number.parseInt(n, 10));
}
const block = {id: Number.parseInt(match.groups.id, 10),
isDeferred: match.groups.deferred != undefined,
pred: predecessors.sort(),
succ: [],
nodes: []};
state.blocks[block.id] = block;
state.currentBlock = block;
}
function setGotoSuccessor(state, match) {
state.currentBlock.succ = [Number.parseInt(match.groups.successor.replace(/\s/g, ''), 10)];
}
const rules = [
{
lineRegexps:
[ /^\s*(?<id>\d+):\ (?<label>.*)\((?<args>.*)\)$/,
/^\s*(?<id>\d+):\ (?<label>.*)\((?<args>.*)\)\ ->\ (?<blocks>.*)$/,
/^\s*(?<id>\d+):\ (?<label>.*)$/
],
process: createNode
},
{
lineRegexps:
[/^\s*---\s*BLOCK\ B(?<id>\d+)\s*(?<deferred>\(deferred\))?(\ <-\ )?(?<in>[^-]*)?\ ---$/
],
process: createBlock
},
{
lineRegexps:
[/^\s*Goto\s*->\s*B(?<successor>\d+)\s*$/
],
process: setGotoSuccessor
}
];
const lines = phase.data.split(/[\n]/);
const state = { currentBlock: undefined, blocks: [], nodes: [] };
nextLine:
for (const line of lines) {
for (const rule of rules) {
for (const lineRegexp of rule.lineRegexps) {
const match = line.match(lineRegexp);
if (match) {
rule.process(state, match);
continue nextLine;
}
}
}
console.log("Warning: unmatched schedule line \"" + line + "\"");
}
phase.schedule = state;
return phase;
}
}
This diff is collapsed.
...@@ -50,24 +50,73 @@ ...@@ -50,24 +50,73 @@
background-color: #FFFF33; background-color: #FFFF33;
} }
.selected.block,
.selected.block-id,
.selected.schedule-block {
background-color: #AAFFAA;
}
ol.linenums {
-webkit-padding-start: 8px;
}
.line-number {
display:inline-block;
min-width: 3ex;
text-align: right;
color: gray;
padding-right:1ex;
font-size: 10px;
user-select: none;
}
.line-number:hover {
background-color: #CCCCCC;
}
.prettyprint ol.linenums > li { .prettyprint ol.linenums > li {
list-style-type: decimal; list-style-type: decimal;
!important padding-top: 3px;
display: block;
}
.source-container {
border-bottom: 2px solid #AAAAAA;
}
.code-header {
background-color: #CCCCCC;
padding-left: 1em;
padding-right: 1em;
padding-top: 1ex;
padding-bottom: 1ex;
font-family: monospace;
user-select: none;
}
.main-source .code-header {
border-top: 2px solid #AAAAAA;
font-weight: bold;
}
.code-header .code-file-function {
font-family: monospace;
float: left;
user-select: text;
} }
.code-header .code-mode {
float: right;
font-family: sans-serif;
font-size: small;
}
body { html, body {
margin: 0; margin: 0;
padding: 0; padding: 0;
height: 100vh; /*height: 99vh;
width: 100vw; width: 99vw;*/
overflow:hidden; overflow: hidden;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
} }
p { p {
...@@ -95,7 +144,7 @@ g.unsorted rect { ...@@ -95,7 +144,7 @@ g.unsorted rect {
} }
div.scrollable { div.scrollable {
overflow-y: _croll; overflow-x: hidden; overflow-y: auto; overflow-x: hidden;
} }
g.turbonode[relToHover="input"] rect { g.turbonode[relToHover="input"] rect {
...@@ -243,10 +292,13 @@ span.linkable-text:hover { ...@@ -243,10 +292,13 @@ span.linkable-text:hover {
#left { #left {
float: left; float: left;
user-select: none;
} }
#middle { #middle {
float:left; background-color: #F8F8F8; float:left;
background-color: #F8F8F8;
user-select: none;
} }
#right { #right {
...@@ -267,6 +319,11 @@ span.linkable-text:hover { ...@@ -267,6 +319,11 @@ span.linkable-text:hover {
left: 0; left: 0;
} }
#graph {
width: 100%;
height: 100%;
}
#graph-toolbox-anchor { #graph-toolbox-anchor {
height: 0px; height: 0px;
} }
...@@ -308,7 +365,7 @@ span.linkable-text:hover { ...@@ -308,7 +365,7 @@ span.linkable-text:hover {
padding: 0.5em; padding: 0.5em;
} }
#hidden-file-upload { #upload-helper {
display: none; display: none;
} }
...@@ -363,4 +420,165 @@ text { ...@@ -363,4 +420,165 @@ text {
.resizer-right.dragged { .resizer-right.dragged {
background: orange; background: orange;
}
.source-position {
/* border-left: 1px solid #FF3333; */
width: 0;
display: inline-block;
}
.source-position .inlining-marker {
content: "";
position: relative;
display: inline-block;
top: -0.5ex;
margin-left: -4px;
margin-right: -4px;
border-width: 5px;
border-style: solid;
border-color: #555 transparent transparent transparent;
}
.source-position .marker {
content: "";
display: inline-block;
position: relative;
bottom: -1ex;
width: 0px;
margin-left: -4px;
margin-right: -4px;
border-width: 5px;
border-style: solid;
border-color: transparent transparent #555 transparent;
}
.source-position.selected .marker {
border-color: transparent transparent #F00 transparent;
}
.source-position .inlining-marker:hover {
border-color: transparent transparent #AA5 transparent;
}
.source-position .inlining-marker[data-descr]:hover::after {
content: attr(data-descr);
position: absolute;
font-size: 10px;
z-index: 1;
background-color: #555;
color: #fff;
text-align: center;
border-radius: 6px;
padding: 6px;
top: 6px;
left: 50%;
margin-left: -80px;
}
#schedule {
font-family: monospace;
}
.schedule-block {
margin: 5px;
background-color: white;
}
.schedule-block .block-id {
display: inline-block;
font-size:large;
text-decoration: underline;
}
.schedule-block .block-id:hover {
font-weight: bold;
}
.schedule-block > .block-id::before {
content: "Block B";
}
.schedule-block .block-list {
display: inline-block;
}
.schedule-block .block-list * {
display: inline-block;
}
.schedule-block .block-list .block-id {
padding-left: 1ex;
}
.schedule-block .block-list .block-id:before {
content: "B";
}
.schedule-block .predecessor-list::before {
display: inline-block;
content: " \2B05 ";
padding-left: 1ex;
padding-right: 1ex;
}
.schedule-block .successor-list::before {
display: inline-block;
content: " \2B95 ";
padding-left: 1ex;
padding-right: 1ex;
}
.schedule-block .nodes {
padding-left: 5px;
}
.schedule-block .nodes .node * {
display:inline-block;
}
.schedule-block .nodes .node .node-id {
padding-right: 1ex;
min-width: 5ex;
text-align: right;
}
.schedule-block .nodes .node .node-id:after {
content: ":";
}
.schedule-block .nodes .node .node-label {
user-select: text;
}
.schedule-block .nodes .node .parameter-list:before {
content: "(";
}
.schedule-block .nodes .node .parameter-list:after {
content: ")";
}
.clickable:hover {
text-decoration: underline;
}
.clickable:hover {
font-weight: bold;
}
.comma-sep-list > * {
padding-right: 1ex;
}
.comma-sep-list > *:after {
content: ",";
}
.comma-sep-list > *:last-child:after {
content: "";
}
.comma-sep-list > *:last-child {
padding-right: 0ex;
} }
\ No newline at end of file
This diff is collapsed.
This diff was suppressed by a .gitattributes entry.
...@@ -4,19 +4,41 @@ ...@@ -4,19 +4,41 @@
"use strict"; "use strict";
function makeContainerPosVisible(container, pos) { function computeScrollTop(container, element) {
var height = container.offsetHeight; const height = container.offsetHeight;
var margin = Math.floor(height / 4); const margin = Math.floor(height / 4);
if (pos < container.scrollTop + margin) { const pos = element.offsetTop;
pos -= margin; const currentScrollTop = container.scrollTop;
if (pos < 0) pos = 0; if (pos < currentScrollTop + margin) {
container.scrollTop = pos; return Math.max(0, pos - margin);
return; } else if (pos > (currentScrollTop + 3 * margin)) {
return Math.max(0, pos - 3 * margin);
} }
if (pos > (container.scrollTop + 3 * margin)) { return pos;
pos = pos - 3 * margin; }
if (pos < 0) pos = 0;
container.scrollTop = pos; class ViewElements {
constructor(container) {
this.container = container;
this.scrollTop = undefined;
}
consider(element, doConsider) {
if (!doConsider) return;
const newScrollTop = computeScrollTop(this.container, element);
if (isNaN(newScrollTop)) {
console.log("NOO")
}
if (this.scrollTop === undefined) {
this.scrollTop = newScrollTop;
} else {
this.scrollTop = Math.min(this.scrollTop, newScrollTop);
}
}
apply(doApply) {
if (!doApply || this.scrollTop === undefined) return;
this.container.scrollTop = this.scrollTop;
} }
} }
...@@ -59,11 +81,12 @@ function upperBound(a, value, compare, lookup) { ...@@ -59,11 +81,12 @@ function upperBound(a, value, compare, lookup) {
} }
function sortUnique(arr, f) { function sortUnique(arr, f, equal) {
if (arr.length == 0) return arr;
arr = arr.sort(f); arr = arr.sort(f);
let ret = [arr[0]]; let ret = [arr[0]];
for (var i = 1; i < arr.length; i++) { for (var i = 1; i < arr.length; i++) {
if (arr[i-1] !== arr[i]) { if (!equal(arr[i-1], arr[i])) {
ret.push(arr[i]); ret.push(arr[i]);
} }
} }
...@@ -78,3 +101,8 @@ function partial(f) { ...@@ -78,3 +101,8 @@ function partial(f) {
f.apply(this, arguments1.concat(arguments2)); f.apply(this, arguments1.concat(arguments2));
} }
} }
function isIterable(obj) {
return obj != null && obj != undefined
&& typeof obj != 'string' && typeof obj[Symbol.iterator] === 'function';
}
...@@ -6,9 +6,9 @@ ...@@ -6,9 +6,9 @@
class View { class View {
constructor(id, broker) { constructor(id, broker) {
this.divElement = d3.select("#" + id); this.container = document.getElementById(id);
this.divNode = this.divElement[0][0]; this.divNode = this.createViewElement();
this.parentNode = this.divNode.parentNode; this.divElement = d3.select(this.divNode);
} }
isScrollable() { isScrollable() {
...@@ -16,7 +16,7 @@ class View { ...@@ -16,7 +16,7 @@ class View {
} }
show(data, rememberedSelection) { show(data, rememberedSelection) {
this.parentNode.appendChild(this.divElement[0][0]); this.container.appendChild(this.divElement.node());
this.initializeContent(data, rememberedSelection); this.initializeContent(data, rememberedSelection);
this.divElement.attr(VISIBILITY, 'visible'); this.divElement.attr(VISIBILITY, 'visible');
} }
...@@ -24,7 +24,7 @@ class View { ...@@ -24,7 +24,7 @@ class View {
hide() { hide() {
this.divElement.attr(VISIBILITY, 'hidden'); this.divElement.attr(VISIBILITY, 'hidden');
this.deleteContent(); this.deleteContent();
this.parentNode.removeChild(this.divNode); this.container.removeChild(this.divNode);
} }
detachSelection() { detachSelection() {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment