servo/etc/layout_viewer/viewer_2020.html
2023-12-16 09:32:20 +00:00

339 lines
10 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Servo Layout Debugger</title>
<!-- Bootstrap -->
<link href="css/bootstrap.min.css" rel="stylesheet" />
<!-- Treeview -->
<link href="css/bootstrap-treeview.min.css" rel="stylesheet" />
<!-- JSDiffPatch -->
<link href="css/formatters/html.css" rel="stylesheet" />
<!-- Custom -->
<link href="css/main.css" rel="stylesheet" />
<!--[if lt IE 9]>
<script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>
<script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
<![endif]-->
</head>
<body>
<div class="container" role="main">
<div class="row">
<div class="col-sm-12">
<h1>Servo Layout Viewer</h1>
<p>
Check the
<a
href="https://github.com/servo/servo/blob/main/etc/layout_viewer/README"
>README</a
>
for instructions.
</p>
</div>
</div>
<div class="row">
<div class="col-sm-4">
<div class="row">
<div class="col-sm-12">
<div class="well">
<input type="file" />
</div>
</div>
</div>
<div class="row">
<div class="col-sm-12">
<div id="trace-tree"></div>
</div>
</div>
<div class="row">
<div class="col-sm-12">
<ul id="trace-list" class="list-group"></ul>
</div>
</div>
</div>
<div class="col-sm-8">
<div class="row">
<div class="col-sm-12">
<div class="panel panel-default">
<div class="panel-heading">
Box Tree
<a
id="box-tree-collapse"
class="tree-collapse"
data-collapsed="0"
></a>
</div>
<div class="panel-body" id="box-tree"></div>
</div>
</div>
<div class="col-sm-12">
<div id="box-diffs"></div>
</div>
</div>
<div class="row">
<div class="col-sm-12">
<div class="panel panel-default">
<div class="panel-heading">
Fragment Tree
<a
id="fragment-tree-collapse"
class="tree-collapse"
data-collapsed="0"
></a>
</div>
<div class="panel-body" id="fragment-tree"></div>
</div>
</div>
<div class="col-sm-12">
<div id="fragment-diffs"></div>
</div>
</div>
</div>
</div>
</div>
<!-- jQuery -->
<script src="js/jquery.min.js"></script>
<!-- Bootstrap -->
<script src="js/bootstrap.min.js"></script>
<!-- Treeview -->
<script src="js/bootstrap-treeview.min.js"></script>
<!-- JSDiffPatch -->
<script src="js/bundle.min.js"></script>
<script src="js/formatters.min.js"></script>
<script>
function get_inner_boxes(box) {
const box_type = Object.keys(box)[0];
switch (box_type) {
case "BlockLevelBoxes":
return box.BlockLevelBoxes;
case "InlineFormattingContext":
return box.InlineFormattingContext.inline_level_boxes;
case "InlineBox":
return box.InlineBox.children;
case "SameFormattingContextBlock":
case "Independent":
case "Flow":
case "OutOfFlowAbsolutelyPositionedBox":
case "OutOfFlowFloatBox":
case "Atomic":
return box[box_type].contents;
}
return null;
}
function box_tree_from_container(container) {
const box_type = Object.keys(container)[0];
let inner_boxes = get_inner_boxes(container);
let nodes = [];
let text = box_type;
if (Array.isArray(inner_boxes)) {
if (!inner_boxes.length) {
nodes = null;
} else {
for (let box in inner_boxes) {
nodes.push(box_tree_from_container(inner_boxes[box]));
}
}
} else if (inner_boxes != null) {
nodes.push(box_tree_from_container(inner_boxes));
} else {
if (box_type == "TextRun") {
text += ` (${container.TextRun.text})`;
}
nodes = null;
}
let info;
if (
box_type != "BlockLevelBoxes" &&
box_type != "InlineFormattingContext"
) {
info = Object.assign({}, Object.values(container)[0]);
delete info.children;
delete info.contents;
delete info.tag;
}
return {
text,
nodes,
info
};
}
function box_tree_from_bfc(bfc) {
const { contains_floats, contents } = bfc;
let block_container = Object.values(contents)[0];
return {
text: "BlockFormattingContext",
info: {
contains_floats
},
nodes: [box_tree_from_container(contents)]
};
}
function create_fragment_tree(root) {
let fragment = Object.values(root)[0];
let node = {
text: Object.keys(root)[0],
id: fragment.debug_id,
href: "#diff-" + fragment.debug_id
};
if (fragment.children) {
let children = [];
for (let i = 0; i < fragment.children.length; ++i) {
children.push(create_fragment_tree(fragment.children[i]));
}
if (children.length > 0) {
node.nodes = children;
}
}
node.info = Object.assign({}, fragment);
delete node.info.children;
delete node.info.debug_id;
return node;
}
function flatten_trace(trace_node) {
const fragment_tree_root = Object.values(
trace_node.fragment_tree.root_fragments
)[0];
return {
fragment_tree: create_fragment_tree(fragment_tree_root),
box_tree: box_tree_from_bfc(trace_node.box_tree.root)
};
}
function create_trace_tree_node(trace_node) {
const trace = flatten_trace(trace_node.pre);
let tree_node = {
text: trace_node.name,
icon: "dummy",
box_tree: trace.box_tree,
fragment_tree: trace.fragment_tree
};
let node = Object.values(trace_node)[0];
if (node.children) {
let children = [];
for (let i = 0; i < node.children.length; ++i) {
children.push(create_trace_tree_node(node.children[i]));
}
if (children.length > 0) {
tree_node.nodes = children;
}
}
return tree_node;
}
function new_data_loaded(data) {
jsondiffpatch.formatters.html.hideUnchanged();
let node_color_hash = {};
let tree = [create_trace_tree_node(data)];
$("#trace-tree").treeview({ data: tree, levels: 3 });
$("#trace-tree").on("nodeSelected", function(event, node) {
$("#fragment-diffs").empty();
$("#trace-tree")
.treeview(true)
.revealNode(node);
const on_tree_node_selected = tree => (event, data) => {
$(`#${tree}-diffs`).empty();
if (!data.info) return;
// XXX(ferjm) no diff for now.
const delta = jsondiffpatch
.create({
objectHash: function(obj) {
return JSON.stringify(obj);
}
})
.diff({}, data.info);
const json = jsondiffpatch.formatters.html.format(delta, data.info);
$(`#${tree}-diffs`).append(
"<div class='panel panel-default'><div class='panel-heading'>" +
data.text +
"</div><div class='panel-body'>" +
json +
"</div></div>"
);
};
const on_fragment_tree_node_selected = on_tree_node_selected(
"fragment"
);
const on_box_tree_node_selected = on_tree_node_selected("box");
$("#fragment-tree").treeview({
data: [node.fragment_tree],
levels: 100,
enableLinks: false,
emptyIcon: "glyphicon glyphicon-unchecked hidden-glyphicon",
onNodeSelected: on_fragment_tree_node_selected
});
$("#box-tree").treeview({
data: [node.box_tree],
levels: 100,
enableLinks: false,
emptyIcon: "glyphicon glyphicon-unchecked hidden-glyphicon",
onNodeSelected: on_box_tree_node_selected
});
["fragment", "box"].forEach(key => {
const collapsable = $(`#${key}-tree-collapse`);
collapsable.html("Collapse all").on("click", () => {
const collapsed = collapsable.data("collapsed");
if (collapsed == 0) {
$(`#${key}-tree`).treeview("collapseAll");
} else {
$(`#${key}-tree`).treeview("expandAll");
}
collapsable.html(collapsed == 0 ? "Expand all" : "Collapse all");
collapsable.data("collapsed", collapsed == 0 ? 1 : 0);
});
});
});
$("#trace-tree")
.treeview(true)
.selectNode(0);
}
$(document).ready(function() {
let upload = document.getElementsByTagName("input")[0];
upload.onchange = function(e) {
e.preventDefault();
let file = upload.files[0],
reader = new FileReader();
reader.onload = function(event) {
new_data_loaded(JSON.parse(event.target.result));
};
reader.readAsText(file);
return false;
};
});
</script>
</body>
</html>