Skip to content

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

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

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions