Skip to content

Commit 08dc12a

Browse files
avivsinaiclaude
andauthored
fix: resolve CSP violations preventing UI interaction in VS Code extension (#18)
Fixes #17 - File selection UI was completely non-functional due to Content Security Policy violations. Changes: - Removed all inline event handlers (onclick, onload, onerror) from HTML - Replaced with delegated event listeners using addEventListener() - Added defer attribute to script tags for proper load sequencing - Updated verification script to use DOMContentLoaded instead of setTimeout - Added CSP-compliant nonce to all inline scripts The webview now complies with VS Code's strict CSP requirements, allowing folder expansion/collapse and all UI interactions to work properly. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-authored-by: Claude <noreply@anthropic.com>
1 parent 1505072 commit 08dc12a

File tree

4 files changed

+60
-54
lines changed

4 files changed

+60
-54
lines changed
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# CSP Fix Testing Preset
2+
# Files needed to verify the Content Security Policy violations fix
3+
# for VS Code extension issue #17
4+
5+
# Core webview files with CSP fixes
6+
src/webview/selectFilesTab.js
7+
src/webview/tabs/selectFilesTabContent.ts
8+
src/webviewProvider.ts
9+
10+
# File explorer for context
11+
src/fileExplorer.ts
12+
13+
# Test files to verify the fix
14+
src/test/suite/fileExplorerDecorationWebviewBug.test.ts
15+
src/test/decorationCache.test.ts
16+
17+
# Extension entry point
18+
src/extension.ts
19+
20+
# Package info
21+
package.json
22+
23+
# Issue context
24+
.github/ISSUE_TEMPLATE/bug_report.md

src/webview/selectFilesTab.js

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -334,7 +334,7 @@ if (typeof window !== 'undefined') {
334334

335335
return `
336336
<div class="directory-section ${isExpanded ? '' : 'collapsed'}" data-dir-id="${id}" data-workspace-folder="${workspaceFolderName}">
337-
<div class="directory-header" onclick="toggleDirectoryFiles(this)">
337+
<div class="directory-header">
338338
<div class="header-left">
339339
<svg class="collapse-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
340340
<polyline points="9 6 15 12 9 18"></polyline>
@@ -842,6 +842,19 @@ if (typeof window !== 'undefined') {
842842
});
843843
}
844844

845+
// Toggle a directory by clicking anywhere on its header (CSP-safe)
846+
selectedFilesListEl.addEventListener('click', (e) => {
847+
const header = e.target.closest('.directory-header');
848+
if (!header || !selectedFilesListEl.contains(header)) {return;}
849+
// Don't toggle if the click was on the remove button inside the header
850+
if (e.target.closest('.js-dir-remove')) {return;}
851+
e.preventDefault();
852+
e.stopPropagation();
853+
if (typeof window.toggleDirectoryFiles === 'function') {
854+
window.toggleDirectoryFiles(header);
855+
}
856+
});
857+
845858
// Add listener for preset updates from host
846859
// This listener needs to be accessible to the global message handler
847860
window.selectFilesTab.handlePresetUpdate = function(presets, selectPreset) {

src/webview/tabs/selectFilesTabContent.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -158,9 +158,11 @@ export function renderDirectoryHeader(dirPath: string, workspaceFolderName: stri
158158
window.directoryMap[id] = dirPath || ROOT_DIR_KEY;
159159

160160
return /* html */ `
161-
<div class="directory-header flex items-center" onclick="toggleDirectoryFiles(this)" data-dir-id="${id}">
161+
<div class="directory-header flex items-center" data-dir-id="${id}">
162162
<span class="folder-label truncate">${dirPath || 'workspace root'}</span>
163-
<button class="trash-btn action-button ml-auto" title="Remove folder" onclick="event.stopPropagation(); removeDirectory('${dirPath || ''}', '${workspaceFolderName}');">
163+
<button class="trash-btn action-button ml-auto js-dir-remove" title="Remove folder"
164+
data-dir="${dirPath || ''}"
165+
data-workspace="${workspaceFolderName}">
164166
<i class="codicon codicon-trash"></i>
165167
</button>
166168
</div>

src/webviewProvider.ts

Lines changed: 18 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -873,58 +873,25 @@ export class PromptCodeWebViewProvider {
873873
window.samplePrompts = ${promptsJson};
874874
</script>
875875
876-
<!-- Load selectFilesTab.js first -->
877-
<script src="${selectFilesTabJsUri}" nonce="${nonce}"
878-
onload="window._scriptLoaded.selectFilesTab = true; console.log('selectFilesTab.js loaded')"
879-
onerror="console.error('Failed to load selectFilesTab.js')">
880-
</script>
881-
882-
<!-- Load instructionsTab.js next -->
883-
<script src="${instructionsTabJsUri}" nonce="${nonce}"
884-
onload="window._scriptLoaded.instructionsTab = true; console.log('instructionsTab.js loaded')"
885-
onerror="console.error('Failed to load instructionsTab.js')">
886-
</script>
887-
888-
<!-- Load generatePromptTab.js -->
889-
<script src="${generatePromptTabJsUri}" nonce="${nonce}"
890-
onload="window._scriptLoaded.generatePromptTab = true; console.log('generatePromptTab.js loaded')"
891-
onerror="console.error('Failed to load generatePromptTab.js')">
892-
</script>
893-
894-
<!-- Load mergeTab.js -->
895-
<script src="${mergeTabJsUri}" nonce="${nonce}"
896-
onload="window._scriptLoaded.mergeTab = true; console.log('mergeTab.js loaded')"
897-
onerror="console.error('Failed to load mergeTab.js')">
898-
</script>
899-
900-
<!-- Then load webview.js which depends on them -->
901-
<script src="${jsUri}" nonce="${nonce}"
902-
onload="window._scriptLoaded.webview = true; console.log('webview.js loaded')"
903-
onerror="console.error('Failed to load webview.js')">
904-
</script>
876+
<!-- Load all scripts with defer for proper sequencing -->
877+
<script src="${selectFilesTabJsUri}" nonce="${nonce}" defer></script>
878+
<script src="${instructionsTabJsUri}" nonce="${nonce}" defer></script>
879+
<script src="${generatePromptTabJsUri}" nonce="${nonce}" defer></script>
880+
<script src="${mergeTabJsUri}" nonce="${nonce}" defer></script>
881+
<script src="${jsUri}" nonce="${nonce}" defer></script>
905882
906-
<!-- Verify everything loaded -->
907-
<script>
908-
setTimeout(() => {
909-
if (!window._scriptLoaded.selectFilesTab) {
910-
console.error('selectFilesTab.js failed to load properly');
911-
}
912-
if (!window._scriptLoaded.instructionsTab) {
913-
console.error('instructionsTab.js failed to load properly');
914-
}
915-
if (!window._scriptLoaded.webview) {
916-
console.error('webview.js failed to load properly');
917-
}
918-
if (typeof window.initSelectFilesTab !== 'function') {
919-
console.error('initSelectFilesTab is not available');
920-
}
921-
if (typeof window.initInstructionsTab !== 'function') {
922-
console.error('initInstructionsTab is not available');
923-
}
924-
if (typeof window.initMergeTab !== 'function') {
925-
console.error('initMergeTab is not available');
926-
}
927-
}, 1000);
883+
<!-- CSP-safe verification using DOMContentLoaded -->
884+
<script nonce="${nonce}">
885+
window.addEventListener('DOMContentLoaded', () => {
886+
const checks = [
887+
['initSelectFilesTab', typeof window.initSelectFilesTab === 'function'],
888+
['initInstructionsTab', typeof window.initInstructionsTab === 'function'],
889+
['initGeneratePromptTab', typeof window.initGeneratePromptTab === 'function'],
890+
['initMergeTab', typeof window.initMergeTab === 'function'],
891+
];
892+
const missing = checks.filter(([, ok]) => !ok).map(([name]) => name);
893+
if (missing.length) console.error('Missing webview boot functions:', missing);
894+
});
928895
</script>
929896
</body>
930897
</html>

0 commit comments

Comments
 (0)