mirror of
https://github.com/servo/servo.git
synced 2025-08-03 04:30:10 +01:00
Keep layout viewer versions for both 2013 and 2020 engines
This commit is contained in:
parent
f81a2f021e
commit
c33a517387
2 changed files with 591 additions and 315 deletions
|
@ -1,339 +1,276 @@
|
|||
<!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>
|
||||
<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" />
|
||||
<!-- Bootstrap -->
|
||||
<link href="css/bootstrap.min.css" rel="stylesheet">
|
||||
|
||||
<!-- Treeview -->
|
||||
<link href="css/bootstrap-treeview.min.css" rel="stylesheet" />
|
||||
<!-- Treeview -->
|
||||
<link href="css/bootstrap-treeview.min.css" rel="stylesheet">
|
||||
|
||||
<!-- JSDiffPatch -->
|
||||
<link href="css/formatters/html.css" rel="stylesheet" />
|
||||
<!-- JSDiffPatch -->
|
||||
<link href="css/formatters/html.css" rel="stylesheet">
|
||||
|
||||
<!-- Custom -->
|
||||
<link href="css/main.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/master/etc/layout_viewer/README.md"
|
||||
>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>
|
||||
<!--[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/master/etc/layout_viewer/README.md">README</a> for instructions.</p>
|
||||
</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 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'>Flow Tree</div>
|
||||
<div class='panel-body' id="flow-tree"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-12">
|
||||
<div id="flow-diffs"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-body" id="fragment-tree"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-12">
|
||||
<div id="fragment-diffs"></div>
|
||||
<div id="toolbar">
|
||||
<a href="#" id="prev_trace">< Prev step</a>
|
||||
|
|
||||
<a href="#" id="next_trace">Next step ></a>
|
||||
<br>
|
||||
<input type="checkbox" name="show_unchanged" id="show_unchanged" />
|
||||
<label for="show_unchanged">Show unchanged code</label>
|
||||
<br>
|
||||
<a href="#top">Back to top</a>
|
||||
</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>
|
||||
<!-- 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.children
|
||||
)[0];
|
||||
return {
|
||||
fragment_tree: create_fragment_tree(fragment_tree_root),
|
||||
box_tree: box_tree_from_bfc(trace_node.box_tree)
|
||||
};
|
||||
}
|
||||
|
||||
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);
|
||||
<script>
|
||||
function get_base(trace_node) {
|
||||
if (typeof(trace_node.data.base) == "undefined" && typeof(trace_node.data.block_flow) != "undefined") {
|
||||
return trace_node.data.block_flow.base;
|
||||
}
|
||||
})
|
||||
.diff({}, data.info);
|
||||
else {
|
||||
return trace_node.data.base;
|
||||
}
|
||||
}
|
||||
|
||||
const json = jsondiffpatch.formatters.html.format(delta, data.info);
|
||||
function create_flow_tree(trace_node) {
|
||||
var base = get_base(trace_node);
|
||||
|
||||
$(`#${tree}-diffs`).append(
|
||||
"<div class='panel panel-default'><div class='panel-heading'>" +
|
||||
data.text +
|
||||
"</div><div class='panel-body'>" +
|
||||
json +
|
||||
"</div></div>"
|
||||
);
|
||||
};
|
||||
var node = {
|
||||
text: trace_node.class + " (" + base.id + ")",
|
||||
id: base.id,
|
||||
icon: "dummy",
|
||||
href: "#diff-" + base.id
|
||||
};
|
||||
|
||||
const on_fragment_tree_node_selected = on_tree_node_selected(
|
||||
"fragment"
|
||||
);
|
||||
const on_box_tree_node_selected = on_tree_node_selected("box");
|
||||
var children = [];
|
||||
for (var i=0 ; i < base.children.length ; ++i) {
|
||||
children.push(create_flow_tree(base.children[i]));
|
||||
}
|
||||
|
||||
$("#fragment-tree").treeview({
|
||||
data: [node.fragment_tree],
|
||||
levels: 100,
|
||||
enableLinks: false,
|
||||
emptyIcon: "glyphicon glyphicon-unchecked hidden-glyphicon",
|
||||
onNodeSelected: on_fragment_tree_node_selected
|
||||
});
|
||||
if (children.length > 0) {
|
||||
node.nodes = children;
|
||||
}
|
||||
|
||||
$("#box-tree").treeview({
|
||||
data: [node.box_tree],
|
||||
levels: 100,
|
||||
enableLinks: false,
|
||||
emptyIcon: "glyphicon glyphicon-unchecked hidden-glyphicon",
|
||||
onNodeSelected: on_box_tree_node_selected
|
||||
});
|
||||
return node;
|
||||
}
|
||||
|
||||
["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);
|
||||
function create_flow_hash(trace_node, flow_hash) {
|
||||
var base = get_base(trace_node);
|
||||
flow_hash[base.id] = trace_node;
|
||||
|
||||
for (var i=0 ; i < base.children.length ; ++i) {
|
||||
create_flow_hash(base.children[i], flow_hash);
|
||||
}
|
||||
|
||||
delete base.children;
|
||||
}
|
||||
|
||||
function flatten_trace(trace_node) {
|
||||
var flow_tree = create_flow_tree(trace_node.children[0]);
|
||||
|
||||
var flow_hash = {};
|
||||
create_flow_hash(trace_node.children[0], flow_hash);
|
||||
|
||||
return {
|
||||
tree: flow_tree,
|
||||
flows: flow_hash,
|
||||
}
|
||||
}
|
||||
|
||||
function create_tree_node(trace_node) {
|
||||
var pre_trace = flatten_trace(trace_node.pre);
|
||||
var post_trace = flatten_trace(trace_node.post);
|
||||
|
||||
var tree_node = {
|
||||
text: trace_node.name,
|
||||
icon: "dummy",
|
||||
flow_tree: pre_trace.tree, // assume pre/post trace always have same tree!
|
||||
pre: pre_trace.flows,
|
||||
post: post_trace.flows,
|
||||
};
|
||||
|
||||
var children = [];
|
||||
|
||||
for (var i=0 ; i < trace_node.children.length ; ++i) {
|
||||
children.push(create_tree_node(trace_node.children[i]));
|
||||
}
|
||||
|
||||
if (children.length > 0) {
|
||||
tree_node.nodes = children;
|
||||
}
|
||||
|
||||
return tree_node;
|
||||
}
|
||||
|
||||
function update_flow_tree_bgcolor(flow_tree_node, node_color_hash) {
|
||||
flow_tree_node.backColor = node_color_hash[flow_tree_node.id];
|
||||
if (flow_tree_node.nodes !== undefined) {
|
||||
for (var i=0 ; i < flow_tree_node.nodes.length ; ++i) {
|
||||
update_flow_tree_bgcolor(flow_tree_node.nodes[i], node_color_hash)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function new_data_loaded(data) {
|
||||
jsondiffpatch.formatters.html.hideUnchanged();
|
||||
|
||||
var node_color_hash = {};
|
||||
var tree = [ create_tree_node(data) ];
|
||||
$('#trace-tree').treeview({data: tree, levels: 3});
|
||||
$('#trace-tree').on('nodeSelected', function(event, node) {
|
||||
$("#flow-diffs").empty();
|
||||
$('#trace-tree').treeview(true).revealNode(node);
|
||||
|
||||
for (var key in node.pre) {
|
||||
var flow_left = node.pre[key];
|
||||
var flow_right = node.post[key];
|
||||
|
||||
var delta = jsondiffpatch.create({
|
||||
objectHash: function(obj) {
|
||||
if (obj.data !== undefined && obj.data.base !== undefined) {
|
||||
return obj.data.base.id;
|
||||
}
|
||||
if (obj.id !== undefined) {
|
||||
return obj.id;
|
||||
}
|
||||
if (obj.index !== undefined) {
|
||||
// FlexItem and FlexLine
|
||||
return obj.index;
|
||||
}
|
||||
return JSON.stringify(obj);
|
||||
}
|
||||
}).diff(flow_left, flow_right);
|
||||
|
||||
if (delta !== undefined) {
|
||||
var diff_id = "diff-" + key;
|
||||
$("#flow-diffs").append(
|
||||
"<div class='panel panel-default' id='" +
|
||||
diff_id +
|
||||
"'><div class='panel-heading'>" +
|
||||
flow_left.class + " (" + key + ")" +
|
||||
"</div><div class='panel-body'></div></div>");
|
||||
|
||||
document.getElementById(diff_id).getElementsByClassName('panel-body')[0].innerHTML =
|
||||
jsondiffpatch.formatters.html.format(delta, flow_left);
|
||||
node_color_hash[key] = "rgba(255, 0, 0, 0.7)";
|
||||
} else {
|
||||
node_color_hash[key] = "rgb(212, 248, 212)";
|
||||
}
|
||||
}
|
||||
|
||||
update_flow_tree_bgcolor(node.flow_tree, node_color_hash);
|
||||
$('#flow-tree').treeview({data: [node.flow_tree], levels: 100, enableLinks: true, emptyIcon: "glyphicon glyphicon-unchecked hidden-glyphicon"});
|
||||
});
|
||||
|
||||
$('#trace-tree').treeview(true).selectNode(0);
|
||||
}
|
||||
|
||||
function register_toggle_unchanaged_code_handler() {
|
||||
var show_unchange_box = document.getElementById("show_unchanged");
|
||||
show_unchange_box.addEventListener("change", function(evt){
|
||||
jsondiffpatch.formatters.html.showUnchanged(show_unchange_box.checked, null, 800);
|
||||
});
|
||||
}
|
||||
|
||||
function register_prev_next_trace_node() {
|
||||
var prev_btn = document.getElementById("prev_trace");
|
||||
var next_btn = document.getElementById("next_trace");
|
||||
prev_btn.addEventListener("click", function(evt){
|
||||
var node_id = $("#trace-tree").treeview(true).getSelected()[0].nodeId;
|
||||
// We deliberatly choose to ignore the node_id out of bound case,
|
||||
// since it won't break the UI usability
|
||||
$("#trace-tree").treeview(true).selectNode(node_id - 1);
|
||||
});
|
||||
next_btn.addEventListener("click", function(evt){
|
||||
var node_id = $("#trace-tree").treeview(true).getSelected()[0].nodeId;
|
||||
$("#trace-tree").treeview(true).selectNode(node_id + 1);
|
||||
});
|
||||
}
|
||||
|
||||
$( document ).ready(function() {
|
||||
var upload = document.getElementsByTagName('input')[0];
|
||||
upload.onchange = function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
var file = upload.files[0],
|
||||
reader = new FileReader();
|
||||
reader.onload = function (event) {
|
||||
new_data_loaded(JSON.parse(event.target.result));
|
||||
};
|
||||
|
||||
reader.readAsText(file);
|
||||
return false;
|
||||
};
|
||||
register_toggle_unchanaged_code_handler();
|
||||
register_prev_next_trace_node();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
$("#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>
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
339
etc/layout_viewer/viewer_2020.html
Normal file
339
etc/layout_viewer/viewer_2020.html
Normal file
|
@ -0,0 +1,339 @@
|
|||
<!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/master/etc/layout_viewer/README.md"
|
||||
>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.children
|
||||
)[0];
|
||||
return {
|
||||
fragment_tree: create_fragment_tree(fragment_tree_root),
|
||||
box_tree: box_tree_from_bfc(trace_node.box_tree)
|
||||
};
|
||||
}
|
||||
|
||||
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>
|
Loading…
Add table
Add a link
Reference in a new issue