Skip to content

paint: Correctly apply viewport <meta> initial-scale#43160

Open
shubhamg13 wants to merge 2 commits intoservo:mainfrom
shubhamg13:veiwport_resize
Open

paint: Correctly apply viewport <meta> initial-scale#43160
shubhamg13 wants to merge 2 commits intoservo:mainfrom
shubhamg13:veiwport_resize

Conversation

@shubhamg13
Copy link
Copy Markdown
Member

@shubhamg13 shubhamg13 commented Mar 11, 2026

Fixup: #40055

Instead of applying initial scale to page.

Specs: https://www.w3.org/TR/css-viewport-1/#the-viewport

This specification introduces a way of overriding the size of the viewport provided by the user agent (UA). Because of this, we need to introduce the difference between the initial viewport and the actual viewport.

initial viewport
This refers to the viewport before any UA or author styles have overridden the viewport given by the window or viewing area of the UA. Note that the initial viewport size will change with the size of the window or viewing area.
actual viewport
This is the viewport you get after processing the viewport tag.

Sample Page: viewport_meta_with_style_2.html

Testing: tests/webview.rs: test_viewport_meta_tag_initial_scale

Fixes: #39002

@codecov-commenter
Copy link
Copy Markdown

codecov-commenter commented Mar 11, 2026

⚠️ JUnit XML file not found

The CLI was unable to find any JUnit XML files to upload.
For more help, visit our troubleshooting guide.

@shubhamg13 shubhamg13 force-pushed the veiwport_resize branch 3 times, most recently from 4f0c72f to 81110a7 Compare March 11, 2026 04:45
@shubhamg13 shubhamg13 marked this pull request as ready for review March 11, 2026 04:46
@servo-highfive servo-highfive added the S-awaiting-review There is new code that needs to be reviewed. label Mar 11, 2026
let load_webview = webview.clone();
let _ = servo_test.spin(move || load_webview.load_status() != LoadStatus::Complete);
show_webview_and_wait_for_rendering_to_be_ready(&servo_test, &webview, &delegate);
let eval_visual_viewport = |attr: &str| {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function is not used in this PR.

@xiaochengh
Copy link
Copy Markdown
Contributor

@stevennovaryo might know better about this one.

@shubhamg13 shubhamg13 marked this pull request as draft March 26, 2026 09:25
@shubhamg13 shubhamg13 self-assigned this Mar 26, 2026
@shubhamg13 shubhamg13 marked this pull request as ready for review March 26, 2026 10:09
@shubhamg13 shubhamg13 requested a review from xiaochengh March 26, 2026 10:21
@shubhamg13 shubhamg13 marked this pull request as draft March 26, 2026 10:21
@shubhamg13 shubhamg13 changed the title paint: Resize layout viewport according to viewport <meta> initial-scale paint: Correctly apply viewport <meta> initial-scale Mar 26, 2026
@shubhamg13 shubhamg13 marked this pull request as ready for review March 26, 2026 11:00
@shubhamg13 shubhamg13 added the T-linux-wpt Do a try run of the WPT label Mar 26, 2026
@github-actions github-actions bot removed the T-linux-wpt Do a try run of the WPT label Mar 26, 2026
@github-actions
Copy link
Copy Markdown

🔨 Triggering try run (#23597795448) for Linux (WPT)

@github-actions
Copy link
Copy Markdown

Test results for linux-wpt from try job (#23597795448):

Flaky unexpected result (33)
  • OK /_mozilla/css/offset_properties_inline.html (#40543)
    • FAIL [expected PASS] subtest: offsetTop

      assert_equals: offsetTop of #inline-1 should be 0. expected 0 but got -1
      

    • FAIL [expected PASS] subtest: offsetLeft

      assert_equals: offsetLeft of #inline-2 should be 40. expected 40 but got 25
      

  • OK /_mozilla/mozilla/getBoundingClientRect.html (#39668)
    • FAIL [expected PASS] subtest: getBoundingClientRect 1

      assert_equals: expected 62 but got 60.35
      

  • CRASH [expected ERROR] /_webgl/conformance2/misc/uninitialized-test-2.html (#41656)
  • TIMEOUT [expected OK] /content-security-policy/inheritance/document-write-iframe.html (#41195)
    • TIMEOUT [expected PASS] subtest: document.open() keeps inherited CSPs on transient about:blank.

      Test timed out
      

  • CRASH [expected OK] /content-security-policy/meta/sandbox-iframe.html (#43478)
  • FAIL [expected PASS] /css/css-backgrounds/background-size-042.html
  • OK [expected ERROR] /fetch/fetch-later/quota/same-origin-iframe/multiple-iframes.https.window.html (#35176)
  • TIMEOUT /fetch/metadata/generated/css-images.sub.tentative.html (#29047)
    • FAIL [expected PASS] subtest: content sec-fetch-site - HTTPS downgrade-upgrade

      assert_unreached: Reached unreachable code
      

  • OK /fetch/metadata/window-open.https.sub.html (#40339)
    • FAIL [expected PASS] subtest: Cross-site window, forced, reloaded

      The operation is insecure.
      

  • OK /html/browsers/browsing-the-web/navigating-across-documents/navigation-unload-same-origin.window.html (#29049)
    • PASS [expected FAIL] subtest: Same-origin navigation started from unload handler must be ignored
  • CRASH [expected OK] /html/browsers/browsing-the-web/unloading-documents/beforeunload-canceling.html
  • CRASH [expected OK] /html/browsers/sandboxing/sandbox-initial-empty-document-toward-same-origin.html (#35948)
  • OK [expected TIMEOUT] /html/infrastructure/urls/base-url/document-base-url-window-initiator-is-not-opener.https.window.html (#30970)
  • OK /html/semantics/document-metadata/the-meta-element/pragma-directives/attr-meta-http-equiv-refresh/allow-scripts-flag-changing-2.html (#39703)
    • FAIL [expected PASS] subtest: Meta refresh of the original iframe is not blocked if moved into a sandboxed iframe

      uncaught exception: Error: assert_unreached: The iframe into which the meta was moved must not refresh Reached unreachable code
      

  • OK /html/semantics/embedded-content/media-elements/audio_loop_seek_to_eos.html (#41226)
    • PASS [expected FAIL] subtest: seeking to the end of looping audio
  • OK [expected TIMEOUT] /html/semantics/embedded-content/media-elements/src_object_blob.html (#40340)
    • PASS [expected TIMEOUT] subtest: HTMLMediaElement.srcObject blob
  • TIMEOUT [expected OK] /html/semantics/embedded-content/the-iframe-element/iframe_sandbox_navigate_other_frame_popup.sub.html (#39702)
    • TIMEOUT [expected FAIL] subtest: Sandboxed iframe can not navigate other frame's popup

      Test timed out
      

  • OK /html/semantics/forms/form-submission-0/jsurl-form-submit.tentative.html (#36489)
    • PASS [expected FAIL] subtest: Verifies that form submissions scheduled inside javascript: urls take precedence over the javascript: url's return value.
  • CRASH [expected ERROR] /html/semantics/forms/the-select-element/customizable-select/select-appearance-button-after-span.html
  • OK /html/semantics/scripting-1/the-script-element/execution-timing/077.html (#22139)
    • FAIL [expected PASS] subtest: adding several types of scripts through the DOM and removing some of them confuses scheduler

      assert_array_equals: expected property 1 to be "Script #1 ran" but got "Script #3 ran" (expected array ["Script #2 ran", "Script #1 ran", "Script #3 ran", "Script #4 ran"] got ["Script #2 ran", "Script #3 ran", "Script #4 ran", "Script #1 ran"])
      

  • TIMEOUT [expected OK] /html/user-activation/navigation-state-reset-sameorigin.html
    • TIMEOUT [expected PASS] subtest: Post-navigation state reset.

      Test timed out
      

  • PASS [expected FAIL] /png/apng/acTL-plays-two.html (#41191)
  • TIMEOUT [expected OK] /preload/modulepreload-sri-importmap.html (#43354)
    • TIMEOUT [expected PASS] subtest: Script should not be loaded if modulepreload's integrity is invalid

      Test timed out
      

  • TIMEOUT [expected OK] /preload/modulepreload-sri.html (#43354)
    • TIMEOUT [expected PASS] subtest: Script should not be loaded if modulepreload's integrity is invalid

      Test timed out
      

  • OK /resource-timing/test_resource_timing.html (#25720)
    • PASS [expected FAIL] subtest: PerformanceEntry has correct name, initiatorType, startTime, and duration (xmlhttprequest)
  • OK /resource-timing/test_resource_timing.https.html (#25216)
    • PASS [expected FAIL] subtest: PerformanceEntry has correct name, initiatorType, startTime, and duration (xmlhttprequest)
  • OK [expected TIMEOUT] /trusted-types/trusted-types-navigation.html?26-30 (#38807)
    • PASS [expected TIMEOUT] subtest: Navigate a window via form-submission with javascript:-urls in report-only mode.
    • PASS [expected NOTRUN] subtest: Navigate a window via form-submission with javascript:-urls w/ default policy in report-only mode.
    • PASS [expected NOTRUN] subtest: Navigate a frame via form-submission with javascript:-urls in enforcing mode.
    • PASS [expected NOTRUN] subtest: Navigate a frame via form-submission with javascript:-urls w/ default policy in enforcing mode.
  • TIMEOUT /trusted-types/trusted-types-navigation.html?31-35 (#38034)
    • TIMEOUT [expected PASS] subtest: Navigate a frame via form-submission with javascript:-urls in report-only mode.

      Test timed out
      

    • NOTRUN [expected TIMEOUT] subtest: Navigate a frame via form-submission with javascript:-urls w/ default policy in report-only mode.
  • PASS [expected TIMEOUT] /visual-viewport/viewport-apply-initial-scale-after-navigation.html (#41582)
  • OK /webdriver/tests/classic/element_click/scroll_into_view.py
    • FAIL [expected PASS] subtest: test_scroll_into_view

      webdriver.error.NoSuchElementException: no such element (404)
      

  • OK /webdriver/tests/classic/element_click/shadow_dom.py
    • FAIL [expected PASS] subtest: test_shadow_element_click[host_element]

      webdriver.error.NoSuchWindowException: no such window (404): No such window
      

  • OK /webdriver/tests/classic/fullscreen_window/from_minimized_window.py
    • ERROR [expected FAIL] subtest: test_fullscreen_from_minimized_window

      setup error: webdriver.error.NoSuchWindowException: no such window (404): No such window
      

  • OK /xhr/send-redirect.htm (#32026)
    • FAIL [expected PASS] subtest: XMLHttpRequest: send() - Redirects (basics) (302, GET, content.py)

      assert_equals: expected (string) "GET" but got (object) null
      

Stable unexpected results that are known to be intermittent (19)
  • TIMEOUT /FileAPI/url/url-in-tags-revoke.window.html (#19978)
    • PASS [expected TIMEOUT] subtest: Fetching a blob URL immediately before revoking it works in &lt;script&gt; tags.
  • FAIL [expected PASS] /_mozilla/mozilla/sslfail.html (#10760)
  • TIMEOUT [expected OK] /_mozilla/mozilla/window_resize_event.html (#36741)
    • TIMEOUT [expected PASS] subtest: Popup onresize event fires after resizeTo

      Test timed out
      

  • OK /_webgl/conformance/textures/misc/texture-upload-size.html (#21770)
    • FAIL [expected PASS] subtest: WebGL test #45

      assert_true: Texture was smaller than the expected size 2x2 expected true got false
      

    • FAIL [expected PASS] subtest: WebGL test #47

      assert_true: getError expected: INVALID_VALUE. Was NO_ERROR : when calling texSubImage2D with the same texture upload with offset 1, 1 expected true got false
      

    • FAIL [expected PASS] subtest: WebGL test #49

      assert_true: Texture was smaller than the expected size 2x2 expected true got false
      

    • FAIL [expected PASS] subtest: WebGL test #51

      assert_true: getError expected: INVALID_VALUE. Was NO_ERROR : when calling texSubImage2D with the same texture upload with offset 1, 1 expected true got false
      

    • PASS [expected FAIL] subtest: WebGL test #53
    • PASS [expected FAIL] subtest: WebGL test #55
    • PASS [expected FAIL] subtest: WebGL test #57
    • PASS [expected FAIL] subtest: WebGL test #59
    • FAIL [expected PASS] subtest: WebGL test #61

      assert_true: Texture was smaller than the expected size 2x2 expected true got false
      

    • FAIL [expected PASS] subtest: WebGL test #63

      assert_true: getError expected: INVALID_VALUE. Was NO_ERROR : when calling texSubImage2D with the same texture upload with offset 1, 1 expected true got false
      

    • And 14 more unexpected results...
  • TIMEOUT [expected OK] /credential-management/credentialscontainer-frame-basics.https.html (#39430)
    • TIMEOUT [expected FAIL] subtest: navigator.credentials should be undefined in documents generated from data: URLs.

      Test timed out
      

  • OK /css/css-fonts/generic-family-keywords-001.html (#37467)
    • PASS [expected FAIL] subtest: @font-face matching for quoted and unquoted generic(fangsong)
  • OK /css/css-fonts/generic-family-keywords-003.html (#38994)
    • PASS [expected FAIL] subtest: @font-face matching for quoted and unquoted sans-serif (drawing text in a canvas)
    • PASS [expected FAIL] subtest: @font-face matching for quoted and unquoted math (drawing text in a canvas)
    • PASS [expected FAIL] subtest: @font-face matching for quoted and unquoted generic(fangsong) (drawing text in a canvas)
    • PASS [expected FAIL] subtest: @font-face matching for quoted and unquoted generic(kai) (drawing text in a canvas)
    • PASS [expected FAIL] subtest: @font-face matching for quoted and unquoted generic(khmer-mul) (drawing text in a canvas)
    • FAIL [expected PASS] subtest: @font-face matching for quoted and unquoted generic(nastaliq) (drawing text in a canvas)

      assert_equals: quoted generic(nastaliq) matches  @font-face rule expected 125 but got 40
      

    • PASS [expected FAIL] subtest: @font-face matching for quoted and unquoted ui-serif (drawing text in a canvas)
  • ERROR [expected OK] /fetch/fetch-later/quota/same-origin-iframe/accumulated-oversized-payload.https.window.html (#41705)
  • OK [expected ERROR] /fetch/fetch-later/quota/same-origin-iframe/sandboxed-iframe.https.window.html (#41704)
  • OK /fetch/metadata/generated/css-font-face.https.sub.tentative.html (#32732)
    • PASS [expected FAIL] subtest: sec-fetch-dest
    • PASS [expected FAIL] subtest: sec-fetch-user
    • PASS [expected FAIL] subtest: sec-fetch-storage-access - Same site
  • OK /html/browsers/browsing-the-web/navigating-across-documents/005.html (#27062)
    • FAIL [expected PASS] subtest: Link with onclick navigation and href navigation

      assert_equals: expected "href" but got "click"
      

  • OK /html/browsers/history/the-history-interface/traverse_the_history_4.html (#21383)
    • PASS [expected FAIL] subtest: Multiple history traversals, last would be aborted
  • OK /html/browsers/windows/embedded-opener-remove-frame.html (#23867)
    • FAIL [expected PASS] subtest: opener of discarded auxiliary browsing context

      assert_object_equals: property "get" expected function "function opener() {
          [native code]
      }" got function "function opener() {
          [native code]
      }"
      

  • TIMEOUT /html/interaction/focus/the-autofocus-attribute/supported-elements.html (#24145)
    • TIMEOUT [expected FAIL] subtest: Element with tabindex should support autofocus

      Test timed out
      

    • NOTRUN [expected TIMEOUT] subtest: Non-HTMLElement should not support autofocus
  • TIMEOUT /html/semantics/embedded-content/media-elements/preserves-pitch.html (#40352)
    • PASS [expected TIMEOUT] subtest: Speed-ups should not change the pitch when preservesPitch=true
    • PASS [expected NOTRUN] subtest: Slow-downs should not change the pitch when preservesPitch=true
    • TIMEOUT [expected NOTRUN] subtest: Speed-ups should change the pitch when preservesPitch=false

      Test timed out
      

  • OK /html/webappapis/dynamic-markup-insertion/document-write/iframe_005.html (#43671)
    • FAIL [expected PASS] subtest: document.write external script into iframe write back into parent

      assert_array_equals: lengths differ, expected array [1, 2, 3, 4, 5] length 5, got [1, 2] length 2
      

  • OK /touch-events/single-tap-when-touchend-listener-use-sync-xhr.html (#41175)
    • FAIL [expected PASS] subtest: Click event should be fired when touchend opens synchronous XHR

      assert_equals: expected "touchend@div, mousedown@div, mouseup@div, click@div" but got "touchend@div, mousedown@div"
      

  • OK [expected TIMEOUT] /webmessaging/with-ports/018.html (#24485)
    • PASS [expected TIMEOUT] subtest: origin of the script that invoked the method, javascript:
  • OK [expected TIMEOUT] /webmessaging/without-ports/018.html (#24485)
    • PASS [expected TIMEOUT] subtest: origin of the script that invoked the method, javascript:

@github-actions
Copy link
Copy Markdown

✨ Try run (#23597795448) succeeded.

Comment on lines +1090 to +1093
self.adjust_pinch_zoom(
self.viewport_description.initial_scale.get(),
DevicePoint::origin(),
);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am unsure whether we should apply this directly into the pinch zoom. From a simple demo it seems to affect layout like page zoom, but it also adds on into VisualViewport.scale.

For example, lets consider the following HTML:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta name="viewport" content="initial-scale=1.0">
    <style>
        #target {
            background-color: green;
            padding: 20px;
            border-radius: 8px;
            max-width: 220px;
            word-wrap: break-word;
        }
        .child {
            background-color: palegreen;
            display: inline-block;
            width: 50px;
            height: 50px;
            padding: 10px;
        }
    </style>
</head>
<body>
    <div id="target">
        <div class="child"></div>
        <div class="child"></div>
        <div class="child"></div>
        <div class="child"></div>
        <div class="child"></div>
        <div class="child"></div>
    </div>
</body>
</html>

If we change the initial-scale to a certain amount, the layout will follow the available space and the values returned by layout queries will be different as well.

Copy link
Copy Markdown
Member Author

@shubhamg13 shubhamg13 Mar 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, some values will be different as we skip parsing device-width from meta and its application in any calculation. I checked with your sample with scale 2.0 it works consistent.

As far as considering current architecture of Servo and broken bits of specs, it felt right to apply to pinch-zoom and resize layout viewport accordingly.

If you have any suggestions, I'm open to it.

github-merge-queue bot pushed a commit that referenced this pull request Apr 1, 2026
)

Scenario proposed should be best tested using unit tests.

Testing: TODO (Waiting for #43160)
Fixes: #41582

Signed-off-by: Shubham Gupta <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

S-awaiting-review There is new code that needs to be reviewed.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

<meta name=viewport> is applied when it shouldn't

5 participants