From 0bdfe705274fc6488c52428f971cd749f15fa763 Mon Sep 17 00:00:00 2001 From: wenytang-ms Date: Tue, 30 Jun 2026 14:10:19 +0800 Subject: [PATCH 1/4] Fix Change Method Signature editable cells not rendering typed text (#4417) The parameter Type/Name/Default and exception Type cells relied on contentEditable directly on the deprecated webview-ui-toolkit scode-data-grid-cell. The cell's shadow root only contains a , so text typed onto the host text node is never slotted/rendered: the field looks blank while editing, and on commit the typed text was read via outerText and appended to the original value instead of replacing it. Render a real as slotted content of the cell when editing, and read the value via .value. This makes typed text visible and gives correct replace-on-commit semantics. Focus/select now run in the setState callback since the input mounts after re-render, and the obsolete contentEditable DOM-reset on cancel is removed. --- src/webview/changeSignature/App.css | 13 +++++ src/webview/changeSignature/App.tsx | 88 +++++++++++++++-------------- 2 files changed, 59 insertions(+), 42 deletions(-) diff --git a/src/webview/changeSignature/App.css b/src/webview/changeSignature/App.css index 2feef25ea..867f579c6 100644 --- a/src/webview/changeSignature/App.css +++ b/src/webview/changeSignature/App.css @@ -110,6 +110,19 @@ main { background-color: var(--vscode-input-background); } +.parameter-input { + box-sizing: border-box; + width: 100%; + height: 100%; + padding: 0; + border: 0; + outline: none; + background-color: transparent; + color: var(--vscode-input-foreground); + font-family: inherit; + font-size: inherit; +} + .parameter-cell-edit-button { padding: 0; border: 0; diff --git a/src/webview/changeSignature/App.tsx b/src/webview/changeSignature/App.tsx index 1531efeda..b2010a8d3 100644 --- a/src/webview/changeSignature/App.tsx +++ b/src/webview/changeSignature/App.tsx @@ -175,11 +175,13 @@ export class App extends React.Component<{}, State> { editParameterRow: selectedRowNumber, editExceptionRow: -1, focusRow: -1, + }, () => { + const elementToSelect = document.getElementById(`parameterType-${selectedRowNumber}`) as HTMLInputElement | null; + if (elementToSelect) { + elementToSelect.focus(); + elementToSelect.select(); + } }); - const elementToSelect = document.getElementById(`parameterType-${selectedRowNumber}`); - if (elementToSelect) { - elementToSelect.focus(); - } } else if (id.startsWith("editException")) { const selectedRowNumber: number | undefined = this.getSelectedRowNumber(id); if (selectedRowNumber === undefined) { @@ -189,11 +191,13 @@ export class App extends React.Component<{}, State> { editParameterRow: -1, editExceptionRow: selectedRowNumber, focusRow: -1, + }, () => { + const elementToSelect = document.getElementById(`exceptionType-${selectedRowNumber}`) as HTMLInputElement | null; + if (elementToSelect) { + elementToSelect.focus(); + elementToSelect.select(); + } }); - const elementToSelect = document.getElementById(`exceptionType-${selectedRowNumber}`); - if (elementToSelect) { - elementToSelect.focus(); - } } else if (id.startsWith("upParameter")) { const selectedRowNumber: number | undefined = this.getSelectedRowNumber(id); if (selectedRowNumber === undefined) { @@ -253,20 +257,20 @@ export class App extends React.Component<{}, State> { if (selectedRowNumber === undefined) { return; } - const parameterType = document.getElementById(`parameterType-${selectedRowNumber}`); - const parameterName = document.getElementById(`parameterName-${selectedRowNumber}`); - const parameterDefault = this.isDefaultValueEditable(selectedRowNumber) ? document.getElementById(`parameterDefault-${selectedRowNumber}`) : undefined; + const parameterType = document.getElementById(`parameterType-${selectedRowNumber}`) as HTMLInputElement | null; + const parameterName = document.getElementById(`parameterName-${selectedRowNumber}`) as HTMLInputElement | null; + const parameterDefault = this.isDefaultValueEditable(selectedRowNumber) ? document.getElementById(`parameterDefault-${selectedRowNumber}`) as HTMLInputElement | null : undefined; this.setState({ parameters: this.state.parameters.map((e, i) => { if (i === selectedRowNumber) { - if (parameterType?.outerText) { - e.type = parameterType.outerText; + if (parameterType?.value) { + e.type = parameterType.value; } - if (parameterName?.outerText) { - e.name = parameterName.outerText; + if (parameterName?.value) { + e.name = parameterName.value; } - if (parameterDefault?.outerText) { - e.defaultValue = parameterDefault.outerText; + if (parameterDefault?.value) { + e.defaultValue = parameterDefault.value; } } return e; @@ -280,20 +284,6 @@ export class App extends React.Component<{}, State> { if (selectedRowNumber === undefined) { return; } - const parameterType = document.getElementById(`parameterType-${selectedRowNumber}`); - if (parameterType) { - parameterType.textContent = this.state.parameters[selectedRowNumber].type; - } - const parameterName = document.getElementById(`parameterName-${selectedRowNumber}`); - if (parameterName) { - parameterName.textContent = this.state.parameters[selectedRowNumber].name; - } - if (this.isDefaultValueEditable(selectedRowNumber)) { - const parameterDefault = document.getElementById(`parameterDefault-${selectedRowNumber}`); - if (parameterDefault) { - parameterDefault.textContent = this.state.parameters[selectedRowNumber].defaultValue; - } - } this.setState({ editParameterRow: -1, editExceptionRow: -1, @@ -304,12 +294,12 @@ export class App extends React.Component<{}, State> { if (selectedRowNumber === undefined) { return; } - const exceptionType = document.getElementById(`exceptionType-${selectedRowNumber}`); + const exceptionType = document.getElementById(`exceptionType-${selectedRowNumber}`) as HTMLInputElement | null; this.setState({ exceptions: this.state.exceptions.map((e, i) => { if (i === selectedRowNumber) { - if (exceptionType?.outerText) { - e.type = exceptionType.outerText; + if (exceptionType?.value) { + e.type = exceptionType.value; } } return e; @@ -323,10 +313,6 @@ export class App extends React.Component<{}, State> { if (selectedRowNumber === undefined) { return; } - const exceptionType = document.getElementById(`exceptionType-${selectedRowNumber}`); - if (exceptionType) { - exceptionType.textContent = this.state.exceptions[selectedRowNumber].type; - } this.setState({ editParameterRow: -1, editExceptionRow: -1, @@ -431,11 +417,28 @@ export class App extends React.Component<{}, State> { return this.isDefaultValueEditable(row) ? this.state.parameters[row].defaultValue : "-"; }; + /** + * Render a data grid cell whose value can be edited. When editing, a real + * control is rendered as slotted content of the cell so that the + * typed text is visibly rendered and read back reliably. This avoids relying + * on `contentEditable` directly on the (deprecated) webview-ui-toolkit + * `vscode-data-grid-cell`, whose shadow-DOM slot does not render text nodes + * placed on the host (see redhat-developer/vscode-java#4417). + */ + renderEditableCell = (id: string, value: string, editing: boolean, editable: boolean, gridColumn: string) => { + return + {editable + ? + : {value}} + ; + }; + generateParameterDataGridRow = (row: number) => { + const editing = row === this.state.editParameterRow; return - {this.state.parameters[row].type} - {this.state.parameters[row].name} - {this.getDefaultValue(row)} + {this.renderEditableCell(`parameterType-${row}`, this.state.parameters[row].type, editing, editing, "1")} + {this.renderEditableCell(`parameterName-${row}`, this.state.parameters[row].name, editing, editing, "2")} + {this.renderEditableCell(`parameterDefault-${row}`, this.getDefaultValue(row), editing, editing && this.isDefaultValueEditable(row), "3")} {row === this.state.editParameterRow ?
@@ -461,8 +464,9 @@ export class App extends React.Component<{}, State> { }; generateExceptionDataGridRow = (row: number) => { + const editing = row === this.state.editExceptionRow; return - {this.state.exceptions[row].type} + {this.renderEditableCell(`exceptionType-${row}`, this.state.exceptions[row].type, editing, editing, "1")} {row === this.state.editExceptionRow ?
From ad9672a4a54488f76386964fd7f8b6a6066b6bfd Mon Sep 17 00:00:00 2001 From: wenytang-ms Date: Tue, 30 Jun 2026 15:18:18 +0800 Subject: [PATCH 2/4] Migrate Change Method Signature webview off deprecated webview-ui-toolkit Replace all @vscode/webview-ui-toolkit React components in the Change Method Signature dialog with native HTML elements styled via --vscode-* CSS variables, and drop the archived dependency entirely. - Buttons -> + +
); From 963f4d4826c448476d00f75267482fcb9258bb1a Mon Sep 17 00:00:00 2001 From: wenytang-ms Date: Tue, 30 Jun 2026 16:08:37 +0800 Subject: [PATCH 3/4] Tighten Change Signature webview CSP: drop now-unneeded style-src 'unsafe-inline' The 'unsafe-inline' style-src was only required by the webview-ui-toolkit web components, which set inline style attributes on their hosts at runtime. After migrating to native elements, CSS is bundled to a file and loaded via (covered by cspSource) and the codicon font is a data: URL (covered by font-src data:), so no inline styles remain. --- src/refactoring/changeSignaturePanel.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/refactoring/changeSignaturePanel.ts b/src/refactoring/changeSignaturePanel.ts index 5f50742d0..f5f7728a0 100644 --- a/src/refactoring/changeSignaturePanel.ts +++ b/src/refactoring/changeSignaturePanel.ts @@ -116,8 +116,8 @@ export class ChangeSignaturePanel { - - + + Change Signature From bb0889374dbc064bb2e288b1c3e2d0f4a33d9e5c Mon Sep 17 00:00:00 2001 From: wenytang-ms Date: Wed, 1 Jul 2026 11:26:37 +0800 Subject: [PATCH 4/4] Add tab ARIA attributes and explicit button types to Change Signature webview Restore accessibility that the toolkit VSCodePanels provided: tab buttons now expose aria-selected/aria-controls and each view is a role=tabpanel labelled by its tab. Also set type=button on all native buttons so they never default to submit. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/webview/changeSignature/App.tsx | 38 ++++++++++++++--------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/src/webview/changeSignature/App.tsx b/src/webview/changeSignature/App.tsx index d2b03fce4..2903cf9fa 100644 --- a/src/webview/changeSignature/App.tsx +++ b/src/webview/changeSignature/App.tsx @@ -432,19 +432,19 @@ export class App extends React.Component<{}, State> { {editing ?
- - + +
: row === this.state.focusRow ?
- {row === 0 ? <> : } - {row === this.state.parameters.length - 1 ? <> : } - -
:
@@ -460,13 +460,13 @@ export class App extends React.Component<{}, State> { {editing ?
- - + +
: row === this.state.focusRow ?
- -
:
@@ -502,11 +502,11 @@ export class App extends React.Component<{}, State> {
- - + +
{this.state.activeTab === "parameters" ? -
+
@@ -529,10 +529,10 @@ export class App extends React.Component<{}, State> {
- +
: -
+
@@ -553,7 +553,7 @@ export class App extends React.Component<{}, State> {
- +
} @@ -565,9 +565,9 @@ export class App extends React.Component<{}, State> { Keep original method as delegate to changed method
- - - + + +
);