Skip to content

Instantly share code, notes, and snippets.

@VerteDinde
Created March 19, 2026 19:16
Show Gist options
  • Select an option

  • Save VerteDinde/bc546fd4718d11767169fb199ce56a6d to your computer and use it in GitHub Desktop.

Select an option

Save VerteDinde/bc546fd4718d11767169fb199ce56a6d to your computer and use it in GitHub Desktop.
aria-setsize-423673297
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>aria-setsize Repro — crbug.com/423673297</title>
<style>
body { font-family: system-ui; padding: 20px; }
ul { margin: 12px 0; }
li { padding: 4px 0; }
h3 { margin-top: 24px; }
#status { margin-top: 16px; color: #666; font-size: 13px; }
</style>
</head>
<body>
<!--
Repro for crbug.com/423673297 — aria-setsize="-1" causes VoiceOver
to read "18,446,744,073,709,551,615" instead of ignoring the value.
HOW TO TEST:
1. Open Accessibility Inspector (Xcode > Open Developer Tool > Accessibility Inspector)
2. Select this Electron window from the target dropdown
3. Navigate to the tree items below using the Inspector or VoiceOver (Cmd+F5)
4. Focus on "Item 1" in the "Bug" section and check its reported set size
EXPECTED (with patch):
VoiceOver says "Item 1 of 3" (or omits set size), because
aria-setsize="-1" means "unknown size" per the ARIA spec.
ACTUAL (without patch):
VoiceOver says "Item 1 of 18,446,744,073,709,551,615" because
-1 is interpreted as UINT64_MAX.
The "Working" section uses aria-setsize="5" as a control to confirm
set size reporting works normally.
-->
<h2>aria-setsize="-1" VoiceOver Bug</h2>
<h3>Bug: aria-setsize="-1" (unknown size)</h3>
<div role="tree" aria-label="Bug Demo Tree">
<div role="treeitem" aria-level="1" aria-setsize="-1" aria-posinset="1" tabindex="0">
Item 1 (setsize=-1)
</div>
<div role="treeitem" aria-level="1" aria-setsize="-1" aria-posinset="2" tabindex="-1">
Item 2 (setsize=-1)
</div>
<div role="treeitem" aria-level="1" aria-setsize="-1" aria-posinset="3" tabindex="-1">
Item 3 (setsize=-1)
</div>
</div>
<h3>Control: aria-setsize="5" (explicit size)</h3>
<div role="tree" aria-label="Control Demo Tree">
<div role="treeitem" aria-level="1" aria-setsize="5" aria-posinset="1" tabindex="0">
Item 1 (setsize=5)
</div>
<div role="treeitem" aria-level="1" aria-setsize="5" aria-posinset="2" tabindex="-1">
Item 2 (setsize=5)
</div>
<div role="treeitem" aria-level="1" aria-setsize="5" aria-posinset="3" tabindex="-1">
Item 3 (setsize=5)
</div>
</div>
<h3>Control: no aria-setsize (computed)</h3>
<div role="tree" aria-label="Computed Demo Tree">
<div role="treeitem" aria-level="1" tabindex="0">
Item 1 (no setsize)
</div>
<div role="treeitem" aria-level="1" tabindex="-1">
Item 2 (no setsize)
</div>
<div role="treeitem" aria-level="1" tabindex="-1">
Item 3 (no setsize)
</div>
</div>
<div id="status">
<p>Focus tree items with VoiceOver or inspect with Accessibility Inspector to compare set size announcements.</p>
</div>
<script src="./renderer.js"></script>
</body>
</html>
const { app, BrowserWindow } = require('electron');
const path = require('path');
function createWindow () {
const win = new BrowserWindow({
width: 600,
height: 400,
webPreferences: {
preload: path.join(__dirname, 'preload.js')
}
});
win.loadFile(path.join(__dirname, 'index.html'));
}
app.whenReady().then(createWindow);
{
"name": "teeny-side-wash-o57wj",
"productName": "teeny-side-wash-o57wj",
"description": "My Electron application description",
"keywords": [],
"main": "./main.js",
"version": "1.0.0",
"author": "khammond",
"scripts": {
"start": "electron ."
},
"dependencies": {},
"devDependencies": {
"electron": "42.0.0-alpha.2"
}
}
/**
* The preload script runs before `index.html` is loaded
* in the renderer. It has access to web APIs as well as
* Electron's renderer process modules and some polyfilled
* Node.js functions.
*
* https://www.electronjs.org/docs/latest/tutorial/sandbox
*/
const container = document.getElementById('dynamic-container');
const status = document.getElementById('status');
function log (msg) {
status.textContent = msg;
console.log(msg);
}
// --- Dynamic creation / removal (the buggy path) ---
document.getElementById('show-dynamic').addEventListener('click', () => {
if (container.querySelector('[role="menu"]')) {
log('Menu already shown.');
return;
}
const menu = document.createElement('div');
menu.setAttribute('role', 'menu');
menu.setAttribute('aria-label', 'Dynamic Menu');
const items = ['Cut', 'Copy', 'Paste'];
for (const label of items) {
const item = document.createElement('div');
item.setAttribute('role', 'menuitem');
item.textContent = label;
menu.appendChild(item);
}
container.appendChild(menu);
log('Dynamic menu added via appendChild. AXMenuOpened should fire.');
});
document.getElementById('hide-dynamic').addEventListener('click', () => {
const menu = container.querySelector('[role="menu"]');
if (menu) {
container.removeChild(menu);
log('Dynamic menu removed via removeChild. AXMenuClosed should fire.');
} else {
log('No dynamic menu to remove.');
}
});
// --- Visibility toggle (the working path) ---
const visMenu = document.getElementById('visibility-menu');
document.getElementById('toggle-visibility').addEventListener('click', () => {
const hidden = visMenu.getAttribute('aria-hidden') === 'true';
visMenu.setAttribute('aria-hidden', String(!hidden));
visMenu.style.display = hidden ? '' : 'none';
log(hidden
? 'Visibility menu shown (aria-hidden=false). AXMenuOpened should fire.'
: 'Visibility menu hidden (aria-hidden=true). AXMenuClosed should fire.');
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment