mirror of
https://github.com/microsoft/vscode.git
synced 2026-04-22 01:29:04 +01:00
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:
@@ -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();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user