Files
orca/src/shared/browser-viewport-presets.ts
T
JinjingandOrca 46a26252bb feat(browser): viewport-size emulation via CDP (#1369)
* feat(browser): viewport-size emulation via CDP

Adds a Viewport Size submenu in the browser toolbar "…" menu.
Presets apply width/height/deviceScaleFactor/mobile/touch via
Emulation.setDeviceMetricsOverride + setTouchEmulationEnabled, and
swap the UA to a mobile iPhone CriOS string on mobile presets.
Responsive clears the override. Selection persists per-tab and
re-applies on dom-ready so it survives navigations.

Co-authored-by: Orca <help@stably.ai>

* chore(browser): rename 'Responsive' viewport option to 'Default'

Co-authored-by: Orca <help@stably.ai>

* fix(browser): harden viewport emulation — serialize, validate, client-hints

- Chain per-tab setViewportOverride calls to prevent rapid-toggle races
- Validate viewport metrics at IPC trust boundary (reject non-finite/out-of-range)
- Emit userAgentMetadata alongside mobile UA to avoid UA/CH mismatch
- Always reapply on dom-ready (incl. null) to clear stale emulation
- Persist viewportPresetId in session schema (optional+nullable for back-compat)
- Convert preset submenu to DropdownMenuRadioGroup for a11y
- Log debugger.attach failures and cover with a unit test

Co-authored-by: Orca <help@stably.ai>

---------

Co-authored-by: Orca <help@stably.ai>
2026-05-02 20:22:40 -07:00

93 lines
2.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import type { BrowserViewportOverride, BrowserViewportPresetId } from './types'
export type BrowserViewportPreset = {
id: BrowserViewportPresetId
label: string
width: number
height: number
deviceScaleFactor: number
mobile: boolean
}
// Why: deviceScaleFactor=2 on mobile/tablet mirrors Chrome's device toolbar so
// retina-aware sites pick the correct asset tier; mobile=true enables touch
// emulation + small-viewport CSS. Dimensions match Chrome DevTools presets.
export const BROWSER_VIEWPORT_PRESETS = [
{
id: 'mobile-s',
label: 'Mobile S — 320 × 568',
width: 320,
height: 568,
deviceScaleFactor: 2,
mobile: true
},
{
id: 'mobile-m',
label: 'Mobile M — 375 × 667',
width: 375,
height: 667,
deviceScaleFactor: 2,
mobile: true
},
{
id: 'mobile-l',
label: 'Mobile L — 425 × 812',
width: 425,
height: 812,
deviceScaleFactor: 2,
mobile: true
},
{
id: 'tablet',
label: 'Tablet — 768 × 1024',
width: 768,
height: 1024,
deviceScaleFactor: 2,
mobile: true
},
{
id: 'laptop',
label: 'Laptop — 1024 × 768',
width: 1024,
height: 768,
deviceScaleFactor: 1,
mobile: false
},
{
id: 'laptop-l',
label: 'Laptop L — 1440 × 900',
width: 1440,
height: 900,
deviceScaleFactor: 1,
mobile: false
},
{
id: 'desktop',
label: 'Desktop — 1920 × 1080',
width: 1920,
height: 1080,
deviceScaleFactor: 1,
mobile: false
}
] as const satisfies readonly BrowserViewportPreset[]
export function getBrowserViewportPreset(
id: BrowserViewportPresetId | null | undefined
): BrowserViewportPreset | null {
if (!id) {
return null
}
return BROWSER_VIEWPORT_PRESETS.find((p) => p.id === id) ?? null
}
export function browserViewportPresetToOverride(
preset: BrowserViewportPreset
): BrowserViewportOverride {
return {
width: preset.width,
height: preset.height,
deviceScaleFactor: preset.deviceScaleFactor,
mobile: preset.mobile
}
}