Spaces:
Running
Running
GitHub Actions commited on
Commit Β·
19031e8
1
Parent(s): 35043c2
sync from abhijitramesh/webgpu-bench@3d3db948b0
Browse files- data/combined.json +3 -3
- js/run/controller.js +51 -84
- js/run/hub.js +17 -68
- run.html +0 -23
data/combined.json
CHANGED
|
@@ -7,7 +7,7 @@
|
|
| 7 |
"platform": "darwin",
|
| 8 |
"arch": "arm",
|
| 9 |
"totalMemoryGB": 4,
|
| 10 |
-
"submittedAt": "2026-04-
|
| 11 |
"resultCount": 4,
|
| 12 |
"passCount": 4,
|
| 13 |
"llamaCppCommit": null,
|
|
@@ -27,7 +27,7 @@
|
|
| 27 |
"platform": "darwin",
|
| 28 |
"arch": "arm",
|
| 29 |
"totalMemoryGB": 8,
|
| 30 |
-
"submittedAt": "2026-04-
|
| 31 |
"resultCount": 2,
|
| 32 |
"passCount": 2,
|
| 33 |
"llamaCppCommit": null,
|
|
@@ -50,7 +50,7 @@
|
|
| 50 |
"browsers": [
|
| 51 |
"chromium-147"
|
| 52 |
],
|
| 53 |
-
"generatedAt": "2026-04-
|
| 54 |
},
|
| 55 |
"results": [
|
| 56 |
{
|
|
|
|
| 7 |
"platform": "darwin",
|
| 8 |
"arch": "arm",
|
| 9 |
"totalMemoryGB": 4,
|
| 10 |
+
"submittedAt": "2026-04-25T19:42:00.460Z",
|
| 11 |
"resultCount": 4,
|
| 12 |
"passCount": 4,
|
| 13 |
"llamaCppCommit": null,
|
|
|
|
| 27 |
"platform": "darwin",
|
| 28 |
"arch": "arm",
|
| 29 |
"totalMemoryGB": 8,
|
| 30 |
+
"submittedAt": "2026-04-25T19:42:00.459Z",
|
| 31 |
"resultCount": 2,
|
| 32 |
"passCount": 2,
|
| 33 |
"llamaCppCommit": null,
|
|
|
|
| 50 |
"browsers": [
|
| 51 |
"chromium-147"
|
| 52 |
],
|
| 53 |
+
"generatedAt": "2026-04-25T19:42:00.502Z"
|
| 54 |
},
|
| 55 |
"results": [
|
| 56 |
{
|
js/run/controller.js
CHANGED
|
@@ -7,7 +7,6 @@ import { localSource, hostedSource, inventoryOpfs, purgeOpfs } from './source.js
|
|
| 7 |
import { getDeviceBudgetMB, variantFits, describeDevice } from './device.js';
|
| 8 |
import {
|
| 9 |
resumeHFSession, beginHFSignIn, signOutHF, submitResultsToDataset,
|
| 10 |
-
HF_TOKEN_STORAGE_KEY, HF_POPUP_DONE_MESSAGE, isHFPopupCallback,
|
| 11 |
} from './hub.js';
|
| 12 |
import { isHubConfigured, HF_DATASET_REPO } from './config.js';
|
| 13 |
|
|
@@ -241,13 +240,17 @@ function renderHfSection() {
|
|
| 241 |
|
| 242 |
if (state.hfSession) {
|
| 243 |
signinBtn.textContent = 'Sign out';
|
| 244 |
-
|
|
|
|
|
|
|
| 245 |
submitBtn.hidden = false;
|
| 246 |
const eligible = submittableResults();
|
| 247 |
-
submitBtn.disabled = eligible.length === 0;
|
| 248 |
-
submitBtn.title =
|
| 249 |
-
?
|
| 250 |
-
:
|
|
|
|
|
|
|
| 251 |
const who = state.hfSession.userName ? `@${state.hfSession.userName}` : 'signed in';
|
| 252 |
const hint = eligible.length > 0
|
| 253 |
? ` Β· ${eligible.length}/${state.results.length} variants eligible`
|
|
@@ -255,7 +258,15 @@ function renderHfSection() {
|
|
| 255 |
userEl.textContent = `${who} Β· β ${HF_DATASET_REPO}${hint}`;
|
| 256 |
} else {
|
| 257 |
signinBtn.textContent = 'Sign in with Hugging Face';
|
| 258 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 259 |
submitBtn.hidden = true;
|
| 260 |
userEl.textContent = '';
|
| 261 |
}
|
|
@@ -598,6 +609,9 @@ function updateButtons() {
|
|
| 598 |
const rn = $('btn-run'); if (rn) rn.disabled = state.running || checked.length === 0;
|
| 599 |
const ab = $('btn-abort'); if (ab) { ab.disabled = !state.running; ab.hidden = !state.running; }
|
| 600 |
renderBudgetMeter(checked, cachedChecked);
|
|
|
|
|
|
|
|
|
|
| 601 |
}
|
| 602 |
|
| 603 |
/* Show selected size as a fill bar against the device's max model size.
|
|
@@ -1485,35 +1499,14 @@ function wirePurgeHandler() {
|
|
| 1485 |
});
|
| 1486 |
}
|
| 1487 |
|
| 1488 |
-
async function refreshHfSessionFromStorage() {
|
| 1489 |
-
try {
|
| 1490 |
-
state.hfSession = await resumeHFSession();
|
| 1491 |
-
} catch {
|
| 1492 |
-
state.hfSession = null;
|
| 1493 |
-
}
|
| 1494 |
-
renderHfSection();
|
| 1495 |
-
}
|
| 1496 |
-
|
| 1497 |
-
function wireCrossTabHubSync() {
|
| 1498 |
-
// Popup writes the token to localStorage and posts a message to opener.
|
| 1499 |
-
// We listen to both: postMessage is the fast path; the storage event also
|
| 1500 |
-
// fires for any other open tab on the same origin so they all stay in sync.
|
| 1501 |
-
window.addEventListener('storage', (e) => {
|
| 1502 |
-
if (e.key !== HF_TOKEN_STORAGE_KEY) return;
|
| 1503 |
-
refreshHfSessionFromStorage();
|
| 1504 |
-
});
|
| 1505 |
-
window.addEventListener('message', (e) => {
|
| 1506 |
-
if (e.origin !== location.origin) return;
|
| 1507 |
-
if (e.data?.type !== HF_POPUP_DONE_MESSAGE) return;
|
| 1508 |
-
refreshHfSessionFromStorage();
|
| 1509 |
-
});
|
| 1510 |
-
}
|
| 1511 |
-
|
| 1512 |
function wireHubHandlers() {
|
| 1513 |
const signinBtn = $('btn-signin');
|
| 1514 |
const submitBtn = $('btn-submit');
|
| 1515 |
if (signinBtn) {
|
| 1516 |
signinBtn.addEventListener('click', async () => {
|
|
|
|
|
|
|
|
|
|
| 1517 |
try {
|
| 1518 |
if (state.hfSession) {
|
| 1519 |
signOutHF();
|
|
@@ -1521,20 +1514,8 @@ function wireHubHandlers() {
|
|
| 1521 |
renderHfSection();
|
| 1522 |
return;
|
| 1523 |
}
|
| 1524 |
-
|
| 1525 |
-
//
|
| 1526 |
-
// blocked by the browser.
|
| 1527 |
-
try {
|
| 1528 |
-
await beginHFSignIn({ popup: true });
|
| 1529 |
-
logLine('Opened HF sign-in in a new tab β finish there and come back.');
|
| 1530 |
-
} catch (err) {
|
| 1531 |
-
if (err.code === 'popup-blocked') {
|
| 1532 |
-
logLine('Popup blocked β falling back to full-page sign-in. Your in-progress run will be lost.');
|
| 1533 |
-
await beginHFSignIn({ popup: false });
|
| 1534 |
-
} else {
|
| 1535 |
-
throw err;
|
| 1536 |
-
}
|
| 1537 |
-
}
|
| 1538 |
} catch (err) {
|
| 1539 |
logLine(`Sign-in failed: ${err.message}`);
|
| 1540 |
}
|
|
@@ -1584,43 +1565,6 @@ function wireRunHandlers() {
|
|
| 1584 |
|
| 1585 |
export async function mountRunSection() {
|
| 1586 |
if (state.mounted) return;
|
| 1587 |
-
|
| 1588 |
-
// OAuth popup fast-path: when this page is loaded inside the sign-in
|
| 1589 |
-
// popup, the URL carries `code`+`state` from HF. We can't rely on
|
| 1590 |
-
// `window.opener` alone because HF's OAuth response sets COOP, which
|
| 1591 |
-
// severs the opener reference in Chrome/Safari. `isHFPopupCallback()`
|
| 1592 |
-
// also checks the localStorage marker the opener writes pre-popup.
|
| 1593 |
-
if (typeof window !== 'undefined') {
|
| 1594 |
-
const params = new URLSearchParams(location.search);
|
| 1595 |
-
if (params.get('code') && params.get('state') && isHFPopupCallback()) {
|
| 1596 |
-
document.body.innerHTML = `
|
| 1597 |
-
<div style="padding:48px 32px;font-family:system-ui,sans-serif;color:#222;max-width:480px;margin:0 auto;text-align:center">
|
| 1598 |
-
<div id="hf-popup-status" style="font-size:15px;line-height:1.5">Completing sign-inβ¦</div>
|
| 1599 |
-
<button id="hf-popup-close" type="button" hidden
|
| 1600 |
-
style="margin-top:24px;padding:8px 16px;font:inherit;border:1px solid #ccc;border-radius:6px;background:#f6f6f6;cursor:pointer">
|
| 1601 |
-
Close this tab
|
| 1602 |
-
</button>
|
| 1603 |
-
</div>
|
| 1604 |
-
`;
|
| 1605 |
-
// Restore visibility β the inline guard in run.html hid <html> to avoid
|
| 1606 |
-
// a bench-skeleton flash; we own the body now and want our message visible.
|
| 1607 |
-
document.documentElement.style.visibility = '';
|
| 1608 |
-
try { await resumeHFSession(); } catch { /* logged inside */ }
|
| 1609 |
-
// resumeHFSession already calls window.close(); if the browser denies
|
| 1610 |
-
// it (Firefox is strict; some COOP cases too), surface a clear message
|
| 1611 |
-
// and a manual close button. The parent tab already updated via the
|
| 1612 |
-
// storage event triggered by the token write.
|
| 1613 |
-
const status = document.getElementById('hf-popup-status');
|
| 1614 |
-
const closeBtn = document.getElementById('hf-popup-close');
|
| 1615 |
-
if (status) status.textContent = 'Signed in. You can close this tab.';
|
| 1616 |
-
if (closeBtn) {
|
| 1617 |
-
closeBtn.hidden = false;
|
| 1618 |
-
closeBtn.addEventListener('click', () => { try { window.close(); } catch {} });
|
| 1619 |
-
}
|
| 1620 |
-
return;
|
| 1621 |
-
}
|
| 1622 |
-
}
|
| 1623 |
-
|
| 1624 |
state.mounted = true;
|
| 1625 |
|
| 1626 |
state.surface = await detectSurface();
|
|
@@ -1663,14 +1607,37 @@ export async function mountRunSection() {
|
|
| 1663 |
wireAbortHandler();
|
| 1664 |
wirePurgeHandler();
|
| 1665 |
wireHubHandlers();
|
| 1666 |
-
wireCrossTabHubSync();
|
| 1667 |
wireOutputHandlers();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1668 |
updateButtons();
|
| 1669 |
renderOutput();
|
| 1670 |
-
hideProgressUntilFirstRow();
|
| 1671 |
maybeShowCrashBanner();
|
| 1672 |
}
|
| 1673 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1674 |
export function teardownRunSection() {
|
| 1675 |
// Placeholder β no explicit teardown today. Future: abort in-flight runs,
|
| 1676 |
// detach listeners. For now the Run tab just sits idle.
|
|
|
|
| 7 |
import { getDeviceBudgetMB, variantFits, describeDevice } from './device.js';
|
| 8 |
import {
|
| 9 |
resumeHFSession, beginHFSignIn, signOutHF, submitResultsToDataset,
|
|
|
|
| 10 |
} from './hub.js';
|
| 11 |
import { isHubConfigured, HF_DATASET_REPO } from './config.js';
|
| 12 |
|
|
|
|
| 240 |
|
| 241 |
if (state.hfSession) {
|
| 242 |
signinBtn.textContent = 'Sign out';
|
| 243 |
+
// Sign-out itself is fine mid-run, but stay consistent with the disabled
|
| 244 |
+
// sign-in state so the row doesn't toggle look mid-run.
|
| 245 |
+
signinBtn.disabled = state.running;
|
| 246 |
submitBtn.hidden = false;
|
| 247 |
const eligible = submittableResults();
|
| 248 |
+
submitBtn.disabled = state.running || eligible.length === 0;
|
| 249 |
+
submitBtn.title = state.running
|
| 250 |
+
? 'Wait for the benchmark to finish before submitting'
|
| 251 |
+
: (eligible.length === 0 && state.results.length > 0
|
| 252 |
+
? `Need at least ${MIN_ITERATIONS_FOR_SUBMIT} successful iterations per variant to submit`
|
| 253 |
+
: '');
|
| 254 |
const who = state.hfSession.userName ? `@${state.hfSession.userName}` : 'signed in';
|
| 255 |
const hint = eligible.length > 0
|
| 256 |
? ` Β· ${eligible.length}/${state.results.length} variants eligible`
|
|
|
|
| 258 |
userEl.textContent = `${who} Β· β ${HF_DATASET_REPO}${hint}`;
|
| 259 |
} else {
|
| 260 |
signinBtn.textContent = 'Sign in with Hugging Face';
|
| 261 |
+
// Sign-in triggers a full-page redirect, which would kill an in-flight
|
| 262 |
+
// worker. Disable the button while the benchmark is running so the user
|
| 263 |
+
// can't accidentally lose their run; results are saved progressively to
|
| 264 |
+
// localStorage and restored on the next mount, so finishing the run and
|
| 265 |
+
// signing in afterwards still lets them submit.
|
| 266 |
+
signinBtn.disabled = state.running;
|
| 267 |
+
signinBtn.title = state.running
|
| 268 |
+
? 'Wait for the benchmark to finish before signing in'
|
| 269 |
+
: '';
|
| 270 |
submitBtn.hidden = true;
|
| 271 |
userEl.textContent = '';
|
| 272 |
}
|
|
|
|
| 609 |
const rn = $('btn-run'); if (rn) rn.disabled = state.running || checked.length === 0;
|
| 610 |
const ab = $('btn-abort'); if (ab) { ab.disabled = !state.running; ab.hidden = !state.running; }
|
| 611 |
renderBudgetMeter(checked, cachedChecked);
|
| 612 |
+
// Keep the Sign in / Submit buttons in sync with the running flag β they
|
| 613 |
+
// depend on it so the user can't kick off a redirect mid-run.
|
| 614 |
+
renderHfSection();
|
| 615 |
}
|
| 616 |
|
| 617 |
/* Show selected size as a fill bar against the device's max model size.
|
|
|
|
| 1499 |
});
|
| 1500 |
}
|
| 1501 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1502 |
function wireHubHandlers() {
|
| 1503 |
const signinBtn = $('btn-signin');
|
| 1504 |
const submitBtn = $('btn-submit');
|
| 1505 |
if (signinBtn) {
|
| 1506 |
signinBtn.addEventListener('click', async () => {
|
| 1507 |
+
// Sign in / Sign out is disabled while a run is in flight; this guard
|
| 1508 |
+
// catches a stale-event-during-state-change race and keeps results safe.
|
| 1509 |
+
if (state.running) return;
|
| 1510 |
try {
|
| 1511 |
if (state.hfSession) {
|
| 1512 |
signOutHF();
|
|
|
|
| 1514 |
renderHfSection();
|
| 1515 |
return;
|
| 1516 |
}
|
| 1517 |
+
await beginHFSignIn();
|
| 1518 |
+
// beginHFSignIn redirects β unreachable after.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1519 |
} catch (err) {
|
| 1520 |
logLine(`Sign-in failed: ${err.message}`);
|
| 1521 |
}
|
|
|
|
| 1565 |
|
| 1566 |
export async function mountRunSection() {
|
| 1567 |
if (state.mounted) return;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1568 |
state.mounted = true;
|
| 1569 |
|
| 1570 |
state.surface = await detectSurface();
|
|
|
|
| 1607 |
wireAbortHandler();
|
| 1608 |
wirePurgeHandler();
|
| 1609 |
wireHubHandlers();
|
|
|
|
| 1610 |
wireOutputHandlers();
|
| 1611 |
+
// Restore the last completed run from localStorage so it survives a page
|
| 1612 |
+
// reload β including the OAuth redirect taking the user to HF and back.
|
| 1613 |
+
// Must run before updateButtons/renderOutput/hideProgress so they pick up
|
| 1614 |
+
// the rehydrated state.results.
|
| 1615 |
+
restoreSavedResults();
|
| 1616 |
updateButtons();
|
| 1617 |
renderOutput();
|
| 1618 |
+
if (state.results.length === 0) hideProgressUntilFirstRow();
|
| 1619 |
maybeShowCrashBanner();
|
| 1620 |
}
|
| 1621 |
|
| 1622 |
+
const RESULTS_STORAGE_KEY = 'webgpu-bench:lastRun';
|
| 1623 |
+
|
| 1624 |
+
function restoreSavedResults() {
|
| 1625 |
+
let saved;
|
| 1626 |
+
try {
|
| 1627 |
+
const raw = localStorage.getItem(RESULTS_STORAGE_KEY);
|
| 1628 |
+
if (!raw) return;
|
| 1629 |
+
saved = JSON.parse(raw);
|
| 1630 |
+
} catch { return; }
|
| 1631 |
+
if (!Array.isArray(saved) || saved.length === 0) return;
|
| 1632 |
+
|
| 1633 |
+
state.results = saved;
|
| 1634 |
+
for (const record of saved) {
|
| 1635 |
+
const v = state.variants.find(x => x.repo === record.repo && x.filename === record.filename);
|
| 1636 |
+
if (!v) continue;
|
| 1637 |
+
progressRowFor(v).fillFromRecord(record);
|
| 1638 |
+
}
|
| 1639 |
+
}
|
| 1640 |
+
|
| 1641 |
export function teardownRunSection() {
|
| 1642 |
// Placeholder β no explicit teardown today. Future: abort in-flight runs,
|
| 1643 |
// detach listeners. For now the Run tab just sits idle.
|
js/run/hub.js
CHANGED
|
@@ -4,11 +4,13 @@
|
|
| 4 |
// 1. On page load, the run controller calls `resumeHFSession()` to handle
|
| 5 |
// the OAuth redirect (if the URL has the expected query params) and to
|
| 6 |
// reuse any previously-stored access token.
|
| 7 |
-
// 2. Clicking [Sign in] calls `beginHFSignIn(
|
| 8 |
-
//
|
| 9 |
-
// the
|
| 10 |
-
//
|
| 11 |
-
//
|
|
|
|
|
|
|
| 12 |
// 3. Clicking [Submit] calls `submitResultsToDataset()` which commits a
|
| 13 |
// JSON file (one per machine-slug / browser / session) as a PR to the
|
| 14 |
// leaderboard dataset.
|
|
@@ -26,34 +28,9 @@ import {
|
|
| 26 |
isHubConfigured,
|
| 27 |
} from './config.js';
|
| 28 |
|
| 29 |
-
// localStorage (not sessionStorage) so the
|
| 30 |
-
//
|
| 31 |
-
|
| 32 |
-
export const HF_TOKEN_STORAGE_KEY = 'webgpu-bench:hfOauth';
|
| 33 |
-
export const HF_POPUP_DONE_MESSAGE = 'webgpu-bench:hf-signed-in';
|
| 34 |
-
// Marker written by the opener tab right before window.open(). The popup
|
| 35 |
-
// reads it on callback to know it's running inside a popup β `window.opener`
|
| 36 |
-
// is unreliable because HF's OAuth page sets a COOP header that severs the
|
| 37 |
-
// opener relationship in Chrome/Safari. Marker is cleared after callback.
|
| 38 |
-
const HF_POPUP_PENDING_KEY = 'webgpu-bench:hfPopupPending';
|
| 39 |
-
const HF_POPUP_PENDING_TTL_MS = 10 * 60 * 1000;
|
| 40 |
-
|
| 41 |
-
export function isHFPopupCallback() {
|
| 42 |
-
if (typeof window === 'undefined') return false;
|
| 43 |
-
if (window.opener && window.opener !== window) return true;
|
| 44 |
-
try {
|
| 45 |
-
const raw = localStorage.getItem(HF_POPUP_PENDING_KEY);
|
| 46 |
-
if (!raw) return false;
|
| 47 |
-
const data = JSON.parse(raw);
|
| 48 |
-
if (!data?.ts || (Date.now() - data.ts) > HF_POPUP_PENDING_TTL_MS) {
|
| 49 |
-
localStorage.removeItem(HF_POPUP_PENDING_KEY);
|
| 50 |
-
return false;
|
| 51 |
-
}
|
| 52 |
-
return true;
|
| 53 |
-
} catch {
|
| 54 |
-
return false;
|
| 55 |
-
}
|
| 56 |
-
}
|
| 57 |
|
| 58 |
// ββββββββββββββββ session ββββββββββββββββ
|
| 59 |
|
|
@@ -64,30 +41,18 @@ export async function resumeHFSession() {
|
|
| 64 |
try {
|
| 65 |
const res = await oauthHandleRedirectIfPresent();
|
| 66 |
if (res) {
|
| 67 |
-
|
| 68 |
accessToken: res.accessToken,
|
| 69 |
expiresAt: res.accessTokenExpiresAt,
|
| 70 |
userName: res.userInfo?.preferred_username || res.userInfo?.name || null,
|
| 71 |
avatarUrl: res.userInfo?.picture || null,
|
| 72 |
hubId: res.userInfo?.sub || null,
|
| 73 |
-
};
|
| 74 |
-
localStorage.setItem(HF_TOKEN_STORAGE_KEY, JSON.stringify(session));
|
| 75 |
// Clean up the OAuth query params so a reload doesn't retry.
|
| 76 |
const url = new URL(location.href);
|
| 77 |
for (const k of ['code', 'state']) url.searchParams.delete(k);
|
| 78 |
history.replaceState({}, '', url.toString());
|
| 79 |
-
|
| 80 |
-
// opener also gets a `storage` event from the localStorage write β
|
| 81 |
-
// postMessage is the fast path; storage event is the safety net for
|
| 82 |
-
// when COOP severs window.opener.
|
| 83 |
-
if (isHFPopupCallback()) {
|
| 84 |
-
localStorage.removeItem(HF_POPUP_PENDING_KEY);
|
| 85 |
-
if (window.opener && window.opener !== window && !window.opener.closed) {
|
| 86 |
-
try { window.opener.postMessage({ type: HF_POPUP_DONE_MESSAGE }, location.origin); } catch { /* opener gone */ }
|
| 87 |
-
}
|
| 88 |
-
try { window.close(); } catch { /* not script-opened */ }
|
| 89 |
-
}
|
| 90 |
-
return session;
|
| 91 |
}
|
| 92 |
} catch (err) {
|
| 93 |
console.warn('OAuth redirect handling failed:', err.message);
|
|
@@ -98,12 +63,12 @@ export async function resumeHFSession() {
|
|
| 98 |
|
| 99 |
function readStoredSession() {
|
| 100 |
try {
|
| 101 |
-
const raw = localStorage.getItem(
|
| 102 |
if (!raw) return null;
|
| 103 |
const data = JSON.parse(raw);
|
| 104 |
if (!data.accessToken) return null;
|
| 105 |
if (data.expiresAt && new Date(data.expiresAt).getTime() < Date.now()) {
|
| 106 |
-
localStorage.removeItem(
|
| 107 |
return null;
|
| 108 |
}
|
| 109 |
return data;
|
|
@@ -112,7 +77,7 @@ function readStoredSession() {
|
|
| 112 |
}
|
| 113 |
}
|
| 114 |
|
| 115 |
-
export async function beginHFSignIn(
|
| 116 |
if (!isHubConfigured()) {
|
| 117 |
throw new Error('HF hub is not configured. Set HF_DATASET_REPO in run/config.js.');
|
| 118 |
}
|
|
@@ -135,27 +100,11 @@ export async function beginHFSignIn({ popup = false } = {}) {
|
|
| 135 |
scopes,
|
| 136 |
redirectUrl: location.origin + location.pathname,
|
| 137 |
});
|
| 138 |
-
if (popup) {
|
| 139 |
-
// Set marker BEFORE window.open so the popup callback can detect popup
|
| 140 |
-
// mode even when COOP nullifies window.opener.
|
| 141 |
-
try {
|
| 142 |
-
localStorage.setItem(HF_POPUP_PENDING_KEY, JSON.stringify({ ts: Date.now() }));
|
| 143 |
-
} catch { /* quota / disabled β best effort */ }
|
| 144 |
-
const win = window.open(url, 'hf-oauth', 'popup=yes,width=520,height=720');
|
| 145 |
-
if (!win) {
|
| 146 |
-
try { localStorage.removeItem(HF_POPUP_PENDING_KEY); } catch { /* noop */ }
|
| 147 |
-
const err = new Error('Popup blocked β allow popups for this site, or use full-page sign-in.');
|
| 148 |
-
err.code = 'popup-blocked';
|
| 149 |
-
throw err;
|
| 150 |
-
}
|
| 151 |
-
return { popup: win };
|
| 152 |
-
}
|
| 153 |
location.assign(url);
|
| 154 |
-
return null;
|
| 155 |
}
|
| 156 |
|
| 157 |
export function signOutHF() {
|
| 158 |
-
localStorage.removeItem(
|
| 159 |
}
|
| 160 |
|
| 161 |
export async function fetchWhoAmI(token) {
|
|
|
|
| 4 |
// 1. On page load, the run controller calls `resumeHFSession()` to handle
|
| 5 |
// the OAuth redirect (if the URL has the expected query params) and to
|
| 6 |
// reuse any previously-stored access token.
|
| 7 |
+
// 2. Clicking [Sign in] calls `beginHFSignIn()` which redirects the
|
| 8 |
+
// browser to HF; after consent, HF redirects back to the page with
|
| 9 |
+
// the OAuth response encoded in the URL. The next page load calls
|
| 10 |
+
// `resumeHFSession()` again, completes the exchange, and stores the
|
| 11 |
+
// token in localStorage. The Run controller restores any previously
|
| 12 |
+
// saved benchmark results from localStorage so they survive the
|
| 13 |
+
// OAuth round-trip.
|
| 14 |
// 3. Clicking [Submit] calls `submitResultsToDataset()` which commits a
|
| 15 |
// JSON file (one per machine-slug / browser / session) as a PR to the
|
| 16 |
// leaderboard dataset.
|
|
|
|
| 28 |
isHubConfigured,
|
| 29 |
} from './config.js';
|
| 30 |
|
| 31 |
+
// localStorage (not sessionStorage) so the token survives the OAuth redirect
|
| 32 |
+
// (different document load) and is shared across tabs on the same origin.
|
| 33 |
+
const TOKEN_STORAGE_KEY = 'webgpu-bench:hfOauth';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 34 |
|
| 35 |
// ββββββββββββββββ session ββββββββββββββββ
|
| 36 |
|
|
|
|
| 41 |
try {
|
| 42 |
const res = await oauthHandleRedirectIfPresent();
|
| 43 |
if (res) {
|
| 44 |
+
localStorage.setItem(TOKEN_STORAGE_KEY, JSON.stringify({
|
| 45 |
accessToken: res.accessToken,
|
| 46 |
expiresAt: res.accessTokenExpiresAt,
|
| 47 |
userName: res.userInfo?.preferred_username || res.userInfo?.name || null,
|
| 48 |
avatarUrl: res.userInfo?.picture || null,
|
| 49 |
hubId: res.userInfo?.sub || null,
|
| 50 |
+
}));
|
|
|
|
| 51 |
// Clean up the OAuth query params so a reload doesn't retry.
|
| 52 |
const url = new URL(location.href);
|
| 53 |
for (const k of ['code', 'state']) url.searchParams.delete(k);
|
| 54 |
history.replaceState({}, '', url.toString());
|
| 55 |
+
return readStoredSession();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 56 |
}
|
| 57 |
} catch (err) {
|
| 58 |
console.warn('OAuth redirect handling failed:', err.message);
|
|
|
|
| 63 |
|
| 64 |
function readStoredSession() {
|
| 65 |
try {
|
| 66 |
+
const raw = localStorage.getItem(TOKEN_STORAGE_KEY);
|
| 67 |
if (!raw) return null;
|
| 68 |
const data = JSON.parse(raw);
|
| 69 |
if (!data.accessToken) return null;
|
| 70 |
if (data.expiresAt && new Date(data.expiresAt).getTime() < Date.now()) {
|
| 71 |
+
localStorage.removeItem(TOKEN_STORAGE_KEY);
|
| 72 |
return null;
|
| 73 |
}
|
| 74 |
return data;
|
|
|
|
| 77 |
}
|
| 78 |
}
|
| 79 |
|
| 80 |
+
export async function beginHFSignIn() {
|
| 81 |
if (!isHubConfigured()) {
|
| 82 |
throw new Error('HF hub is not configured. Set HF_DATASET_REPO in run/config.js.');
|
| 83 |
}
|
|
|
|
| 100 |
scopes,
|
| 101 |
redirectUrl: location.origin + location.pathname,
|
| 102 |
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 103 |
location.assign(url);
|
|
|
|
| 104 |
}
|
| 105 |
|
| 106 |
export function signOutHF() {
|
| 107 |
+
localStorage.removeItem(TOKEN_STORAGE_KEY);
|
| 108 |
}
|
| 109 |
|
| 110 |
export async function fetchWhoAmI(token) {
|
run.html
CHANGED
|
@@ -5,29 +5,6 @@
|
|
| 5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
<meta name="color-scheme" content="light dark">
|
| 7 |
<script>(function(){var s=localStorage.getItem('theme');if(!s){s=(window.matchMedia&&matchMedia('(prefers-color-scheme: dark)').matches)?'dark':'light';}document.documentElement.setAttribute('data-theme',s);})();</script>
|
| 8 |
-
<script>
|
| 9 |
-
// OAuth-popup flash guard: if this load is the HF sign-in popup callback,
|
| 10 |
-
// hide the page immediately so the user doesn't see the bench skeleton
|
| 11 |
-
// flash before mountRunSection() runs the close path.
|
| 12 |
-
(function () {
|
| 13 |
-
try {
|
| 14 |
-
var p = new URLSearchParams(location.search);
|
| 15 |
-
if (!p.get('code') || !p.get('state')) return;
|
| 16 |
-
var hasOpener = !!(window.opener && window.opener !== window);
|
| 17 |
-
var marker = false;
|
| 18 |
-
try {
|
| 19 |
-
var raw = localStorage.getItem('webgpu-bench:hfPopupPending');
|
| 20 |
-
if (raw) {
|
| 21 |
-
var data = JSON.parse(raw);
|
| 22 |
-
marker = !!(data && data.ts && (Date.now() - data.ts) < 10 * 60 * 1000);
|
| 23 |
-
}
|
| 24 |
-
} catch (e) { /* localStorage disabled */ }
|
| 25 |
-
if (hasOpener || marker) {
|
| 26 |
-
document.documentElement.style.visibility = 'hidden';
|
| 27 |
-
}
|
| 28 |
-
} catch (e) { /* noop */ }
|
| 29 |
-
})();
|
| 30 |
-
</script>
|
| 31 |
<title>Run β WebGPU Bench</title>
|
| 32 |
<link rel="preconnect" href="https://fonts.googleapis.com">
|
| 33 |
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
|
|
| 5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
<meta name="color-scheme" content="light dark">
|
| 7 |
<script>(function(){var s=localStorage.getItem('theme');if(!s){s=(window.matchMedia&&matchMedia('(prefers-color-scheme: dark)').matches)?'dark':'light';}document.documentElement.setAttribute('data-theme',s);})();</script>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
<title>Run β WebGPU Bench</title>
|
| 9 |
<link rel="preconnect" href="https://fonts.googleapis.com">
|
| 10 |
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|