Skip to content

Commit 1ffd522

Browse files
authored
Merge pull request #198153 from microsoft/merogge/write-screen-reader
fine tune when native text area is updated
2 parents 9cc10ae + f068aa4 commit 1ffd522

File tree

4 files changed

+45
-24
lines changed

4 files changed

+45
-24
lines changed

src/vs/editor/browser/controller/textAreaHandler.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import { ColorId, ITokenPresentation } from 'vs/editor/common/encodedTokenAttrib
3636
import { Color } from 'vs/base/common/color';
3737
import { IME } from 'vs/base/common/ime';
3838
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
39+
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
3940

4041
export interface IVisibleRangeProvider {
4142
visibleRangeForPosition(position: Position): HorizontalPosition | null;
@@ -145,7 +146,8 @@ export class TextAreaHandler extends ViewPart {
145146
context: ViewContext,
146147
viewController: ViewController,
147148
visibleRangeProvider: IVisibleRangeProvider,
148-
@IKeybindingService private readonly _keybindingService: IKeybindingService
149+
@IKeybindingService private readonly _keybindingService: IKeybindingService,
150+
@IInstantiationService private readonly _instantiationService: IInstantiationService
149151
) {
150152
super(context);
151153

@@ -303,7 +305,7 @@ export class TextAreaHandler extends ViewPart {
303305
};
304306

305307
const textAreaWrapper = this._register(new TextAreaWrapper(this.textArea.domNode));
306-
this._textAreaInput = this._register(new TextAreaInput(textAreaInputHost, textAreaWrapper, platform.OS, {
308+
this._textAreaInput = this._register(this._instantiationService.createInstance(TextAreaInput, textAreaInputHost, textAreaWrapper, platform.OS, {
307309
isAndroid: browser.isAndroid,
308310
isChrome: browser.isChrome,
309311
isFirefox: browser.isFirefox,
@@ -478,7 +480,7 @@ export class TextAreaHandler extends ViewPart {
478480
}
479481

480482
public writeScreenReaderContent(reason: string): void {
481-
this._textAreaInput.writeScreenReaderContent(reason);
483+
this._textAreaInput.writeNativeTextAreaContent(reason);
482484
}
483485

484486
public override dispose(): void {
@@ -629,7 +631,7 @@ export class TextAreaHandler extends ViewPart {
629631
}
630632

631633
if (e.hasChanged(EditorOption.accessibilitySupport)) {
632-
this._textAreaInput.writeScreenReaderContent('strategy changed');
634+
this._textAreaInput.writeNativeTextAreaContent('strategy changed');
633635
}
634636

635637
return true;
@@ -639,7 +641,7 @@ export class TextAreaHandler extends ViewPart {
639641
this._modelSelections = e.modelSelections.slice(0);
640642
// We must update the <textarea> synchronously, otherwise long press IME on macos breaks.
641643
// See https://github.com/microsoft/vscode/issues/165821
642-
this._textAreaInput.writeScreenReaderContent('selection changed');
644+
this._textAreaInput.writeNativeTextAreaContent('selection changed');
643645
return true;
644646
}
645647
public override onDecorationsChanged(e: viewEvents.ViewDecorationsChangedEvent): boolean {
@@ -726,7 +728,7 @@ export class TextAreaHandler extends ViewPart {
726728
}
727729

728730
public render(ctx: RestrictedRenderingContext): void {
729-
this._textAreaInput.writeScreenReaderContent('render');
731+
this._textAreaInput.writeNativeTextAreaContent('render');
730732
this._render();
731733
}
732734

src/vs/editor/browser/controller/textAreaInput.ts

Lines changed: 30 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,15 @@ import { inputLatency } from 'vs/base/browser/performance';
1111
import { RunOnceScheduler } from 'vs/base/common/async';
1212
import { Emitter, Event } from 'vs/base/common/event';
1313
import { KeyCode } from 'vs/base/common/keyCodes';
14-
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
14+
import { Disposable, IDisposable, MutableDisposable } from 'vs/base/common/lifecycle';
1515
import { Mimes } from 'vs/base/common/mime';
1616
import { OperatingSystem } from 'vs/base/common/platform';
1717
import * as strings from 'vs/base/common/strings';
1818
import { ITextAreaWrapper, ITypeData, TextAreaState, _debugComposition } from 'vs/editor/browser/controller/textAreaState';
1919
import { Position } from 'vs/editor/common/core/position';
2020
import { Selection } from 'vs/editor/common/core/selection';
21+
import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility';
22+
import { ILogService } from 'vs/platform/log/common/log';
2123

2224
export namespace TextAreaSyntethicEvents {
2325
export const Tap = '-monaco-textarea-synthetic-tap';
@@ -193,7 +195,8 @@ export class TextAreaInput extends Disposable {
193195
// ---
194196

195197
private readonly _asyncTriggerCut: RunOnceScheduler;
196-
private readonly _asyncFocusGainWriteScreenReaderContent: RunOnceScheduler;
198+
199+
private _asyncFocusGainWriteScreenReaderContent: MutableDisposable<RunOnceScheduler> = this._register(new MutableDisposable());
197200

198201
private _textAreaState: TextAreaState;
199202

@@ -210,16 +213,24 @@ export class TextAreaInput extends Disposable {
210213
private readonly _host: ITextAreaInputHost,
211214
private readonly _textArea: ICompleteTextAreaWrapper,
212215
private readonly _OS: OperatingSystem,
213-
private readonly _browser: IBrowser
216+
private readonly _browser: IBrowser,
217+
@IAccessibilityService private readonly _accessibilityService: IAccessibilityService,
218+
@ILogService private readonly _logService: ILogService
214219
) {
215220
super();
216221
this._asyncTriggerCut = this._register(new RunOnceScheduler(() => this._onCut.fire(), 0));
217-
this._asyncFocusGainWriteScreenReaderContent = this._register(new RunOnceScheduler(() => this.writeScreenReaderContent('asyncFocusGain'), 0));
218-
219222
this._textAreaState = TextAreaState.EMPTY;
220223
this._selectionChangeListener = null;
221-
this.writeScreenReaderContent('ctor');
222-
224+
if (this._accessibilityService.isScreenReaderOptimized()) {
225+
this.writeNativeTextAreaContent('ctor');
226+
}
227+
this._register(Event.runAndSubscribe(this._accessibilityService.onDidChangeScreenReaderOptimized, () => {
228+
if (this._accessibilityService.isScreenReaderOptimized() && !this._asyncFocusGainWriteScreenReaderContent.value) {
229+
this._asyncFocusGainWriteScreenReaderContent.value = this._register(new RunOnceScheduler(() => this.writeNativeTextAreaContent('asyncFocusGain'), 0));
230+
} else {
231+
this._asyncFocusGainWriteScreenReaderContent.clear();
232+
}
233+
}));
223234
this._hasFocus = false;
224235
this._currentComposition = null;
225236

@@ -431,10 +442,13 @@ export class TextAreaInput extends Disposable {
431442

432443
this._setHasFocus(true);
433444

434-
if (this._browser.isSafari && !hadFocus && this._hasFocus) {
445+
if (this._accessibilityService.isScreenReaderOptimized() && this._browser.isSafari && !hadFocus && this._hasFocus) {
435446
// When "tabbing into" the textarea, immediately after dispatching the 'focus' event,
436447
// Safari will always move the selection at offset 0 in the textarea
437-
this._asyncFocusGainWriteScreenReaderContent.schedule();
448+
if (!this._asyncFocusGainWriteScreenReaderContent.value) {
449+
this._asyncFocusGainWriteScreenReaderContent.value = new RunOnceScheduler(() => this.writeNativeTextAreaContent('asyncFocusGain'), 0);
450+
}
451+
this._asyncFocusGainWriteScreenReaderContent.value.schedule();
438452
}
439453
}));
440454
this._register(this._textArea.onBlur(() => {
@@ -447,7 +461,7 @@ export class TextAreaInput extends Disposable {
447461
this._currentComposition = null;
448462

449463
// Clear the textarea to avoid an unwanted cursor type
450-
this.writeScreenReaderContent('blurWithoutCompositionEnd');
464+
this.writeNativeTextAreaContent('blurWithoutCompositionEnd');
451465

452466
// Fire artificial composition end
453467
this._onCompositionEnd.fire();
@@ -463,7 +477,7 @@ export class TextAreaInput extends Disposable {
463477
this._currentComposition = null;
464478

465479
// Clear the textarea to avoid an unwanted cursor type
466-
this.writeScreenReaderContent('tapWithoutCompositionEnd');
480+
this.writeNativeTextAreaContent('tapWithoutCompositionEnd');
467481

468482
// Fire artificial composition end
469483
this._onCompositionEnd.fire();
@@ -602,7 +616,7 @@ export class TextAreaInput extends Disposable {
602616
}
603617

604618
if (this._hasFocus) {
605-
this.writeScreenReaderContent('focusgain');
619+
this.writeNativeTextAreaContent('focusgain');
606620
}
607621

608622
if (this._hasFocus) {
@@ -621,12 +635,13 @@ export class TextAreaInput extends Disposable {
621635
this._textAreaState = textAreaState;
622636
}
623637

624-
public writeScreenReaderContent(reason: string): void {
625-
if (this._currentComposition) {
638+
public writeNativeTextAreaContent(reason: string): void {
639+
if ((!this._accessibilityService.isScreenReaderOptimized() && reason === 'render') || this._currentComposition) {
640+
// Do not write to the text on render unless a screen reader is being used #192278
626641
// Do not write to the text area when doing composition
627642
return;
628643
}
629-
644+
this._logService.trace(`writeTextAreaState(reason: ${reason})`);
630645
this._setAndWriteTextAreaState(reason, this._host.getScreenReaderContent());
631646
}
632647

src/vs/editor/test/browser/controller/imeTester.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import * as dom from 'vs/base/browser/dom';
1212
import * as browser from 'vs/base/browser/browser';
1313
import * as platform from 'vs/base/common/platform';
1414
import { mainWindow } from 'vs/base/browser/window';
15+
import { TestAccessibilityService } from 'vs/platform/accessibility/test/common/testAccessibilityService';
16+
import { NullLogService } from 'vs/platform/log/common/log';
1517

1618
// To run this test, open imeTester.html
1719

@@ -127,7 +129,7 @@ function doCreateTest(description: string, inputStr: string, expectedStr: string
127129
isFirefox: browser.isFirefox,
128130
isChrome: browser.isChrome,
129131
isSafari: browser.isSafari,
130-
});
132+
}, new TestAccessibilityService(), new NullLogService());
131133

132134
const output = document.createElement('pre');
133135
output.className = 'output';
@@ -146,7 +148,7 @@ function doCreateTest(description: string, inputStr: string, expectedStr: string
146148
const updatePosition = (off: number, len: number) => {
147149
cursorOffset = off;
148150
cursorLength = len;
149-
handler.writeScreenReaderContent('selection changed');
151+
handler.writeNativeTextAreaContent('selection changed');
150152
handler.focusTextArea();
151153
};
152154

src/vs/editor/test/browser/controller/textAreaInput.test.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import { ClipboardDataToCopy, IBrowser, ICompleteTextAreaWrapper, ITextAreaInput
1212
import { TextAreaState } from 'vs/editor/browser/controller/textAreaState';
1313
import { Position } from 'vs/editor/common/core/position';
1414
import { IRecorded, IRecordedEvent, IRecordedTextareaState } from 'vs/editor/test/browser/controller/imeRecordedTypes';
15+
import { TestAccessibilityService } from 'vs/platform/accessibility/test/common/testAccessibilityService';
16+
import { NullLogService } from 'vs/platform/log/common/log';
1517

1618
suite('TextAreaInput', () => {
1719

@@ -203,7 +205,7 @@ suite('TextAreaInput', () => {
203205

204206
public hasFocus(): boolean { return true; }
205207
});
206-
const input = disposables.add(new TextAreaInput(host, wrapper, recorded.env.OS, recorded.env.browser));
208+
const input = disposables.add(new TextAreaInput(host, wrapper, recorded.env.OS, recorded.env.browser, new TestAccessibilityService(), new NullLogService()));
207209

208210
wrapper._initialize(recorded.initial);
209211
input._initializeFromTest();

0 commit comments

Comments
 (0)