blob: b1d3c34013802ca208374c86acc5c06a9351bbfa [file] [log] [blame]
<!DOCTYPE html>
<html lang="en">
<!--
Copyright 2022 The Chromium Authors
Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file.
-->
<head>
<style>
#scrubberframe::-webkit-slider-thumb {
appearance: none;
width: 20px;
height: 20px;
background-color: grey;
}
#scrubberframe::slider-thumb {
appearance: none;
width: 20px;
height: 20px;
background-color: grey;
}
#scrubberframe {
margin-top: 10px;
flex-grow: 1;
appearance: none;
background-color: #f0f0f0;
width: 100%;
}
#scrubberdraw::-webkit-slider-thumb {
appearance: none;
width: 15px;
height: 20px;
background-color: grey;
}
#scrubberdraw::slider-thumb {
appearance: none;
width: 20px;
height: 20px;
background-color: grey;
}
#scrubberdraw {
margin-top: 10px;
flex-grow: 1;
appearance: none;
background-color: #f0f0f0;
width: 100%;
}
#url {
font-family: monospace;
font-size: smaller;
}
#controls {}
#controls>#buttons {
display: flex;
}
#log {
min-height: 100px;
max-height: 100%;
font-family: monospace;
overflow: auto;
}
#connectionPanel,
#saveload {
display: inline-block;
}
#connectionPanel,
#saveload,
#topPanel {
padding-bottom: 10px;
}
#topPanel {
display: flex;
}
#settings {
display: flex;
flex-direction: column;
font-size: small;
}
.panelSection {
margin-right: 20px;
}
.panelSection:last-child {
margin-right: 0px;
flex-grow: 1;
}
#connection-status {
color: limegreen;
font-size: large;
padding-right: 5px;
}
#connection-status.disconnected {
color: orange;
}
</style>
<link href="https://unpkg.com/material-components-web@latest/dist/material-components-web.min.css" rel="stylesheet">
<script src="https://unpkg.com/material-components-web@latest/dist/material-components-web.min.js"></script>
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons+Outlined">
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined" />
<link rel='stylesheet' href='style.css'>
<script src='filter.js'></script>
<script src='filter-ui.js'></script>
<script src='frame.js'></script>
<script src='connection.js'></script>
<script src='thread.js'></script>
<script src='thread-ui.js'></script>
</head>
<div id='connectionPanel'>
<div class='sectionTitle'>
<font class='disconnected' id='connection-status'>&#9679;</font>Connection
</div>
<div class='section'>
<div title="Expert usage only. Autoconnect should provide the dev tools websocket through /json/version discovery.">
<input id='url' name='url' size=60 type="text"
placeholder='WebSocket URL or leave empty for autoconnect...'></input>
</div>
<button class="mdc-button mdc-button--outline" id='connect'>
<div class="mdc-button__ripple"></div>
<span class="mdc-button__label">Connect</span>
</button>
<button class="mdc-button mdc-button--outline" id='disconnect' disabled=true>
<div class="mdc-button__ripple"></div>
<span class="mdc-button__label">Disconnect</span>
</button>
<input type="checkbox" id="autoconnect" name="autoconnect" checked="true">
<label for="autoconnect" style="font-size:small; font:Roboto" > Autoconnect</label><br>
</div>
</div>
<div id='saveload'>
<div class='sectionTitle'>Save/Load ...</div>
<div class='section'>
<button id='demo' class="mdc-button mdc-button--outline">
<div class="mdc-button__ripple"></div>
<span class="mdc-button__label">Load demo data</span>
</button>
<button id='savedata' class="mdc-button mdc-button--outline"
title="Serializes current session stream to json file.">
<div class="mdc-button__ripple"></div>
<span class="mdc-button__label">Save to disk</span>
</button>
<button id='loaddata' class="mdc-button mdc-button--outline"
title="Deserializes json file of previous session and imports these frames into the App.">
<div class="mdc-button__ripple"></div>
<span class="mdc-button__label">Load from disk</span>
</button>
</div>
</div>
<div id='logsPanel'>
<div class='panelSection'>
<div class='sectionTitle'>
Logs
<!--input size=40 placeholder='Comma separated filter ...'>(TODO)</input-->
</div>
<div class='section'>
<div id='log'></div>
</div>
</div>
</div>
<div class='panelSection'>
<div class='sectionTitle'
title="Filter debug data stream. Filter operations occur in a left to right order with first match being applied.">
<i class="material-icons-outlined">filter_list</i> Filters
</div>
<div class='section'>
<div id='filters' class="mdc-chip-set mdc-chip-set--filter" role="grid"
title="Filter debug data stream. Filter operations occur in a left to right order with first match being applied.">
</div>
<button class="mdc-button mdc-button--outline" id='createfilter'>
<div class="mdc-button__ripple"></div>
<span class="mdc-button__label"><i class="material-icons-outlined">add_box</i> Add new filter</span>
</button>
</div>
</div>
<div class='panelSection'>
<div class='sectionTitle'>
<i class="material-symbols-outlined">
airwave
</i>
Threads
</div>
<div id='threads' class='section'></div>
</div>
<div class='panelSection'>
<div class='sectionTitle'>Viewer Controls</div>
<div class='section' id='controls'>
<div id='buttons'>
<button class="mdc-button mdc-button--outline" id='prev'>
<span class="mdc-button__label"><i class="material-icons-outlined">skip_previous</i> Previous frame</span>
</button>
<button class="mdc-button mdc-button--outline" id='play'>
<div class="mdc-button__ripple"></div>
<span class="mdc-button__label"><i class="material-icons-outlined">play_circle_outline</i> Play</span>
</button>
<button class="mdc-button mdc-button--outline" id='pause'>
<div class="mdc-button__ripple"></div>
<span class="mdc-button__label"><i class="material-icons-outlined">pause_circle_outline</i> Pause</span>
</button>
<button class="mdc-button mdc-button--outline" id='next'>
<span class="mdc-button__label">Next frame <i class="material-icons-outlined">skip_next</i></span>
</button>
<button class="mdc-button mdc-button--raised" id='live'>
<div class="mdc-button__ripple"></div>
<span class="mdc-button__label">Live <i class="material-icons-outlined">fast_forward</i></span>
</button>
</div>
<div class='panelSection'>
<div class='sectionTitle'>Frame Selection</div>
<input type='range' min='0' max='0' value='0' id='scrubberframe'></input>
</div>
<div class='panelSection'>
<div class='sectionTitle'>Draw Selection</div>
<input type='range' min='0' max='0' value='0' id='scrubberdraw'></input>
</div>
</div>
</div>
<div>
<div class='sectionTitle'>
Viewer
</div>
<div style='float: right' class='sectionTitle'>
Scale
<select id="viewerscale">
<option>100%</option>
<option>50%</option>
<option>200%</option>
<option disabled>Free Camera</option>
</select>
</div>
<div style='float: right' class='sectionTitle'>
Orientation
<select id="viewerorientation">
<option class="default-orientation">0 deg clockwise</option>
<option class="default-orientation">90 deg clockwise</option>
<option class="default-orientation">180 deg clockwise</option>
<option class="default-orientation">270 deg clockwise</option>
<option class="default-orientation">Horizontal Flip</option>
<option class="default-orientation">Vertical Flip</option>
</select>
</div>
<div class='section'>
<!--We need to fix viewer size to avoid scroll position change when
multiple displays are present. crbug.com/1358526-->
<div style="height:4000px; border: 1px dotted gray; background-color: #f0f0f0">
<canvas id='canvas' style="top :0px"></canvas>
</div>
</div>
<div class='modalContainer'></div>
</div>
<div id='importtracing'>
<div class='sectionTitle'>Tracing (Prototype)...</div>
<div class='section'>
<button id='importtracebutton' class="mdc-button mdc-button--outline"
title="Import tracing data (json) into visual debugger app.">
<div class="mdc-button__ripple"></div>
<span class="mdc-button__label">Import Trace</span>
</button>
</div>
</div>
<script>
function processIncomingFrame(json) {
if (!json) return;
new DrawFrame(json);
Player.instance.onNewFrame();
}
async function testAnimate() {
const f = await fetch('demo.json');
const text = await f.text();
const json = JSON.parse(text);
for (const frame of json) {
processIncomingFrame(frame);
}
}
async function saveDemoDataToDisk() {
const text = JSON.stringify(DrawFrame.frameBuffer.instances.map(d => d.toJSON()));
const blob = new Blob([text], { type: 'text/plain' });
const link = document.createElement('a');
link.download = 'cvd-stream.json';
link.href = window.URL.createObjectURL(blob);
link.click();
}
window.onload = function () {
Connection.initialize();
const addFilterButton = document.querySelector('#createfilter');
addFilterButton.addEventListener('click', () => {
showCreateFilterPopup(addFilterButton);
});
const container = document.querySelector('.modalContainer');
container.addEventListener('click', (event) => {
if (event.target == container)
hideModal();
});
const demo = document.querySelector('#demo');
demo.addEventListener('click', testAnimate);
const savedata = document.querySelector('#savedata');
savedata.addEventListener('click', saveDemoDataToDisk);
const loaddata = document.querySelector('#loaddata');
loaddata.addEventListener('click', () => {
const f = document.createElement('input');
f.type = 'file';
f.addEventListener('change', () => {
const file = new FileReader(f.files[0]);
file.addEventListener('load', () => {
const json = JSON.parse(file.result);
for (const frame of json) {
processIncomingFrame(frame);
}
});
file.readAsText(f.files[0]);
});
f.click();
});
const importtracedata = document.querySelector('#importtracebutton');
importtracedata.addEventListener('click', () => {
const f = document.createElement('input');
f.type = 'file';
f.addEventListener('change', () => {
const file = new FileReader(f.files[0]);
file.addEventListener('load', () => {
const json = JSON.parse(file.result);
const traceEvents = json.traceEvents;
let src_frame = {
"drawcalls": [], "frame": "80631", "logs": [], "new_sources": [{ "anno": "frame.root.display_rect", "file": "x", "func": "x", "index": 0, "line": 0 }], "text": [], "time": "0",
"version": 1, "windowx": 2400, "windowy": 1600
};
processIncomingFrame(src_frame);
curr_frame = "0";
curr_draws = [];
for (const event of traceEvents) {
if (event.name == "VisualDebuggerSync") {
single_frame = { "drawcalls": [], "frame": "80631", "logs": [], "new_sources": [], "text": [], "time": "0", "version": 1, "windowx": 2400, "windowy": 1600 };
single_frame.drawcalls = curr_draws;
curr_frame = event.args.last_presented_trace_id;
single_frame.frame = curr_frame;
processIncomingFrame(single_frame);
curr_draws = []
}
if (event.name == "VizTestRootRect") {
let single_call = { "drawindex": curr_draws.length, "option": { "alpha": 0, "color": "#ff0000" }, "pos": [832, 670], "size": [112, 112], "source_index": 0 };
single_call.pos[0] = parseInt(event.args.args.pos_x);
single_call.pos[1] = parseInt(event.args.args.pos_y);
single_call.size[0] = parseInt(event.args.args.size_x);
single_call.size[1] = parseInt(event.args.args.size_y);
curr_draws.push(single_call);
}
}
});
file.readAsText(f.files[0]);
});
f.click();
});
setUpPlayer();
restoreFilters();
}
function setUpPlayer() {
// First, set up the viewer.
const canvas = document.querySelector('#canvas');
const logContainer = document.querySelector('#log');
const viewer = new Viewer(canvas, logContainer);
// Now create the player for the viewer.
const player = new Player(viewer, (frame) => {
// TODO: This feels like a hack. Find a cleaner way to update the scrubbers?
const scrubberFrame = document.querySelector('#scrubberframe');
scrubberFrame.value = player.currentFrameIndex;
const scrubberDraw = document.querySelector('#scrubberdraw');
scrubberDraw.max = frame.submissionCount() - 1;
scrubberDraw.value = frame.submissionFreezeIndex();
});
document.querySelector('#pause').addEventListener('click', () => {
player.pause();
pause.setAttribute('style', 'background:#000000;color:white');
});
document.querySelector('#play').addEventListener('click', () => {
player.play();
pause.removeAttribute('style');
});
document.querySelector('#prev').addEventListener('click', () => {
player.rewind();
});
document.querySelector('#next').addEventListener('click', () => {
player.forward();
});
document.querySelector('#live').addEventListener('click', () => {
player.freezeFrame(DrawFrame.frameBuffer.numFrames - 1);
player.live();
has_disconnected = false;
pause.removeAttribute('style');
});
const scrubberFrame = document.querySelector('#scrubberframe');
scrubberFrame.addEventListener('input', () => {
player.freezeFrame(scrubberFrame.value);
pause.setAttribute('style', 'background:#000000;color:white');
});
const scrubberDraw = document.querySelector('#scrubberdraw');
scrubberDraw.addEventListener('input', () => {
player.freezeFrame(scrubberFrame.value, scrubberDraw.value);
});
let currentMouseX = 0;
let currentMouseY = 0;
let zoom = 100;
const viewerScale = document.querySelector("#viewerscale");
viewerScale.addEventListener('input', () => {
if (viewerScale.value != "Free Camera") {
player.setViewerScale(viewerScale.value);
zoom = parseInt(viewerScale.value);
}
else {
player.setViewerScale(zoom);
canvas.addEventListener('mouseenter', () => {
canvas.addEventListener('mousemove', function (event) {
currentMouseX = Math.round(event.offsetX);
currentMouseY = Math.round(event.offsetY);
});
});
canvas.addEventListener('wheel', function(e) {
e.preventDefault();
var delta = e.deltaY;
viewer.zoomToMouse(currentMouseX, currentMouseY, delta);
}, {passive:false});
}
});
const viewerOrientation = document.querySelector("#viewerorientation");
viewerOrientation.addEventListener('input', () => {
player.setViewerOrientation(viewerOrientation.value);
});
viewerOrientation.addEventListener('change', () => {
// change dropdown style when rotation occurs
if (viewerOrientation.value !== "0 deg clockwise") {
document.getElementById("viewerorientation").className = "selected-orientation";
}
else {
document.getElementById("viewerorientation").className = "default-orientation";
}
});
}
function showModal(element, focusSelector) {
const container = document.querySelector('.modalContainer');
container.appendChild(element);
container.style.display = 'block';
element.querySelector(focusSelector).focus();
}
function hideModal() {
const container = document.querySelector('.modalContainer');
container.style.display = 'none';
container.textContent = '';
}
</script>
</html>