Path: blob/main/src/vs/workbench/contrib/browserView/electron-browser/overlayManager.ts
4779 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { Disposable } from '../../../../base/common/lifecycle.js';6import { Emitter, Event } from '../../../../base/common/event.js';7import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js';8import { getDomNodePagePosition, IDomNodePagePosition } from '../../../../base/browser/dom.js';9import { CodeWindow } from '../../../../base/browser/window.js';1011const OVERLAY_CLASSES: string[] = [12'monaco-menu-container',13'quick-input-widget',14'monaco-hover',15'monaco-dialog-modal-block',16'notifications-center',17'notification-toast-container',18'context-view'19];2021export const IBrowserOverlayManager = createDecorator<IBrowserOverlayManager>('browserOverlayManager');2223export interface IBrowserOverlayManager {24readonly _serviceBrand: undefined;2526/**27* Event fired when overlay state changes28*/29readonly onDidChangeOverlayState: Event<void>;3031/**32* Check if the given element overlaps with any overlay33*/34isOverlappingWithOverlays(element: HTMLElement): boolean;35}3637export class BrowserOverlayManager extends Disposable implements IBrowserOverlayManager {38declare readonly _serviceBrand: undefined;3940private readonly _onDidChangeOverlayState = this._register(new Emitter<void>({41onWillAddFirstListener: () => {42// Start observing the document for structural changes43this._observerIsConnected = true;44this._structuralObserver.observe(this.targetWindow.document.body, {45childList: true,46subtree: true47});48this.updateTrackedElements();49},50onDidRemoveLastListener: () => {51// Stop observing when no listeners are present52this._observerIsConnected = false;53this._structuralObserver.disconnect();54this.stopTrackingElements();55}56}));57readonly onDidChangeOverlayState = this._onDidChangeOverlayState.event;5859private readonly _overlayCollections = new Map<string, HTMLCollectionOf<Element>>();60private _overlayRectangles = new WeakMap<HTMLElement, IDomNodePagePosition>();61private _elementObservers = new WeakMap<HTMLElement, MutationObserver>();62private _structuralObserver: MutationObserver;63private _observerIsConnected: boolean = false;6465constructor(66private readonly targetWindow: CodeWindow67) {68super();6970// Initialize live collections for each overlay selector71for (const className of OVERLAY_CLASSES) {72// We need dynamic collections for overlay detection, using getElementsByClassName is intentional here73// eslint-disable-next-line no-restricted-syntax74this._overlayCollections.set(className, this.targetWindow.document.getElementsByClassName(className));75}7677// Setup structural observer to watch for element additions/removals78this._structuralObserver = new MutationObserver((mutations) => {79let didRemove = false;80for (const mutation of mutations) {81for (const node of mutation.removedNodes) {82if (this._elementObservers.has(node as HTMLElement)) {83const observer = this._elementObservers.get(node as HTMLElement);84observer?.disconnect();85this._elementObservers.delete(node as HTMLElement);86didRemove = true;87}8889if (this._overlayRectangles.delete(node as HTMLElement)) {90didRemove = true;91}92}93}94this.updateTrackedElements(didRemove);95});96}9798private *overlays(): Iterable<HTMLElement> {99for (const collection of this._overlayCollections.values()) {100for (const element of collection) {101yield element as HTMLElement;102}103}104}105106private updateTrackedElements(shouldEmit = false): void {107// Scan all overlay collections for elements and ensure they have observers108for (const overlay of this.overlays()) {109// Create a new observer for this specific element if we don't already have one110if (!this._elementObservers.has(overlay)) {111const observer = new MutationObserver(() => {112this._overlayRectangles.delete(overlay);113this._onDidChangeOverlayState.fire();114});115116// Store the observer in the WeakMap117this._elementObservers.set(overlay, observer);118119// Start observing this element120observer.observe(overlay, {121attributes: true,122attributeFilter: ['style', 'class'],123childList: true,124subtree: true125});126127shouldEmit = true;128}129}130131if (shouldEmit) {132this._onDidChangeOverlayState.fire();133}134}135136private getRect(element: HTMLElement): IDomNodePagePosition {137if (!this._overlayRectangles.has(element)) {138const rect = getDomNodePagePosition(element);139// If the observer is not connected (no listeners), do not cache rectangles as we won't know when they change.140if (!this._observerIsConnected) {141return rect;142}143this._overlayRectangles.set(element, rect);144}145return this._overlayRectangles.get(element)!;146}147148isOverlappingWithOverlays(element: HTMLElement): boolean {149const elementRect = getDomNodePagePosition(element);150151// Check against all precomputed overlay rectangles152for (const overlay of this.overlays()) {153const overlayRect = this.getRect(overlay);154if (overlayRect && this.isRectanglesOverlapping(elementRect, overlayRect)) {155return true;156}157}158159return false;160}161162private isRectanglesOverlapping(rect1: IDomNodePagePosition, rect2: IDomNodePagePosition): boolean {163// If elements are offscreen or set to zero size, consider them non-overlapping164if (rect1.width === 0 || rect1.height === 0 || rect2.width === 0 || rect2.height === 0) {165return false;166}167168return !(rect1.left + rect1.width <= rect2.left ||169rect2.left + rect2.width <= rect1.left ||170rect1.top + rect1.height <= rect2.top ||171rect2.top + rect2.height <= rect1.top);172}173174private stopTrackingElements(): void {175for (const overlay of this.overlays()) {176const observer = this._elementObservers.get(overlay);177observer?.disconnect();178}179this._overlayRectangles = new WeakMap();180this._elementObservers = new WeakMap();181}182183override dispose(): void {184this._observerIsConnected = false;185this._structuralObserver.disconnect();186this.stopTrackingElements();187188super.dispose();189}190}191192193