| <!DOCTYPE html> |
| <html lang="en"> |
| <!-- |
| Copyright 2022 The Chromium Authors. All rights reserved. |
| 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='style.css'> |
| <script src='filter.js'></script> |
| <script src='filter-ui.js'></script> |
| <script src='frame.js'></script> |
| <script src='connection.js'></script> |
| </head> |
| |
| <div id='connectionPanel'> |
| <div class='sectionTitle'> |
| <font class='disconnected' id='connection-status'>●</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> |
| </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'>Viewer Controls</div> |
| <div class='section' id='controls'> |
| <div id='buttons'> |
| <button class="mdc-button mdc-button--outline" id='prev'> |
| <div class="mdc-button__ripple"></div> |
| <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'> |
| <div class="mdc-button__ripple"></div> |
| <span class="mdc-button__label">Next frame <i class="material-icons-outlined">skip_next</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> |
| </select> |
| </div> |
| <div class='section'> |
| <canvas id='canvas'></canvas> |
| </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.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(); |
| FilterUIDefault.initialize(); |
| } |
| |
| 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.max = DrawFrame.count(); |
| 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(); |
| }); |
| |
| document.querySelector('#play').addEventListener('click', () => { |
| player.play(); |
| }); |
| |
| document.querySelector('#prev').addEventListener('click', () => { |
| player.rewind(); |
| }); |
| |
| document.querySelector('#next').addEventListener('click', () => { |
| player.forward(); |
| }); |
| |
| const scrubberFrame = document.querySelector('#scrubberframe'); |
| scrubberFrame.addEventListener('input', () => { |
| player.freezeFrame(scrubberFrame.value); |
| }); |
| |
| const scrubberDraw = document.querySelector('#scrubberdraw'); |
| scrubberDraw.addEventListener('input', () => { |
| player.freezeFrame(scrubberFrame.value, scrubberDraw.value); |
| }); |
| const viewerScale = document.querySelector("#viewerscale"); |
| viewerScale.addEventListener('input', () => { |
| player.setViewerScale(viewerScale.value); |
| }); |
| } |
| |
| |
| function showModal(element) { |
| const container = document.querySelector('.modalContainer'); |
| container.appendChild(element); |
| container.style.display = 'block'; |
| element.focus(); |
| } |
| |
| function hideModal() { |
| const container = document.querySelector('.modalContainer'); |
| container.style.display = 'none'; |
| container.textContent = ''; |
| } |
| |
| </script> |
| |
| </html> |