mirror of
https://github.com/stablyai/orca.git
synced 2026-10-19 16:03:26 +00:00
* 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>
93 lines
2.0 KiB
TypeScript
93 lines
2.0 KiB
TypeScript
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
|
||
}
|
||
}
|