Skip to content

Commit

Permalink
aux window - support Firefox (#213769)
Browse files Browse the repository at this point in the history
* aux window - support Firefox

* add more

* only use workaround for FF

* enable issue repoter again

* .
  • Loading branch information
bpasero authored May 29, 2024
1 parent 8ed0323 commit 0cb971b
Show file tree
Hide file tree
Showing 48 changed files with 145 additions and 100 deletions.
4 changes: 4 additions & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,10 @@
"selector": "BinaryExpression[operator='instanceof'][right.name='MouseEvent']",
"message": "Use DOM.isMouseEvent() to support multi-window scenarios."
},
{
"selector": "BinaryExpression[operator='instanceof'][right.name=/^HTML\\w+/]",
"message": "Use DOM.isHTMLElement() and related methods to support multi-window scenarios."
},
{
"selector": "BinaryExpression[operator='instanceof'][right.name='KeyboardEvent']",
"message": "Use DOM.isKeyboardEvent() to support multi-window scenarios."
Expand Down
47 changes: 41 additions & 6 deletions src/vs/base/browser/dom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -795,7 +795,7 @@ export function isAncestorUsingFlowTo(testChild: Node, testAncestor: Node): bool
return true;
}

if (node instanceof HTMLElement) {
if (isHTMLElement(node)) {
const flowToParentElement = getParentFlowToElement(node);
if (flowToParentElement) {
node = flowToParentElement;
Expand Down Expand Up @@ -1148,6 +1148,41 @@ function isCSSStyleRule(rule: CSSRule): rule is CSSStyleRule {
return typeof (rule as CSSStyleRule).selectorText === 'string';
}

export function isHTMLElement(e: unknown): e is HTMLElement {
// eslint-disable-next-line no-restricted-syntax
return e instanceof HTMLElement || e instanceof getWindow(e as Node).HTMLElement;
}

export function isHTMLAnchorElement(e: unknown): e is HTMLAnchorElement {
// eslint-disable-next-line no-restricted-syntax
return e instanceof HTMLAnchorElement || e instanceof getWindow(e as Node).HTMLAnchorElement;
}

export function isHTMLSpanElement(e: unknown): e is HTMLSpanElement {
// eslint-disable-next-line no-restricted-syntax
return e instanceof HTMLSpanElement || e instanceof getWindow(e as Node).HTMLSpanElement;
}

export function isHTMLTextAreaElement(e: unknown): e is HTMLTextAreaElement {
// eslint-disable-next-line no-restricted-syntax
return e instanceof HTMLTextAreaElement || e instanceof getWindow(e as Node).HTMLTextAreaElement;
}

export function isHTMLInputElement(e: unknown): e is HTMLInputElement {
// eslint-disable-next-line no-restricted-syntax
return e instanceof HTMLInputElement || e instanceof getWindow(e as Node).HTMLInputElement;
}

export function isHTMLButtonElement(e: unknown): e is HTMLButtonElement {
// eslint-disable-next-line no-restricted-syntax
return e instanceof HTMLButtonElement || e instanceof getWindow(e as Node).HTMLButtonElement;
}

export function isHTMLDivElement(e: unknown): e is HTMLDivElement {
// eslint-disable-next-line no-restricted-syntax
return e instanceof HTMLDivElement || e instanceof getWindow(e as Node).HTMLDivElement;
}

export function isMouseEvent(e: unknown): e is MouseEvent {
// eslint-disable-next-line no-restricted-syntax
return e instanceof MouseEvent || e instanceof getWindow(e as UIEvent).MouseEvent;
Expand Down Expand Up @@ -1286,7 +1321,7 @@ class FocusTracker extends Disposable implements IFocusTracker {
private _refreshStateHandler: () => void;

private static hasFocusWithin(element: HTMLElement | Window): boolean {
if (element instanceof HTMLElement) {
if (isHTMLElement(element)) {
const shadowRoot = getShadowRoot(element);
const activeElement = (shadowRoot ? shadowRoot.activeElement : element.ownerDocument.activeElement);
return isAncestor(activeElement, element);
Expand All @@ -1312,7 +1347,7 @@ class FocusTracker extends Disposable implements IFocusTracker {
const onBlur = () => {
if (hasFocus) {
loosingFocus = true;
(element instanceof HTMLElement ? getWindow(element) : element).setTimeout(() => {
(isHTMLElement(element) ? getWindow(element) : element).setTimeout(() => {
if (loosingFocus) {
loosingFocus = false;
hasFocus = false;
Expand All @@ -1335,7 +1370,7 @@ class FocusTracker extends Disposable implements IFocusTracker {

this._register(addDisposableListener(element, EventType.FOCUS, onFocus, true));
this._register(addDisposableListener(element, EventType.BLUR, onBlur, true));
if (element instanceof HTMLElement) {
if (isHTMLElement(element)) {
this._register(addDisposableListener(element, EventType.FOCUS_IN, () => this._refreshStateHandler()));
this._register(addDisposableListener(element, EventType.FOCUS_OUT, () => this._refreshStateHandler()));
}
Expand Down Expand Up @@ -1488,7 +1523,7 @@ export function hide(...elements: HTMLElement[]): void {

function findParentWithAttribute(node: Node | null, attribute: string): HTMLElement | null {
while (node && node.nodeType === node.ELEMENT_NODE) {
if (node instanceof HTMLElement && node.hasAttribute(attribute)) {
if (isHTMLElement(node) && node.hasAttribute(attribute)) {
return node;
}

Expand Down Expand Up @@ -2304,7 +2339,7 @@ export function h(tag: string, ...args: [] | [attributes: { $: string } & Partia

if (children) {
for (const c of children) {
if (c instanceof HTMLElement) {
if (isHTMLElement(c)) {
el.appendChild(c);
} else if (typeof c === 'string') {
el.append(c);
Expand Down
2 changes: 1 addition & 1 deletion src/vs/base/browser/ui/actionbar/actionbar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,7 @@ export class ActionBar extends Disposable implements IActionRunner {
}

// by element
if (indexOrElement instanceof HTMLElement) {
if (DOM.isHTMLElement(indexOrElement)) {
while (indexOrElement.parentElement !== this.actionsList) {
if (!indexOrElement.parentElement) {
return undefined;
Expand Down
2 changes: 1 addition & 1 deletion src/vs/base/browser/ui/contextview/contextview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,7 @@ export class ContextView extends Disposable {
let around: IView;

// Get the element's position and size (to anchor the view)
if (anchor instanceof HTMLElement) {
if (DOM.isHTMLElement(anchor)) {
const elementPosition = DOM.getDomNodePagePosition(anchor);

// In areas where zoom is applied to the element or its ancestors, we need to adjust the size of the element
Expand Down
2 changes: 1 addition & 1 deletion src/vs/base/browser/ui/iconLabel/iconLabel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ export class IconLabel extends Disposable {
const existingIconNode = this.domNode.element.querySelector('.monaco-icon-label-iconpath');
if (options?.iconPath) {
let iconNode;
if (!existingIconNode || !(existingIconNode instanceof HTMLElement)) {
if (!existingIconNode || !(dom.isHTMLElement(existingIconNode))) {
iconNode = dom.$('.monaco-icon-label-iconpath');
this.domNode.element.prepend(iconNode);
} else {
Expand Down
4 changes: 2 additions & 2 deletions src/vs/base/browser/ui/list/listView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/

import { DataTransfers, IDragAndDropData } from 'vs/base/browser/dnd';
import { $, addDisposableListener, animate, Dimension, getContentHeight, getContentWidth, getTopLeftOffset, getWindow, isAncestor, scheduleAtNextAnimationFrame } from 'vs/base/browser/dom';
import { $, addDisposableListener, animate, Dimension, getContentHeight, getContentWidth, getTopLeftOffset, getWindow, isAncestor, isHTMLElement, scheduleAtNextAnimationFrame } from 'vs/base/browser/dom';
import { DomEmitter } from 'vs/base/browser/event';
import { IMouseWheelEvent } from 'vs/base/browser/mouseEvent';
import { EventType as TouchEventType, Gesture, GestureEvent } from 'vs/base/browser/touch';
Expand Down Expand Up @@ -1384,7 +1384,7 @@ export class ListView<T> implements IListView<T> {
const scrollableElement = this.scrollableElement.getDomNode();
let element: HTMLElement | null = target as (HTMLElement | null);

while (element instanceof HTMLElement && element !== this.rowsContainer && scrollableElement.contains(element)) {
while (isHTMLElement(element) && element !== this.rowsContainer && scrollableElement.contains(element)) {
const rawIndex = element.getAttribute('data-index');

if (rawIndex) {
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, getWindow, isActiveElement, isMouseEvent } from 'vs/base/browser/dom';
import { asCssValueWithDefault, createStyleSheet, Dimension, EventHelper, getActiveElement, getWindow, isActiveElement, isHTMLElement, 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 @@ -635,7 +635,7 @@ class DOMFocusController<T> implements IDisposable {

const tabIndexElement = focusedDomElement.querySelector('[tabIndex]');

if (!tabIndexElement || !(tabIndexElement instanceof HTMLElement) || tabIndexElement.tabIndex === -1) {
if (!tabIndexElement || !(isHTMLElement(tabIndexElement)) || tabIndexElement.tabIndex === -1) {
return;
}

Expand Down
4 changes: 2 additions & 2 deletions src/vs/base/browser/ui/sash/sash.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 { $, append, createStyleSheet, EventHelper, EventLike, getWindow } from 'vs/base/browser/dom';
import { $, append, createStyleSheet, EventHelper, EventLike, getWindow, isHTMLElement } from 'vs/base/browser/dom';
import { DomEmitter } from 'vs/base/browser/event';
import { EventType, Gesture } from 'vs/base/browser/touch';
import { Delayer } from 'vs/base/common/async';
Expand Down Expand Up @@ -670,7 +670,7 @@ export class Sash extends Disposable {
private getOrthogonalSash(e: PointerEvent): Sash | undefined {
const target = e.initialTarget ?? e.target;

if (!target || !(target instanceof HTMLElement)) {
if (!target || !(isHTMLElement(target))) {
return undefined;
}

Expand Down
4 changes: 2 additions & 2 deletions src/vs/base/browser/ui/splitview/paneview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import { isFirefox } from 'vs/base/browser/browser';
import { DataTransfers } from 'vs/base/browser/dnd';
import { $, addDisposableListener, append, clearNode, EventHelper, EventType, getWindow, trackFocus } from 'vs/base/browser/dom';
import { $, addDisposableListener, append, clearNode, EventHelper, EventType, getWindow, isHTMLElement, trackFocus } from 'vs/base/browser/dom';
import { DomEmitter } from 'vs/base/browser/event';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { Gesture, EventType as TouchEventType } from 'vs/base/browser/touch';
Expand Down Expand Up @@ -513,7 +513,7 @@ export class PaneView extends Disposable {

const eventDisposables = this._register(new DisposableStore());
const onKeyDown = this._register(new DomEmitter(this.element, 'keydown'));
const onHeaderKeyDown = Event.map(Event.filter(onKeyDown.event, e => e.target instanceof HTMLElement && e.target.classList.contains('pane-header'), eventDisposables), e => new StandardKeyboardEvent(e), eventDisposables);
const onHeaderKeyDown = Event.map(Event.filter(onKeyDown.event, e => isHTMLElement(e.target) && e.target.classList.contains('pane-header'), eventDisposables), e => new StandardKeyboardEvent(e), eventDisposables);

this._register(Event.filter(onHeaderKeyDown, e => e.keyCode === KeyCode.UpArrow, eventDisposables)(() => this.focusPrevious()));
this._register(Event.filter(onHeaderKeyDown, e => e.keyCode === KeyCode.DownArrow, eventDisposables)(() => this.focusNext()));
Expand Down
14 changes: 7 additions & 7 deletions src/vs/base/test/browser/dom.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/

import * as assert from 'assert';
import { $, asCssValueWithDefault, h, multibyteAwareBtoa, trackAttributes, copyAttributes, disposableWindowInterval, getWindows, getWindowsCount, getWindowId, getWindowById, hasWindow, getWindow, getDocument } from 'vs/base/browser/dom';
import { $, asCssValueWithDefault, h, multibyteAwareBtoa, trackAttributes, copyAttributes, disposableWindowInterval, getWindows, getWindowsCount, getWindowId, getWindowById, hasWindow, getWindow, getDocument, isHTMLElement } from 'vs/base/browser/dom';
import { ensureCodeWindow, isAuxiliaryWindow, mainWindow } from 'vs/base/browser/window';
import { DeferredPromise, timeout } from 'vs/base/common/async';
import { runWithFakedTimers } from 'vs/base/test/common/timeTravelScheduler';
Expand Down Expand Up @@ -85,23 +85,23 @@ suite('dom', () => {
test('should build simple nodes', () => {
const div = $('div');
assert(div);
assert(div instanceof HTMLElement);
assert(isHTMLElement(div));
assert.strictEqual(div.tagName, 'DIV');
assert(!div.firstChild);
});

test('should build nodes with id', () => {
const div = $('div#foo');
assert(div);
assert(div instanceof HTMLElement);
assert(isHTMLElement(div));
assert.strictEqual(div.tagName, 'DIV');
assert.strictEqual(div.id, 'foo');
});

test('should build nodes with class-name', () => {
const div = $('div.foo');
assert(div);
assert(div instanceof HTMLElement);
assert(isHTMLElement(div));
assert.strictEqual(div.tagName, 'DIV');
assert.strictEqual(div.className, 'foo');
});
Expand Down Expand Up @@ -136,15 +136,15 @@ suite('dom', () => {
suite('h', () => {
test('should build simple nodes', () => {
const div = h('div');
assert(div.root instanceof HTMLElement);
assert(isHTMLElement(div.root));
assert.strictEqual(div.root.tagName, 'DIV');

const span = h('span');
assert(span.root instanceof HTMLElement);
assert(isHTMLElement(span.root));
assert.strictEqual(span.root.tagName, 'SPAN');

const img = h('img');
assert(img.root instanceof HTMLElement);
assert(isHTMLElement(img.root));
assert.strictEqual(img.root.tagName, 'IMG');
});

Expand Down
3 changes: 2 additions & 1 deletion src/vs/base/test/browser/iconLabels.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
*--------------------------------------------------------------------------------------------*/

import * as assert from 'assert';
import { isHTMLElement } from 'vs/base/browser/dom';
import { renderLabelWithIcons } from 'vs/base/browser/ui/iconLabel/iconLabels';
import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils';

Expand Down Expand Up @@ -47,7 +48,7 @@ suite('renderLabelWithIcons', () => {

const elementsToString = (elements: Array<HTMLElement | string>): string => {
return elements
.map(elem => elem instanceof HTMLElement ? elem.outerHTML : elem)
.map(elem => isHTMLElement(elem) ? elem.outerHTML : elem)
.reduce((a, b) => a + b, '');
};

Expand Down
8 changes: 4 additions & 4 deletions src/vs/editor/browser/services/hoverService/hoverService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
import { HoverWidget } from 'vs/editor/browser/services/hoverService/hoverWidget';
import { IContextViewProvider, IDelegate } from 'vs/base/browser/ui/contextview/contextview';
import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { addDisposableListener, EventType, getActiveElement, isAncestorOfActiveElement, isAncestor, getWindow } from 'vs/base/browser/dom';
import { addDisposableListener, EventType, getActiveElement, isAncestorOfActiveElement, isAncestor, getWindow, isHTMLElement } from 'vs/base/browser/dom';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { ResultKind } from 'vs/platform/keybinding/common/keybindingResolver';
Expand Down Expand Up @@ -90,7 +90,7 @@ export class HoverService extends Disposable implements IHoverService {
}, undefined, hoverDisposables);
// Set the container explicitly to enable aux window support
if (!options.container) {
const targetElement = options.target instanceof HTMLElement ? options.target : options.target.targetElements[0];
const targetElement = isHTMLElement(options.target) ? options.target : options.target.targetElements[0];
options.container = this._layoutService.getContainer(getWindow(targetElement));
}

Expand Down Expand Up @@ -259,7 +259,7 @@ export class HoverService extends Disposable implements IHoverService {
// track the mouse position
const onMouseMove = (e: MouseEvent) => {
target.x = e.x + 10;
if ((e.target instanceof HTMLElement) && getHoverTargetElement(e.target, htmlElement) !== htmlElement) {
if ((isHTMLElement(e.target)) && getHoverTargetElement(e.target, htmlElement) !== htmlElement) {
hideHover(true, true);
}
};
Expand All @@ -268,7 +268,7 @@ export class HoverService extends Disposable implements IHoverService {

hoverPreparation = toDispose;

if ((e.target instanceof HTMLElement) && getHoverTargetElement(e.target as HTMLElement, htmlElement) !== htmlElement) {
if ((isHTMLElement(e.target)) && getHoverTargetElement(e.target as HTMLElement, htmlElement) !== htmlElement) {
return; // Do not show hover when the mouse is over another hover target
}

Expand Down
2 changes: 1 addition & 1 deletion src/vs/editor/browser/services/hoverService/hoverWidget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ export class HoverWidget extends Widget implements IHoverWidget {
contentsElement.textContent = options.content;
contentsElement.style.whiteSpace = 'pre-wrap';

} else if (options.content instanceof HTMLElement) {
} else if (dom.isHTMLElement(options.content)) {
contentsElement.appendChild(options.content);
contentsElement.classList.add('html-hover-contents');

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { isHTMLElement } from 'vs/base/browser/dom';
import type { IHoverWidget, IUpdatableHoverContent, IUpdatableHoverOptions } from 'vs/base/browser/ui/hover/hover';
import type { IHoverDelegate, IHoverDelegateOptions, IHoverDelegateTarget } from 'vs/base/browser/ui/hover/hoverDelegate';
import { HoverPosition } from 'vs/base/browser/ui/hover/hoverWidget';
Expand Down Expand Up @@ -33,7 +34,7 @@ export class UpdatableHoverWidget implements IDisposable {
}

let resolvedContent;
if (content === undefined || isString(content) || content instanceof HTMLElement) {
if (content === undefined || isString(content) || isHTMLElement(content)) {
resolvedContent = content;
} else if (!isFunction(content.markdown)) {
resolvedContent = content.markdown ?? content.markdownNotSupportedFallback;
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 { ModifierKeyEmitter } from 'vs/base/browser/dom';
import { isHTMLElement, ModifierKeyEmitter } from 'vs/base/browser/dom';
import { isNonEmptyArray } from 'vs/base/common/arrays';
import { RunOnceScheduler } from 'vs/base/common/async';
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
Expand Down Expand Up @@ -358,7 +358,7 @@ export class InlayHintsController implements IEditorContribution {

private _installContextMenu(): IDisposable {
return this._editor.onContextMenu(async e => {
if (!(e.event.target instanceof HTMLElement)) {
if (!(isHTMLElement(e.event.target))) {
return;
}
const part = this._getInlayHintLabelPart(e);
Expand Down
4 changes: 2 additions & 2 deletions src/vs/platform/clipboard/browser/clipboardService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/

import { isSafari, isWebkitWebView } from 'vs/base/browser/browser';
import { $, addDisposableListener, getActiveDocument, getActiveWindow, onDidRegisterWindow } from 'vs/base/browser/dom';
import { $, addDisposableListener, getActiveDocument, getActiveWindow, isHTMLElement, onDidRegisterWindow } from 'vs/base/browser/dom';
import { mainWindow } from 'vs/base/browser/window';
import { DeferredPromise } from 'vs/base/common/async';
import { Event } from 'vs/base/common/event';
Expand Down Expand Up @@ -130,7 +130,7 @@ export class BrowserClipboardService extends Disposable implements IClipboardSer

activeDocument.execCommand('copy');

if (activeElement instanceof HTMLElement) {
if (isHTMLElement(activeElement)) {
activeElement.focus();
}

Expand Down
Loading

0 comments on commit 0cb971b

Please sign in to comment.