Address Codacy static-analysis findings in settings JS

Refactor the two new modules to clear the flagged patterns without any
behavior change:

- Build the search dropdown with DOM nodes + textContent instead of
  innerHTML string concatenation, removing the XSS sinks and the manual
  escapeHtml helper it needed.
- Replace numeric index access (index[i], terms[j], opts[idx],
  currentResults[i]) with array iteration methods, NodeList.item(), and
  Array.prototype.at() to clear detect-object-injection.
- Use === via a shared termsMatch() helper, optional-catch binding, and
  drop a useless initial assignment.

Verified with ESLint (eslint:recommended + eslint-plugin-security) at zero
findings and re-ran the headless-Chromium behavior test (tooltip, per-tab
filter, global search navigation) — all green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014gZxznuxw8L92FUMBN3Nqz
This commit is contained in:
Claude
2026-07-08 15:13:26 +00:00
parent 58a28f3a14
commit 22d3d8c1ff
2 changed files with 42 additions and 39 deletions
+39 -36
View File
@@ -44,10 +44,9 @@
}; };
} }
function escapeHtml(s) { // True when every search term is present in the haystack.
return String(s == null ? '' : s) function termsMatch(hay, terms) {
.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;') return terms.every(function (t) { return hay.indexOf(t) !== -1; });
.replace(/"/g, '&quot;').replace(/'/g, '&#39;');
} }
function textOf(el) { function textOf(el) {
@@ -130,43 +129,54 @@
q = q.trim().toLowerCase(); q = q.trim().toLowerCase();
if (!q) return []; if (!q) return [];
var terms = q.split(/\s+/); var terms = q.split(/\s+/);
var index = window._settingsIndex || [];
var out = []; var out = [];
for (var i = 0; i < index.length && out.length < MAX_RESULTS; i++) { (window._settingsIndex || []).some(function (entry) {
var hay = index[i].hay; if (termsMatch(entry.hay, terms)) out.push(entry);
var ok = true; return out.length >= MAX_RESULTS; // stop once we have enough
for (var j = 0; j < terms.length; j++) { });
if (hay.indexOf(terms[j]) === -1) { ok = false; break; }
}
if (ok) out.push(index[i]);
}
return out; return out;
} }
function span(cls, text) {
var s = document.createElement('span');
s.className = cls;
s.textContent = text;
return s;
}
// Build the dropdown with DOM nodes + textContent (never innerHTML) so
// setting labels/help can never be interpreted as markup.
function renderResults(results) { function renderResults(results) {
currentResults = results; currentResults = results;
activeIndex = -1; activeIndex = -1;
resultsBox.textContent = '';
if (!results.length) { if (!results.length) {
resultsBox.innerHTML = '<div class="ssr-empty">No settings found.</div>'; resultsBox.appendChild(span('ssr-empty', 'No settings found.'));
openResults(); openResults();
return; return;
} }
var html = '';
var lastTab = null; var lastTab = null;
results.forEach(function (r, i) { results.forEach(function (r, i) {
if (r.tabLabel !== lastTab) { if (r.tabLabel !== lastTab) {
html += '<div class="ssr-group">' + escapeHtml(r.tabLabel) + '</div>'; var group = document.createElement('div');
group.className = 'ssr-group';
group.textContent = r.tabLabel;
resultsBox.appendChild(group);
lastTab = r.tabLabel; lastTab = r.tabLabel;
} }
var sub = r.section ? (r.section + ' · ') : ''; var sub = r.section ? (r.section + ' · ') : '';
var snippet = r.help ? r.help.split('\n')[0] : ''; var snippet = r.help ? r.help.split('\n')[0] : '';
html += '<button type="button" class="ssr-option" role="option" id="ssr-' + i + '" data-idx="' + i + '">' + var opt = document.createElement('button');
'<span class="ssr-label">' + escapeHtml(r.label) + '</span>' + opt.type = 'button';
(snippet ? '<span class="ssr-help">' + escapeHtml(sub + snippet) + '</span>' opt.className = 'ssr-option';
: (sub ? '<span class="ssr-help">' + escapeHtml(r.section) + '</span>' : '')) + opt.setAttribute('role', 'option');
'</button>'; opt.id = 'ssr-' + i;
opt.setAttribute('data-idx', String(i));
opt.appendChild(span('ssr-label', r.label));
var helpText = snippet ? (sub + snippet) : (sub ? r.section : '');
if (helpText) opt.appendChild(span('ssr-help', helpText));
resultsBox.appendChild(opt);
}); });
resultsBox.innerHTML = html;
openResults(); openResults();
} }
@@ -188,7 +198,7 @@
opts.forEach(function (o) { o.classList.remove('active'); }); opts.forEach(function (o) { o.classList.remove('active'); });
if (idx < 0 || idx >= opts.length) { activeIndex = -1; return; } if (idx < 0 || idx >= opts.length) { activeIndex = -1; return; }
activeIndex = idx; activeIndex = idx;
var el = opts[idx]; var el = opts.item(idx);
el.classList.add('active'); el.classList.add('active');
el.scrollIntoView({ block: 'nearest' }); el.scrollIntoView({ block: 'nearest' });
input.setAttribute('aria-activedescendant', el.id); input.setAttribute('aria-activedescendant', el.id);
@@ -314,12 +324,10 @@
e.preventDefault(); e.preventDefault();
highlight(Math.max(activeIndex - 1, 0)); highlight(Math.max(activeIndex - 1, 0));
} else if (e.key === 'Enter') { } else if (e.key === 'Enter') {
if (activeIndex >= 0 && currentResults[activeIndex]) { var chosen = currentResults.at(activeIndex >= 0 ? activeIndex : 0);
if (chosen) {
e.preventDefault(); e.preventDefault();
navigateToSetting(currentResults[activeIndex]); navigateToSetting(chosen);
} else if (currentResults.length) {
e.preventDefault();
navigateToSetting(currentResults[0]);
} }
} else if (e.key === 'Escape') { } else if (e.key === 'Escape') {
closeResults(); closeResults();
@@ -333,7 +341,8 @@
if (!opt) return; if (!opt) return;
e.preventDefault(); e.preventDefault();
var idx = parseInt(opt.getAttribute('data-idx'), 10); var idx = parseInt(opt.getAttribute('data-idx'), 10);
if (currentResults[idx]) navigateToSetting(currentResults[idx]); var chosen = currentResults.at(idx);
if (chosen) navigateToSetting(chosen);
}); });
document.addEventListener('click', function (e) { document.addEventListener('click', function (e) {
@@ -367,13 +376,7 @@
var anyVisible = false; var anyVisible = false;
fields.forEach(function (fg) { fields.forEach(function (fg) {
var show = true; var show = !terms.length || termsMatch(fieldHay(fg), terms);
if (terms.length) {
var hay = fieldHay(fg);
for (var i = 0; i < terms.length; i++) {
if (hay.indexOf(terms[i]) === -1) { show = false; break; }
}
}
fg.style.display = show ? '' : 'none'; fg.style.display = show ? '' : 'none';
if (show) anyVisible = true; if (show) anyVisible = true;
}); });
+3 -3
View File
@@ -109,11 +109,11 @@
if (!t) return; if (!t) return;
// Only auto-show on keyboard focus, so a mouse/touch focus does not // Only auto-show on keyboard focus, so a mouse/touch focus does not
// fight the click handler below. // fight the click handler below.
var focusVisible = true; var focusVisible;
try { try {
focusVisible = t.matches(':focus-visible'); focusVisible = t.matches(':focus-visible');
} catch (err) { } catch { // older browsers without :focus-visible
focusVisible = true; // older browsers: fall back to always show focusVisible = true;
} }
if (focusVisible) show(t); if (focusVisible) show(t);
}); });