Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

aux window - add and use DOM utilities for checking active element #195797

Merged
merged 1 commit into from
Oct 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
16 changes: 16 additions & 0 deletions src/vs/base/browser/dom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -717,6 +717,22 @@ export function getActiveElement(): Element | null {
return result;
}

/**
* Returns whether the active element of the `document` that owns
* the `element` is `element`.
*/
export function isActiveElement(element: Element): boolean {
return element.ownerDocument.activeElement === element;
}

/**
* Returns whether the active element of the `document` that owns
* the `ancestor` is contained in `ancestor`.
*/
export function isAncestorOfActiveElement(ancestor: Element): boolean {
return isAncestor(ancestor.ownerDocument.activeElement, ancestor);
}

/**
* Returns the active document across all child windows.
* Use this instead of `document` when reacting to dom events to handle multiple windows.
Expand Down
9 changes: 1 addition & 8 deletions src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -177,14 +177,7 @@ export class BreadcrumbsWidget {
}

isDOMFocused(): boolean {
let candidate = this._domNode.ownerDocument.activeElement;
while (candidate) {
if (this._domNode === candidate) {
return true;
}
candidate = candidate.parentElement;
}
return false;
return dom.isAncestorOfActiveElement(this._domNode);
}

getFocused(): BreadcrumbsItem {
Expand Down
4 changes: 2 additions & 2 deletions src/vs/base/browser/ui/button/button.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/

import { IContextMenuProvider } from 'vs/base/browser/contextmenu';
import { addDisposableListener, EventHelper, EventType, IFocusTracker, reset, trackFocus } from 'vs/base/browser/dom';
import { addDisposableListener, EventHelper, EventType, IFocusTracker, isActiveElement, reset, trackFocus } from 'vs/base/browser/dom';
import { sanitize } from 'vs/base/browser/dompurify/dompurify';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { renderMarkdown, renderStringAsPlaintext } from 'vs/base/browser/markdownRenderer';
Expand Down Expand Up @@ -281,7 +281,7 @@ export class Button extends Disposable implements IButton {
}

hasFocus(): boolean {
return this._element === this._element.ownerDocument.activeElement;
return isActiveElement(this._element);
}
}

Expand Down
8 changes: 4 additions & 4 deletions src/vs/base/browser/ui/contextview/contextview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -195,13 +195,13 @@ export class ContextView extends Disposable {
const toDisposeOnSetContainer = new DisposableStore();

ContextView.BUBBLE_UP_EVENTS.forEach(event => {
toDisposeOnSetContainer.add(DOM.addStandardDisposableListener(this.container!, event, (e: Event) => {
toDisposeOnSetContainer.add(DOM.addStandardDisposableListener(this.container!, event, e => {
this.onDOMEvent(e, false);
}));
});

ContextView.BUBBLE_DOWN_EVENTS.forEach(event => {
toDisposeOnSetContainer.add(DOM.addStandardDisposableListener(this.container!, event, (e: Event) => {
toDisposeOnSetContainer.add(DOM.addStandardDisposableListener(this.container!, event, e => {
this.onDOMEvent(e, true);
}, true));
});
Expand Down Expand Up @@ -370,10 +370,10 @@ export class ContextView extends Disposable {
return !!this.delegate;
}

private onDOMEvent(e: Event, onCapture: boolean): void {
private onDOMEvent(e: UIEvent, onCapture: boolean): void {
if (this.delegate) {
if (this.delegate.onDOMEvent) {
this.delegate.onDOMEvent(e, <HTMLElement>this.view.ownerDocument.activeElement);
this.delegate.onDOMEvent(e, <HTMLElement>DOM.getWindow(e).document.activeElement);
} else if (onCapture && !DOM.isAncestor(<HTMLElement>e.target, this.container)) {
this.hide();
}
Expand Down
4 changes: 2 additions & 2 deletions src/vs/base/browser/ui/dialog/dialog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { $, addDisposableListener, clearNode, EventHelper, EventType, getWindow, hide, isAncestor, show } from 'vs/base/browser/dom';
import { $, addDisposableListener, clearNode, EventHelper, EventType, getWindow, hide, isActiveElement, isAncestor, show } from 'vs/base/browser/dom';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
import { ButtonBar, ButtonWithDescription, IButtonStyles } from 'vs/base/browser/ui/button/button';
Expand Down Expand Up @@ -269,7 +269,7 @@ export class Dialog extends Disposable {
const links = this.messageContainer.querySelectorAll('a');
for (const link of links) {
focusableElements.push(link);
if (link === link.ownerDocument.activeElement) {
if (isActiveElement(link)) {
focusedIndex = focusableElements.length - 1;
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/vs/base/browser/ui/inputbox/inputBox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,7 @@ export class InputBox extends Widget {
}

public hasFocus(): boolean {
return this.input.ownerDocument.activeElement === this.input;
return dom.isActiveElement(this.input);
}

public select(range: IRange | null = null): void {
Expand Down Expand Up @@ -628,7 +628,7 @@ export class HistoryInputBox extends InputBox implements IHistoryNavigationWidge
if (options.showHistoryHint && options.showHistoryHint() && !this.placeholder.endsWith(NLS_PLACEHOLDER_HISTORY_HINT_SUFFIX) && !this.placeholder.endsWith(NLS_PLACEHOLDER_HISTORY_HINT_SUFFIX_IN_PARENS) && this.history.getHistory().length) {
const suffix = this.placeholder.endsWith(')') ? NLS_PLACEHOLDER_HISTORY_HINT_SUFFIX : NLS_PLACEHOLDER_HISTORY_HINT_SUFFIX_IN_PARENS;
const suffixedPlaceholder = this.placeholder + suffix;
if (options.showPlaceholderOnFocus && this.input.ownerDocument.activeElement !== this.input) {
if (options.showPlaceholderOnFocus && !dom.isActiveElement(this.input)) {
this.placeholder = suffixedPlaceholder;
}
else {
Expand Down
4 changes: 2 additions & 2 deletions src/vs/base/browser/ui/list/listPaging.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { ScrollbarVisibility } from 'vs/base/common/scrollable';
import 'vs/css!./list';
import { IListContextMenuEvent, IListEvent, IListMouseEvent, IListRenderer, IListVirtualDelegate } from './list';
import { IListAccessibilityProvider, IListOptions, IListOptionsUpdate, IListStyles, List, TypeNavigationMode } from './listWidget';
import { isActiveElement } from 'vs/base/browser/dom';

export interface IPagedRenderer<TElement, TTemplateData> extends IListRenderer<TElement, TTemplateData> {
renderPlaceholder(index: number, templateData: TTemplateData): void;
Expand Down Expand Up @@ -144,8 +145,7 @@ export class PagedList<T> implements IDisposable {
}

isDOMFocused(): boolean {
const element = this.getHTMLElement();
return element === element.ownerDocument.activeElement;
return isActiveElement(this.getHTMLElement());
}

domFocus(): void {
Expand Down
4 changes: 2 additions & 2 deletions src/vs/base/browser/ui/list/listWidget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/

import { IDragAndDropData } from 'vs/base/browser/dnd';
import { asCssValueWithDefault, createStyleSheet, Dimension, EventHelper, getActiveElement, isMouseEvent } from 'vs/base/browser/dom';
import { asCssValueWithDefault, createStyleSheet, Dimension, EventHelper, getActiveElement, isActiveElement, isMouseEvent } from 'vs/base/browser/dom';
import { DomEmitter } from 'vs/base/browser/event';
import { IKeyboardEvent, StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { Gesture } from 'vs/base/browser/touch';
Expand Down Expand Up @@ -1880,7 +1880,7 @@ export class List<T> implements ISpliceable<T>, IDisposable {
}

isDOMFocused(): boolean {
return this.view.domNode === this.view.domNode.ownerDocument.activeElement;
return isActiveElement(this.view.domNode);
}

getHTMLElement(): HTMLElement {
Expand Down
3 changes: 2 additions & 1 deletion src/vs/base/browser/ui/toggle/toggle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { ThemeIcon } from 'vs/base/common/themables';
import { Emitter, Event } from 'vs/base/common/event';
import { KeyCode } from 'vs/base/common/keyCodes';
import 'vs/css!./toggle';
import { isActiveElement } from 'vs/base/browser/dom';

export interface IToggleOpts extends IToggleStyles {
readonly actionClassName?: string;
Expand Down Expand Up @@ -252,7 +253,7 @@ export class Checkbox extends Widget {
}

hasFocus(): boolean {
return this.domNode === this.domNode.ownerDocument.activeElement;
return isActiveElement(this.domNode);
}

protected applyStyles(): void {
Expand Down
5 changes: 2 additions & 3 deletions src/vs/base/browser/ui/tree/abstractTree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/

import { IDragAndDropData } from 'vs/base/browser/dnd';
import { $, append, clearNode, createStyleSheet, getWindow, h, hasParentWithClass } from 'vs/base/browser/dom';
import { $, append, clearNode, createStyleSheet, getWindow, h, hasParentWithClass, isActiveElement } from 'vs/base/browser/dom';
import { DomEmitter } from 'vs/base/browser/event';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
Expand Down Expand Up @@ -1781,8 +1781,7 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable
}

isDOMFocused(): boolean {
const element = this.getHTMLElement();
return element === element.ownerDocument.activeElement;
return isActiveElement(this.getHTMLElement());
}

layout(height?: number, width?: number): void {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { ContextKeyExpr, IContextKey, IContextKeyService, RawContextKey } from '
import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { localize } from 'vs/nls';
import { DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { isActiveElement } from 'vs/base/browser/dom';

export const historyNavigationVisible = new RawContextKey<boolean>('suggestWidgetVisible', false, localize('suggestWidgetVisible', "Whether suggestion are visible"));

Expand Down Expand Up @@ -52,7 +53,7 @@ export function registerAndCreateHistoryNavigationContext(scopedContextKeyServic
};

// Check for currently being focused
if (widget.element === widget.element.ownerDocument.activeElement) {
if (isActiveElement(widget.element)) {
onDidFocus();
}

Expand Down
5 changes: 2 additions & 3 deletions src/vs/platform/list/browser/listService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { createStyleSheet, isKeyboardEvent } from 'vs/base/browser/dom';
import { createStyleSheet, isActiveElement, isKeyboardEvent } from 'vs/base/browser/dom';
import { IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview';
import { IListMouseEvent, IListRenderer, IListTouchEvent, IListVirtualDelegate } from 'vs/base/browser/ui/list/list';
import { IPagedListOptions, IPagedRenderer, PagedList } from 'vs/base/browser/ui/list/listPaging';
Expand Down Expand Up @@ -92,8 +92,7 @@ export class ListService implements IListService {
this.lists.push(registeredList);

// Check for currently being focused
const element = widget.getHTMLElement();
if (element === element.ownerDocument.activeElement) {
if (isActiveElement(widget.getHTMLElement())) {
this.setLastFocusedList(widget);
}

Expand Down
2 changes: 1 addition & 1 deletion src/vs/platform/quickinput/browser/quickInputController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -587,7 +587,7 @@ export class QuickInputController extends Disposable {
}

const container = this.ui?.container;
const focusChanged = container && !dom.isAncestor(container.ownerDocument.activeElement, container);
const focusChanged = container && !dom.isAncestorOfActiveElement(container);
this.controller = null;
this.onHideEmitter.fire();
this.getUI().container.style.display = 'none';
Expand Down
3 changes: 2 additions & 1 deletion src/vs/workbench/browser/actions/listCommands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,15 @@ import { ITreeNode } from 'vs/base/browser/ui/tree/tree';
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
import { Table } from 'vs/base/browser/ui/table/tableWidget';
import { AbstractTree, TreeFindMode } from 'vs/base/browser/ui/tree/abstractTree';
import { isActiveElement } from 'vs/base/browser/dom';

function ensureDOMFocus(widget: ListWidget | undefined): void {
// it can happen that one of the commands is executed while
// DOM focus is within another focusable control within the
// list/tree item. therefor we should ensure that the
// list/tree has DOM focus again after the command ran.
const element = widget?.getHTMLElement();
if (element && element !== element.ownerDocument.activeElement) {
if (element && !isActiveElement(element)) {
widget?.domFocus();
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/vs/workbench/browser/parts/editor/editorGroupView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { EditorInput } from 'vs/workbench/common/editor/editorInput';
import { SideBySideEditorInput } from 'vs/workbench/common/editor/sideBySideEditorInput';
import { Emitter, Relay } from 'vs/base/common/event';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { Dimension, trackFocus, addDisposableListener, EventType, EventHelper, findParentWithClass, isAncestor, IDomNodePagePosition, isMouseEvent } from 'vs/base/browser/dom';
import { Dimension, trackFocus, addDisposableListener, EventType, EventHelper, findParentWithClass, isAncestor, IDomNodePagePosition, isMouseEvent, isActiveElement } from 'vs/base/browser/dom';
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar';
Expand Down Expand Up @@ -501,7 +501,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
// stolen accidentally on startup when the user already
// clicked somewhere.

if (this.groupsView.activeGroup === this && activeElement === this.editorContainer.ownerDocument.activeElement) {
if (this.groupsView.activeGroup === this && activeElement && isActiveElement(activeElement)) {
this.focus();
}
});
Expand Down
4 changes: 2 additions & 2 deletions src/vs/workbench/browser/parts/statusbar/statusbarModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import { Disposable, DisposableStore } from 'vs/base/common/lifecycle';
import { isStatusbarEntryLocation, IStatusbarEntryPriority, StatusbarAlignment } from 'vs/workbench/services/statusbar/browser/statusbar';
import { hide, show, isAncestor } from 'vs/base/browser/dom';
import { hide, show, isAncestorOfActiveElement } from 'vs/base/browser/dom';
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
import { Emitter } from 'vs/base/common/event';

Expand Down Expand Up @@ -174,7 +174,7 @@ export class StatusbarViewModel extends Disposable {
}

private getFocusedEntry(): IStatusbarViewModelEntry | undefined {
return this._entries.find(entry => isAncestor(entry.container.ownerDocument.activeElement, entry.container));
return this._entries.find(entry => isAncestorOfActiveElement(entry.container));
}

private focusEntry(delta: number, restartPosition: number): void {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { EventType, addDisposableListener } from 'vs/base/browser/dom';
import { EventType, addDisposableListener, isActiveElement } from 'vs/base/browser/dom';
import { IKeyboardEvent, StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar';
import { alert } from 'vs/base/browser/ui/aria/aria';
Expand Down Expand Up @@ -526,8 +526,7 @@ export class AccessibleView extends Disposable {
}
}));
disposableStore.add(this._editorWidget.onDidBlurEditorWidget(() => {
const element = this._toolbar.getElement();
if (element.ownerDocument.activeElement !== element) {
if (!isActiveElement(this._toolbar.getElement())) {
this._contextViewService.hideContextView();
}
}));
Expand Down
2 changes: 1 addition & 1 deletion src/vs/workbench/contrib/chat/browser/chatQuick.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export class QuickChatService extends Disposable implements IQuickChatService {
if (!widget) {
return false;
}
return dom.isAncestor(widget.ownerDocument.activeElement, widget);
return dom.isAncestorOfActiveElement(widget);
}

toggle(providerId?: string, query?: string | undefined): void {
Expand Down
2 changes: 1 addition & 1 deletion src/vs/workbench/contrib/comments/browser/commentsView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ export class CommentsPanel extends FilterViewPane implements ICommentsView {

public override focus(): void {
const element = this.tree?.getHTMLElement();
if (element && element === element.ownerDocument.activeElement) {
if (element && dom.isActiveElement(element)) {
return;
}

Expand Down
2 changes: 1 addition & 1 deletion src/vs/workbench/contrib/debug/browser/debugHover.ts
Original file line number Diff line number Diff line change
Expand Up @@ -341,7 +341,7 @@ export class DebugHoverWidget implements IContentWidget {
return;
}

if (dom.isAncestor(this.domNode.ownerDocument.activeElement, this.domNode)) {
if (dom.isAncestorOfActiveElement(this.domNode)) {
this.editor.focus();
}
this._isVisible = false;
Expand Down
2 changes: 1 addition & 1 deletion src/vs/workbench/contrib/debug/browser/exceptionWidget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,6 @@ export class ExceptionWidget extends ZoneWidget {
return false;
}

return dom.isAncestor(this.container.ownerDocument.activeElement, this.container);
return dom.isAncestorOfActiveElement(this.container);
}
}
7 changes: 4 additions & 3 deletions src/vs/workbench/contrib/files/browser/files.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { IEditableData } from 'vs/workbench/common/views';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { ResourceFileEdit } from 'vs/editor/browser/services/bulkEditService';
import { ProgressLocation } from 'vs/platform/progress/common/progress';
import { isActiveElement } from 'vs/base/browser/dom';

export interface IExplorerService {
readonly _serviceBrand: undefined;
Expand Down Expand Up @@ -63,7 +64,7 @@ export interface IExplorerView {
function getFocus(listService: IListService): unknown | undefined {
const list = listService.lastFocusedList;
const element = list?.getHTMLElement();
if (element && element === element.ownerDocument.activeElement) {
if (element && isActiveElement(element)) {
let focus: unknown;
if (list instanceof List) {
const focused = list.getFocusedElements();
Expand Down Expand Up @@ -103,7 +104,7 @@ export function getResourceForCommand(resource: URI | object | undefined, listSe
export function getMultiSelectedResources(resource: URI | object | undefined, listService: IListService, editorService: IEditorService, explorerService: IExplorerService): Array<URI> {
const list = listService.lastFocusedList;
const element = list?.getHTMLElement();
if (element && element === element.ownerDocument.activeElement) {
if (element && isActiveElement(element)) {
// Explorer
if (list instanceof AsyncDataTree && list.getFocus().every(item => item instanceof ExplorerItem)) {
// Explorer
Expand Down Expand Up @@ -139,7 +140,7 @@ export function getMultiSelectedResources(resource: URI | object | undefined, li
export function getOpenEditorsViewMultiSelection(listService: IListService, editorGroupService: IEditorGroupsService): Array<IEditorIdentifier> | undefined {
const list = listService.lastFocusedList;
const element = list?.getHTMLElement();
if (element && element === element.ownerDocument.activeElement) {
if (element && isActiveElement(element)) {
// Open editors view
if (list instanceof List) {
const selection = coalesce(list.getSelectedElements().filter(s => s instanceof OpenEditor));
Expand Down
4 changes: 1 addition & 3 deletions src/vs/workbench/contrib/notebook/browser/notebookEditor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,9 +179,7 @@ export class NotebookEditor extends EditorPane implements INotebookEditorPane {
return false;
}

const activeElement = value.getDomNode().ownerDocument.activeElement;

return !!value && (DOM.isAncestor(activeElement, value.getDomNode() || DOM.isAncestor(activeElement, value.getOverflowContainerDomNode())));
return !!value && (DOM.isAncestorOfActiveElement(value.getDomNode() || DOM.isAncestorOfActiveElement(value.getOverflowContainerDomNode())));
}

override async setInput(input: NotebookEditorInput, options: INotebookEditorOptions | undefined, context: IEditorOpenContext, token: CancellationToken, noRetry?: boolean): Promise<void> {
Expand Down