(parentPath, item)
| 2079 | } |
| 2080 | |
| 2081 | function makeChildLi(parentPath, item) { |
| 2082 | const it = normalizeItem(item); |
| 2083 | if (!it) return document.createElement('li'); |
| 2084 | const { name, locked } = it; |
| 2085 | const encrypted = !locked && !!it.encrypted; |
| 2086 | |
| 2087 | const fullPath = parentPath === 'root' ? name : `${parentPath}/${name}`; |
| 2088 | if (!isSafeFolderPath(fullPath)) { |
| 2089 | // Fail closed if something looks odd; don’t render a clickable node. |
| 2090 | return document.createElement('li'); |
| 2091 | } |
| 2092 | |
| 2093 | // <li class="folder-item" role="treeitem" aria-expanded="false"> |
| 2094 | const li = document.createElement('li'); |
| 2095 | li.className = 'folder-item'; |
| 2096 | li.setAttribute('role', 'treeitem'); |
| 2097 | li.setAttribute('aria-expanded', 'false'); |
| 2098 | |
| 2099 | // <div class="folder-row"> |
| 2100 | const row = document.createElement('div'); |
| 2101 | row.className = 'folder-row'; |
| 2102 | |
| 2103 | // <span class="folder-spacer" aria-hidden="true"></span> |
| 2104 | const spacer = document.createElement('span'); |
| 2105 | spacer.className = 'folder-spacer'; |
| 2106 | spacer.setAttribute('aria-hidden', 'true'); |
| 2107 | |
| 2108 | // <span class="folder-option[ locked]" [draggable]> |
| 2109 | const opt = document.createElement('span'); |
| 2110 | opt.className = 'folder-option' + (locked ? ' locked' : '') + (encrypted ? ' encrypted' : ''); |
| 2111 | if (!locked) opt.setAttribute('draggable', 'true'); |
| 2112 | // Use dataset instead of attribute string interpolation. |
| 2113 | opt.dataset.folder = fullPath; |
| 2114 | |
| 2115 | // <span class="folder-icon" aria-hidden="true" data-kind="empty">[svg]</span> |
| 2116 | const icon = document.createElement('span'); |
| 2117 | icon.className = 'folder-icon'; |
| 2118 | icon.setAttribute('aria-hidden', 'true'); |
| 2119 | icon.dataset.kind = 'empty'; |
| 2120 | // Safe: SVG is generated locally, not from user input. |
| 2121 | // nosemgrep: javascript.browser.security.dom-xss.innerhtml |
| 2122 | icon.innerHTML = folderSVG('empty', { locked, encrypted }); |
| 2123 | |
| 2124 | // <span class="folder-label">name</span> |
| 2125 | const label = document.createElement('span'); |
| 2126 | label.className = 'folder-label'; |
| 2127 | // Critical: never innerHTML here — textContent avoids XSS. |
| 2128 | label.textContent = name; |
| 2129 | |
| 2130 | opt.append(icon, label); |
| 2131 | row.append(spacer, opt); |
| 2132 | |
| 2133 | // Add 3-dot actions button for unlocked folders |
| 2134 | if (!locked) addFolderActionButton(row, fullPath); |
| 2135 | |
| 2136 | li.append(row); |
| 2137 | |
| 2138 | // <ul class="folder-tree collapsed" role="group"></ul> |
no test coverage detected