diff --git a/src/js/palette.js b/src/js/palette.js index 995914e..54002cb 100644 --- a/src/js/palette.js +++ b/src/js/palette.js @@ -16,8 +16,11 @@ function initPalette() { // Deckt Settings (.panel-overlay), Theme/Add-Board/Add-Bookmark/Rename // (.modal-overlay) sowie HellionDialog UND Onboarding (.dialog-overlay) ab. function isBlocked() { + // .palette-overlay ausklammern: das eigene (beim Schliessen noch deferred .active + // tragende) Overlay darf den Reopen-Guard nicht selbst blockieren (Self-Block-Race + // beim Toggle-Spam, da close() .active erst in withViewTransition entfernt). return !!document.querySelector( - '.panel-overlay.active, .modal-overlay.active, .dialog-overlay.active' + '.panel-overlay.active, .modal-overlay.active, .dialog-overlay.active:not(.palette-overlay)' ); } @@ -48,6 +51,11 @@ function initPalette() { // ---- Treffer oeffnen (wie boards.js:270) ---- function openResult(item) { if (!item || !item.url) return; + // Sicherheit: nur sichere Protokolle oeffnen. Verhindert javascript:/data:-URLs aus + // importierten Bookmarks (XSS im Extension-Origin, besonders bei _self). http/https/ftp only. + let safe = false; + try { safe = ['http:', 'https:', 'ftp:'].includes(new URL(item.url).protocol); } catch (e) { /* ungueltige URL */ } + if (!safe) { close(); return; } window.open(item.url, settings.newTab ? '_blank' : '_self', 'noopener,noreferrer'); close(); } @@ -58,11 +66,11 @@ function initPalette() { activeIndex = results.length ? 0 : -1; if (results.length === 0) { + // Kein role="option": der Leerzustand ist keine auswaehlbare Option, sondern eine + // Statuszeile. Die Ansage uebernimmt die aria-live-Region (liveEl) unten. const empty = document.createElement('li'); empty.className = 'palette-empty'; - empty.id = 'palette-opt-empty'; - empty.setAttribute('role', 'option'); - empty.setAttribute('aria-disabled', 'true'); + empty.setAttribute('role', 'presentation'); empty.textContent = t('palette.no_results'); listEl.appendChild(empty); liveEl.textContent = t('palette.no_results'); @@ -97,6 +105,10 @@ function initPalette() { // ---- aktiven Eintrag setzen (aria-activedescendant + aria-selected) ---- function setActive(listEl, idx) { + // Guard: close() nullt overlay synchron, das DOM-Removal laeuft aber deferred in + // withViewTransition. In diesem Frame-Fenster kann ein mousemove auf einem noch + // lebenden Treffer-