| <!doctype html> |
| <html lang="en"> |
| <head> |
| <meta charset="utf-8"> |
| <title>Perfetto Trace Relay</title> |
| <style> |
| body { font-family: sans-serif; padding: 2em; } |
| </style> |
| </head> |
| <body> |
| <h1>Perfetto Trace Relay</h1> |
| <p id="status">Initializing...</p> |
|
|
| <script> |
| (async () => { |
| const statusElement = document.getElementById('status'); |
| const PERFETTO_UI_URL = 'https://ui.perfetto.dev'; |
| |
| |
| |
| |
| |
| |
| const setStatus = (message, isError = false) => { |
| console.log(message); |
| statusElement.textContent = message; |
| if (isError) { |
| statusElement.style.color = 'red'; |
| console.error(message); |
| } |
| }; |
| |
| |
| |
| |
| |
| |
| |
| const establishHandshake = (ui) => { |
| return new Promise((resolve, reject) => { |
| let intervalId = null; |
| let timeoutId = null; |
| |
| const cleanup = () => { |
| clearInterval(intervalId); |
| clearTimeout(timeoutId); |
| window.removeEventListener('message', messageHandler); |
| }; |
| |
| const messageHandler = (event) => { |
| if (event.source === ui && event.data === 'PONG') { |
| cleanup(); |
| resolve(); |
| } |
| }; |
| |
| window.addEventListener('message', messageHandler); |
| |
| |
| intervalId = setInterval(() => { |
| |
| try { |
| ui.postMessage('PING', PERFETTO_UI_URL); |
| } catch (e) { |
| |
| cleanup(); |
| reject(new Error('Perfetto UI window was closed before handshake could complete.')); |
| } |
| }, 200); |
| |
| |
| timeoutId = setTimeout(() => { |
| cleanup(); |
| reject(new Error('Handshake with Perfetto UI timed out. The UI might not have loaded correctly.')); |
| }, 10000); |
| }); |
| }; |
| |
| |
| |
| |
| |
| |
| |
| const fetchTraceAndPost = async (ui, srcUrl, customTitle) => { |
| |
| const response = await fetch(srcUrl, { |
| mode: 'cors', |
| credentials: 'omit', |
| cache: 'no-store', |
| }); |
| |
| if (!response.ok) { |
| const errorMsg = `Failed to load trace: ${response.status} ${response.statusText}`; |
| ui.postMessage({ perfetto: { title: errorMsg } }, PERFETTO_UI_URL); |
| throw new Error(errorMsg); |
| } |
| |
| const buffer = await response.arrayBuffer(); |
| |
| |
| const title = customTitle || srcUrl.split('/').pop() || 'trace'; |
| const fileName = title.endsWith('.gz') || title.endsWith('.zip') ? title : `${title}.perfetto-trace`; |
| |
| |
| ui.postMessage({ |
| perfetto: { |
| buffer, |
| title, |
| fileName, |
| |
| keepApiOpen: true, |
| } |
| }, PERFETTO_UI_URL, [buffer]); |
| }; |
| |
| |
| |
| try { |
| const params = new URLSearchParams(location.search); |
| const src = params.get('src'); |
| const title = params.get('title'); |
| |
| if (!src) { |
| throw new Error('Required "src" URL parameter is missing.'); |
| } |
| |
| setStatus('Opening Perfetto UI...'); |
| const ui = window.open(PERFETTO_UI_URL); |
| if (!ui) { |
| throw new Error('Failed to open Perfetto UI. Please disable your popup blocker for this site.'); |
| } |
| |
| setStatus('Waiting for handshake with Perfetto UI...'); |
| await establishHandshake(ui); |
| |
| setStatus(`Fetching trace from ${src}...`); |
| await fetchTraceAndPost(ui, src, title); |
| |
| setStatus('Trace successfully sent to Perfetto UI. You can now close this tab.'); |
| |
| |
| |
| } catch (error) { |
| setStatus(error.message, true); |
| } |
| })(); |
| </script> |
| </body> |
| </html> |
|
|