Design Doc: Support platform-controlled window-driven scaling in Chromium
---
>[!info]- Info
> Author:
[email protected]
> Publish date: 2024/05/20
> Latest update: 2024/06/13
> Bug tracker: https://issues.chromium.org/336007385
> Affected platforms: Linux/Wayland
> Status: in-progress
>
> >[!faq] Open questions / TODOs
> > - [x] How many files affected? any showstopper?
> > - [x] Cursor code? specific API for it? eg: [here](https://source.chromium.org/chromium/chromium/src/+/main:content/browser/renderer_host/render_widget_host_view_aura.cc;l=2035-2038;drc=071a85757d9d95316f83f50e1fe087a6d87908f9)
> > - [x] What about `DisplayObserver` impls?
> > - [x] How about the affected Web APIs?
> > - [x] Engage with upstream proposals and discussions
> > - [x] Finish and validate the [[#Prototyping|PoC]]
## Context
In (re)alignment with one of its core design principles _"policy, not mechanism"_, the usage of `wl_output` and `xdg_output` protocol events to collect display information, such as scale, has been increasingly [discouraged](https://gitlab.freedesktop.org/wayland/wayland-protocols/-/merge_requests/303#note_2381531) and there is even a recent and active [discussion](https://gitlab.freedesktop.org/wayland/wayland/-/issues/458) about their deprecation (unlikely to happen now?).
Quoting one of the upstream Wayland maintainers [comment](https://gitlab.freedesktop.org/wayland/wayland-protocols/-/merge_requests/303#note_2381531) about a recent related proposal:
> Per-output info goes against the "policy, not mechanism" principle of Wayland. It puts the decision of which scale factor to use in the client. This decision may not match what the compositor/user wants. Maybe only a very small part of the window is visible on a 1.5 scale display, maybe the user is using a zoom accessibility lens, maybe it's a compositor where outputs don't make sense (e.g. a VR compositor where the scale is adjusted based on the distance to the eye), maybe a high-res screen recording is running and targeting a specific toplevel. There are many use-cases where the per-output scale factor falls short.
^74926b
The above comment summarizes well the motivations behind the current surface-oriented scaling protocols currently embraced and implemented in major Linux/Wayland compositors, namely [[#Appendix#wl_surface.preferred_buffer_scale event|wl_surface.preferred_buffer_scale]] and [[#Appendix#wp-fractional-scale|wp-fractional-scale-v1]], discussed in more details below.
The goal here is to document both known and potential problems, current design incompatibilities between the Wayland protocols and Chromium architecture and, based on that, come up with a tentative proposal for a long-term solution which finds a good compromise between the platform-specific constraints and the web browser requirements.
## Analysis
**TL;DR:** Chromium currently does not support platform-controller window-driven scaling.
- Historically, scale has been assumed to be a per-display info, stored and advertised primarily by the platform-agnostic [display::Display](https://source.chromium.org/chromium/chromium/src/+/main:ui/display/display.h;l=34;drc=071a85757d9d95316f83f50e1fe087a6d87908f9) abstraction.
- So, when UI framework or content/chrome code needs to determine which scale to apply for a given window, for example, it's done in 2 steps: ^77ce53
1. Get the display for that window using [display::Screen::GetDisplayNearestWindow](https://source.chromium.org/chromium/chromium/src/+/main:ui/display/screen.h;l=110;drc=2313c7b1eac8a865df914f036ae10de3b4607dfa)
2. Get the display's [device_scale_factor](https://source.chromium.org/chromium/chromium/src/+/main:ui/display/display.h;l=145;drc=c0265133106c7647e90f9aaa4377d28190b1a6a9)
- `Display`'s scale usage is analyzed in the details in the [[#Internal usage|next section]].
- Such assumption is valid in (1) platforms where Chromium takes control of the full graphics stack, eg: Ash Chrome on top of the DRM Ozone backend; or (2) platforms whose display server / graphics API impose no (or minimal) restrictions on providing display information, such as, monitor layout, scaling, etc, e.g: Linux/X11, Win32. That's not the case of Wayland, for which some reasoning has been outlined in the [[#Context|previous section]]
- `display::Display` information is retrieved from and kept in sync with the underlying platform by platform-specific code under the Ozone abstraction layer, a [WaylandScreen]() in this case.
- To work around the protocol limitations / design incompatibility explained above, currently Ozone/Wayland backend code currently infers fractional scale out of xdg-output's logical size [^1]
- That is considered a misuse of the protocols and shouldn't be done. Reasoning:
- It's a protocol violation. It breaks most of the use cases described [[#^74926b|above]].
- Minimal rounding precision mismatches between the values calculated at client and the ones used at compositor side will imply in overall performance penalties, due to re-scaling buffers at compositor side;
- Blurriness issues observed in the wild, due to [rounding differences](https://issues.chromium.org/issues/40279238), leading to per-compositor [quirks](https://chromium-review.googlesource.com/c/chromium/src/+/5404799) proposals, etc.
- Going a bit further, currently in Ozone/Wayland, display scale "calculation" currently relies solely on the `xdg-output` protocol extension, there's no notion of "window's preferred scale", so `wp_fractional_scale.preferred_buffer_scale` is basically ignored.
- For platform-agnostic code to determine the scale that must be used for a given window, `display::Screen` API is used to figure out the display on which a window predominantly is (which is unsupported under Wayland, see the item [[#^c00a68|above]]), and that display's scale is used;
- The no-op impl for that event was introduced [here](https://chromium-review.googlesource.com/c/chromium/src/+/4370091/21..28/ui/ozone/platform/wayland/host/wayland_surface.cc#905) along with its motivation.
- Recent crbug filed to properly support `preferred_buffer_scale`: https://issues.chromium.org/issues/336007385
- Initial attempt from Thomas (later reverted): https://crrev.com/c/4322839
- The described fractional scaling support is currently enabled by default on Chromium Linux, but can be disabled using the `WaylandFractionalScaleV1` [feature](https://source.chromium.org/chromium/chromium/src/+/main:ui/ozone/common/features.cc;l=31;drc=4c2d0f00bc87614ef325db3811efa2a905a15482)
- Another (orthogonal) problematic assumption is that applications know where exactly in the display layout their surfaces are. On Wayland, such information is not available and [current implementation](https://source.chromium.org/chromium/chromium/src/+/main:ui/ozone/platform/wayland/host/wayland_window.cc;l=263-290;drc=2313c7b1eac8a865df914f036ae10de3b4607dfa) returns the entered output with the largest scale factor (sigh!). ^c00a68
[^1]: **Side-note:** For Lacros, specific protocols are used, such as aura-shell, surface-augmenter, wp-viewporter.
### Internal usage
Display scale information is retrieved from the platform, stored in a few core classes/structs, and is propagated all the way up through the UI stack. The main relevant references to scale properties and APIs are listed below.
Additionally, a deep dive into the components involved in the process of producing new frames (triggered by scale changes) can be found in [[#Appendix#Producing new frames|an appendix section]].
#### display::Display::device_scale_factor
At the moment, there are ~40 references in non-test code. Excluding non-Linux Desktop, eg: win, android, chromeos, ash.
- [chrome/browser/download/download_offline_content_provider.cc](https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/download/download_offline_content_provider.cc;drc=3f4203f7dca2f7e804f30cfa783e24f90acd9059?q=device_scale_factor) (1 result)
- [chrome/browser/extensions/extension_uninstall_dialog.cc](https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/extensions/extension_uninstall_dialog.cc;drc=3f4203f7dca2f7e804f30cfa783e24f90acd9059?q=device_scale_factor) (2 results)
- [chrome/browser/ui/views/download/bubble/download_bubble_row_view.cc](https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/ui/views/download/bubble/download_bubble_row_view.cc;drc=3f4203f7dca2f7e804f30cfa783e24f90acd9059?q=device_scale_factor) (1 result)
- [chrome/browser/ui/views/download/download_item_view.cc](https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/ui/views/download/download_item_view.cc;drc=3f4203f7dca2f7e804f30cfa783e24f90acd9059?q=device_scale_factor) (1 result)
- [chrome/browser/ui/views/web_apps/web_app_detailed_install_dialog.cc](https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/ui/views/web_apps/web_app_detailed_install_dialog.cc;drc=3f4203f7dca2f7e804f30cfa783e24f90acd9059?q=device_scale_factor) (1 result)
- [chrome/browser/ui/webui/downloads/downloads_dom_handler.cc](https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/ui/webui/downloads/downloads_dom_handler.cc;drc=3f4203f7dca2f7e804f30cfa783e24f90acd9059?q=device_scale_factor) (1 result)
- [components/eye_dropper/eye_dropper_view.cc](https://source.chromium.org/chromium/chromium/src/+/main:components/eye_dropper/eye_dropper_view.cc;drc=3f4203f7dca2f7e804f30cfa783e24f90acd9059?q=device_scale_factor) (1 result)
- [components/metrics/ui/screen_info_metrics_provider.cc](https://source.chromium.org/chromium/chromium/src/+/main:components/metrics/ui/screen_info_metrics_provider.cc;drc=3f4203f7dca2f7e804f30cfa783e24f90acd9059?q=device_scale_factor) (1 result)
- [components/payments/content/icon/icon_size.cc](https://source.chromium.org/chromium/chromium/src/+/main:components/payments/content/icon/icon_size.cc;drc=3f4203f7dca2f7e804f30cfa783e24f90acd9059?q=device_scale_factor) (1 result)
- [content/browser/client_hints/client_hints.cc](https://source.chromium.org/chromium/chromium/src/+/main:content/browser/client_hints/client_hints.cc;drc=3f4203f7dca2f7e804f30cfa783e24f90acd9059?q=device_scale_factor) (1 result)
- [content/browser/renderer_host/render_widget_host_view_aura.cc](https://source.chromium.org/chromium/chromium/src/+/main:content/browser/renderer_host/render_widget_host_view_aura.cc;drc=3f4203f7dca2f7e804f30cfa783e24f90acd9059?q=device_scale_factor) (2 results)
- [content/common/cursors/webcursor_aura.cc](https://source.chromium.org/chromium/chromium/src/+/main:content/common/cursors/webcursor_aura.cc;drc=3f4203f7dca2f7e804f30cfa783e24f90acd9059?q=device_scale_factor) (2 results)
- [extensions/browser/image_loader.cc](https://source.chromium.org/chromium/chromium/src/+/main:extensions/browser/image_loader.cc;drc=3f4203f7dca2f7e804f30cfa783e24f90acd9059?q=device_scale_factor) (1 result)
- [extensions/shell/browser/shell_desktop_controller_aura.cc](https://source.chromium.org/chromium/chromium/src/+/main:extensions/shell/browser/shell_desktop_controller_aura.cc;drc=3f4203f7dca2f7e804f30cfa783e24f90acd9059?q=device_scale_factor) (1 result)
- [third_party/blink/common/page_state/page_state_serialization.cc](https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/common/page_state/page_state_serialization.cc;drc=3f4203f7dca2f7e804f30cfa783e24f90acd9059?q=device_scale_factor) (1 result)
- [ui/aura/window_tree_host.cc](https://source.chromium.org/chromium/chromium/src/+/main:ui/aura/window_tree_host.cc;drc=3f4203f7dca2f7e804f30cfa783e24f90acd9059?q=device_scale_factor) (3 results)
- [ui/base/layout.cc](https://source.chromium.org/chromium/chromium/src/+/main:ui/base/layout.cc;drc=3f4203f7dca2f7e804f30cfa783e24f90acd9059?q=device_scale_factor) (1 result)
- [ui/display/display_change_notifier.cc](https://source.chromium.org/chromium/chromium/src/+/main:ui/display/display_change_notifier.cc;drc=3f4203f7dca2f7e804f30cfa783e24f90acd9059?q=device_scale_factor) (2 results)
- [ui/display/display_list.cc](https://source.chromium.org/chromium/chromium/src/+/main:ui/display/display_list.cc;drc=3f4203f7dca2f7e804f30cfa783e24f90acd9059?q=device_scale_factor) (3 results)
- [ui/display/display_util.cc](https://source.chromium.org/chromium/chromium/src/+/main:ui/display/display_util.cc;drc=3f4203f7dca2f7e804f30cfa783e24f90acd9059?q=device_scale_factor) (1 result)
- [ui/display/manager/display_manager.cc](https://source.chromium.org/chromium/chromium/src/+/main:ui/display/manager/display_manager.cc;drc=3f4203f7dca2f7e804f30cfa783e24f90acd9059?q=device_scale_factor) (4 results)
- [ui/display/screen.cc](https://source.chromium.org/chromium/chromium/src/+/main:ui/display/screen.cc;drc=3f4203f7dca2f7e804f30cfa783e24f90acd9059?q=device_scale_factor) (2 results)
- [ui/views/accessibility/view_accessibility.cc](https://source.chromium.org/chromium/chromium/src/+/main:ui/views/accessibility/view_accessibility.cc;drc=3f4203f7dca2f7e804f30cfa783e24f90acd9059?q=device_scale_factor) (1 result)
- [ui/views/widget/desktop_aura/desktop_window_tree_host_linux.cc](https://source.chromium.org/chromium/chromium/src/+/main:ui/views/widget/desktop_aura/desktop_window_tree_host_linux.cc;drc=3f4203f7dca2f7e804f30cfa783e24f90acd9059?q=device_scale_factor) (1 result)
- [ui/views/widget/desktop_aura/desktop_window_tree_host_platform.cc](https://source.chromium.org/chromium/chromium/src/+/main:ui/views/widget/desktop_aura/desktop_window_tree_host_platform.cc;drc=3f4203f7dca2f7e804f30cfa783e24f90acd9059?q=device_scale_factor) (1 result)
- [ui/wm/core/cursor_loader.cc](https://source.chromium.org/chromium/chromium/src/+/main:ui/wm/core/cursor_loader.cc;drc=3f4203f7dca2f7e804f30cfa783e24f90acd9059?q=device_scale_factor) (1 result)
#### display::DisplayObserver::OnDisplayMetricsChanged
Here are the relevant (Linux/Wayland) `DisplayObserver` implementers, along with their usage description. Basically none require changes as either (1) they do not use display scale to derive window scale, or (2) in some cases, it's already covered by the proposed changes [[#^3b39bb|below]].
- [chrome/browser/media/router/providers/wired_display/wired_display_media_route_provider.cc](https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/media/router/providers/wired_display/wired_display_media_route_provider.cc;drc=3f4203f7dca2f7e804f30cfa783e24f90acd9059?q=OnDisplayMetricsChanged) (1 result)
- Only refreshes an internal list of `Sink`s - 1:1 to `display::Display`, got using `Screen::GetAllDisplays()` - with metadata, which does not include scale info).
- [chrome/browser/metrics/chrome_browser_main_extra_parts_metrics.cc](https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/metrics/chrome_browser_main_extra_parts_metrics.cc;drc=3f4203f7dca2f7e804f30cfa783e24f90acd9059?q=OnDisplayMetricsChanged) (1 result)
- HDR-related metrics gathering only.
- [chrome/browser/ui/views/overlay/video_overlay_window_views.cc](https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/ui/views/overlay/video_overlay_window_views.cc;drc=3f4203f7dca2f7e804f30cfa783e24f90acd9059?q=OnDisplayMetricsChanged) (1 result)
- Uses [work_area()](https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/ui/views/overlay/video_overlay_window_views.cc;l=707-714;drc=c0265133106c7647e90f9aaa4377d28190b1a6a9) only (on which scale influence on?) to determine bounds of an "video overlay window", used to implement Picture-in-Picture windows.
- PiP is still [unsupported](https://issues.chromium.org/issues/40285423) in Ozone/Wayland, as it'll require specific protocol-extension, ie: positioning/handling done at compositor-side.
- [content/browser/gpu/gpu_data_manager_impl.cc](https://source.chromium.org/chromium/chromium/src/+/main:content/browser/gpu/gpu_data_manager_impl.cc;drc=3f4203f7dca2f7e804f30cfa783e24f90acd9059?q=OnDisplayMetricsChanged) (1 result)
- Calls into (via a mojo IPC [call](https://source.chromium.org/chromium/chromium/src/+/main:services/viz/privileged/mojom/gl/gpu_service.mojom;l=208;drc=c3d3c1cd7ba54db403838d9ecc60d24c725edd6e)) [GpuServiceImpl\:\:DiplayMetricsChanged](https://source.chromium.org/chromium/chromium/src/+/main:components/viz/service/gl/gpu_service_impl.cc;l=1307;drc=c0265133106c7647e90f9aaa4377d28190b1a6a9), where a single Windows-only [observer](https://source.chromium.org/chromium/chromium/src/+/main:ui/gl/direct_composition_support.cc;l=1001;drc=c0265133106c7647e90f9aaa4377d28190b1a6a9) is triggered.
- [content/browser/renderer_host/render_widget_host_view_aura.cc](https://source.chromium.org/chromium/chromium/src/+/main:content/browser/renderer_host/render_widget_host_view_aura.cc;drc=3f4203f7dca2f7e804f30cfa783e24f90acd9059?q=OnDisplayMetricsChanged) (1 result)
- Used in conjunction with `Widget::OnDeviceScaleFactorChanged`, see changes proposed for it [[#^3b39bb|below]].
- [content/browser/screen_details/screen_change_monitor.cc](https://source.chromium.org/chromium/chromium/src/+/main:content/browser/screen_details/screen_change_monitor.cc;drc=3f4203f7dca2f7e804f30cfa783e24f90acd9059?q=OnDisplayMetricsChanged) (1 result)
- Part of the [Window Management](https://developer.mozilla.org/en-US/docs/Web/API/ScreenDetails) Web API impl at browser-side
- Monitors changes in `GetAllDisplays()` list
- [extensions/browser/api/system_display/display_info_provider.cc](https://source.chromium.org/chromium/chromium/src/+/main:extensions/browser/api/system_display/display_info_provider.cc;drc=3f4203f7dca2f7e804f30cfa783e24f90acd9059?q=OnDisplayMetricsChanged) (1 result)
- Seems part of the [chrome.system.display](https://developer.chrome.com/docs/extensions/reference/api/system/display) Chrome's extension API.
- Exposes a [deviceScaleFactor](https://developer.chrome.com/docs/extensions/reference/api/system/display#properties_2) property.
- [ui/aura/window_tree_host.cc](https://source.chromium.org/chromium/chromium/src/+/main:ui/aura/window_tree_host.cc;drc=3f4203f7dca2f7e804f30cfa783e24f90acd9059?q=OnDisplayMetricsChanged) (1 result)
- Triggers new frame upon display scale changes, though already covered by the proposed changes [[#^3b39bb|below]].
- [ui/message_center/views/desktop_message_popup_collection.cc](https://source.chromium.org/chromium/chromium/src/+/main:ui/message_center/views/desktop_message_popup_collection.cc;drc=3f4203f7dca2f7e804f30cfa783e24f90acd9059?q=OnDisplayMetricsChanged) (1 result)
- ChromeOS-only.
- [ui/views/accessibility/ax_root_obj_wrapper.cc](https://source.chromium.org/chromium/chromium/src/+/main:ui/views/accessibility/ax_root_obj_wrapper.cc;drc=3f4203f7dca2f7e804f30cfa783e24f90acd9059?q=OnDisplayMetricsChanged) (1 result)
- Use primary display bounds info to figure out if this screen is in landscape or portrait.
#### display::ScreenInfo::device_scale_factor
From a comment on its [declaration](https://source.chromium.org/chromium/chromium/src/+/main:ui/display/screen_info.h;l=24;drc=c0265133106c7647e90f9aaa4377d28190b1a6a9):
> This structure roughly parallels display::Display. It may be desirable to deprecate derived counterparts of ui/display types; see crbug.com/1208469.
- It is used in `RenderWidgetHost` code:
- To provide associated renderers with screen info, including scaling (see [[#Debugging a web contents rendering issue|Appendix]] for the kind of issues that may happen if the per-window scale is not used here).
- When determining the size of a Renderer in pixels. Which is notified via `blink::VisualProperties::compositor_viewport_pixel_rect`
- To implement the browser-side bits of the [Window Placement](https://developer.mozilla.org/en-US/docs/Web/API/Window_Management_API) Web API (_TODO: Check if this is accurate_)
- Besides being copied over from `display::Display`, scale factor used in [RenderWidgetHostViewBase](https://source.chromium.org/chromium/chromium/src/+/main:content/browser/renderer_host/render_widget_host_view_base.cc;l=546-561;drc=c0265133106c7647e90f9aaa4377d28190b1a6a9) when handling the recently launched [WebContentsCaptureHiDPI](https://source.chromium.org/chromium/chromium/src/+/main:media/base/media_switches.cc;l=1074;drc=c0265133106c7647e90f9aaa4377d28190b1a6a9) feature.
- As for the Web API problem, a short-term fix is probably to disable that Web API which is broken (and likely impossible to be implemented as is) on Linux/Wayland.
##### Short-term approach
> [!question] Still under discussion
A minimal, self-contained and least risky solution is proposed in [crrev.com/c/5627169](https://chromium-review.googlesource.com/c/chromium/src/+/5627169). Which basically overrides the ScreenInfo's scale for the display "containing" the widget.
##### Long-term refactors
- For the long-term, the preferred scale should probably be stored separately, probably as part of `ScreenInfo`, as it is aimed to represent display info for a given widget, and add special handling for it in renderer/blink side.
- As a downside, it involves a significant refactor in itself.
- A follow-up issue was filed to track this refactor as it is not so critical: https://issues.chromium.org/348590032
Below is the list of source directly referencing `ScreenInfo::device_scale_factor`:
- [chrome/browser/apps/guest_view/web_view_browsertest.cc](https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/apps/guest_view/web_view_browsertest.cc;drc=90cac1911508d3d682a67c97aa62483eb712f69a?q=device_scale_factor) (3 results)
- [components/pdf/renderer/pdf_view_web_plugin_client.cc](https://source.chromium.org/chromium/chromium/src/+/main:components/pdf/renderer/pdf_view_web_plugin_client.cc;drc=90cac1911508d3d682a67c97aa62483eb712f69a?q=device_scale_factor) (1 result)
- [content/public/test/browser_test_utils.cc](https://source.chromium.org/chromium/chromium/src/+/main:content/public/test/browser_test_utils.cc;drc=90cac1911508d3d682a67c97aa62483eb712f69a?q=device_scale_factor) (1 result)
- [content/public/test/synchronize_visual_properties_interceptor.cc](https://source.chromium.org/chromium/chromium/src/+/main:content/public/test/synchronize_visual_properties_interceptor.cc;drc=90cac1911508d3d682a67c97aa62483eb712f69a?q=device_scale_factor) (1 result)
- [content/renderer/pepper/pepper_plugin_instance_impl.cc](https://source.chromium.org/chromium/chromium/src/+/main:content/renderer/pepper/pepper_plugin_instance_impl.cc;drc=90cac1911508d3d682a67c97aa62483eb712f69a?q=device_scale_factor) (1 result)
- [content/renderer/render_frame_impl.cc](https://source.chromium.org/chromium/chromium/src/+/main:content/renderer/render_frame_impl.cc;drc=90cac1911508d3d682a67c97aa62483eb712f69a?q=device_scale_factor) (2 results)
- [content/renderer/render_view_browsertest.cc](https://source.chromium.org/chromium/chromium/src/+/main:content/renderer/render_view_browsertest.cc;drc=90cac1911508d3d682a67c97aa62483eb712f69a?q=device_scale_factor) (4 results)
- [content/web_test/renderer/event_sender.cc](https://source.chromium.org/chromium/chromium/src/+/main:content/web_test/renderer/event_sender.cc;drc=90cac1911508d3d682a67c97aa62483eb712f69a?q=device_scale_factor) (1 result)
- [third_party/blink/renderer/core/css/media_values.cc](https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/css/media_values.cc;drc=90cac1911508d3d682a67c97aa62483eb712f69a?q=device_scale_factor) (2 results)
- [third_party/blink/renderer/core/frame/frame_view.cc](https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/frame/frame_view.cc;drc=90cac1911508d3d682a67c97aa62483eb712f69a?q=device_scale_factor) (1 result)
- [third_party/blink/renderer/core/frame/local_dom_window.cc](https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/frame/local_dom_window.cc;drc=90cac1911508d3d682a67c97aa62483eb712f69a?q=device_scale_factor) (4 results)
- [third_party/blink/renderer/core/frame/remote_frame.cc](https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/frame/remote_frame.cc;drc=90cac1911508d3d682a67c97aa62483eb712f69a?q=device_scale_factor) (1 result)
- [third_party/blink/renderer/core/frame/screen.cc](https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/frame/screen.cc;drc=90cac1911508d3d682a67c97aa62483eb712f69a?q=device_scale_factor) (2 results)
- [third_party/blink/renderer/core/frame/screen_metrics_emulator.cc](https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/frame/screen_metrics_emulator.cc;drc=90cac1911508d3d682a67c97aa62483eb712f69a?q=device_scale_factor) (2 results)
- [third_party/blink/renderer/core/frame/web_frame_widget_impl.cc](https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/frame/web_frame_widget_impl.cc;drc=90cac1911508d3d682a67c97aa62483eb712f69a?q=device_scale_factor) (8 results)
- [third_party/blink/renderer/core/html/forms/external_popup_menu.cc](https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/html/forms/external_popup_menu.cc;drc=90cac1911508d3d682a67c97aa62483eb712f69a?q=device_scale_factor) (1 result)
- [third_party/blink/renderer/core/input/event_handler.cc](https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/input/event_handler.cc;drc=90cac1911508d3d682a67c97aa62483eb712f69a?q=device_scale_factor) (1 result)
- [third_party/blink/renderer/core/input/pointer_event_manager.cc](https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/input/pointer_event_manager.cc;drc=90cac1911508d3d682a67c97aa62483eb712f69a?q=device_scale_factor) (1 result)
- [third_party/blink/renderer/core/layout/layout_view.cc](https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/layout/layout_view.cc;drc=90cac1911508d3d682a67c97aa62483eb712f69a?q=device_scale_factor) (1 result)
- [third_party/blink/renderer/core/page/drag_controller.cc](https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/page/drag_controller.cc;drc=90cac1911508d3d682a67c97aa62483eb712f69a?q=device_scale_factor) (1 result)
- [third_party/blink/renderer/core/page/touch_adjustment.cc](https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/page/touch_adjustment.cc;drc=90cac1911508d3d682a67c97aa62483eb712f69a?q=device_scale_factor) (1 result)
- [third_party/blink/renderer/core/paint/paint_auto_dark_mode.cc](https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/paint/paint_auto_dark_mode.cc;drc=90cac1911508d3d682a67c97aa62483eb712f69a?q=device_scale_factor) (1 result)
- [third_party/blink/renderer/core/view_transition/view_transition_style_tracker.cc](https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/view_transition/view_transition_style_tracker.cc;drc=90cac1911508d3d682a67c97aa62483eb712f69a?q=device_scale_factor) (1 result)
- [third_party/blink/renderer/modules/mediastream/media_stream_utils.cc](https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/modules/mediastream/media_stream_utils.cc;drc=90cac1911508d3d682a67c97aa62483eb712f69a?q=device_scale_factor) (3 results)
- [third_party/blink/renderer/modules/screen_details/screen_detailed.cc](https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/modules/screen_details/screen_detailed.cc;drc=90cac1911508d3d682a67c97aa62483eb712f69a?q=device_scale_factor) (1 result)
- [third_party/blink/renderer/platform/widget/widget_base.cc](https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/platform/widget/widget_base.cc;drc=90cac1911508d3d682a67c97aa62483eb712f69a?q=device_scale_factor) (5 results)
### Affected Web APIs
- Besides the internal usage of per-display scale information, it is currently exposed to web applications. This sections tentatively lists the affected Web API surfaces.
- A follow-up issue was filed to track this and other non-critical and/or controversial refactors: https://issues.chromium.org/348590032
#### window.devicePixelRatio
Docs: https://developer.mozilla.org/en-US/docs/Web/API/Window/devicePixelRatio
> The **`devicePixelRatio`** of [`Window`](https://developer.mozilla.org/en-US/docs/Web/API/Window) interface returns the ratio of the resolution in _physical pixels_ to the resolution in _CSS pixels_ for the current display device.
Status:
- Stable API
- Supported by basically [all major browsers](https://developer.mozilla.org/en-US/docs/Web/API/Window/devicePixelRatio#browser_compatibility)
#### ScreenDetailed.devicePixelRatio
Part of the Window Management Web API.
Docs: https://developer.mozilla.org/en-US/docs/Web/API/ScreenDetailed/devicePixelRatio
> The **`devicePixelRatio`** read-only property of the [`ScreenDetailed`](https://developer.mozilla.org/en-US/docs/Web/API/ScreenDetailed) interface is a number representing the screen's device pixel ratio.
Status:
- Experimental
- Supported only in [Chromium-based browsers](https://developer.mozilla.org/en-US/docs/Web/API/ScreenDetailed/devicePixelRatio#browser_compatibility)
#### chrome.system.display extension API
Docs: https://developer.chrome.com/docs/extensions/reference/api/system/display
Exposes a [deviceScaleFactor](https://developer.chrome.com/docs/extensions/reference/api/system/display#properties_2) property.
> - The display mode device scale factor.
Status:
- Chrome-only.
- Used in general or mostly on ChromeOS?
## Proposal
Adapt the minimum required UI components and APIs such that a per-window scaling can be supported. The scope here is limited to Aura/Ozone-based ports targeting Desktop, ie: CrOS-specific code manipulating display scales, for example, will be kept unchanged. The required changes are outlined below:
1. Add a virtual function to `display::Screen` ([crrev/5591429](https://crrev.com/c/5591429))
```cpp
virtual std::optional<float> GetPreferredScaleFactorForWindow(
gfx::NativeWindow window);
virtual std::optional<float> GetPreferredScaleFactorForView(
gfx::NativeView view);
```
such that [[#^77ce53|the pattern]] below
```cpp
const display::Display display =
display::Screen::GetScreen()->GetDisplayNearestWindow(window_);
float scale = display.device_scale_factor();
```
would be rewritten as:
```cpp
float scale = display::Screen->GetScreen()->GetPreferredScaleFactorForWindow(window_).value_or(1.f);
```
Its initial implementation would just wrap the original per-display based one (above).
2. Add the Ozone bits to be optionally implemented if the underlying platform supports per-window scale information, and implement in for ozone/wayland using the `wp-fraction-scale-v1` protocol. ([crrev/5591533](https://crrev.com/c/5591533))
- Guard under a new feature flag `WaylandPerSurfaceScale`, initially disabled by default.
- Then override it in `display::ScreenOzone` to return this new API output if available. Which would look like:
```cpp
std::optional<float> ScreenOzone::GetPreferredScaleFactorForWindow(gfx::NativeWindow window) {
// If supported, get the platform-provided preferred scale.
if (auto preferred = platform_screen_->GetPreferredScaleFactorForWidget(
GetAcceleratedWidgetForWindow(window))) {
return preferred;
}
// If unavailable, fallback to display scale.
display::Display display = screen->GetDisplayNearestView(window());
if (display.is_valid()) {
return display.device_scale_factor();
}
return std::nullopt;
}
```
Wayland-specifics were omitted here for the sake of simplicity, refer to the linked CL for further details.
3. Migrate incrementally existing code to the new API ([crrev/5591533](https://crrev.com/c/5591533), [crrev/5591430](https://crrev.com/c/5591430))
4. Add `chrome://flags/#wayland-per-surface-scaling` to easy early testing and as a way of troubleshooting during finch trials. ([crrev.com/5646346](https://crrev.com/c/5646346))
5. Finch trial for initially with @google.com glinux users (ozone/wayland internal finch [crbug.com/40250220](https://crbug.com/40250220) on-hold (?))
6. Enable the `WaylandPerSurfaceScale` feature by default on Linux/Desktop. ([crrev.com/](https://crrev.com/c/))
#### Affected files
>[!faq] Move to [[#Appendix]] ?
Below are the references which must be refactored along with a few remarks for each of them:
- [chrome/browser/extensions/extension_uninstall_dialog.cc](https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/extensions/extension_uninstall_dialog.cc;drc=3f4203f7dca2f7e804f30cfa783e24f90acd9059?q=device_scale_factor) (2 results)
- 1 ref to `GetDisplayNearestWindow`
- [chrome/browser/ui/views/download/bubble/download_bubble_row_view.cc](https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/ui/views/download/bubble/download_bubble_row_view.cc;drc=3f4203f7dca2f7e804f30cfa783e24f90acd9059?q=device_scale_factor) (1 result)
- 1 ref to `GetDisplayNearestView`
- Observes scale changes by overriding `View::OnDeviceScaleFactorChanged`, which should be fine, so no change needed in that part.
- [chrome/browser/ui/views/download/download_item_view.cc](https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/ui/views/download/download_item_view.cc;drc=3f4203f7dca2f7e804f30cfa783e24f90acd9059?q=device_scale_factor) (1 result)
- 1 ref to `GetDisplayNearestView`
- [chrome/browser/ui/views/web_apps/web_app_detailed_install_dialog.cc](https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/ui/views/web_apps/web_app_detailed_install_dialog.cc;drc=3f4203f7dca2f7e804f30cfa783e24f90acd9059?q=device_scale_factor) (1 result)
- 1 ref to `GetDisplayNearestView`
- [chrome/browser/ui/webui/downloads/downloads_dom_handler.cc](https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/ui/webui/downloads/downloads_dom_handler.cc;drc=3f4203f7dca2f7e804f30cfa783e24f90acd9059?q=device_scale_factor) (1 result)
- 1 ref to `GetDisplayNearestView`
- [components/payments/content/icon/icon_size.cc](https://source.chromium.org/chromium/chromium/src/+/main:components/payments/content/icon/icon_size.cc;drc=3f4203f7dca2f7e804f30cfa783e24f90acd9059?q=device_scale_factor) (1 result)
- 1 ref to `GetDisplayNearestView`
- [content/browser/renderer_host/render_widget_host_view_aura.cc](https://source.chromium.org/chromium/chromium/src/+/main:content/browser/renderer_host/render_widget_host_view_aura.cc;drc=3f4203f7dca2f7e804f30cfa783e24f90acd9059?q=device_scale_factor) (2 results) ^3b39bb
- 5 ref to `GetDisplayNearestWindow` (4 relevant)
- Watches for window scale change via [OnDeviceScaleFactorChanged](https://source.chromium.org/chromium/chromium/src/+/main:content/browser/renderer_host/render_widget_host_view_aura.cc;l=2015;drc=3f4203f7dca2f7e804f30cfa783e24f90acd9059)
- Additionally, overrides `display::DisplayObserver` to monitor scale changes.
- [This block](https://source.chromium.org/chromium/chromium/src/+/main:content/browser/renderer_host/render_widget_host_view_aura.cc;l=2031-2038;drc=c0265133106c7647e90f9aaa4377d28190b1a6a9) probably might be removed, as it is already called from `ProcessDisplayMetricsChange` above.
- `GetDisplayNearestWindow` return is passed in to [content::WebCursor::SetDisplayInfo](https://source.chromium.org/chromium/chromium/src/+/main:content/browser/renderer_host/render_widget_host_view_aura.cc;l=3050;drc=c0265133106c7647e90f9aaa4377d28190b1a6a9)
- [ui/aura/window_tree_host.cc](https://source.chromium.org/chromium/chromium/src/+/main:ui/aura/window_tree_host.cc;drc=3f4203f7dca2f7e804f30cfa783e24f90acd9059?q=device_scale_factor) (3 results)
- 3 ref to `GetDisplayNearestWindow`
- [ui/base/layout.cc](https://source.chromium.org/chromium/chromium/src/+/main:ui/base/layout.cc;drc=3f4203f7dca2f7e804f30cfa783e24f90acd9059?q=device_scale_factor) (1 result)
- 3 ref to `GetDisplayNearestView`
- [ui/views/accessibility/view_accessibility.cc](https://source.chromium.org/chromium/chromium/src/+/main:ui/views/accessibility/view_accessibility.cc;drc=3f4203f7dca2f7e804f30cfa783e24f90acd9059?q=device_scale_factor) (1 result)
- 1 ref to `GetDisplayNearestWindow`
- [ui/views/widget/desktop_aura/desktop_window_tree_host_platform.cc](https://source.chromium.org/chromium/chromium/src/+/main:ui/views/widget/desktop_aura/desktop_window_tree_host_platform.cc;drc=3f4203f7dca2f7e804f30cfa783e24f90acd9059?q=device_scale_factor) (1 result)
- 1 ref to `GetDisplayNearestWindow`
- Rewrite [GetDisplayNearestRootWindow](https://source.chromium.org/chromium/chromium/src/+/main:ui/views/widget/desktop_aura/desktop_window_tree_host_platform.cc;l=843-859;drc=3f4203f7dca2f7e804f30cfa783e24f90acd9059) to use solely `GetPreferredScaleFactor` instead.
- [ui/wm/core/cursor_loader.cc](https://source.chromium.org/chromium/chromium/src/+/main:ui/wm/core/cursor_loader.cc;drc=3f4203f7dca2f7e804f30cfa783e24f90acd9059?q=device_scale_factor) (1 result)
- Call-sites use `GetDisplayNearestWindow`: [WTH](https://source.chromium.org/chromium/chromium/src/+/main:ui/aura/window_tree_host.cc;l=785-787;drc=c0265133106c7647e90f9aaa4377d28190b1a6a9), [WTHPlatform](https://source.chromium.org/chromium/chromium/src/+/main:ui/aura/window_tree_host_platform.cc;l=330;drc=c0265133106c7647e90f9aaa4377d28190b1a6a9), [DNWA](https://source.chromium.org/chromium/chromium/src/+/main:ui/views/widget/desktop_aura/desktop_native_widget_aura.cc;l=618-620;drc=c0265133106c7647e90f9aaa4377d28190b1a6a9), [aura\:\:Window](https://source.chromium.org/chromium/chromium/src/+/main:ui/aura/window.cc;l=156-158;drc=c0265133106c7647e90f9aaa4377d28190b1a6a9)
- Add a `SetScaleFactor()` overload or an additional param (optional?) to `Cursor{Client,Manager,Loader}`, `{Desktop}NativeCursorManager`
- Might be tricky.
And below, the ones the will not be affected along with reasoning:
- [chrome/browser/apps/app_service/app_icon/dip_px_util.cc](https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/apps/app_service/app_icon/dip_px_util.cc;drc=3f4203f7dca2f7e804f30cfa783e24f90acd9059?q=device_scale_factor) (1 result)
- Uses only scale from primary display to impl some util functions that convert scale <=> [ui\:\:ResourceScaleFactor](https://source.chromium.org/chromium/chromium/src/+/main:ui/base/resource/resource_scale_factor.h;l=20;drc=c0265133106c7647e90f9aaa4377d28190b1a6a9) values.
- Has a [TODO](https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/apps/app_service/app_icon/dip_px_util.cc;l=13-16;drc=c0265133106c7647e90f9aaa4377d28190b1a6a9) from 2019 to plumb info to be able to use `GetDisplayNearestXXX` instead of `GetPrimaryDisplay`
-[chrome/browser/download/download_offline_content_provider.cc](https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/download/download_offline_content_provider.cc;drc=3f4203f7dca2f7e804f30cfa783e24f90acd9059?q=device_scale_factor) (1 result)
- Also uses only primary display's scale to compute the icon size for downloaded items.
- [components/eye_dropper/eye_dropper_view.cc](https://source.chromium.org/chromium/chromium/src/+/main:components/eye_dropper/eye_dropper_view.cc;drc=3f4203f7dca2f7e804f30cfa783e24f90acd9059?q=device_scale_factor) (1 result)
- Iterates over all displays, getting and processing scale.
- [Broken](https://chromiumdash.appspot.com/commit/8fc2fe17bd63eedd17b632d5a07aa4352ac06bbe) under Wayland and disabled, so no-op.
- [components/metrics/ui/screen_info_metrics_provider.cc](https://source.chromium.org/chromium/chromium/src/+/main:components/metrics/ui/screen_info_metrics_provider.cc;drc=3f4203f7dca2f7e804f30cfa783e24f90acd9059?q=device_scale_factor) (1 result)
- Use only primary display's scale.
- Used only on Windows.
- [content/browser/client_hints/client_hints.cc](https://source.chromium.org/chromium/chromium/src/+/main:content/browser/client_hints/client_hints.cc;drc=3f4203f7dca2f7e804f30cfa783e24f90acd9059?q=device_scale_factor) (1 result)
- Use only primary display's scale, to compute [DPR](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/DPR) http header
- [content/common/cursors/webcursor_aura.cc](https://source.chromium.org/chromium/chromium/src/+/main:content/common/cursors/webcursor_aura.cc;drc=3f4203f7dca2f7e804f30cfa783e24f90acd9059?q=device_scale_factor) (2 results)
- Display's scale factor used to determine the cursor physical size.
- Orthogonal issue, though needs to be addressed as a follow-up. ^8d06ca
- [extensions/browser/image_loader.cc](https://source.chromium.org/chromium/chromium/src/+/main:extensions/browser/image_loader.cc;drc=3f4203f7dca2f7e804f30cfa783e24f90acd9059?q=device_scale_factor) (1 result)
- Iterates over all displays, getting scales. Used to tentatively load icon images for all scales.
- [extensions/shell/browser/shell_desktop_controller_aura.cc](https://source.chromium.org/chromium/chromium/src/+/main:extensions/shell/browser/shell_desktop_controller_aura.cc;drc=3f4203f7dca2f7e804f30cfa783e24f90acd9059?q=device_scale_factor) (1 result)
- Chrome Apps-related code and likely unsupported on Wayland.
- [third_party/blink/common/page_state/page_state_serialization.cc](https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/common/page_state/page_state_serialization.cc;drc=3f4203f7dca2f7e804f30cfa783e24f90acd9059?q=device_scale_factor) (1 result)
- Android-only. Only primary display is used.
- [ui/display/display_change_notifier.cc](https://source.chromium.org/chromium/chromium/src/+/main:ui/display/display_change_notifier.cc;drc=3f4203f7dca2f7e804f30cfa783e24f90acd9059?q=device_scale_factor) (2 results)
- Part of the `display::Display` impl
- [ui/display/display_list.cc](https://source.chromium.org/chromium/chromium/src/+/main:ui/display/display_list.cc;drc=3f4203f7dca2f7e804f30cfa783e24f90acd9059?q=device_scale_factor) (3 results)
- Part of the `display::Display` impl
- [ui/display/display_util.cc](https://source.chromium.org/chromium/chromium/src/+/main:ui/display/display_util.cc;drc=3f4203f7dca2f7e804f30cfa783e24f90acd9059?q=device_scale_factor) (1 result)
- Part of the `display::Display` impl
- [ui/display/manager/display_manager.cc](https://source.chromium.org/chromium/chromium/src/+/main:ui/display/manager/display_manager.cc;drc=3f4203f7dca2f7e804f30cfa783e24f90acd9059?q=device_scale_factor) (4 results)
- Part of the `display::Display` impl
- [ui/display/screen.cc](https://source.chromium.org/chromium/chromium/src/+/main:ui/display/screen.cc;drc=3f4203f7dca2f7e804f30cfa783e24f90acd9059?q=device_scale_factor) (2 results)
- Used in DIPToScreen/ScreenToDIP functions, which are basically used on windows
- The only one used in [WTH](https://source.chromium.org/chromium/chromium/src/+/main:ui/aura/window_tree_host.cc;l=338-343;drc=c0265133106c7647e90f9aaa4377d28190b1a6a9), is overridden by [WTHPlatform](https://source.chromium.org/chromium/chromium/src/+/main:ui/views/widget/desktop_aura/desktop_window_tree_host_platform.cc;l=879-881;drc=c0265133106c7647e90f9aaa4377d28190b1a6a9)
- [ui/views/widget/desktop_aura/desktop_window_tree_host_linux.cc](https://source.chromium.org/chromium/chromium/src/+/main:ui/views/widget/desktop_aura/desktop_window_tree_host_linux.cc;drc=3f4203f7dca2f7e804f30cfa783e24f90acd9059?q=device_scale_factor) (1 result)
- X11-only. Does not make sense to be supported on Wayland.
### Prototyping
>[!attention] Status: Done
An exploratory CL was put up (linked below) in order to validate the design proposed above, as well as to verify the current status of the protocol implementation on different Wayland compositors.
Proof-of-concept CL: https://chromium-review.googlesource.com/c/chromium/src/+/5516502
### Testing
- Tested only with Mutter 46.1 and KWin 6.0.4 so far.
- Multi-display mixed dpi setup, with an external ultra-wide display res=2560x1080 scale=150% + a built-in 4k display res=3840x2400 scale=200%
- No blurriness issues observed.
- `fractional_scale.preferred_scale` event reports scale=178, which results in scale_factor=1.48333 (when divided by 120), which matches (with slight precision mismatch) the value calculated out of xdg-output event, which in this case is 1.48148.
> [!attention] TODO
> - Test with Sway
## Appendix
### Producing new frames
- [CompositorFrameSink](https://source.chromium.org/chromium/chromium/src/+/main:services/viz/public/mojom/compositing/compositor_frame_sink.mojom;l=31;drc=c0265133106c7647e90f9aaa4377d28190b1a6a9) is the mojo interface used by the browser process (as well as other frame sources, eg: renderer process) to submit new frames to be composited by Viz's display compositor in the GPU process.
- In the browser process, it is used by the [ui\:\:Compositor](https://source.chromium.org/chromium/chromium/src/+/main:ui/compositor/compositor.h;l=149;drc=c0265133106c7647e90f9aaa4377d28190b1a6a9), via its [LayerTreeHost](https://source.chromium.org/chromium/chromium/src/+/main:ui/compositor/compositor.h;l=576;drc=c0265133106c7647e90f9aaa4377d28190b1a6a9) instance, which calls into [SubmitCompositorFrame](https://source.chromium.org/chromium/chromium/src/+/main:cc/mojo_embedder/async_layer_tree_frame_sink.cc;l=260-261;drc=c0265133106c7647e90f9aaa4377d28190b1a6a9), triggered by [cc\:\:Scheduler](https://source.chromium.org/chromium/chromium/src/+/main:cc/scheduler/scheduler.cc;l=835;drc=c0265133106c7647e90f9aaa4377d28190b1a6a9), when a new frame is [created, drawn and submitted](https://source.chromium.org/chromium/chromium/src/+/main:cc/trees/layer_tree_host_impl.cc;l=2567-2595;drc=c0265133106c7647e90f9aaa4377d28190b1a6a9), in response for example to a window [scale factor change](https://source.chromium.org/chromium/chromium/src/+/main:ui/compositor/compositor.cc;l=482;drc=c0265133106c7647e90f9aaa4377d28190b1a6a9).
Rough relationship of some browser process' classes involved in ui frames compositing[^2], with focus in display/window scale changes:
```mermaid
classDiagram
namespace ozone-wayland {
class WaylandWindow
class WaylandScreen
}
namespace ui {
class PlatformWindow["ui::PlatformWindow"] {
<<interface>>
}
class PlatformWindowDelegate {
<<interface>>
+OnBoundsChanged(change)
}
class PlatformScreen["ui::PlatformScreen"] {
<<interface>>
}
class ScreenOzone["views::DesktopScreenOzone"]
class Screen["display::Screen"] {
<<interface>>
+GetDisplayNearestWindow(window)
+GetDisplayNearestView(view)
}
class Display["display::Display"] {
+float device_scale_factor()
}
class WindowTreeHost["aura::WindowTreeHost"] {
-OnHostResizedInPixels(new_size)
}
class WindowTreeHostPlatform["aura::WindowTreeHostPlatform"] {
+OnBoundsChanged(change)
}
class Compositor["ui::Compositor"] {
+SetScaleAndSize(scale, size, surface_id)
}
}
namespace cc {
class LayerTreeHost["cc::LayerTreeHost"] {
+SetViewPortRectAndScale(viewport_rect, scale)
+SetNeedsCommit()
}
class LayerTreeHostClient["cc::LayerTreeHostClient"] {
+BeginMainFrame(args)
}
class PropertyTrees["cc::PropertyTrees"] {
+set_needs_rebuild()
}
class Proxy["cc::Proxy"] {
<<interface>>
+SetNeedsUpdateLayers()
+SetNeedsCommit()
}
class SingleThreadedProxy["cc::SingleThreadedProxy"]{
+SetNeedsUpdateLayers()
+SetNeedsCommit()
+ScheduledActionSendBeginMainFrame(args)
}
class Scheduler["cc::Scheduler"] {
+SetNeedsBeginMainFrame()
+ProcessScheduledActions()
}
class StateMachine["cc::SchedulerStateMachine"] {
+SetNeedsBeginMainFrame()
}
class SchedulerClient ["cc::SchedulerClient"] {
+ScheduledActionSendBeginMainFrame(args)
}
class LayerTreeHostImpl["LayerTreeHostImpl"] {
+DrawLayers(frame)
}
class LayerTreeFrameSink["LayerTreeFrameSink"] {
<<interface>>
+SubmitCompositorFrame(frame)
}
}
WaylandWindow --|> PlatformWindow
PlatformWindow --o WindowTreeHostPlatform
WaylandWindow o-- PlatformWindowDelegate
PlatformWindowDelegate <|-- WindowTreeHostPlatform
WaylandScreen --|> PlatformScreen
PlatformScreen --o ScreenOzone
ScreenOzone --|> Screen
Screen .. Display
Display .. WindowTreeHost
WindowTreeHostPlatform --|> WindowTreeHost
WindowTreeHost o-- Compositor
Compositor o-- LayerTreeHost
LayerTreeHost o-- PropertyTrees
LayerTreeHost o-- Proxy
LayerTreeHost o-- LayerTreeHostClient
LayerTreeHostClient <|-- Compositor
Proxy <|-- SingleThreadedProxy
Proxy o-- Scheduler
Proxy o-- LayerTreeHost
Scheduler o-- StateMachine
Scheduler o-- SchedulerClient
SchedulerClient <|-- SingleThreadedProxy
SingleThreadedProxy o-- LayerTreeHostImpl
LayerTreeHostImpl o-- LayerTreeFrameSink
```
[^2]: For the sake of simplicity, `cc::SingleThreadedProxy` is used in the diagrams, instead of `cc::ProxyImpl` multi-threaded one which is used in practice in Chrome on Desktop.
Mojo and Viz-side classes involved in frame compositing:
```mermaid
classDiagram
namespace cc {
class LayerTreeFrameSink["LayerTreeFrameSink"] {
<<interface>>
+SubmitCompositorFrame(frame)
}
class AsyncLayerTreeFrameSink["mojo_embedder::AsyncLayerTreeFrameSink"] {
+SubmitCompositorFrame(frame)
}
}
namespace viz {
class FrameSinkImpl["viz::RootCompositorFrameSinkImpl"] {
+SubmitCompositorFrame(frame)
}
class Display["viz::Display"] {
+SetLocalSurfaceId(id, device_scale_factor)
+DrawAndSwap()
-device_scale_factor_
}
class DirectRenderer["viz::DirectRenderer"]
class DamageTracker["viz::DisplayDamageTracker"]
class DamageTrackerDelegate["viz::DisplayDamageTracker::Delegate"] {
<<interface>>
+OnDisplayDamaged(surface_id)
}
class Surface["viz::Surface"] {
+QueueFrame(frame, ...)
}
class SurfaceObserver["viz::SurfaceObserver"]
class SurfaceManager["SurfaceManager"] {
+CreateSurface(surface_info, id)
}
class DisplayScheduler["viz::DisplayScheduler"] {
-MaybeStartObservingBeginFrames()
-ScheduleBeginFrameDeadline()
-DrawAndSwap()
}
class DisplaySchedulerClient["viz::DisplaySchedulerClient"] {
<<interface>>
+DrawAndSwap()
}
class FrameSinkSupport["viz::CompositorFrameSinkSupport"] {
+MaybeSubmitCompositorFrame(id, frame)
}
}
LayerTreeFrameSink <|-- AsyncLayerTreeFrameSink
AsyncLayerTreeFrameSink ..> FrameSinkImpl
FrameSinkImpl o-- Display
FrameSinkImpl o-- FrameSinkSupport
Display o-- DirectRenderer
Display o-- DamageTracker
DamageTracker --|> SurfaceObserver
DamageTracker o-- DamageTrackerDelegate
DamageTrackerDelegate <|-- DisplayScheduler
DisplaySchedulerClient <|-- Display
DisplaySchedulerClient --o DisplayScheduler
SurfaceManager --o FrameSinkSupport
FrameSinkSupport <-- Surface
Surface <-- SurfaceManager
SurfaceObserver --o SurfaceManager
note for AsyncLayerTreeFrameSink "Communicates over a Mojo\nIPC channel with Viz-side\nRootCompositorFrameSinkImpl"
```
Debugging tricks:
- Dump `ui::Compositor`'s property trees and layers if run with `--vmodule=*ui/compositor*=3`
### Debugging a web contents rendering issue
- Initially we suspected it could be a compositor issue and filed https://gitlab.gnome.org/GNOME/mutter/-/issues/3527
- `scale-monitor-framebuffers` experimental flag enabled
- Reproducible only with the whole [patch series](https://chromium-review.googlesource.com/c/chromium/src/+/5598721) applied
- [[#Debugging notes|Log messages]] were collected with the following command:
```shell
chr_run --disable-features=WaylandPerSurfaceScale \
--vmodule="'wayland*=10'" igalia.com 2>&1 | \
grep -e 'wayland_\|] \(wl_surface\|wl_output\|wp_fractional\)\|] -> wl_surface.*\(attach\|damage\|commit\)\|] -> wp_viewport\|configure\| -> zwp_linux_buffer.*create'
```
_Remove the `--disable-features=WaylandPerSurfaceScale` arg to turn on the WIP per-surface scaling path._
> [!note] Update Jun/11: we discovered it was actually a Chromium-side issue.
- It turns out that display scale info was still leaking and being used by web contents embedding code in [content\:\:RenderWidgetHostView](https://source.chromium.org/chromium/chromium/src/+/main:content/browser/renderer_host/render_widget_host_view_base.cc;l=525;drc=20135c10f0869fdefb75d990ec84143a649d84c3).
- Refactor [RenderWidgetHostViewBase::UdpateScreenInfo]() seems enough to get web contents scaling working properly.
- Overriding `device_scale_factor` property of all screens works, but looks pretty hacky.
- Consider creating a "fake" `ScreenInfo` when `OzonePlatform::RuntimeProperty::supports_per_surface_scaling` platforms?
- WIP CL: https://chromium-review.googlesource.com/c/chromium/src/+/5598721
- TODO: Check the following call-sites:
- [ ] [RenderWidgetHostImpl::SetupInputRouter](https://source.chromium.org/chromium/chromium/src/+/main:content/browser/renderer_host/render_widget_host_impl.cc;l=3719;drc=aef1f146e39ffed5aa699d60e6d0f6e9c6515c24)
- [ ] [WebCursor::SetDisplayInfo](https://source.chromium.org/chromium/chromium/src/+/main:content/common/cursors/webcursor_aura.cc;l=49;drc=aef1f146e39ffed5aa699d60e6d0f6e9c6515c24)
- Already mentioned in [[#^8d06ca|Affected files]] section
Below there is a simplified class diagram of the main components involved in web contents embedding at browser process side, especially the ones manipulating screen scale:
```mermaid
classDiagram
class RenderWidgetHost {
<<interface>>
+GetDeviceScaleFactor(): float
}
class RenderWidgetHostImpl {
+GetDeviceScaleFactor(): float
+SynchronizeVisualProperties()
}
class BlinkWidget["blink.mojom.Widget"] {
<<interface>>
+UpdateVisualProperties(visual_properties)
+UpdateScreenRects(widget_rect, window_rect)
}
class BlinkVisualProps["blink.mojom.VisualProperties"] {
+screen_infos: ScreenInfos
+page_scale_factor: float
+compositing_scale_factor: float
}
class RenderWidgetHostView {
<<interface>>
}
class RenderWidgetHostViewBase {
+UpdateScreenInfo()
+GetDeviceScaleFactor(): float
}
class RenderInputRouter{
+SetDeviceScaleFactor(scale)
}
class WindowDelegate["aura::WindowDelegate"] {
<<interface>>
+OnDeviceScaleFactorChange(old, new);
}
class RenderWidgetHostViewAura {
+OnDeviceScaleFactorChange(old, new);
}
class ScreenInfos["display::ScreenInfos"] {
+current(): ScreenInfo
}
class ScreenInfo["display::ScreenInfo"] {
+device_scale_factor: float
}
RenderWidgetHost <|-- RenderWidgetHostImpl
RenderWidgetHostView <|-- RenderWidgetHostViewBase
RenderWidgetHostImpl o.. BlinkWidget
BlinkWidget .. BlinkVisualProps
RenderWidgetHostImpl o-- RenderInputRouter
RenderWidgetHostViewBase o-- ScreenInfos
ScreenInfos o-- ScreenInfo
WindowDelegate <|-- RenderWidgetHostViewAura
RenderWidgetHostViewBase <|-- RenderWidgetHostViewAura
RenderWidgetHostImpl o.. RenderWidgetHostViewBase
note for ScreenInfo "This structure roughly parallels display::Display.\nHas a deprecation TODO(crbug.com/1208469)"
```
Further relevant docs:
- [What is content/browser/renderer\_host?](https://chromium.googlesource.com/chromium/src/+/main/content/browser/renderer_host/README.md)
- [Rendering and compositing out of process iframes](https://www.chromium.org/developers/design-documents/oop-iframes/oop-iframes-rendering/)
- [Out-of-Process iframes (OOPIFs)](https://www.chromium.org/developers/design-documents/oop-iframes/)
#### Debugging notes
##### With `WaylandPerSurfaceScale` disabled
- Moving from external display (scale=1) to builtin (scale=3)
```
[2482023.726]
[email protected]_scale(360)
[65183:65183:0610/215644.802425:VERBOSE1:wayland_surface.cc(1006)] Per-surface scaling is disabled.
[2482053.716]
[email protected](wl_output@8)
[65183:65183:0610/215644.846580:VERBOSE1:wayland_window.cc(150)] UpdateWindowScale setting scale=3
[2482197.890] ->
[email protected]_immed(new id wl_buffer@39, 3584, 3072, 875708993, 0)
[2482198.075] ->
[email protected](wl_buffer@39, 0, 0)
[2482198.189] ->
[email protected]_source(0.00000000, 0.00000000, 3243.00000000, 2565.00000000)
[2482198.205] ->
[email protected]_destination(1081, 855)
[2482198.211] ->
[email protected](0, 0, 1081, 855)
[2482198.218] ->
[email protected]()
[2482220.599] ->
[email protected]_immed(new id wl_buffer@56, 3584, 3072, 875708993, 0)
[2482267.680] ->
[email protected](wl_buffer@56, 0, 0)
[2482267.693] ->
[email protected](16, 134, 1034, 689)
[2482267.701] ->
[email protected]()
```
- Moving from builtin display (scale=3) to external (scale=1)
```
[2524874.164]
[email protected]_scale(120)
[65183:65183:0610/215727.652868:VERBOSE1:wayland_surface.cc(1006)] Per-surface scaling is disabled.
[2525045.839]
[email protected](wl_output@8)
[65183:65183:0610/215727.833987:VERBOSE1:wayland_window.cc(150)] UpdateWindowScale setting scale=1
[2525129.245] ->
[email protected](wl_buffer@49, 0, 0)
[2525129.311] ->
[email protected]_source(0.00000000, 0.00000000, 1081.00000000, 855.00000000)
[2525129.318] ->
[email protected]_destination(-1, -1)
[2525129.323] ->
[email protected](0, 0, 1081, 855)
[2525129.329] ->
[email protected]()
[2525147.210] ->
[email protected](wl_buffer@55, 0, 0)
[2525147.221] ->
[email protected](16, 134, 1034, 689)
[2525147.229] ->
[email protected]()
```
##### With `WaylandPerSurfaceScale` enabled
- Moving from external display (scale=1) to builtin (scale=3)
```
[2706814.132]
[email protected]_scale(360)
[65513:65513:0610/220029.592843:VERBOSE1:wayland_surface.cc(1015)] OnPreferredScale scale=360 scale_factor=3
[65513:65513:0610/220029.603270:VERBOSE1:wayland_window.cc(150)] UpdateWindowScale setting scale=3
[2706906.219] ->
[email protected]_immed(new id wl_buffer@55, 3328, 2304, 875708993, 0)
[2706906.401] ->
[email protected](wl_buffer@55, 0, 0)
[2706906.507] ->
[email protected]_source(0.00000000, 0.00000000, 2895.00000000, 1986.00000000)
[2706906.515] ->
[email protected]_destination(965, 662)
[2706906.520] ->
[email protected](0, 0, 965, 662)
[2706906.527] ->
[email protected]()
[2706929.594]
[email protected](wl_output@8)
[65513:65513:0610/220029.708264:VERBOSE3:wayland_window.cc(169)] OnEnteredOutputScaleChanged no-op
[2707044.655]
[email protected](wl_output@31)
[65513:65513:0610/220029.823308:VERBOSE3:wayland_window.cc(169)] OnEnteredOutputScaleChanged no-op
```
- Moving from builtin display (scale=3) to external (scale=1)
```
[2751526.888]
[email protected]_scale(120)
[65513:65513:0610/220114.305594:VERBOSE1:wayland_surface.cc(1015)] OnPreferredScale scale=120 scale_factor=1
[65513:65513:0610/220114.322411:VERBOSE1:wayland_window.cc(150)] UpdateWindowScale setting scale=1
[2751637.652]
[email protected](wl_output@31)
[65513:65513:0610/220114.416304:VERBOSE3:wayland_window.cc(169)] OnEnteredOutputScaleChanged no-op
[2751651.541] ->
[email protected](wl_buffer@55, 0, 0)
[2751651.575] ->
[email protected]_source(0.00000000, 0.00000000, 965.00000000, 662.00000000)
[2751651.578] ->
[email protected]_destination(-1, -1)
[2751651.581] ->
[email protected](0, 0, 965, 662)
[2751651.584] ->
[email protected]()
[2751758.647]
[email protected](wl_output@8)
[65513:65513:0610/220114.537284:VERBOSE3:wayland_window.cc(169)] OnEnteredOutputScaleChanged no-op
```
- One more interesting sample collected while moving from builtin display (scale=3) to external (scale=1)
- Now with a few more debug logging
```
[ 914283.936]
[email protected]_scale(120)
[150913:150913:0611/141246.604756:VERBOSE1:wayland_surface.cc(1015)] OnPreferredScale scale=120 scale_factor=1
[150913:150913:0611/141246.604788:VERBOSE1:wayland_window.cc(149)] UpdateWindowScale setting scale=1
[150913:150913:0611/141246.605055:ERROR:window_tree_host_platform.cc(264)] ------------------------------------ OnBoundsChanged current[size=2334x2115 scale=3] new[size=778x705 scale=1]
[150913:150913:0611/141246.614288:VERBOSE1:wayland_window.cc(1605)] MaybeApplyLatestStateRequest viz_seq=13
[150913:150913:0611/141246.678567:VERBOSE1:wayland_buffer_manager_host.cc(210)] CreateDmabufBasedBuffer size=2816x2560
[ 914357.958] ->
[email protected]_immed(new id wl_buffer@55, 2816, 2560, 875708993, 0)
[ 914358.091] ->
[email protected](wl_buffer@55, 0, 0)
[ 914358.119] ->
[email protected]_source(0.00000000, 0.00000000, 778.00000000, 705.00000000)
[ 914358.122] ->
[email protected]_destination(-1, -1)
[ 914358.124] ->
[email protected](0, 0, 778, 705)
[ 914358.127] ->
[email protected]()
[ 914420.347]
[email protected](wl_output@8)
[150913:150913:0611/141246.741145:VERBOSE3:wayland_window.cc(169)] OnEnteredOutputScaleChanged no-op
[150913:150913:0611/141246.810904:VERBOSE1:wayland_buffer_manager_host.cc(210)] CreateDmabufBasedBuffer size=2816x2560
[ 914490.171] ->
[email protected]_immed(new id wl_buffer@50, 2816, 2560, 875708993, 0)
[ 914490.238] ->
[email protected](wl_buffer@50, 0, 0)
[ 914490.243] ->
[email protected](16, 97, 746, 576)
[ 914490.246] ->
[email protected]()
[ 914512.660]
[email protected](wl_output@9)
[150913:150913:0611/141246.833441:VERBOSE3:wayland_window.cc(169)] OnEnteredOutputScaleChanged no-op
```
### Automated tests
- To far, nearly no tests needed to be modified, which is odd.
- The changes should affect and be (ideally) covered at different levels, including:
- ozone/wayland "unit" tests
- aura unittests
- views unittests
- content unittests
- Probably helpful test suite samples can be found at:
- For verifying for example `RenderWidgetHost` related behavior: [here](https://source.chromium.org/chromium/chromium/src/+/main:content/browser/renderer_host/render_widget_host_view_android_unittest.cc;l=566;drc=ade7a83ef7f9cfc209ae46c90babcf6a84aefccf)
### Relevant Wayland protocols
#### wl_surface.preferred_buffer_scale event
- Landed on Feb 2023. version 6
- MR: [protocol: add wl\_surface.preferred\_buffer\_scale and transform (!220) · Merge requests · wayland / wayland · GitLab](https://gitlab.freedesktop.org/wayland/wayland/-/merge_requests/220)
- Motivation: Make scale update independent from wl_output events. Supports non-fractional cases only.
#### wp-fractional-scale
- Landed on Nov 2022
- MR: [wp-fractional-scale-v1: New protocol for fractional scaling (!143) · Merge requests · wayland / wayland-protocols · GitLab](https://gitlab.freedesktop.org/wayland/wayland-protocols/-/merge_requests/143)
- Similar to `wl_surface.preferred_buffer_scale` but for fractional scaling.
### Other relevant discussions
- [Deprecate parts of wl\_output and xdg\_output (#458) · Issues · wayland / wayland · GitLab](https://gitlab.freedesktop.org/wayland/wayland/-/issues/458)
- [Incorrect xdg\_output logical size for scaled outputs (#2631) · Issues · GNOME / mutter · GitLab](https://gitlab.gnome.org/GNOME/mutter/-/issues/2631)
- [Draft: fractional-scale-v1: Add support for binding to the output (!303) · Merge requests · wayland / wayland-protocols · GitLab](https://gitlab.freedesktop.org/wayland/wayland-protocols/-/merge_requests/303)