Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dynamic DPI support may not use the right monitor #58

Closed
wravery opened this issue Oct 17, 2019 · 10 comments
Closed

Dynamic DPI support may not use the right monitor #58

wravery opened this issue Oct 17, 2019 · 10 comments
Labels
bug Something isn't working

Comments

@wravery
Copy link

wravery commented Oct 17, 2019

Scenario:

  • Arrange multiple monitors side by side with different DPI settings (e.g. 200% and 100%).
  • Create a top-level window in PerMonitorV2 DPI awareness mode.
  • Create a WebView2 on one of the sides of the top-level window and make it less than half the width of the top-level window (e.g. 1/4).
  • Load some content into the WebView2 so you can see the effect of DPI scaling.
  • Drag the top-level window slowly back and forth between monitors.
  • Handle WM_SIZE or otherwise re-layout the the WebView2 when the top-level window is resized by calling put_Bounds so it maintains the same width in proportion to the top-level window.

Result:

  • If the top-level window switches DPI scales halfway between monitors, and the WebView2 control is still on the old monitor, the top-level window resizes and resets the bounds of the WebView2 control.
  • The WebView2 control resizes using the scaled size of the new monitor (correct), but it gets its DPI scale from the old monitor that it's still on. Now it doesn't match the top-level window's bounds.
  • If you keep dragging the window the rest of the way until the WebView2 control is on the new monitor, then trigger a resize of the pane, it snaps to the new monitor's DPI. The threshold where it starts working again seems to be once the majority of the WebView2 window is on the new monitor.

Hypothesis:

  • The WebView2 control and/or Edge is not handling a DPI change directly, it's querying the DPI of the monitor with the out-of-proc MSEdge child window. If that window overlaps entirely with the top-level window and the parent is updating the bounds, it'll just trigger a re-layout at what it thinks is the current DPI, but it's using the wrong window to figure that out.

Suggested fix:

  • Use something like GetAncestor(GA_ROOT) to get the top-level window in the host process and figure out which monitor that's on to determine the target monitor's DPI.

AB#25343774

@david-risney david-risney added the bug Something isn't working label Oct 17, 2019
@david-risney
Copy link
Contributor

Great bug and thanks for the detailed instructions! We'll look into this.

@verelpode
Copy link

@david-risney -- Although @wravery's suggested fix (GetAncestor(GA_ROOT)) looks worthwhile to consider, I think other potential solutions are also worthwhile to consider. I suggest considering whether to use the following way of solving the problem:

Don't make the Win32 part/layer of WebView2 respond to DPI changes of the OS display settings, and don't try to determine the correct DPI or pixels-per-DIP or monitor to use. Instead make a manually settable property in IWebView2WebView or IWebView2Settings that has the same meaning as:

Make it the responsibility of the next layer (WinUI/XAML) to set the IWebView2WebView.RawPixelsPerViewPixel property to the correct value, including changing it again whenever the OS display DPI setting changes.

In the WinUI/UWP layer, in the WinUI/XAML WebView2 Control that wraps the underlying Win32 WebView2 layer, subscribe to this DpiChanged event:

When the DpiChanged event is triggered in UWP, the event handler in the WinUI/XAML WebView2 Control would execute:

double pixelsPerDip = Windows.Graphics.Display.DisplayInformation.GetForCurrentView().RawPixelsPerViewPixel;
IWebView2WebView underlyingWin32Component = ...;
underlyingWin32Component.put_RawPixelsPerViewPixel(pixelsPerDip);

Thus the WinUI/XAML layer would tell the Win32 WebView2 layer what DPI or RawPixelsPerViewPixel value to use.

There exists a bunch of things that the Win32 part/layer of WebView2 should NOT try to do by itself, and automatic DPI management could be one of these things. Some things are better handled in other layers, in order to increase reliability and compatibility. Also, there exists a bunch of things that none of the layers of WebView2 should try to do -- rather WebView2 should use events to notify the app to do these things, and let the app handle these things in whatever way the app wants/needs.

Thus I suggest that DPI could be a simple manual setting in the Win32 layer of WebView2, but automatic in the WinUI/XAML layer, by making the WinUI/XAML WebView2 Control explicitly tell the Win32 WebView2 instance whenever the event Windows.Graphics.Display.DisplayInformation.DpiChanged occurs.

To clarify, the Win32 WebView2 layer wouldn't have any knowledge of the UWP DisplayInformation.DpiChanged event, rather the WinUI source code would subscribe to this DisplayInformation.DpiChanged event in order to tell the Win32 WebView2 layer what DPI to use, and to update it whenever the DPI changes. Thus the layers are cleanly separated, and the WinUI layer builds upon and adds additional functionality to the Win32 WebView2 layer. It makes good sense to design the underlying Win32 WebView2 layer as a lower-level layer that has less functionality or less automated functionality or less friendliness than the higher-level WinUI/XAML WebView2 Control.

I hope this helps! I'm eager to see WebView2 advance as fast as possible.

@david-risney
Copy link
Contributor

Generally we want WebView2 to work well without requiring the end developer to set properties or handle particular events. If there are cases where we cannot get the DPI correct or otherwise need to allow it to be customized, then a property to explicitly change the DPI like you suggest is a good idea.

@verelpode
Copy link

verelpode commented Oct 23, 2019

If there are cases where we cannot get the DPI correct

I guess such cases do exist? The DPI of WebView2 needs to be identical to the DPI determined by the WinUI/XAML layer, in all situations and corner cases. I suspect that the suggested GetAncestor(GA_ROOT) technique won't produce identical results as the DPI determined by the WinUI layer, but I could be mistaken about this.

For example, consider the case where the WebView2 is hosted inside an instance of Windows.UI.WindowManagement.AppWindow. In this case, the DPI of WebView2 needs to operate the same as (or consistent with) the following part of AppWindow:

Windows.UI.WindowManagement.AppWindow appWinInstance = ....;
var regionList = appWinInstance.WindowingEnvironment.GetDisplayRegions();
DisplayRegion region = regionList[x];
string display = region.DisplayMonitorDeviceId;

And that's not all. If the WebView2 instance is hosted in an ApplicationView, not in an AppWindow, then the DPI of WebView2 needs to be identical to

Windows.Graphics.Display.DisplayInformation.GetForCurrentView().LogicalDpi or RawPixelsPerViewPixel 

while keeping in mind that each ApplicationView instance has a separate thread/dispatcher whereas AppWindow instances can share the same thread, and Win32 WebView2 lives in another thread or rather process.

That's complex. It seems too difficult to make the Win32 WebView2 layer reliably produce identical results as the WinUI layer. It seems that an easier and more reliable solution is to make the WinUI layer explicitly tell the Win32 layer what DPI to use.

Even if Win32 WebView2 somehow manages to reliably produce identical DPI results as the WinUI layer, it breaks when the next layer is WPF or WinForms instead of WinUI. Win32 WebView2 doesn't have knowledge of the DPI determination algorithms and quirks of the WinUI, WPF, and WinForms layers, and a Win32 WebView2 instance doesn't know what the next layer is.

In the case of a WPF wrapper of WebView2, again it's a headache to attempt to make Win32 WebView2 consistent with WPF's DPI determination algorithm because of various WPF DPI issues such as whether or not the "app.manifest" contains these settings:

<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitor</dpiAwareness>
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
<compatibility><application><supportedOS Id="..." /></application></compatibility>

Generally we want WebView2 to work well without requiring the end developer to set properties or handle particular events.

I'm presuming that more than 95% of the developers using WebView2 won't use the Win32 WebView2 component directly, rather they'll use the WinUI/XAML, WPF, or WinForms wrappers of the Win32 WebView2 component, and these wrappers would set the DPI property and any other low-level/manual properties, thus nearly all end developers would experience the DPI being handled automatically.

It seems like a bunch of headaches and bugs can be avoided by making the Win32 WebView2 layer do less work, and shift more work into the WinUI layer.

@wravery
Copy link
Author

wravery commented Oct 24, 2019

Speaking as someone using the Win32 version, having explicit control over the DPI scaling would actually be most useful to me as well. We only support PerMonitorV1 for compatibility back to Windows 8, and we explicitly flow the DPI of the top-level window to all of its children.

On a related note, the WebView2 sample mentions a known issue that it currently has to be created on a DDPI-aware thread with PerMonitorV2:

// Step 3 - Create a single WebView within the parent window
// Known issue - app needs to run on PerMonitorV2 DPI awareness for WebView to look properly
// This is only available on Win10, comment out the line on older OS versions.
SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);

This bug as originally reported may depend on using PerMonitorV1, since as I understand it, Win10 would flow a DPI change message to the WebVIew2 HWND in PerMonitorV2. I confirmed that replacing that with DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE also works in my test app (which had been using PerMonitorV2 based on that note in the docs), but the window doesn't automatically resize without adding a handler for WM_DPICHANGED. After dragging across monitors, resizing the test app immediately snaps to the DPI of the new monitor, which emulates the bug as observed in our real app but without the wrinkle that the WebView2 is on a different monitor than the majority of its top-level parent.

It's also pretty important to us that this eventually supports System Aware DPI mode. For compatibility with a bunch of non-DPI aware add-ins, we have an escape hatch to turn off even PerMonitorV1 support and revert to System Aware. I just tested with that escape hatch and by replacing and commenting out that line in my test app and it seemed to work. Dragging to another monitor was blurry with virtual DPI scaling, but it behaved as expected in that fallback despite the comment.

@verelpode
Copy link

Speaking as someone using the Win32 version, having explicit control over the DPI scaling would actually be most useful to me as well.

This makes sense to me. I find it understandable that developers directly using the Win32 WebView2 component need more control over certain issues (such as DPI) than developers using the WinUI/XAML wrapper of WebView2 or other wrappers of WebView2.

I think it would be very reasonable if an explicit DPI scaling setting is exposed only in the API of the Win32 WebView2 and not in the API of WinUI (or other) WebView2. The internal implementation of WinUI/other WebView2 would set the explicit DPI setting in Win32 WebView2, thereby eliminating the problem of Win32 WebView using a different DPI determination algorithm than WinUI/other, without inconveniencing any developers using the WinUI/other wrapper of WebView2.

It's also pretty important to us that this eventually supports System Aware DPI mode. For compatibility with a bunch of non-DPI aware add-ins, we have an escape hatch to turn off even PerMonitorV1

It sounds like you're saying that if Win32 WebView2 let you explicitly tell it what DPI scaling value to use, then you would have a comfortable guarantee that you will definitely always be able to make the DPI scaling work in any manner that you need (including PerMonitorV2, PerMonitorV1, and System Aware), even when you need to activate older DPI determination algorithms for compatibility reasons.

It sounds like Win32 WebView2 would end up making life difficult for developers if it attempts to be too smart or too automatic, or if it tries to do various things that actually need to be performed outside of the Win32 WebView2 component.

@david-risney
Copy link
Contributor

When using HWND based hosting we're forced to share the same DPI awareness as the parent window. I believe that comment about requiring PerMonitor v2 is out of date. I'll open a separate doc bug for that.

I'll open a separate feature request for explicit control over the DPI. I know that explicit DPI control is planned for the windowless hosting we're working on, but not sure how feasible that is for HWND hosting.

I'll leave this issue to cover ensuring we get the DPI right by default (if possible/practical).

@verelpode
Copy link

@david-risney

the windowless hosting we're working on

Windowless hosting? If you have time, I'd love to read some more info or any details about this upcoming windowless hosting feature, either in a message in GitHub or in new text added to the documentation webpages. This info would help me with planning.

@Eilon
Copy link

Eilon commented Jul 22, 2020

BTW in my case and in another person's case we are seeing odd behavior when all screens are at 200%. No problem at 100% and 150%.

When all screens are at 200% we are seeing the WebView2 WPF control sized to 50% of the expected size:

image

@bradp0721
Copy link
Member

We are working on an API to give the app control over the DPI scale the WebView uses to render its html content, popups, context menus, scroll bars, and so on, called RasterizationScale. Please feel free to review and provide feedback.

#991

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

6 participants