From 0e48e291fd3667bc8be02067de6d6878295b880f Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt Date: Tue, 4 Apr 2023 15:26:40 -0700 Subject: [PATCH 1/8] Remove screenreader specific behavior of quick pick (#179193) ref https://github.com/microsoft/vscode/issues/176081 --- .../platform/quickinput/browser/quickInput.ts | 15 ++++----------- .../quickinput/browser/quickInputList.ts | 18 ++++++++++++++++++ 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/src/vs/platform/quickinput/browser/quickInput.ts b/src/vs/platform/quickinput/browser/quickInput.ts index 685dc0a1341..2763ae394f4 100644 --- a/src/vs/platform/quickinput/browser/quickInput.ts +++ b/src/vs/platform/quickinput/browser/quickInput.ts @@ -1054,10 +1054,6 @@ class QuickPick extends QuickInput implements IQuickPi this.ui.checkAll.checked = this.ui.list.getAllVisibleChecked(); this.ui.visibleCount.setCount(this.ui.list.getVisibleCount()); this.ui.count.setCount(this.ui.list.getCheckedCount()); - // Ensure no item is focused when using a screenreader when items update (#57501 & #166920 & #176848) - if (this.ui.isScreenReaderOptimized() && ariaLabel && visibilities.inputBox) { - this._itemActivation = ItemActivation.NONE; - } switch (this._itemActivation) { case ItemActivation.NONE: this._itemActivation = ItemActivation.FIRST; // only valid once, then unset @@ -1364,11 +1360,6 @@ export class QuickInputController extends Disposable { } }, 0); })); - this._register(list.onDidChangeFocus(() => { - if (this.comboboxAccessibility) { - this.getUI().inputBox.setAttribute('aria-activedescendant', this.getUI().list.getActiveDescendant() || ''); - } - })); const focusTracker = dom.trackFocus(container); this._register(focusTracker); @@ -1733,12 +1724,14 @@ export class QuickInputController extends Disposable { ui.inputBox.setAttribute('role', 'combobox'); ui.inputBox.setAttribute('aria-haspopup', 'true'); ui.inputBox.setAttribute('aria-autocomplete', 'list'); - ui.inputBox.setAttribute('aria-activedescendant', ui.list.getActiveDescendant() || ''); + ui.inputBox.setAttribute('aria-controls', ui.list.id); + ui.inputBox.setAttribute('aria-expanded', 'true'); } else { ui.inputBox.removeAttribute('role'); ui.inputBox.removeAttribute('aria-haspopup'); ui.inputBox.removeAttribute('aria-autocomplete'); - ui.inputBox.removeAttribute('aria-activedescendant'); + ui.inputBox.removeAttribute('aria-controls'); + ui.inputBox.removeAttribute('aria-expanded'); } } } diff --git a/src/vs/platform/quickinput/browser/quickInputList.ts b/src/vs/platform/quickinput/browser/quickInputList.ts index aac73b3a9a1..19bb8d2e36c 100644 --- a/src/vs/platform/quickinput/browser/quickInputList.ts +++ b/src/vs/platform/quickinput/browser/quickInputList.ts @@ -286,6 +286,7 @@ export class QuickInputList { readonly id: string; private container: HTMLElement; + private liveElement: HTMLElement; private list: List; private inputElements: Array = []; private elements: ListElement[] = []; @@ -325,6 +326,10 @@ export class QuickInputList { ) { this.id = id; this.container = dom.append(this.parent, $('.quick-input-list')); + this.liveElement = dom.append(this.container, $('div', { + 'aria-live': 'polite', + 'aria-relevant': 'additions' + })); const delegate = new ListElementDelegate(); const accessibilityProvider = new QuickInputAccessibilityProvider(); @@ -337,6 +342,19 @@ export class QuickInputList { } as IListOptions); this.list.getHTMLElement().id = id; this.disposables.push(this.list); + this.disposables.push(this.list.onDidChangeFocus(e => { + const item = $('div', { + 'aria-labelledby': this.getActiveDescendant() ?? '' + }); + if (this.liveElement.hasChildNodes()) { + dom.reset(this.liveElement, item); + } else { + // give NVDA time to register that the newly created live region - ref https://github.com/nvaccess/nvda/issues/8873 + setTimeout(() => { + dom.reset(this.liveElement, item); + }, 500); + } + })); this.disposables.push(this.list.onKeyDown(e => { const event = new StandardKeyboardEvent(e); switch (event.keyCode) { From 93026118c5c68f54220d5a39993a5e29a8df0029 Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Tue, 4 Apr 2023 15:27:13 -0700 Subject: [PATCH 2/8] Remove Node.js walkthrough/getting started guide (#176325) Remove Node.js walkthrough/getting started. Co-authored-by: Matt Bierner --- .../media/nodejsWalkthroughIcon.png | Bin 5904 -> 0 bytes .../typescript-language-features/package.json | 53 ----- .../src/experimentationService.ts | 4 +- .../src/extension.ts | 5 - .../src/ui/jsNodeWalkthrough.electron.ts | 198 ------------------ 5 files changed, 2 insertions(+), 258 deletions(-) delete mode 100644 extensions/typescript-language-features/media/nodejsWalkthroughIcon.png delete mode 100644 extensions/typescript-language-features/src/ui/jsNodeWalkthrough.electron.ts diff --git a/extensions/typescript-language-features/media/nodejsWalkthroughIcon.png b/extensions/typescript-language-features/media/nodejsWalkthroughIcon.png deleted file mode 100644 index 10a8e5b822b874e72869cac58f921e571d3435a1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5904 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 diff --git a/extensions/typescript-language-features/package.json b/extensions/typescript-language-features/package.json index 30ccbd8558f..9fef982d600 100644 --- a/extensions/typescript-language-features/package.json +++ b/extensions/typescript-language-features/package.json @@ -1562,59 +1562,6 @@ } ] } - ], - "walkthroughs": [ - { - "id": "nodejsWelcome", - "title": "%walkthroughs.nodejsWelcome.title%", - "icon": "media/nodejsWalkthroughIcon.png", - "description": "%walkthroughs.nodejsWelcome.description%", - "when": "false", - "steps": [ - { - "id": "walkthroughs.nodejsWelcome.downloadNode.forMacOrWindows", - "title": "%walkthroughs.nodejsWelcome.downloadNode.forMacOrWindows.title%", - "description": "%walkthroughs.nodejsWelcome.downloadNode.forMacOrWindows.description%", - "media": { - "svg": "resources/walkthroughs/install-node-js.svg" - }, - "when": "isWindows || isMac" - }, - { - "id": "walkthroughs.nodejsWelcome.downloadNode.forLinux", - "title": "%walkthroughs.nodejsWelcome.downloadNode.forLinux.title%", - "description": "%walkthroughs.nodejsWelcome.downloadNode.forLinux.description%", - "media": { - "svg": "resources/walkthroughs/install-node-js.svg" - }, - "when": "isLinux" - }, - { - "id": "walkthroughs.nodejsWelcome.makeJsFile", - "title": "%walkthroughs.nodejsWelcome.makeJsFile.title%", - "description": "%walkthroughs.nodejsWelcome.makeJsFile.description%", - "media": { - "svg": "resources/walkthroughs/create-a-js-file.svg" - } - }, - { - "id": "walkthroughs.nodejsWelcome.debugJsFile", - "title": "%walkthroughs.nodejsWelcome.debugJsFile.title%", - "description": "%walkthroughs.nodejsWelcome.debugJsFile.description%", - "media": { - "svg": "resources/walkthroughs/debug-and-run.svg" - } - }, - { - "id": "walkthroughs.nodejsWelcome.learnMoreAboutJs", - "title": "%walkthroughs.nodejsWelcome.learnMoreAboutJs.title%", - "description": "%walkthroughs.nodejsWelcome.learnMoreAboutJs.description%", - "media": { - "svg": "resources/walkthroughs/learn-more.svg" - } - } - ] - } ] }, "repository": { diff --git a/extensions/typescript-language-features/src/experimentationService.ts b/extensions/typescript-language-features/src/experimentationService.ts index 86d1fd42b55..5dc458277d4 100644 --- a/extensions/typescript-language-features/src/experimentationService.ts +++ b/extensions/typescript-language-features/src/experimentationService.ts @@ -18,7 +18,7 @@ export class ExperimentationService { constructor(telemetryReporter: IExperimentationTelemetryReporter, id: string, version: string, globalState: vscode.Memento) { this._telemetryReporter = telemetryReporter; - this._experimentationServicePromise = createExperimentationService(this._telemetryReporter, id, version, globalState); + this._experimentationServicePromise = createTasExperimentationService(this._telemetryReporter, id, version, globalState); } public async getTreatmentVariable(name: K, defaultValue: ExperimentTypes[K]): Promise { @@ -32,7 +32,7 @@ export class ExperimentationService { } } -export async function createExperimentationService( +export async function createTasExperimentationService( reporter: IExperimentationTelemetryReporter, id: string, version: string, diff --git a/extensions/typescript-language-features/src/extension.ts b/extensions/typescript-language-features/src/extension.ts index c92617f80a2..22fdd25bb71 100644 --- a/extensions/typescript-language-features/src/extension.ts +++ b/extensions/typescript-language-features/src/extension.ts @@ -17,7 +17,6 @@ import { NodeLogDirectoryProvider } from './tsServer/logDirectoryProvider.electr import { ElectronServiceProcessFactory } from './tsServer/serverProcess.electron'; import { DiskTypeScriptVersionProvider } from './tsServer/versionProvider.electron'; import { ActiveJsTsEditorTracker } from './ui/activeJsTsEditorTracker'; -import { JsWalkthroughState, registerJsNodeWalkthrough } from './ui/jsNodeWalkthrough.electron'; import { ElectronServiceConfigurationProvider } from './configuration/configuration.electron'; import { onCaseInsensitiveFileSystem } from './utils/fs.electron'; import { Logger } from './logging/logger'; @@ -43,9 +42,6 @@ export function activate( const activeJsTsEditorTracker = new ActiveJsTsEditorTracker(); context.subscriptions.push(activeJsTsEditorTracker); - const jsWalkthroughState = new JsWalkthroughState(); - context.subscriptions.push(jsWalkthroughState); - let experimentTelemetryReporter: IExperimentationTelemetryReporter | undefined; const packageInfo = getPackageInfo(context); if (packageInfo) { @@ -77,7 +73,6 @@ export function activate( }); registerBaseCommands(commandManager, lazyClientHost, pluginManager, activeJsTsEditorTracker); - registerJsNodeWalkthrough(commandManager, jsWalkthroughState); import('./task/taskProvider').then(module => { context.subscriptions.push(module.register(lazyClientHost.map(x => x.serviceClient))); diff --git a/extensions/typescript-language-features/src/ui/jsNodeWalkthrough.electron.ts b/extensions/typescript-language-features/src/ui/jsNodeWalkthrough.electron.ts deleted file mode 100644 index 1c9ceeb87ff..00000000000 --- a/extensions/typescript-language-features/src/ui/jsNodeWalkthrough.electron.ts +++ /dev/null @@ -1,198 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as cp from 'child_process'; -import * as vscode from 'vscode'; - -import { CommandManager } from '../commands/commandManager'; -import { Disposable } from '../utils/dispose'; - - -export async function nodeWasResolvable(): Promise { - let execStr: string; - switch (process.platform) { - case 'win32': - execStr = 'where node'; - break; - case 'aix': - case 'cygwin': - case 'darwin': - case 'freebsd': - case 'haiku': - case 'linux': - case 'netbsd': - case 'openbsd': - case 'sunos': - execStr = 'which node'; - break; - default: - return false; - } - - return new Promise(resolve => { - cp.exec(execStr, { windowsHide: true }, err => { - resolve(!err); - }); - }); -} - -export class JsWalkthroughState extends Disposable { - exampleJsDocument: vscode.TextDocument | undefined = undefined; - - override dispose() { - this.exampleJsDocument = undefined; - } -} - -export class CreateNewJSFileCommand { - public static readonly id = 'javascript-walkthrough.commands.createJsFile'; - public readonly id = CreateNewJSFileCommand.id; - - constructor( - private readonly walkthroughState: JsWalkthroughState - ) { } - - public execute() { - createNewJSFile(this.walkthroughState); - } -} - -export class DebugJsFileCommand { - public static readonly id = 'javascript-walkthrough.commands.debugJsFile'; - public readonly id = DebugJsFileCommand.id; - - constructor( - private readonly walkthroughState: JsWalkthroughState - ) { } - - public execute() { - debugJsFile(this.walkthroughState); - } -} - -export class NodeInstallationFoundCommand { - public static readonly id = 'javascript-walkthrough.commands.nodeInstallationFound'; - public readonly id = NodeInstallationFoundCommand.id; - public execute() { } -} - -async function createNewJSFile(walkthroughState: JsWalkthroughState) { - const newFile = await vscode.workspace.openTextDocument({ - language: 'javascript', - content: `// Write a message to the console.\nconsole.log('hello world!');\n`, - }); - walkthroughState.exampleJsDocument = newFile; - return vscode.window.showTextDocument(newFile, vscode.ViewColumn.Beside); -} - -async function debugJsFile(walkthroughState: JsWalkthroughState) { - const hasNode = await nodeWasResolvable(); - if (!hasNode) { - const reloadResponse = vscode.l10n.t("Reload VS Code"); - const debugAnywayResponse = vscode.l10n.t("Try Debugging Anyway"); - const dismissResponse = vscode.l10n.t("Dismiss"); - const response = await vscode.window.showErrorMessage( - // The message - vscode.l10n.t("We couldn\'t find Node.js on this computer. If you just installed it, you might need to reload VS Code."), - // The options - reloadResponse, - debugAnywayResponse, - dismissResponse, - ); - - if (response === undefined || response === dismissResponse) { - return; - } - if (response === reloadResponse) { - vscode.commands.executeCommand('workbench.action.reloadWindow'); - return; - } - } - tryDebugRelevantDocument(walkthroughState.exampleJsDocument, 'javascript', ['.mjs', '.js'], () => createNewJSFile(walkthroughState)); -} - -type DocSearchResult = - | { kind: 'visible'; editor: vscode.TextEditor } - | { kind: 'hidden'; uri: vscode.Uri } - | { kind: 'not-found' }; - -async function tryDebugRelevantDocument(lastDocument: vscode.TextDocument | undefined, languageId: string, languageExtensions: [string, ...string[]], createFileAndFocus: () => Promise): Promise { - let searchResult!: DocSearchResult; - for (const languageExtension of languageExtensions) { - searchResult = tryFindRelevantDocument(lastDocument, languageId, languageExtension); - if (searchResult.kind !== 'not-found') { - break; - } - } - - let editor: vscode.TextEditor; - // If not, make one. - switch (searchResult.kind) { - case 'visible': - // Focus if necessary. - editor = searchResult.editor; - if (vscode.window.activeTextEditor !== editor) { - await vscode.window.showTextDocument(editor.document, { - viewColumn: vscode.ViewColumn.Beside, - }); - } - break; - case 'hidden': - editor = await vscode.window.showTextDocument(searchResult.uri, { - viewColumn: vscode.ViewColumn.Beside, - }); - break; - case 'not-found': - editor = await createFileAndFocus(); - break; - } - - await Promise.all([ - vscode.commands.executeCommand('workbench.action.debug.start'), - vscode.commands.executeCommand('workbench.debug.action.focusRepl'), - ]); - -} - -/** Tries to find a relevant {@link vscode.TextEditor} or a {@link vscode.Uri} for an open document */ -function tryFindRelevantDocument(lastDocument: vscode.TextDocument | undefined, languageId: string, languageExtension: string): DocSearchResult { - let editor: vscode.TextEditor | undefined; - - // Try to find the document created from the last step. - if (lastDocument) { - editor = vscode.window.visibleTextEditors.find(editor => editor.document === lastDocument); - } - - // If we couldn't find that, find a visible document with the desired language. - editor ??= vscode.window.visibleTextEditors.find(editor => editor.document.languageId === languageId); - if (editor) { - return { - kind: 'visible', - editor, - }; - } - - // If we still couldn't find that, find a possibly not-visible document. - for (const tabGroup of vscode.window.tabGroups.all) { - for (const tab of tabGroup.tabs) { - if (tab.input instanceof vscode.TabInputText && tab.input.uri.path.endsWith(languageExtension)) { - return { - kind: 'hidden', - uri: tab.input.uri, - }; - } - } - } - - return { kind: 'not-found' }; -} - -export function registerJsNodeWalkthrough( - commandManager: CommandManager, - jsWalkthroughState: JsWalkthroughState, -) { - commandManager.register(new CreateNewJSFileCommand(jsWalkthroughState)); - commandManager.register(new DebugJsFileCommand(jsWalkthroughState)); -} From 6e5727e12576b19801611168f7e6dfeb81775401 Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Tue, 4 Apr 2023 16:19:05 -0700 Subject: [PATCH 3/8] Update jsdoc for onWillSaveNotebookDocument (#179179) --- .../extHostNotebookDocumentSaveParticipant.ts | 2 +- ...ode.proposed.notebookDocumentWillSave.d.ts | 48 +++++++++++++++++-- 2 files changed, 45 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/api/common/extHostNotebookDocumentSaveParticipant.ts b/src/vs/workbench/api/common/extHostNotebookDocumentSaveParticipant.ts index e9547b63069..1e6a706a305 100644 --- a/src/vs/workbench/api/common/extHostNotebookDocumentSaveParticipant.ts +++ b/src/vs/workbench/api/common/extHostNotebookDocumentSaveParticipant.ts @@ -53,7 +53,7 @@ export class ExtHostNotebookDocumentSaveParticipant implements ExtHostNotebookDo const edits: WorkspaceEdit[] = []; - await this._onWillSaveNotebookDocumentEvent.fireAsync({ document: document.apiNotebook, reason: TextDocumentSaveReason.to(reason) }, token, async (thenable: Promise, listener) => { + await this._onWillSaveNotebookDocumentEvent.fireAsync({ notebook: document.apiNotebook, reason: TextDocumentSaveReason.to(reason) }, token, async (thenable: Promise, listener) => { const now = Date.now(); const data = await await Promise.resolve(thenable); if (Date.now() - now > this._thresholds.timeout) { diff --git a/src/vscode-dts/vscode.proposed.notebookDocumentWillSave.d.ts b/src/vscode-dts/vscode.proposed.notebookDocumentWillSave.d.ts index e751bbcf669..03281761e1d 100644 --- a/src/vscode-dts/vscode.proposed.notebookDocumentWillSave.d.ts +++ b/src/vscode-dts/vscode.proposed.notebookDocumentWillSave.d.ts @@ -5,11 +5,11 @@ declare module 'vscode' { /** - * An event that is fired when a {@link NotebookDocument document} will be saved. + * An event that is fired when a {@link NotebookDocument notebook document} will be saved. * * To make modifications to the document before it is being saved, call the * {@linkcode NotebookDocumentWillSaveEvent.waitUntil waitUntil}-function with a thenable - * that resolves to an array of {@link TextEdit text edits}. + * that resolves to a {@link WorkspaceEdit workspace edit}. */ export interface NotebookDocumentWillSaveEvent { /** @@ -18,22 +18,62 @@ declare module 'vscode' { readonly token: CancellationToken; /** - * The document that will be saved. + * The {@link NotebookDocument notebook document} that will be saved. */ - readonly document: NotebookDocument; + readonly notebook: NotebookDocument; /** * The reason why save was triggered. */ readonly reason: TextDocumentSaveReason; + /** + * Allows to pause the event loop and to apply {@link WorkspaceEdit workspace edit}. + * Edits of subsequent calls to this function will be applied in order. The + * edits will be *ignored* if concurrent modifications of the notebook document happened. + * + * *Note:* This function can only be called during event dispatch and not + * in an asynchronous manner: + * + * ```ts + * workspace.onWillSaveNotebookDocument(event => { + * // async, will *throw* an error + * setTimeout(() => event.waitUntil(promise)); + * + * // sync, OK + * event.waitUntil(promise); + * }) + * ``` + * + * @param thenable A thenable that resolves to {@link WorkspaceEdit workspace edit}. + */ waitUntil(thenable: Thenable): void; + /** + * Allows to pause the event loop until the provided thenable resolved. + * + * *Note:* This function can only be called during event dispatch. + * + * @param thenable A thenable that delays saving. + */ waitUntil(thenable: Thenable): void; } export namespace workspace { + /** + * An event that is emitted when a {@link NotebookDocument notebook document} will be saved to disk. + * + * *Note 1:* Subscribers can delay saving by registering asynchronous work. For the sake of data integrity the editor + * might save without firing this event. For instance when shutting down with dirty files. + * + * *Note 2:* Subscribers are called sequentially and they can {@link NotebookDocumentWillSaveEvent.waitUntil delay} saving + * by registering asynchronous work. Protection against misbehaving listeners is implemented as such: + * * there is an overall time budget that all listeners share and if that is exhausted no further listener is called + * * listeners that take a long time or produce errors frequently will not be called anymore + * + * The current thresholds are 1.5 seconds as overall time budget and a listener can misbehave 3 times before being ignored. + */ export const onWillSaveNotebookDocument: Event; } } From 6b0d9d79e1178e341b8f27dba4cc0f7b8cd45883 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Tue, 4 Apr 2023 16:53:46 -0700 Subject: [PATCH 4/8] build: properly install built-in extensions for tests (#179216) Uses a cache and GH token to avoid hitting rate limits. Fixes #179181 --- build/azure-pipelines/linux/product-build-linux-test.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/build/azure-pipelines/linux/product-build-linux-test.yml b/build/azure-pipelines/linux/product-build-linux-test.yml index a5a30a340fc..b5fa333311f 100644 --- a/build/azure-pipelines/linux/product-build-linux-test.yml +++ b/build/azure-pipelines/linux/product-build-linux-test.yml @@ -38,6 +38,8 @@ steps: stat $ELECTRON_ROOT/chrome-sandbox displayName: Change setuid helper binary permission + - template: ../common/install-builtin-extensions.yml + - ${{ if eq(parameters.VSCODE_RUN_UNIT_TESTS, true) }}: - ${{ if eq(parameters.VSCODE_QUALITY, 'oss') }}: - script: ./scripts/test.sh --tfs "Unit Tests" From eb5da3af195a361dab510d4d41c4ea43c6865a69 Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Tue, 4 Apr 2023 19:01:32 -0700 Subject: [PATCH 5/8] Find in outputs, ignore hidden cells. (#179189) --- .../contrib/notebook/browser/notebookEditorWidget.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index 422ec20e745..bb112d718b1 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -2416,9 +2416,9 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD } } - if (includeOutput) { - for (let i = 0; i < this.getLength(); i++) { - const cell = this.cellAt(i); + if (includeOutput && this._list) { + for (let i = 0; i < this._list.length; i++) { + const cell = this._list.element(i); if (cell?.cellKind === CellKind.Code) { requests.push(this._warmupCell((cell as CodeCellViewModel))); From f7d1b7c8fb4b1937d315eb245ee0a887a16e5c82 Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Tue, 4 Apr 2023 19:01:53 -0700 Subject: [PATCH 6/8] Finalize onWillSaveNotebookDocument (#179188) --- .../workbench/api/common/extHost.api.impl.ts | 1 - .../common/extensionsApiProposals.ts | 1 - src/vscode-dts/vscode.d.ts | 70 ++++++++++++++++ ...ode.proposed.notebookDocumentWillSave.d.ts | 79 ------------------- 4 files changed, 70 insertions(+), 81 deletions(-) delete mode 100644 src/vscode-dts/vscode.proposed.notebookDocumentWillSave.d.ts diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 16edcbb52f0..fd74bfba4e3 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -957,7 +957,6 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I return extHostNotebookDocuments.onDidChangeNotebookDocument(listener, thisArg, disposables); }, onWillSaveNotebookDocument(listener, thisArg, disposables) { - checkProposedApiEnabled(extension, 'notebookDocumentWillSave'); return extHostNotebookDocumentSaveParticipant.getOnWillSaveNotebookDocumentEvent(extension)(listener, thisArg, disposables); }, get onDidOpenNotebookDocument(): Event { diff --git a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts index 6d3ac59883e..5860ada41b2 100644 --- a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts +++ b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts @@ -48,7 +48,6 @@ export const allApiProposals = Object.freeze({ notebookCellExecutionState: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookCellExecutionState.d.ts', notebookControllerAffinityHidden: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookControllerAffinityHidden.d.ts', notebookDeprecated: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookDeprecated.d.ts', - notebookDocumentWillSave: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookDocumentWillSave.d.ts', notebookKernelSource: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookKernelSource.d.ts', notebookLiveShare: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookLiveShare.d.ts', notebookMessaging: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookMessaging.d.ts', diff --git a/src/vscode-dts/vscode.d.ts b/src/vscode-dts/vscode.d.ts index 115daed94aa..6f8088e8aba 100644 --- a/src/vscode-dts/vscode.d.ts +++ b/src/vscode-dts/vscode.d.ts @@ -12489,6 +12489,21 @@ declare module 'vscode' { */ export const onDidChangeNotebookDocument: Event; + /** + * An event that is emitted when a {@link NotebookDocument notebook document} will be saved to disk. + * + * *Note 1:* Subscribers can delay saving by registering asynchronous work. For the sake of data integrity the editor + * might save without firing this event. For instance when shutting down with dirty files. + * + * *Note 2:* Subscribers are called sequentially and they can {@link NotebookDocumentWillSaveEvent.waitUntil delay} saving + * by registering asynchronous work. Protection against misbehaving listeners is implemented as such: + * * there is an overall time budget that all listeners share and if that is exhausted no further listener is called + * * listeners that take a long time or produce errors frequently will not be called anymore + * + * The current thresholds are 1.5 seconds as overall time budget and a listener can misbehave 3 times before being ignored. + */ + export const onWillSaveNotebookDocument: Event; + /** * An event that is emitted when a {@link NotebookDocument notebook} is saved. */ @@ -13558,6 +13573,61 @@ declare module 'vscode' { readonly cellChanges: readonly NotebookDocumentCellChange[]; } + /** + * An event that is fired when a {@link NotebookDocument notebook document} will be saved. + * + * To make modifications to the document before it is being saved, call the + * {@linkcode NotebookDocumentWillSaveEvent.waitUntil waitUntil}-function with a thenable + * that resolves to a {@link WorkspaceEdit workspace edit}. + */ + export interface NotebookDocumentWillSaveEvent { + /** + * A cancellation token. + */ + readonly token: CancellationToken; + + /** + * The {@link NotebookDocument notebook document} that will be saved. + */ + readonly notebook: NotebookDocument; + + /** + * The reason why save was triggered. + */ + readonly reason: TextDocumentSaveReason; + + /** + * Allows to pause the event loop and to apply {@link WorkspaceEdit workspace edit}. + * Edits of subsequent calls to this function will be applied in order. The + * edits will be *ignored* if concurrent modifications of the notebook document happened. + * + * *Note:* This function can only be called during event dispatch and not + * in an asynchronous manner: + * + * ```ts + * workspace.onWillSaveNotebookDocument(event => { + * // async, will *throw* an error + * setTimeout(() => event.waitUntil(promise)); + * + * // sync, OK + * event.waitUntil(promise); + * }) + * ``` + * + * @param thenable A thenable that resolves to {@link WorkspaceEdit workspace edit}. + */ + waitUntil(thenable: Thenable): void; + + /** + * Allows to pause the event loop until the provided thenable resolved. + * + * *Note:* This function can only be called during event dispatch. + * + * @param thenable A thenable that delays saving. + */ + waitUntil(thenable: Thenable): void; + } + /** * The summary of a notebook cell execution. */ diff --git a/src/vscode-dts/vscode.proposed.notebookDocumentWillSave.d.ts b/src/vscode-dts/vscode.proposed.notebookDocumentWillSave.d.ts deleted file mode 100644 index 03281761e1d..00000000000 --- a/src/vscode-dts/vscode.proposed.notebookDocumentWillSave.d.ts +++ /dev/null @@ -1,79 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -declare module 'vscode' { - /** - * An event that is fired when a {@link NotebookDocument notebook document} will be saved. - * - * To make modifications to the document before it is being saved, call the - * {@linkcode NotebookDocumentWillSaveEvent.waitUntil waitUntil}-function with a thenable - * that resolves to a {@link WorkspaceEdit workspace edit}. - */ - export interface NotebookDocumentWillSaveEvent { - /** - * A cancellation token. - */ - readonly token: CancellationToken; - - /** - * The {@link NotebookDocument notebook document} that will be saved. - */ - readonly notebook: NotebookDocument; - - /** - * The reason why save was triggered. - */ - readonly reason: TextDocumentSaveReason; - - /** - * Allows to pause the event loop and to apply {@link WorkspaceEdit workspace edit}. - * Edits of subsequent calls to this function will be applied in order. The - * edits will be *ignored* if concurrent modifications of the notebook document happened. - * - * *Note:* This function can only be called during event dispatch and not - * in an asynchronous manner: - * - * ```ts - * workspace.onWillSaveNotebookDocument(event => { - * // async, will *throw* an error - * setTimeout(() => event.waitUntil(promise)); - * - * // sync, OK - * event.waitUntil(promise); - * }) - * ``` - * - * @param thenable A thenable that resolves to {@link WorkspaceEdit workspace edit}. - */ - waitUntil(thenable: Thenable): void; - - /** - * Allows to pause the event loop until the provided thenable resolved. - * - * *Note:* This function can only be called during event dispatch. - * - * @param thenable A thenable that delays saving. - */ - waitUntil(thenable: Thenable): void; - } - - export namespace workspace { - - /** - * An event that is emitted when a {@link NotebookDocument notebook document} will be saved to disk. - * - * *Note 1:* Subscribers can delay saving by registering asynchronous work. For the sake of data integrity the editor - * might save without firing this event. For instance when shutting down with dirty files. - * - * *Note 2:* Subscribers are called sequentially and they can {@link NotebookDocumentWillSaveEvent.waitUntil delay} saving - * by registering asynchronous work. Protection against misbehaving listeners is implemented as such: - * * there is an overall time budget that all listeners share and if that is exhausted no further listener is called - * * listeners that take a long time or produce errors frequently will not be called anymore - * - * The current thresholds are 1.5 seconds as overall time budget and a listener can misbehave 3 times before being ignored. - */ - export const onWillSaveNotebookDocument: Event; - } -} From 4c9cfa0f0b57544ca87f22a1c291a8060387a766 Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Tue, 4 Apr 2023 19:02:14 -0700 Subject: [PATCH 7/8] Reload webview on trust state change, no cache for output data. (#179075) * Reload webview on trust state change, no cache for output data. * Avoid dup reload --- .../view/renderers/backLayerWebView.ts | 7 ++- .../browser/view/renderers/webviewMessages.ts | 5 -- .../browser/view/renderers/webviewPreloads.ts | 50 ++----------------- 3 files changed, 6 insertions(+), 56 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts index ed02a196a8b..940fb0ebb10 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts @@ -199,10 +199,9 @@ export class BackLayerWebView extends Themable { } this._register(workspaceTrustManagementService.onDidChangeTrust(e => { - this._sendMessageToWebview({ - type: 'updateWorkspaceTrust', - isTrusted: e, - }); + const baseUrl = this.asWebviewUri(this.getNotebookBaseUri(), undefined); + const htmlContent = this.generateContent(baseUrl.toString()); + this.webview?.setHtml(htmlContent); })); this._register(TokenizationRegistry.onDidChange(() => { diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewMessages.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewMessages.ts index 405ab9162ec..8f91b711469 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewMessages.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewMessages.ts @@ -378,10 +378,6 @@ export interface INotebookOptionsMessage { readonly renderOptions: RenderOptions; } -export interface INotebookUpdateWorkspaceTrust { - readonly type: 'updateWorkspaceTrust'; - readonly isTrusted: boolean; -} export interface ITokenizedCodeBlockMessage { readonly type: 'tokenizedCodeBlock'; readonly codeBlockId: string; @@ -529,7 +525,6 @@ export type ToWebviewMessage = IClearMessage | IInitializeMarkupCells | INotebookStylesMessage | INotebookOptionsMessage | - INotebookUpdateWorkspaceTrust | ITokenizedCodeBlockMessage | ITokenizedStylesChangedMessage | IFindMessage | diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts index eede3d34ff9..c188aa78327 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts @@ -90,7 +90,7 @@ async function webviewPreloads(ctx: PreloadContext) { const textDecoder = new TextDecoder(); let currentOptions = ctx.options; - let isWorkspaceTrusted = ctx.isWorkspaceTrusted; + const isWorkspaceTrusted = ctx.isWorkspaceTrusted; let currentRenderOptions = ctx.renderOptions; const settingChange: EmitterLike = createEmitter(); @@ -1397,11 +1397,6 @@ async function webviewPreloads(ctx: PreloadContext) { currentRenderOptions = event.data.renderOptions; settingChange.fire(currentRenderOptions); break; - case 'updateWorkspaceTrust': { - isWorkspaceTrusted = event.data.isTrusted; - viewModel.rerender(); - break; - } case 'tokenizedCodeBlock': { const { codeBlockId, html } = event.data; MarkdownCodeBlock.highlightCodeBlock(codeBlockId, html); @@ -1870,11 +1865,6 @@ async function webviewPreloads(ctx: PreloadContext) { this._outputCells.clear(); } - public rerender() { - this.rerenderMarkupCells(); - this.renderOutputCells(); - } - private async createMarkupCell(init: webviewMessages.IMarkupCellInitialization, top: number, visible: boolean): Promise { const existing = this._markupCells.get(init.cellId); if (existing) { @@ -1929,12 +1919,6 @@ async function webviewPreloads(ctx: PreloadContext) { cell?.unhide(); } - private rerenderMarkupCells() { - for (const cell of this._markupCells.values()) { - cell.rerender(); - } - } - private getExpectedMarkupCell(id: string): MarkupCell | undefined { const cell = this._markupCells.get(id); if (!cell) { @@ -1966,12 +1950,6 @@ async function webviewPreloads(ctx: PreloadContext) { } } - private renderOutputCells() { - for (const outputCell of this._outputCells.values()) { - outputCell.rerender(); - } - } - public async renderOutputCell(data: webviewMessages.ICreationRequestMessage, signal: AbortSignal): Promise { const preloadErrors = await Promise.all( data.requiredPreloads.map(p => kernelPreloads.waitFor(p.uri).then(() => undefined, err => err)) @@ -2266,10 +2244,6 @@ async function webviewPreloads(ctx: PreloadContext) { this.updateMarkupDimensions(); } - public rerender() { - this.updateContentAndRender(this._content.value, this._content.metadata); - } - public remove() { this.element.remove(); } @@ -2378,12 +2352,6 @@ async function webviewPreloads(ctx: PreloadContext) { this.outputElements.get(outputId)?.updateContentAndRender(content); } - public rerender() { - for (const outputElement of this.outputElements.values()) { - outputElement.rerender(); - } - } - public updateOutputHeight(outputId: string, height: number) { this.outputElements.get(outputId)?.updateHeight(height); } @@ -2458,10 +2426,6 @@ async function webviewPreloads(ctx: PreloadContext) { return this._outputNode; } - public rerender() { - this._outputNode?.rerender(); - } - public updateContentAndRender(content: webviewMessages.ICreationContent) { this._outputNode?.updateAndRerender(content); } @@ -2490,7 +2454,6 @@ async function webviewPreloads(ctx: PreloadContext) { class OutputElement { public readonly element: HTMLElement; private _content?: { - readonly content: webviewMessages.ICreationContent; readonly preferredRendererId: string | undefined; readonly preloadErrors: ReadonlyArray; }; @@ -2528,7 +2491,7 @@ async function webviewPreloads(ctx: PreloadContext) { this.renderTaskAbort?.abort(); this.renderTaskAbort = undefined; - this._content = { content, preferredRendererId, preloadErrors }; + this._content = { preferredRendererId, preloadErrors }; if (content.type === 0 /* RenderOutputType.Html */) { const trustedHtml = ttPolicy?.createHTML(content.htmlContent) ?? content.htmlContent; this.element.innerHTML = trustedHtml as string; @@ -2586,16 +2549,9 @@ async function webviewPreloads(ctx: PreloadContext) { } } - public rerender() { - if (this._content) { - this.render(this._content.content, this._content.preferredRendererId, this._content.preloadErrors); - } - } - public updateAndRerender(content: webviewMessages.ICreationContent) { if (this._content) { - this._content = { content, preferredRendererId: this._content.preferredRendererId, preloadErrors: this._content.preloadErrors }; - this.rerender(); + this.render(content, this._content.preferredRendererId, this._content.preloadErrors); } } } From 40662a983e648f6e69a2a9292020082691f2ce06 Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt Date: Tue, 4 Apr 2023 19:11:47 -0700 Subject: [PATCH 8/8] Remove usage of A11yService & go back to aria-activedescendant (#179217) * Use alert instead of aria-live and remove usage of A11yService Follow up of https://github.com/microsoft/vscode/pull/179193 * use aria-activedescendant and set aria-label of list to be the placeholder --- .../quickInput/standaloneQuickInputService.ts | 4 +- .../platform/quickinput/browser/quickInput.ts | 42 ++++--------------- .../quickinput/browser/quickInputBox.ts | 13 +++--- .../quickinput/browser/quickInputList.ts | 27 ++++-------- .../quickinput/browser/quickInputService.ts | 3 -- .../test/browser/quickinput.test.ts | 1 - .../quickinput/browser/quickInputService.ts | 4 +- .../test/browser/workbenchTestServices.ts | 2 +- 8 files changed, 25 insertions(+), 71 deletions(-) diff --git a/src/vs/editor/standalone/browser/quickInput/standaloneQuickInputService.ts b/src/vs/editor/standalone/browser/quickInput/standaloneQuickInputService.ts index 7c3aeb8684e..4aa2558480f 100644 --- a/src/vs/editor/standalone/browser/quickInput/standaloneQuickInputService.ts +++ b/src/vs/editor/standalone/browser/quickInput/standaloneQuickInputService.ts @@ -12,7 +12,6 @@ import { IQuickInputService, IQuickInputButton, IQuickPickItem, IQuickPick, IInp import { CancellationToken } from 'vs/base/common/cancellation'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { EditorScopedLayoutService } from 'vs/editor/standalone/browser/standaloneLayoutService'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { IQuickInputControllerHost, QuickInputController } from 'vs/platform/quickinput/browser/quickInput'; @@ -29,10 +28,9 @@ class EditorScopedQuickInputService extends QuickInputService { @IInstantiationService instantiationService: IInstantiationService, @IContextKeyService contextKeyService: IContextKeyService, @IThemeService themeService: IThemeService, - @IAccessibilityService accessibilityService: IAccessibilityService, @ICodeEditorService codeEditorService: ICodeEditorService ) { - super(instantiationService, contextKeyService, themeService, accessibilityService, new EditorScopedLayoutService(editor.getContainerDomNode(), codeEditorService)); + super(instantiationService, contextKeyService, themeService, new EditorScopedLayoutService(editor.getContainerDomNode(), codeEditorService)); // Use the passed in code editor as host for the quick input widget const contribution = QuickInputEditorContribution.get(editor); diff --git a/src/vs/platform/quickinput/browser/quickInput.ts b/src/vs/platform/quickinput/browser/quickInput.ts index 2763ae394f4..836844272bc 100644 --- a/src/vs/platform/quickinput/browser/quickInput.ts +++ b/src/vs/platform/quickinput/browser/quickInput.ts @@ -40,7 +40,6 @@ export interface IQuickInputOptions { idPrefix: string; container: HTMLElement; ignoreFocusOut(): boolean; - isScreenReaderOptimized(): boolean; backKeybindingLabel(): string | undefined; setContextKey(id?: string): void; linkOpenerDelegate(content: string): void; @@ -114,10 +113,8 @@ interface QuickInputUI { onDidTriggerButton: Event; ignoreFocusOut: boolean; keyMods: Writeable; - isScreenReaderOptimized(): boolean; show(controller: QuickInput): void; setVisibilities(visibilities: Visibilities): void; - setComboboxAccessibility(enabled: boolean): void; setEnabled(enabled: boolean): void; setContextKey(contextKey?: string): void; linkOpenerDelegate(content: string): void; @@ -928,7 +925,7 @@ class QuickPick extends QuickInput implements IQuickPi this.visibleDisposables.add(this.registerQuickNavigation()); this.valueSelectionUpdated = true; } - super.show(); // TODO: Why have show() bubble up while update() trickles down? (Could move setComboboxAccessibility() here.) + super.show(); // TODO: Why have show() bubble up while update() trickles down? } private handleAccept(inBackground: boolean): void { @@ -1039,8 +1036,8 @@ class QuickPick extends QuickInput implements IQuickPi ariaLabel += ` - ${this.title}`; } } - if (this.ui.inputBox.ariaLabel !== ariaLabel) { - this.ui.inputBox.ariaLabel = ariaLabel; + if (this.ui.list.ariaLabel !== ariaLabel) { + this.ui.list.ariaLabel = ariaLabel; } this.ui.list.matchOnDescription = this.matchOnDescription; this.ui.list.matchOnDetail = this.matchOnDetail; @@ -1100,7 +1097,6 @@ class QuickPick extends QuickInput implements IQuickPi } this.ui.customButton.label = this.customLabel || ''; this.ui.customButton.element.title = this.customHover || ''; - this.ui.setComboboxAccessibility(true); if (!visibilities.inputBox) { // we need to move focus into the tree to detect keybindings // properly when the input box is not visible (quick nav) @@ -1231,7 +1227,6 @@ export class QuickInputController extends Disposable { private ui: QuickInputUI | undefined; private dimension?: dom.IDimension; private titleBarOffset?: number; - private comboboxAccessibility = false; private enabled = true; private readonly onDidAcceptEmitter = this._register(new Emitter()); private readonly onDidCustomEmitter = this._register(new Emitter()); @@ -1341,7 +1336,12 @@ export class QuickInputController extends Disposable { const progressBar = new ProgressBar(container, this.styles.progressBar); progressBar.getContainer().classList.add('quick-input-progress'); - const list = this._register(new QuickInputList(container, this.idPrefix + 'list', this.options)); + const listId = this.idPrefix + 'list'; + const list = this._register(new QuickInputList(container, listId, this.options)); + inputBox.setAttribute('aria-controls', listId); + this._register(list.onDidChangeFocus(() => { + inputBox.setAttribute('aria-activedescendant', list.getActiveDescendant() ?? ''); + })); this._register(list.onChangedAllVisibleChecked(checked => { checkAll.checked = checked; })); @@ -1450,11 +1450,9 @@ export class QuickInputController extends Disposable { onDidTriggerButton: this.onDidTriggerButtonEmitter.event, ignoreFocusOut: false, keyMods: this.keyMods, - isScreenReaderOptimized: () => this.options.isScreenReaderOptimized(), show: controller => this.show(controller), hide: () => this.hide(), setVisibilities: visibilities => this.setVisibilities(visibilities), - setComboboxAccessibility: enabled => this.setComboboxAccessibility(enabled), setEnabled: enabled => this.setEnabled(enabled), setContextKey: contextKey => this.options.setContextKey(contextKey), linkOpenerDelegate: content => this.options.linkOpenerDelegate(content) @@ -1685,8 +1683,6 @@ export class QuickInputController extends Disposable { ui.list.matchOnLabel = true; ui.list.sortByLabel = true; ui.ignoreFocusOut = false; - this.setComboboxAccessibility(false); - ui.inputBox.ariaLabel = ''; ui.inputBox.toggles = undefined; const backKeybindingLabel = this.options.backKeybindingLabel(); @@ -1716,26 +1712,6 @@ export class QuickInputController extends Disposable { this.updateLayout(); // TODO } - private setComboboxAccessibility(enabled: boolean) { - if (enabled !== this.comboboxAccessibility) { - const ui = this.getUI(); - this.comboboxAccessibility = enabled; - if (this.comboboxAccessibility) { - ui.inputBox.setAttribute('role', 'combobox'); - ui.inputBox.setAttribute('aria-haspopup', 'true'); - ui.inputBox.setAttribute('aria-autocomplete', 'list'); - ui.inputBox.setAttribute('aria-controls', ui.list.id); - ui.inputBox.setAttribute('aria-expanded', 'true'); - } else { - ui.inputBox.removeAttribute('role'); - ui.inputBox.removeAttribute('aria-haspopup'); - ui.inputBox.removeAttribute('aria-autocomplete'); - ui.inputBox.removeAttribute('aria-controls'); - ui.inputBox.removeAttribute('aria-expanded'); - } - } - } - private setEnabled(enabled: boolean) { if (enabled !== this.enabled) { this.enabled = enabled; diff --git a/src/vs/platform/quickinput/browser/quickInputBox.ts b/src/vs/platform/quickinput/browser/quickInputBox.ts index d85d88f212c..cebb5ce460d 100644 --- a/src/vs/platform/quickinput/browser/quickInputBox.ts +++ b/src/vs/platform/quickinput/browser/quickInputBox.ts @@ -28,6 +28,11 @@ export class QuickInputBox extends Disposable { super(); this.container = dom.append(this.parent, $('.quick-input-box')); this.findInput = this._register(new FindInput(this.container, undefined, { label: '', inputBoxStyles, toggleStyles })); + const input = this.findInput.inputBox.inputElement; + input.role = 'combobox'; + input.ariaHasPopup = 'menu'; + input.ariaAutoComplete = 'list'; + input.ariaExpanded = 'true'; } onKeyDown = (handler: (event: StandardKeyboardEvent) => void): IDisposable => { @@ -74,14 +79,6 @@ export class QuickInputBox extends Disposable { this.findInput.inputBox.setPlaceHolder(placeholder); } - get ariaLabel() { - return this.findInput.inputBox.getAriaLabel(); - } - - set ariaLabel(ariaLabel: string) { - this.findInput.inputBox.setAriaLabel(ariaLabel); - } - get password() { return this.findInput.inputBox.inputElement.type === 'password'; } diff --git a/src/vs/platform/quickinput/browser/quickInputList.ts b/src/vs/platform/quickinput/browser/quickInputList.ts index 19bb8d2e36c..ca447687db6 100644 --- a/src/vs/platform/quickinput/browser/quickInputList.ts +++ b/src/vs/platform/quickinput/browser/quickInputList.ts @@ -286,7 +286,6 @@ export class QuickInputList { readonly id: string; private container: HTMLElement; - private liveElement: HTMLElement; private list: List; private inputElements: Array = []; private elements: ListElement[] = []; @@ -326,11 +325,6 @@ export class QuickInputList { ) { this.id = id; this.container = dom.append(this.parent, $('.quick-input-list')); - this.liveElement = dom.append(this.container, $('div', { - 'aria-live': 'polite', - 'aria-relevant': 'additions' - })); - const delegate = new ListElementDelegate(); const accessibilityProvider = new QuickInputAccessibilityProvider(); this.list = options.createList('QuickInput', this.container, delegate, [new ListElementRenderer()], { @@ -342,19 +336,6 @@ export class QuickInputList { } as IListOptions); this.list.getHTMLElement().id = id; this.disposables.push(this.list); - this.disposables.push(this.list.onDidChangeFocus(e => { - const item = $('div', { - 'aria-labelledby': this.getActiveDescendant() ?? '' - }); - if (this.liveElement.hasChildNodes()) { - dom.reset(this.liveElement, item); - } else { - // give NVDA time to register that the newly created live region - ref https://github.com/nvaccess/nvda/issues/8873 - setTimeout(() => { - dom.reset(this.liveElement, item); - }, 500); - } - })); this.disposables.push(this.list.onKeyDown(e => { const event = new StandardKeyboardEvent(e); switch (event.keyCode) { @@ -482,6 +463,14 @@ export class QuickInputList { this.list.scrollTop = scrollTop; } + get ariaLabel() { + return this.list.getHTMLElement().ariaLabel; + } + + set ariaLabel(label: string | null) { + this.list.getHTMLElement().ariaLabel = label; + } + getAllVisibleChecked() { return this.allVisibleChecked(this.elements, false); } diff --git a/src/vs/platform/quickinput/browser/quickInputService.ts b/src/vs/platform/quickinput/browser/quickInputService.ts index 5e7473475d7..4d43602acb6 100644 --- a/src/vs/platform/quickinput/browser/quickInputService.ts +++ b/src/vs/platform/quickinput/browser/quickInputService.ts @@ -7,7 +7,6 @@ import { IListRenderer, IListVirtualDelegate } from 'vs/base/browser/ui/list/lis import { List } from 'vs/base/browser/ui/list/listWidget'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Emitter } from 'vs/base/common/event'; -import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; @@ -60,7 +59,6 @@ export class QuickInputService extends Themable implements IQuickInputService { @IInstantiationService private readonly instantiationService: IInstantiationService, @IContextKeyService protected readonly contextKeyService: IContextKeyService, @IThemeService themeService: IThemeService, - @IAccessibilityService private readonly accessibilityService: IAccessibilityService, @ILayoutService protected readonly layoutService: ILayoutService ) { super(themeService); @@ -71,7 +69,6 @@ export class QuickInputService extends Themable implements IQuickInputService { idPrefix: 'quickInput_', container: host.container, ignoreFocusOut: () => false, - isScreenReaderOptimized: () => this.accessibilityService.isScreenReaderOptimized(), backKeybindingLabel: () => undefined, setContextKey: (id?: string) => this.setContextKey(id), linkOpenerDelegate: (content) => { diff --git a/src/vs/platform/quickinput/test/browser/quickinput.test.ts b/src/vs/platform/quickinput/test/browser/quickinput.test.ts index 34a0547388b..48ae15c3a1e 100644 --- a/src/vs/platform/quickinput/test/browser/quickinput.test.ts +++ b/src/vs/platform/quickinput/test/browser/quickinput.test.ts @@ -46,7 +46,6 @@ suite('QuickInput', () => { // https://github.com/microsoft/vscode/issues/147543 container: fixture, idPrefix: 'testQuickInput', ignoreFocusOut() { return true; }, - isScreenReaderOptimized() { return false; }, returnFocus() { }, backKeybindingLabel() { return undefined; }, setContextKey() { return undefined; }, diff --git a/src/vs/workbench/services/quickinput/browser/quickInputService.ts b/src/vs/workbench/services/quickinput/browser/quickInputService.ts index 4e511787aa4..e47d590694e 100644 --- a/src/vs/workbench/services/quickinput/browser/quickInputService.ts +++ b/src/vs/workbench/services/quickinput/browser/quickInputService.ts @@ -9,7 +9,6 @@ import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { QuickInputController } from 'vs/platform/quickinput/browser/quickInput'; import { QuickInputService as BaseQuickInputService } from 'vs/platform/quickinput/browser/quickInputService'; import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; @@ -29,11 +28,10 @@ export class QuickInputService extends BaseQuickInputService { @IKeybindingService private readonly keybindingService: IKeybindingService, @IContextKeyService contextKeyService: IContextKeyService, @IThemeService themeService: IThemeService, - @IAccessibilityService accessibilityService: IAccessibilityService, @ILayoutService layoutService: ILayoutService, @IHoverService private readonly hoverService: IHoverService ) { - super(instantiationService, contextKeyService, themeService, accessibilityService, layoutService); + super(instantiationService, contextKeyService, themeService, layoutService); this.registerListeners(); } diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index 7a2bcec8f60..efcae100a18 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -320,7 +320,7 @@ export function workbenchInstantiationService( instantiationService.stub(IPaneCompositePartService, new TestPaneCompositeService()); instantiationService.stub(IListService, new TestListService()); const hoverService = instantiationService.stub(IHoverService, instantiationService.createInstance(TestHoverService)); - instantiationService.stub(IQuickInputService, disposables.add(new QuickInputService(configService, instantiationService, keybindingService, contextKeyService, themeService, accessibilityService, layoutService, hoverService))); + instantiationService.stub(IQuickInputService, disposables.add(new QuickInputService(configService, instantiationService, keybindingService, contextKeyService, themeService, layoutService, hoverService))); instantiationService.stub(IWorkspacesService, new TestWorkspacesService()); instantiationService.stub(IWorkspaceTrustManagementService, new TestWorkspaceTrustManagementService()); instantiationService.stub(ITerminalInstanceService, new TestTerminalInstanceService());