Skip to content

Commit 8fdd00a

Browse files
committed
Avoid innerHTML
And enforce with lint. AI use: Interactive session with Opus 5.5 with decision making, review and testing.
1 parent 6ecacf7 commit 8fdd00a

17 files changed

Lines changed: 680 additions & 329 deletions

‎AGENTS.md‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ User code may only reach the Flock API. See [dev-docs/SANDBOX.md](dev-docs/SANDB
3030
- Generated code must pass the allowlists in `prepareUserCode`; add a new property, global or syntax there
3131
- Add a `@sandbox` test for any API that returns, resolves to, or calls back with non-primitive values
3232

33+
## Page security
34+
35+
- Don't use `innerHTML`; build elements with `ui/dom.js`
36+
3337
## Testing
3438

3539
- See [README_TOOLS.md](README_TOOLS.md) for instructions on running tests

‎accessibility/infoPanel.js‎

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { translate } from '../main/translation.js';
22
import { logViewport } from '../main/viewportDebug.js';
33
import { isPortraitCanvas } from '../main/canvasAspect.js';
4+
import { h, svgIcon } from '../ui/dom.js';
45

56
// Narrow landscape layouts (phones and small tablets alike) set
67
// #info-panel-body to display:none in CSS, so there's no docked area to
@@ -137,10 +138,27 @@ const ModalPanelBehaviour = {
137138
);
138139
},
139140

140-
fontControlsHTML() {
141-
return `
142-
<button class="bigbutton font-decrease-btn" aria-label="${translate('player_decrease_font_size')}" title="${translate('player_decrease_font_size')}"><span aria-hidden="true">A</span></button>
143-
<button class="bigbutton font-increase-btn" aria-label="${translate('player_increase_font_size')}" title="${translate('player_increase_font_size')}"><span aria-hidden="true">A</span></button>`;
141+
// The header shared by the info panels. The spaces keep the gaps between the inline buttons.
142+
panelHeader(titleId) {
143+
const fontButton = (className, key) =>
144+
h(
145+
'button',
146+
{ class: `bigbutton ${className}`, 'aria-label': translate(key), title: translate(key) },
147+
h('span', { 'aria-hidden': 'true' }, 'A')
148+
);
149+
return h(
150+
'div',
151+
{ class: 'shortcuts-panel-header' },
152+
h('h2', { id: titleId, class: 'shortcuts-panel-title' }),
153+
h(
154+
'div',
155+
{ class: 'shortcuts-panel-controls' },
156+
fontButton('font-decrease-btn', 'player_decrease_font_size'),
157+
' ',
158+
fontButton('font-increase-btn', 'player_increase_font_size'),
159+
' '
160+
)
161+
);
144162
},
145163

146164
initFontControls() {
@@ -241,7 +259,7 @@ const ModalPanelBehaviour = {
241259
closeBtn.className = 'bigbutton shortcuts-modal-close';
242260
closeBtn.setAttribute('aria-label', translate(this._closeLabelKey));
243261
closeBtn.setAttribute('title', translate(this._closeLabelKey));
244-
closeBtn.innerHTML = '<span aria-hidden="true">X</span>';
262+
closeBtn.append(h('span', { 'aria-hidden': 'true' }, 'X'));
245263
closeBtn.addEventListener('click', () => this.hide());
246264
panel.querySelector('.shortcuts-panel-controls')?.appendChild(closeBtn);
247265
this._closeBtn = closeBtn;
@@ -321,7 +339,8 @@ const ModalPanelBehaviour = {
321339
},
322340
};
323341

324-
const EXTERNAL_LINK_ICON = `<svg class="external-link-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" aria-hidden="true"><path fill="currentColor" d="M320 0c-17.7 0-32 14.3-32 32s14.3 32 32 32l82.7 0L201.4 265.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L448 109.3l0 82.7c0 17.7 14.3 32 32 32s32-14.3 32-32l0-160c0-17.7-14.3-32-32-32L320 0zM80 32C35.8 32 0 67.8 0 112L0 432c0 44.2 35.8 80 80 80l320 0c44.2 0 80-35.8 80-80l0-112c0-17.7-14.3-32-32-32s-32 14.3-32 32l0 112c0 8.8-7.2 16-16 16L80 448c-8.8 0-16-7.2-16-16l0-320c0-8.8 7.2-16 16-16l112 0c17.7 0 32-14.3 32-32s-14.3-32-32-32L80 32z"/></svg>`;
342+
const EXTERNAL_LINK_PATH =
343+
'M320 0c-17.7 0-32 14.3-32 32s14.3 32 32 32l82.7 0L201.4 265.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L448 109.3l0 82.7c0 17.7 14.3 32 32 32s32-14.3 32-32l0-160c0-17.7-14.3-32-32-32L320 0zM80 32C35.8 32 0 67.8 0 112L0 432c0 44.2 35.8 80 80 80l320 0c44.2 0 80-35.8 80-80l0-112c0-17.7-14.3-32-32-32s-32 14.3-32 32l0 112c0 8.8-7.2 16-16 16L80 448c-8.8 0-16-7.2-16-16l0-320c0-8.8 7.2-16 16-16l112 0c17.7 0 32-14.3 32-32s-14.3-32-32-32L80 32z';
325344

326345
// External links in the help content are marked up automatically, so the docs
327346
// maintainer only ever has to write a plain <a href>. The icon sits inside the link
@@ -330,9 +349,11 @@ function decorateExternalLinks(root) {
330349
root.querySelectorAll('a[href^="http"]').forEach((link) => {
331350
link.target = '_blank';
332351
link.rel = 'noopener noreferrer';
333-
link.insertAdjacentHTML(
334-
'beforeend',
335-
`<span class="sr-only"> (${translate('link_opens_in_new_tab')})</span>${EXTERNAL_LINK_ICON}`
352+
link.append(
353+
h('span', { class: 'sr-only' }, ` (${translate('link_opens_in_new_tab')})`),
354+
svgIcon({ class: 'external-link-icon', viewBox: '0 0 512 512', 'aria-hidden': 'true' }, [
355+
['path', { fill: 'currentColor', d: EXTERNAL_LINK_PATH }],
356+
])
336357
);
337358
});
338359
}

0 commit comments

Comments
 (0)