| | <!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> |
| |
|