diff --git a/src/vs/base/browser/dom.ts b/src/vs/base/browser/dom.ts index beb9436ac70..bebc4a891b6 100644 --- a/src/vs/base/browser/dom.ts +++ b/src/vs/base/browser/dom.ts @@ -1706,18 +1706,6 @@ export class DragAndDropObserver extends Disposable { } } -export function computeClippingRect(elementOrRect: HTMLElement | DOMRectReadOnly, clipper: HTMLElement) { - const frameRect = (elementOrRect instanceof HTMLElement ? elementOrRect.getBoundingClientRect() : elementOrRect); - const rootRect = clipper.getBoundingClientRect(); - - const top = Math.max(rootRect.top - frameRect.top, 0); - const right = Math.max(frameRect.width - (frameRect.right - rootRect.right), 0); - const bottom = Math.max(frameRect.height - (frameRect.bottom - rootRect.bottom), 0); - const left = Math.max(rootRect.left - frameRect.left, 0); - - return { top, right, bottom, left }; -} - type HTMLElementAttributeKeys = Partial<{ [K in keyof T]: T[K] extends Function ? never : T[K] extends object ? HTMLElementAttributeKeys : T[K] }>; type ElementAttributes = HTMLElementAttributeKeys & Record; type RemoveHTMLElement = T extends HTMLElement ? never : T; diff --git a/src/vs/workbench/contrib/webview/browser/overlayWebview.ts b/src/vs/workbench/contrib/webview/browser/overlayWebview.ts index 7cbc534fd58..1626626a2d5 100644 --- a/src/vs/workbench/contrib/webview/browser/overlayWebview.ts +++ b/src/vs/workbench/contrib/webview/browser/overlayWebview.ts @@ -3,7 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { computeClippingRect, Dimension } from 'vs/base/browser/dom'; +import { Dimension } from 'vs/base/browser/dom'; +import { FastDomNode } from 'vs/base/browser/fastDomNode'; import { IMouseWheelEvent } from 'vs/base/browser/mouseEvent'; import { Emitter, Event } from 'vs/base/common/event'; import { Disposable, DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle'; @@ -46,6 +47,8 @@ export class OverlayWebview extends Disposable implements IOverlayWebview { public readonly providedViewType?: string; public readonly origin: string; + private _container: FastDomNode | undefined; + public constructor( initInfo: WebviewInitInfo, @ILayoutService private readonly _layoutService: ILayoutService, @@ -75,7 +78,7 @@ export class OverlayWebview extends Disposable implements IOverlayWebview { override dispose() { this._isDisposed = true; - this._container?.remove(); + this._container?.domNode.remove(); this._container = undefined; for (const msg of this._firstLoadPendingMessages) { @@ -88,7 +91,6 @@ export class OverlayWebview extends Disposable implements IOverlayWebview { super.dispose(); } - private _container: HTMLElement | undefined; public get container(): HTMLElement { if (this._isDisposed) { @@ -96,16 +98,19 @@ export class OverlayWebview extends Disposable implements IOverlayWebview { } if (!this._container) { - this._container = document.createElement('div'); - this._container.id = `webview-${this.id}`; - this._container.style.visibility = 'hidden'; + const node = document.createElement('div'); + node.id = `webview-${this.id}`; + node.style.position = 'absolute'; + node.style.overflow = 'hidden'; + this._container = new FastDomNode(node); + this._container.setVisibility('hidden'); // Webviews cannot be reparented in the dom as it will destroy their contents. // Mount them to a high level node to avoid this. - this._layoutService.container.appendChild(this._container); + this._layoutService.container.appendChild(node); } - return this._container; + return this._container.domNode; } public claim(owner: any, scopedContextKeyService: IContextKeyService | undefined) { @@ -144,7 +149,7 @@ export class OverlayWebview extends Disposable implements IOverlayWebview { this._owner = undefined; if (this._container) { - this._container.style.visibility = 'hidden'; + this._container.setVisibility('hidden'); } if (this._options.retainContextWhenHidden) { @@ -159,24 +164,23 @@ export class OverlayWebview extends Disposable implements IOverlayWebview { } public layoutWebviewOverElement(element: HTMLElement, dimension?: Dimension, clippingContainer?: HTMLElement) { - if (!this._container || !this._container.parentElement) { + if (!this._container || !this._container.domNode.parentElement) { return; } const frameRect = element.getBoundingClientRect(); - const containerRect = this._container.parentElement.getBoundingClientRect(); - const parentBorderTop = (containerRect.height - this._container.parentElement.clientHeight) / 2.0; - const parentBorderLeft = (containerRect.width - this._container.parentElement.clientWidth) / 2.0; - this._container.style.position = 'absolute'; - this._container.style.overflow = 'hidden'; - this._container.style.top = `${frameRect.top - containerRect.top - parentBorderTop}px`; - this._container.style.left = `${frameRect.left - containerRect.left - parentBorderLeft}px`; - this._container.style.width = `${dimension ? dimension.width : frameRect.width}px`; - this._container.style.height = `${dimension ? dimension.height : frameRect.height}px`; + const containerRect = this._container.domNode.parentElement.getBoundingClientRect(); + const parentBorderTop = (containerRect.height - this._container.domNode.parentElement.clientHeight) / 2.0; + const parentBorderLeft = (containerRect.width - this._container.domNode.parentElement.clientWidth) / 2.0; + + this._container.setTop(frameRect.top - containerRect.top - parentBorderTop); + this._container.setLeft(frameRect.left - containerRect.left - parentBorderLeft); + this._container.setWidth(dimension ? dimension.width : frameRect.width); + this._container.setHeight(dimension ? dimension.height : frameRect.height); if (clippingContainer) { const { top, left, right, bottom } = computeClippingRect(frameRect, clippingContainer); - this._container.style.clipPath = `polygon(${left}px ${top}px, ${right}px ${top}px, ${right}px ${bottom}px, ${left}px ${bottom}px)`; + this._container.domNode.style.clipPath = `polygon(${left}px ${top}px, ${right}px ${top}px, ${right}px ${bottom}px, ${left}px ${bottom}px)`; } } @@ -369,3 +373,14 @@ export class OverlayWebview extends Disposable implements IOverlayWebview { this._webview.value?.setContextKeyService(contextKeyService); } } + +function computeClippingRect(frameRect: DOMRectReadOnly, clipper: HTMLElement) { + const rootRect = clipper.getBoundingClientRect(); + + const top = Math.max(rootRect.top - frameRect.top, 0); + const right = Math.max(frameRect.width - (frameRect.right - rootRect.right), 0); + const bottom = Math.max(frameRect.height - (frameRect.bottom - rootRect.bottom), 0); + const left = Math.max(rootRect.left - frameRect.left, 0); + + return { top, right, bottom, left }; +} diff --git a/src/vs/workbench/contrib/webviewView/browser/webviewViewPane.ts b/src/vs/workbench/contrib/webviewView/browser/webviewViewPane.ts index 220eed808a2..b7e590d44f4 100644 --- a/src/vs/workbench/contrib/webviewView/browser/webviewViewPane.ts +++ b/src/vs/workbench/contrib/webviewView/browser/webviewViewPane.ts @@ -3,10 +3,11 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { addDisposableListener, computeClippingRect, EventType } from 'vs/base/browser/dom'; +import { addDisposableListener, Dimension, EventType, findParentWithClass } from 'vs/base/browser/dom'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { Emitter } from 'vs/base/common/event'; import { DisposableStore, IDisposable, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { withNullAsUndefined } from 'vs/base/common/types'; import { generateUuid } from 'vs/base/common/uuid'; import { MenuId } from 'vs/platform/actions/common/actions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -154,11 +155,7 @@ export class WebviewViewPane extends ViewPane { protected override layoutBody(height: number, width: number): void { super.layoutBody(height, width); - if (!this._webview.value) { - return; - } - - this.layoutWebview(); + this.layoutWebview(new Dimension(width, height)); } private updateTreeVisibility() { @@ -284,7 +281,7 @@ export class WebviewViewPane extends ViewPane { this.layoutWebview(); } - private layoutWebview() { + private layoutWebview(dimension?: Dimension) { const webviewEntry = this._webview.value; if (!this._container || !webviewEntry) { return; @@ -294,12 +291,7 @@ export class WebviewViewPane extends ViewPane { this._rootContainer = this.findRootContainer(this._container); } - webviewEntry.layoutWebviewOverElement(this._container); - - if (this._rootContainer) { - const { top, left, right, bottom } = computeClippingRect(this._container, this._rootContainer); - webviewEntry.container.style.clipPath = `polygon(${left}px ${top}px, ${right}px ${top}px, ${right}px ${bottom}px, ${left}px ${bottom}px)`; - } + webviewEntry.layoutWebviewOverElement(this._container, dimension, this._rootContainer); // Temporary fix for https://github.com/microsoft/vscode/issues/110450 // There is an animation that lasts about 200ms, update the webview positioning once this animation is complete. @@ -308,13 +300,6 @@ export class WebviewViewPane extends ViewPane { } private findRootContainer(container: HTMLElement): HTMLElement | undefined { - for (let el: Node | null = container; el; el = el.parentNode) { - if (el instanceof HTMLElement) { - if (el.classList.contains('monaco-scrollable-element')) { - return el; - } - } - } - return undefined; + return withNullAsUndefined(findParentWithClass(container, 'monaco-scrollable-element')); } }