Skip to content

set_viewport is discarded after navigation, silently truncating screenshot(full: false) #592

@brendon

Description

@brendon

First surfaced in the discussion on #580 (the captureBeyondViewport change). This is a separate, pre-existing gap, filing it as its own issue.

Summary

A viewport set with set_viewport before a navigation is silently lost once the page navigates/redirects. set_viewport issues Emulation.setDeviceMetricsOverride, which is per-document CDP state. Chrome's RenderDocument feature swaps to a fresh document (new RenderFrameHost) on navigation, discarding that override. After the swap, window.innerWidth/innerHeight revert to the browser's real window size.

Because screenshot(full: false) derives its capture clip from live window.innerWidth/innerHeight (viewport_area), the result is silently clipped to that smaller window — a top-left crop at plausible-but-wrong dimensions.

Puppeteer and Playwright both re-apply viewport emulation after each navigation; Ferrum does not — that's the underlying gap.

Why it's surfacing now

RenderDocument is rolling out in Chrome, and in puppeteer-launched setups it became active when puppeteer-core 24.41.0 stopped disabling it (puppeteer#14745, "remove RenderDocument from disabled Chrome features"). Before that, the override happened to survive navigation, so the missing re-apply was invisible. As RenderDocument becomes the default more broadly, more Ferrum users will hit this.

(Bisected against browserless/chromium with Ferrum 0.17.2: the behavior flips exactly at the browserless release that bumped puppeteer-core 24.40→24.42 — both sides running the same Chrome 147, so it's the puppeteer flag change, not a Chrome version bump.)

Reproduction

require "ferrum"

# a window smaller than the requested viewport makes the crop obvious
browser = Ferrum::Browser.new(window_size: [800, 600])
page = browser.create_page

page.set_viewport(width: 1280, height: 720)
page.go_to("http://google.com")   # ends on a different origin -> document swap

p page.evaluate("[window.innerWidth, window.innerHeight]")
# expected: [1280, 720]
# actual:   [800, 600]            <- device-metrics override discarded

page.screenshot(path: "out.png", full: false)
# expected: a 1280x720 capture
# actual:   only the top-left 800x600 region

Expected vs actual

  • Expected: the viewport set via set_viewport persists across navigations (as in Puppeteer/Playwright), and screenshot(full: false) captures the full requested viewport.
  • Actual: the override is dropped on navigation; the screenshot is clipped to the browser's real window size.

Suggested fix

  1. Track the active viewport on the page and re-apply it after navigations (e.g. on main-frame Page.frameNavigated / new-document events), matching Puppeteer/Playwright. Note: re-sending setDeviceMetricsOverride with identical params after the reset is a no-op in Chrome, so the re-apply must clear first (Emulation.clearDeviceMetricsOverride) or only fire when the live viewport no longer matches the requested one.
  2. Optionally/additionally, have screenshot(full: false) clip to the explicitly-requested viewport rather than live window.innerWidth/innerHeight.

Workaround

Re-assert the viewport immediately before the screenshot:

page.command("Emulation.clearDeviceMetricsOverride")   # clear is required — an identical re-set is a no-op
page.set_viewport(width: 1280, height: 720, scale_factor: 1)

Environment

  • ferrum 0.17.2
  • Chrome 147 (reproduced via browserless/chromium; also reproducible with a locally-launched browser whose window is smaller than the requested viewport)

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions