diff --git a/src/vs/workbench/contrib/browserView/electron-browser/features/webContentsViewRendererFeature.ts b/src/vs/workbench/contrib/browserView/electron-browser/features/webContentsViewRendererFeature.ts index 5ea30e44a34c3f..ccca278dd48c2b 100644 --- a/src/vs/workbench/contrib/browserView/electron-browser/features/webContentsViewRendererFeature.ts +++ b/src/vs/workbench/contrib/browserView/electron-browser/features/webContentsViewRendererFeature.ts @@ -14,51 +14,17 @@ import { ILogService } from '../../../../../platform/log/common/log.js'; import { IBrowserViewKeyDownEvent, } from '../../../../../platform/browserView/common/browserView.js'; -import { BrowserViewSharingState, IBrowserViewModel } from '../../common/browserView.js'; +import { IBrowserViewModel } from '../../common/browserView.js'; import { BrowserEditor, BrowserEditorContribution, BrowserWidgetLocation, IBrowserEditorWidget, IContainerLayout, - IContainerLayoutPane, IContainerLayoutOverride, } from '../browserEditor.js'; import { BrowserOverlayManager, BrowserOverlayType } from '../overlayManager.js'; -const BROWSER_CONTAINER_BORDER_PADDING = 3; - -/** - * Returns room for the browser page border unless the Modern UI editor card already supplies the resting boundary. - */ -export function getBrowserContainerPadding(browserContainer: HTMLElement, isShared: boolean, hasDeviceEmulation: boolean): { top: number; right: number; bottom: number; left: number } { - const editorPart = browserContainer.closest('.part.editor'); - const hasModernUIEditorCard = !!editorPart - && !editorPart.classList.contains('modal-editor-part') - && !!editorPart.closest('.monaco-workbench.modern-ui'); - const padding = hasModernUIEditorCard && !isShared && !hasDeviceEmulation ? 0 : BROWSER_CONTAINER_BORDER_PADDING; - return { top: padding, right: padding, bottom: padding, left: padding }; -} - -/** - * Snaps Browser bounds to host pixels, covering the available area when the editor card owns its boundary. - */ -export function snapBrowserContainerLayout(current: IContainerLayout, pane: IContainerLayoutPane, zoomFactor: number, coverAvailableArea: boolean): IContainerLayout { - const snapDown = (value: number) => Math.floor(value * zoomFactor) / zoomFactor; - const snapUp = (value: number) => Math.ceil(value * zoomFactor) / zoomFactor; - const absoluteLeft = pane.originX + (current.left ?? 0); - const absoluteTop = pane.originY + (current.top ?? 0); - const snappedAbsoluteLeft = snapDown(absoluteLeft); - const snappedAbsoluteTop = snapDown(absoluteTop); - return { - ...current, - width: coverAvailableArea && current.width !== 0 ? snapUp(absoluteLeft + current.width) - snappedAbsoluteLeft : snapDown(current.width), - height: coverAvailableArea && current.height !== 0 ? snapUp(absoluteTop + current.height) - snappedAbsoluteTop : snapDown(current.height), - left: snappedAbsoluteLeft - pane.originX, - top: snappedAbsoluteTop - pane.originY, - }; -} - /** * Default browser renderer: drives a Chromium WebContentsView. * @@ -122,16 +88,31 @@ class WebContentsViewRendererFeature extends BrowserEditorContribution { } override beforeContainerLayout(): IContainerLayoutOverride { - const padding = getBrowserContainerPadding( - this.editor.browserContainer, - this._model?.sharingState === BrowserViewSharingState.Shared, - !!this._model?.device, - ); - const usesModernUIEditorCardBoundary = padding.top === 0; - return { - padding, - compute: (current, pane): IContainerLayout => snapBrowserContainerLayout(current, pane, getZoomFactor(this.editor.window), usesModernUIEditorCardBoundary), + padding: { top: 3, right: 3, bottom: 3, left: 3 }, + + // Snap CSS-pixel values down so `v × hostZoom` is an exact integer: + // main places the WCV at `round(v × hostZoom) × systemDPR` physical + // pixels while CSS renders it at `v × hostZoom × systemDPR`, so this + // collapses main's rounding to a no-op and keeps the WebContentsView + // aligned with the placeholder screenshot. We snap the absolute + // origin (pane origin + local offset) then derive the corresponding + // local position so the DOM element and the WCV land on the same + // physical pixel. Runs late so it refines whatever sizing upstream + // contributions (e.g. device emulation) produced. + compute: (current, pane): IContainerLayout => { + const z = getZoomFactor(this.editor.window); + const snap = (v: number) => Math.floor(v * z) / z; + const absLeft = pane.originX + (current.left ?? 0); + const absTop = pane.originY + (current.top ?? 0); + return { + ...current, + width: snap(current.width), + height: snap(current.height), + left: snap(absLeft) - pane.originX, + top: snap(absTop) - pane.originY, + }; + }, priority: 1000, }; } @@ -202,7 +183,6 @@ class WebContentsViewRendererFeature extends BrowserEditorContribution { store.add(model.onDidKeyCommand(keyEvent => void this._handleKeyEvent(keyEvent))); store.add(model.onDidNavigate(() => this._refresh(true))); store.add(model.onDidChangeLoadingState(() => this._refresh(true))); - store.add(model.onDidChangeSharingState(() => this.editor.layoutBrowserContainer())); this._refresh(); void this._doScreenshot(); diff --git a/src/vs/workbench/contrib/browserView/test/electron-browser/features/webContentsViewRendererFeature.test.ts b/src/vs/workbench/contrib/browserView/test/electron-browser/features/webContentsViewRendererFeature.test.ts deleted file mode 100644 index 0dea2f4ce8c53f..00000000000000 --- a/src/vs/workbench/contrib/browserView/test/electron-browser/features/webContentsViewRendererFeature.test.ts +++ /dev/null @@ -1,71 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import assert from 'assert'; -import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../../base/test/common/utils.js'; -import { getBrowserContainerPadding, snapBrowserContainerLayout } from '../../../electron-browser/features/webContentsViewRendererFeature.js'; - -suite('WebContentsViewRendererFeature', () => { - - ensureNoDisposablesAreLeakedInTestSuite(); - - function createBrowserContainer(modernUI: boolean, modalEditor = false): HTMLElement { - const workbench = document.createElement('div'); - workbench.className = modernUI ? 'monaco-workbench modern-ui' : 'monaco-workbench'; - const editorPart = document.createElement('div'); - editorPart.className = modalEditor ? 'part editor modal-editor-part' : 'part editor'; - const browserContainer = document.createElement('div'); - browserContainer.className = 'browser-container'; - workbench.appendChild(editorPart); - editorPart.appendChild(browserContainer); - return browserContainer; - } - - test('removes only the redundant Modern UI resting border reservation', () => { - const classicBrowser = createBrowserContainer(false); - const modernBrowser = createBrowserContainer(true); - const modalModernBrowser = createBrowserContainer(true, true); - - assert.deepStrictEqual({ - classicResting: getBrowserContainerPadding(classicBrowser, false, false), - modernResting: getBrowserContainerPadding(modernBrowser, false, false), - modernShared: getBrowserContainerPadding(modernBrowser, true, false), - modernDeviceEmulation: getBrowserContainerPadding(modernBrowser, false, true), - modalModernResting: getBrowserContainerPadding(modalModernBrowser, false, false), - fractionalZoomModernResting: snapBrowserContainerLayout( - { width: 10.5, height: 20.25, left: 0, top: 0 }, - { width: 10.5, height: 20.25, originX: 0.25, originY: 0.75 }, - 2, - true, - ), - fractionalZoomReservedBorder: snapBrowserContainerLayout( - { width: 10.5, height: 20.25, left: 0, top: 0 }, - { width: 10.5, height: 20.25, originX: 0.25, originY: 0.75 }, - 2, - false, - ), - }, { - classicResting: { top: 3, right: 3, bottom: 3, left: 3 }, - modernResting: { top: 0, right: 0, bottom: 0, left: 0 }, - modernShared: { top: 3, right: 3, bottom: 3, left: 3 }, - modernDeviceEmulation: { top: 3, right: 3, bottom: 3, left: 3 }, - modalModernResting: { top: 3, right: 3, bottom: 3, left: 3 }, - fractionalZoomModernResting: { width: 11, height: 20.5, left: -0.25, top: -0.25 }, - fractionalZoomReservedBorder: { width: 10.5, height: 20, left: -0.25, top: -0.25 }, - }); - }); - - test('preserves collapsed dimensions when covering fractional host pixels', () => { - const pane = { width: 10.5, height: 20.25, originX: 0.25, originY: 0.75 }; - - assert.deepStrictEqual({ - zeroWidth: snapBrowserContainerLayout({ width: 0, height: 20.25, left: 0, top: 0 }, pane, 2, true), - zeroHeight: snapBrowserContainerLayout({ width: 10.5, height: 0, left: 0, top: 0 }, pane, 2, true), - }, { - zeroWidth: { width: 0, height: 20.5, left: -0.25, top: -0.25 }, - zeroHeight: { width: 11, height: 0, left: -0.25, top: -0.25 }, - }); - }); -});