Avoid using global document, global window (#193189)

* Avoid using global document, global window

* Fix problem with fake dom element

* Fix problem with tracking focus on the window
This commit is contained in:
Alexandru Dima
2023-09-15 14:55:21 +02:00
committed by GitHub
parent 9a9ef69e1f
commit b2388cebc3
24 changed files with 170 additions and 96 deletions

View File

@@ -17,6 +17,29 @@ import { FileAccess, RemoteAuthorities, Schemas } from 'vs/base/common/network';
import * as platform from 'vs/base/common/platform';
import { URI } from 'vs/base/common/uri';
export const { registerWindow, getWindows, onDidCreateWindow } = (function () {
const windows: Window[] = [];
const onDidCreateWindow = new event.Emitter<{ window: Window; disposableStore: DisposableStore }>();
return {
onDidCreateWindow: onDidCreateWindow.event,
registerWindow(window: Window): IDisposable {
windows.push(window);
const disposableStore = new DisposableStore();
disposableStore.add(toDisposable(() => {
const index = windows.indexOf(window);
if (index !== -1) {
windows.splice(index, 1);
}
}));
onDidCreateWindow.fire({ window, disposableStore });
return disposableStore;
},
getWindows(): Window[] {
return windows;
}
};
})();
export function clearNode(node: HTMLElement): void {
while (node.firstChild) {
node.firstChild.remove();
@@ -282,34 +305,37 @@ export function addDisposableThrottledListener<R, E extends Event = Event>(node:
}
export function getComputedStyle(el: HTMLElement): CSSStyleDeclaration {
return document.defaultView!.getComputedStyle(el, null);
return el.ownerDocument.defaultView!.getComputedStyle(el, null);
}
export function getClientArea(element: HTMLElement): Dimension {
const elDocument = element.ownerDocument;
const elWindow = elDocument.defaultView?.window;
// Try with DOM clientWidth / clientHeight
if (element !== document.body) {
if (element !== elDocument.body) {
return new Dimension(element.clientWidth, element.clientHeight);
}
// If visual view port exits and it's on mobile, it should be used instead of window innerWidth / innerHeight, or document.body.clientWidth / document.body.clientHeight
if (platform.isIOS && window.visualViewport) {
return new Dimension(window.visualViewport.width, window.visualViewport.height);
if (platform.isIOS && elWindow?.visualViewport) {
return new Dimension(elWindow.visualViewport.width, elWindow.visualViewport.height);
}
// Try innerWidth / innerHeight
if (window.innerWidth && window.innerHeight) {
return new Dimension(window.innerWidth, window.innerHeight);
if (elWindow?.innerWidth && elWindow.innerHeight) {
return new Dimension(elWindow.innerWidth, elWindow.innerHeight);
}
// Try with document.body.clientWidth / document.body.clientHeight
if (document.body && document.body.clientWidth && document.body.clientHeight) {
return new Dimension(document.body.clientWidth, document.body.clientHeight);
if (elDocument.body && elDocument.body.clientWidth && elDocument.body.clientHeight) {
return new Dimension(elDocument.body.clientWidth, elDocument.body.clientHeight);
}
// Try with document.documentElement.clientWidth / document.documentElement.clientHeight
if (document.documentElement && document.documentElement.clientWidth && document.documentElement.clientHeight) {
return new Dimension(document.documentElement.clientWidth, document.documentElement.clientHeight);
if (elDocument.documentElement && elDocument.documentElement.clientWidth && elDocument.documentElement.clientHeight) {
return new Dimension(elDocument.documentElement.clientWidth, elDocument.documentElement.clientHeight);
}
throw new Error('Unable to figure out browser width and height');
@@ -431,8 +457,8 @@ export function getTopLeftOffset(element: HTMLElement): IDomPosition {
while (
(element = <HTMLElement>element.parentNode) !== null
&& element !== document.body
&& element !== document.documentElement
&& element !== element.ownerDocument.body
&& element !== element.ownerDocument.documentElement
) {
top -= element.scrollTop;
const c = isShadowRoot(element) ? null : getComputedStyle(element);
@@ -498,8 +524,8 @@ export function position(element: HTMLElement, top: number, right?: number, bott
export function getDomNodePagePosition(domNode: HTMLElement): IDomNodePagePosition {
const bb = domNode.getBoundingClientRect();
return {
left: bb.left + window.scrollX,
top: bb.top + window.scrollY,
left: bb.left + (domNode.ownerDocument.defaultView?.scrollX ?? 0),
top: bb.top + (domNode.ownerDocument.defaultView?.scrollY ?? 0),
width: bb.width,
height: bb.height
};
@@ -518,7 +544,7 @@ export function getDomNodeZoomLevel(domNode: HTMLElement): number {
}
testElement = testElement.parentElement;
} while (testElement !== null && testElement !== document.documentElement);
} while (testElement !== null && testElement !== testElement.ownerDocument.documentElement);
return zoom;
}
@@ -602,7 +628,7 @@ export function setParentFlowTo(fromChildElement: HTMLElement, toParentElement:
function getParentFlowToElement(node: HTMLElement): HTMLElement | null {
const flowToParentId = node.dataset[parentFlowToDataKey];
if (typeof flowToParentId === 'string') {
return document.getElementById(flowToParentId);
return node.ownerDocument.getElementById(flowToParentId);
}
return null;
}
@@ -671,7 +697,7 @@ export function isInShadowDOM(domNode: Node): boolean {
export function getShadowRoot(domNode: Node): ShadowRoot | null {
while (domNode.parentNode) {
if (domNode === document.body) {
if (domNode === domNode.ownerDocument?.body) {
// reached the body
return null;
}
@@ -680,8 +706,12 @@ export function getShadowRoot(domNode: Node): ShadowRoot | null {
return isShadowRoot(domNode) ? domNode : null;
}
/**
* Returns the active element across all child windows.
* Use this instead of `document.activeElement` to handle multiple windows.
*/
export function getActiveElement(): Element | null {
let result = document.activeElement;
let result = getActiveDocument().activeElement;
while (result?.shadowRoot) {
result = result.shadowRoot.activeElement;
@@ -690,6 +720,15 @@ export function getActiveElement(): Element | null {
return result;
}
/**
* Returns the active document across all child windows.
* Use this instead of `document` when reacting to dom events to handle multiple windows.
*/
export function getActiveDocument(): Document {
const documents = [document, ...getWindows().map(w => w.document)];
return documents.find(doc => doc.hasFocus()) ?? document;
}
export function createStyleSheet(container: HTMLElement = document.getElementsByTagName('head')[0], beforeAppend?: (style: HTMLStyleElement) => void): HTMLStyleElement {
const style = document.createElement('style');
style.type = 'text/css';
@@ -875,15 +914,19 @@ class FocusTracker extends Disposable implements IFocusTracker {
private _refreshStateHandler: () => void;
private static hasFocusWithin(element: HTMLElement): boolean {
const shadowRoot = getShadowRoot(element);
const activeElement = (shadowRoot ? shadowRoot.activeElement : document.activeElement);
return isAncestor(activeElement, element);
private static hasFocusWithin(element: HTMLElement | Window): boolean {
if (isHTMLElement(element)) {
const shadowRoot = getShadowRoot(element);
const activeElement = (shadowRoot ? shadowRoot.activeElement : element.ownerDocument.activeElement);
return isAncestor(activeElement, element);
} else {
return isAncestor(window.document.activeElement, window.document);
}
}
constructor(element: HTMLElement | Window) {
super();
let hasFocus = FocusTracker.hasFocusWithin(<HTMLElement>element);
let hasFocus = FocusTracker.hasFocusWithin(element);
let loosingFocus = false;
const onFocus = () => {
@@ -1092,7 +1135,7 @@ export function removeTabIndexAndUpdateFocus(node: HTMLElement): void {
// standard DOM behavior is to move focus to the <body> element. We
// typically never want that, rather put focus to the closest element
// in the hierarchy of the parent DOM nodes.
if (document.activeElement === node) {
if (node.ownerDocument.activeElement === node) {
const parentFocusable = findParentWithAttribute(node.parentElement, 'tabIndex');
parentFocusable?.focus();
}