Merge pull request #198153 from microsoft/merogge/write-screen-reader

fine tune when native text area is updated
This commit is contained in:
Megan Rogge
2023-11-21 09:57:09 -06:00
committed by GitHub
4 changed files with 45 additions and 24 deletions
@@ -36,6 +36,7 @@ import { ColorId, ITokenPresentation } from 'vs/editor/common/encodedTokenAttrib
import { Color } from 'vs/base/common/color';
import { IME } from 'vs/base/common/ime';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
export interface IVisibleRangeProvider {
visibleRangeForPosition(position: Position): HorizontalPosition | null;
@@ -145,7 +146,8 @@ export class TextAreaHandler extends ViewPart {
context: ViewContext,
viewController: ViewController,
visibleRangeProvider: IVisibleRangeProvider,
@IKeybindingService private readonly _keybindingService: IKeybindingService
@IKeybindingService private readonly _keybindingService: IKeybindingService,
@IInstantiationService private readonly _instantiationService: IInstantiationService
) {
super(context);
@@ -303,7 +305,7 @@ export class TextAreaHandler extends ViewPart {
};
const textAreaWrapper = this._register(new TextAreaWrapper(this.textArea.domNode));
this._textAreaInput = this._register(new TextAreaInput(textAreaInputHost, textAreaWrapper, platform.OS, {
this._textAreaInput = this._register(this._instantiationService.createInstance(TextAreaInput, textAreaInputHost, textAreaWrapper, platform.OS, {
isAndroid: browser.isAndroid,
isChrome: browser.isChrome,
isFirefox: browser.isFirefox,
@@ -478,7 +480,7 @@ export class TextAreaHandler extends ViewPart {
}
public writeScreenReaderContent(reason: string): void {
this._textAreaInput.writeScreenReaderContent(reason);
this._textAreaInput.writeNativeTextAreaContent(reason);
}
public override dispose(): void {
@@ -629,7 +631,7 @@ export class TextAreaHandler extends ViewPart {
}
if (e.hasChanged(EditorOption.accessibilitySupport)) {
this._textAreaInput.writeScreenReaderContent('strategy changed');
this._textAreaInput.writeNativeTextAreaContent('strategy changed');
}
return true;
@@ -639,7 +641,7 @@ export class TextAreaHandler extends ViewPart {
this._modelSelections = e.modelSelections.slice(0);
// We must update the <textarea> synchronously, otherwise long press IME on macos breaks.
// See https://github.com/microsoft/vscode/issues/165821
this._textAreaInput.writeScreenReaderContent('selection changed');
this._textAreaInput.writeNativeTextAreaContent('selection changed');
return true;
}
public override onDecorationsChanged(e: viewEvents.ViewDecorationsChangedEvent): boolean {
@@ -726,7 +728,7 @@ export class TextAreaHandler extends ViewPart {
}
public render(ctx: RestrictedRenderingContext): void {
this._textAreaInput.writeScreenReaderContent('render');
this._textAreaInput.writeNativeTextAreaContent('render');
this._render();
}
@@ -11,13 +11,15 @@ import { inputLatency } from 'vs/base/browser/performance';
import { RunOnceScheduler } from 'vs/base/common/async';
import { Emitter, Event } from 'vs/base/common/event';
import { KeyCode } from 'vs/base/common/keyCodes';
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
import { Disposable, IDisposable, MutableDisposable } from 'vs/base/common/lifecycle';
import { Mimes } from 'vs/base/common/mime';
import { OperatingSystem } from 'vs/base/common/platform';
import * as strings from 'vs/base/common/strings';
import { ITextAreaWrapper, ITypeData, TextAreaState, _debugComposition } from 'vs/editor/browser/controller/textAreaState';
import { Position } from 'vs/editor/common/core/position';
import { Selection } from 'vs/editor/common/core/selection';
import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility';
import { ILogService } from 'vs/platform/log/common/log';
export namespace TextAreaSyntethicEvents {
export const Tap = '-monaco-textarea-synthetic-tap';
@@ -193,7 +195,8 @@ export class TextAreaInput extends Disposable {
// ---
private readonly _asyncTriggerCut: RunOnceScheduler;
private readonly _asyncFocusGainWriteScreenReaderContent: RunOnceScheduler;
private _asyncFocusGainWriteScreenReaderContent: MutableDisposable<RunOnceScheduler> = this._register(new MutableDisposable());
private _textAreaState: TextAreaState;
@@ -210,16 +213,24 @@ export class TextAreaInput extends Disposable {
private readonly _host: ITextAreaInputHost,
private readonly _textArea: ICompleteTextAreaWrapper,
private readonly _OS: OperatingSystem,
private readonly _browser: IBrowser
private readonly _browser: IBrowser,
@IAccessibilityService private readonly _accessibilityService: IAccessibilityService,
@ILogService private readonly _logService: ILogService
) {
super();
this._asyncTriggerCut = this._register(new RunOnceScheduler(() => this._onCut.fire(), 0));
this._asyncFocusGainWriteScreenReaderContent = this._register(new RunOnceScheduler(() => this.writeScreenReaderContent('asyncFocusGain'), 0));
this._textAreaState = TextAreaState.EMPTY;
this._selectionChangeListener = null;
this.writeScreenReaderContent('ctor');
if (this._accessibilityService.isScreenReaderOptimized()) {
this.writeNativeTextAreaContent('ctor');
}
this._register(Event.runAndSubscribe(this._accessibilityService.onDidChangeScreenReaderOptimized, () => {
if (this._accessibilityService.isScreenReaderOptimized() && !this._asyncFocusGainWriteScreenReaderContent.value) {
this._asyncFocusGainWriteScreenReaderContent.value = this._register(new RunOnceScheduler(() => this.writeNativeTextAreaContent('asyncFocusGain'), 0));
} else {
this._asyncFocusGainWriteScreenReaderContent.clear();
}
}));
this._hasFocus = false;
this._currentComposition = null;
@@ -431,10 +442,13 @@ export class TextAreaInput extends Disposable {
this._setHasFocus(true);
if (this._browser.isSafari && !hadFocus && this._hasFocus) {
if (this._accessibilityService.isScreenReaderOptimized() && this._browser.isSafari && !hadFocus && this._hasFocus) {
// When "tabbing into" the textarea, immediately after dispatching the 'focus' event,
// Safari will always move the selection at offset 0 in the textarea
this._asyncFocusGainWriteScreenReaderContent.schedule();
if (!this._asyncFocusGainWriteScreenReaderContent.value) {
this._asyncFocusGainWriteScreenReaderContent.value = new RunOnceScheduler(() => this.writeNativeTextAreaContent('asyncFocusGain'), 0);
}
this._asyncFocusGainWriteScreenReaderContent.value.schedule();
}
}));
this._register(this._textArea.onBlur(() => {
@@ -447,7 +461,7 @@ export class TextAreaInput extends Disposable {
this._currentComposition = null;
// Clear the textarea to avoid an unwanted cursor type
this.writeScreenReaderContent('blurWithoutCompositionEnd');
this.writeNativeTextAreaContent('blurWithoutCompositionEnd');
// Fire artificial composition end
this._onCompositionEnd.fire();
@@ -463,7 +477,7 @@ export class TextAreaInput extends Disposable {
this._currentComposition = null;
// Clear the textarea to avoid an unwanted cursor type
this.writeScreenReaderContent('tapWithoutCompositionEnd');
this.writeNativeTextAreaContent('tapWithoutCompositionEnd');
// Fire artificial composition end
this._onCompositionEnd.fire();
@@ -602,7 +616,7 @@ export class TextAreaInput extends Disposable {
}
if (this._hasFocus) {
this.writeScreenReaderContent('focusgain');
this.writeNativeTextAreaContent('focusgain');
}
if (this._hasFocus) {
@@ -621,12 +635,13 @@ export class TextAreaInput extends Disposable {
this._textAreaState = textAreaState;
}
public writeScreenReaderContent(reason: string): void {
if (this._currentComposition) {
public writeNativeTextAreaContent(reason: string): void {
if ((!this._accessibilityService.isScreenReaderOptimized() && reason === 'render') || this._currentComposition) {
// Do not write to the text on render unless a screen reader is being used #192278
// Do not write to the text area when doing composition
return;
}
this._logService.trace(`writeTextAreaState(reason: ${reason})`);
this._setAndWriteTextAreaState(reason, this._host.getScreenReaderContent());
}
@@ -12,6 +12,8 @@ import * as dom from 'vs/base/browser/dom';
import * as browser from 'vs/base/browser/browser';
import * as platform from 'vs/base/common/platform';
import { mainWindow } from 'vs/base/browser/window';
import { TestAccessibilityService } from 'vs/platform/accessibility/test/common/testAccessibilityService';
import { NullLogService } from 'vs/platform/log/common/log';
// To run this test, open imeTester.html
@@ -127,7 +129,7 @@ function doCreateTest(description: string, inputStr: string, expectedStr: string
isFirefox: browser.isFirefox,
isChrome: browser.isChrome,
isSafari: browser.isSafari,
});
}, new TestAccessibilityService(), new NullLogService());
const output = document.createElement('pre');
output.className = 'output';
@@ -146,7 +148,7 @@ function doCreateTest(description: string, inputStr: string, expectedStr: string
const updatePosition = (off: number, len: number) => {
cursorOffset = off;
cursorLength = len;
handler.writeScreenReaderContent('selection changed');
handler.writeNativeTextAreaContent('selection changed');
handler.focusTextArea();
};
@@ -12,6 +12,8 @@ import { ClipboardDataToCopy, IBrowser, ICompleteTextAreaWrapper, ITextAreaInput
import { TextAreaState } from 'vs/editor/browser/controller/textAreaState';
import { Position } from 'vs/editor/common/core/position';
import { IRecorded, IRecordedEvent, IRecordedTextareaState } from 'vs/editor/test/browser/controller/imeRecordedTypes';
import { TestAccessibilityService } from 'vs/platform/accessibility/test/common/testAccessibilityService';
import { NullLogService } from 'vs/platform/log/common/log';
suite('TextAreaInput', () => {
@@ -203,7 +205,7 @@ suite('TextAreaInput', () => {
public hasFocus(): boolean { return true; }
});
const input = disposables.add(new TextAreaInput(host, wrapper, recorded.env.OS, recorded.env.browser));
const input = disposables.add(new TextAreaInput(host, wrapper, recorded.env.OS, recorded.env.browser, new TestAccessibilityService(), new NullLogService()));
wrapper._initialize(recorded.initial);
input._initializeFromTest();