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

WebView2 control is not visible after changing the parent window #985

Open
DominikPalo opened this issue Feb 25, 2021 · 14 comments
Open

WebView2 control is not visible after changing the parent window #985

DominikPalo opened this issue Feb 25, 2021 · 14 comments
Assignees
Labels
feature request feature request tracked We are tracking this work internally.

Comments

@DominikPalo
Copy link

DominikPalo commented Feb 25, 2021

Description
We are migrating our PowerPoint integration from CefSharp (a different Chromium-based webview control) to WebView2 and we have bumped into an issue when trying to change the parent window of a window with WebView2 control inside.

The way how our integration works is that we create a new WinForms window containing a WebView2 control and then during a slideshow, we occasionally cover the slideshow window with our WebView2 window (to "replace" the content of the presented slide). But, before we display that window, we need to change the parent window of our WebView2 window to the PowerPoint slideshow window (by calling the function SetParent with HWND of the PowerPoint Slideshow window) - so apps like Zoom or MS Teams stream also our WebView2 content while streaming just the PowerPoint slideshow window.

Unfortunately, it looks that the WebView2 WinForms control (unlike the CefSharp WinForms control) is not compatible with that solution: after changing the parent window, we can see just the empty WinForms window - the WebView2 control is not visible (although the embedded web is loaded).

Version
SDK: 1.0.790-prerelease
Runtime: 88.0.705.74 (Evergreen Bootstrapper)
Framework: WinForms
OS: Win10

Repro Steps

  1. Create a WinForms window with a WebVew2 control inside
  2. Change the parent window of that window using the SetParent function from Winuser.h to another (already visible) window
  3. Show the WinForms/WebView2 window
  4. Observe, that the parent window is covered by the empty WinForms/WebView2 window (the WebView2 control is not visible)

Additional context
The entire integration is built as a COM add-in (so it is running from the PowerPoint process).

AB#26124929

@DominikPalo DominikPalo added the bug Something isn't working label Feb 25, 2021
@DominikPalo DominikPalo changed the title WebView2 control is not visible after changing the parent window using the SetParent function WebView2 control is not visible after changing the parent window Feb 25, 2021
@pagoe-msft pagoe-msft added feature request feature request tracked We are tracking this work internally. and removed bug Something isn't working labels Mar 1, 2021
@pagoe-msft
Copy link

@DominikPalo

Thanks for submitting this issue! I've added it to our backlog and the team will investigate this.

@champnic champnic added bug Something isn't working and removed feature request feature request bug Something isn't working labels Mar 3, 2021
@champnic
Copy link
Member

champnic commented Mar 3, 2021

We specifically did work to enable reparenting of the Winforms control, so I think this may be a bug or regression.

@DominikPalo Which HWND specifically are you changing the parent to be the slideshow window? Are you able to try changing the Parent property of the WebView2 Winforms control and see if that works?

@DominikPalo
Copy link
Author

DominikPalo commented Mar 15, 2021

The main issue was that I was calling the SetParent method too early (before EnsureCoreWebView2Async)... after moving the SetParent call to the CoreWebView2InitializationCompleted handler, WebView2 is finally rendered, but I'm still not able to set the WebView2 window as a PowerPoint child window - SetParent now fails with ERROR_INVALID_STATE error and according to https://docs.microsoft.com/en-us/windows/win32/hidpi/high-dpi-desktop-application-development-on-windows#common-pitfalls-win32 the issue is caused by different DPI awareness modes.

Update: I've just discovered that if I didn't open a PowerPoint task pane with another WebView2 control instance before opening the window which should be hosted under the SlideShow window, everything works fine (I'm able to change the parent window).

Unfortunately, we need to use WebView2 in both places - in the task pane and also as a separate window hosted under the SlideShow window.

To summarize:

Scenario A:

  1. Open PowerPoint
  2. Open a task-pane with WebView2 control
  3. Start a slideshow
  4. Open a new window X with WebView2 control
  5. Try to change the parent of the window X to the PowerPoint slideshow window
  6. SetParent fails with ERROR_INVALID_STATE (the main msedgewebview2.exe has the same DPI awareness mode as the POWERPNT.EXE process - System Aware, so it should work)

image

Scenario B:

  1. Open PowerPoint
  2. Start a slideshow (without opening a task pane)
  3. Open a new window X with WebView2 control
  4. Try to change the parent of the window X to the PowerPoint slideshow window
  5. SetParent works fine (although the main msedgewebview2.exe has a different DPI awareness mode compared to the POWERPNT.EXE process - System Aware vs. Per-Monitor Aware). This is very strange because according to the MS documentation, the SetParent should work in scenario A and not in scenario B...

image

@jamesoli
Copy link
Contributor

@DominikPalo New APIs in Windows 10 1607 and above allow creating windows with different DPI awarenesses than the process creating it. I wonder if that's what's happening here. I'm not quite sure how you are getting the HWNDs to parent to so can you please check the DPI awareness of the HWND you are parenting to in both scenarios using the following API?

https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getwindowdpiawarenesscontext

@DominikPalo
Copy link
Author

DominikPalo commented Mar 23, 2021

#985

@DominikPalo New APIs in Windows 10 1607 and above allow creating windows with different DPI awarenesses than the process creating it. I wonder if that's what's happening here. I'm not quite sure how you are getting the HWNDs to parent to so can you please check the DPI awareness of the HWND you are parenting to in both scenarios using the following API?
https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getwindowdpiawarenesscontext

Thank you,

because it is not possible to use the SlideShowWindow argument from the Application.SlideShowBegin event handler (that is a known PowerPoint bug ), I'm getting the HWND of the slideshow/expected parent window using this workaround:

/// <summary>
/// Class name of the window with slide show content in PowerPoint 2010 and newer.
/// </summary>
public const string PowerPoint_SlideShowScreenName = "screenClass";

private void OnSlideShowBeginEvent(SlideShowWindow window)
{
    // We can't use window.HWND, it fails with:
    // System.Runtime.InteropServices.COMException HResult=-2147352573 Message=Member not found. (Exception from HRESULT: 0x80020003 (DISP_E_MEMBERNOTFOUND)) ErrorCode=-2147352573
    // See: https://github.com/NetOfficeFw/NetOffice/issues/103 and https://github.com/NetOfficeFw/NetOffice/issues/140

    // WORKAROUND (using https://www.pinvoke.net/default.aspx/user32.FindWindowEx)
    var slideShowWindowHwnd = NativeMethods.FindWindowEx(IntPtr.Zero, IntPtr.Zero, PowerPoint_SlideShowScreenName, null);

    var slideShowWindowDpiAwarenessContext = NativeMethods.GetWindowDpiAwarenessContext(slideShowWindowHwnd);
    var slideShowWindowDpiAwareness =  Utils.GetAwarenessFromDpiAwarenessContext(slideShowWindowDpiAwarenessContext);
    
    var threadDpiAwarenessContext = this.NativeMethods.GetThreadDpiAwarenessContext();
    var threadDpiAwareness = Utils.GetAwarenessFromDpiAwarenessContext(threadDpiAwarenessContext);
    
    var slideShowChildWindowWithWebView2 = new SlideShowChildWindowWithWebView2(slideShowWindowHwnd);
}

in both scenarios, the resulting awareness modes (slideShowWindowDpiAwareness and threadDpiAwareness) are set to SYSTEM_AWARE.

But, the window which I want to set as a child window of the slideshow window (and which is hosting the WebView2 control) has DPI mode set to UNAWARE in Scenario A and SYSTEM_AWARE in Scenario B (not sure why).

SlideShowChildWindowWithWebView2:

public partial class SlideShowChildWindowWithWebView2 : Form
{
    private IntPtr SlideShowWindowHwnd { get; set; }

    public SlideShowChildWindowWithWebView2(IntPtr slideShowWindowHwnd)
    {
        this.SlideShowWindowHwnd = slideShowWindowHwnd;
    
        InitializeComponent();
        this.InitializeAsync();
    }
    
    private async void InitializeAsync()
    {
        var webView2Environment = await WebView2Configuration.CreateEnvironmentAsync();
        await this.webView2.EnsureCoreWebView2Async(webView2Environment);
    }
    
    private void webView2_CoreWebView2InitializationCompleted(object sender, CoreWebView2InitializationCompletedEventArgs e)
    {
        NativeMethods.SetParent(this.Handle, this.SlideShowWindowHwnd); // this method fails in Scenario A
    }
}

I checked also the COMAddin.Application.ActiveWindow (which is passed to the ICTPFactory.CreateCTP method while creating the task pane hosting a WebView2 control) and its awareness mode is also set to SYSTEM_AWARE.

So, I'm not sure why the DPI awareness modes of my WebView2 subprocesses differs in scenario A vs. B.

I also tried to enforce the DPI awareness mode by setting/overriding the --embedded-browser-webview-dpi-awareness argument:

public static async Task<CoreWebView2Environment> CreateEnvironmentAsync()
{
    var options = new CoreWebView2EnvironmentOptions($"--embedded-browser-webview-dpi-awareness=1");
    return await CoreWebView2Environment.CreateAsync(null, UserDataFolder, options);
}

but it didn't help - WebViewLoader.dll overrides this argument with its own value later ...

Maybe there is a way how to make WebView2 subprocesses isolated? So the WebView2 control in the task pane would have its own WebView2 subprocess and also the WebView2 control in the SlideShow child window will have a different WebView2 subprocess - this would probably help to solve the issue.

@DominikPalo
Copy link
Author

DominikPalo commented Mar 23, 2021

I've just found that issue is caused by calling the EnsureCoreWebView2Async() - which changes the DPI awareness mode of the containing window :/

My snippet (part of SlideShowChildWindowWithWebView2.cs):

private async void InitializeAsync()
{
    var dpiContextBefore = NativeMethods().GetWindowDpiAwarenessContext(this.Handle);
    var dpiModeBefore = Utils.GetAwarenessFromDpiAwarenessContext(dpiContextBefore);
    
    var webView2Environment = await WebView2Configuration.CreateEnvironmentAsync();
    await this.webView2.EnsureCoreWebView2Async(webView2Environment);
            
    var dpiContextAfter = NativeMethods().GetWindowDpiAwarenessContext(this.Handle);
    var dpiModeAfter = Utils.GetAwarenessFromDpiAwarenessContext(dpiContextAfter);
}

Results:

Scenario A Scenario B
dpiModeBefore SYSTEM_AWARE SYSTEM_AWARE
dpiModeAfter UNAWARE SYSTEM_AWARE

@jamesoli
Copy link
Contributor

In reference to your last code segment, I'm not sure how the dpi awareness is different for an HWND after EnsureCoreWebView2Async. Windows doesn't allow changing the DPI awareness of a window after it's been created. Can you check if this.Handle refers to the same window handle in both cases?

In the case of a SetParent call, a forced reset of an entire process' DPI awareness can occur (refer to https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setparent) but an HWND cannot change DPI awareness after creation.

As to creating isolated WebView2 processes, the only way to do that is to create a separate environment with a different user data folder. Of course, this also means things like cookies and history aren't shared between your two WebView2 controls. But this would allow you to create two WebViews with different DPI Awareness levels.

@DominikPalo
Copy link
Author

DominikPalo commented Mar 24, 2021

@jamesoli thank you - to get more details, I updated the previous snippet to write results to the console (I got the previous results by using a breakpoint at line await this.webView2.EnsureCoreWebView2Async(webView2Environment);). So here is my new snippet:

private async void InitializeAsync()
{
    var dpiContextBefore = NativeMethods().GetWindowDpiAwarenessContext(this.Handle);
    var dpiModeBefore = Utils.GetAwarenessFromDpiAwarenessContext(dpiContextBefore);
    
    Debug.WriteLine($"Window handle before calling EnsureCoreWebView2Async: {this.Handle}");
    Debug.WriteLine($"DPI awareness mode before calling EnsureCoreWebView2Async: {dpiBefore}");
            
    var webView2Environment = await WebView2Configuration.CreateEnvironmentAsync();
    await this.webView2.EnsureCoreWebView2Async(webView2Environment);
            
    var dpiContextAfter = NativeMethods().GetWindowDpiAwarenessContext(this.Handle);
    var dpiModeAfter = Utils.GetAwarenessFromDpiAwarenessContext(dpiContextAfter);
        
    Debug.WriteLine($"Window handle after calling EnsureCoreWebView2Async: {this.Handle}");
    Debug.WriteLine($"DPI awareness mode after calling EnsureCoreWebView2Async: {dpiAfter}");
}

Now (without using breakpoints) the results are more interesting:

Scenario A

Window handle before calling EnsureCoreWebView2Async: 4917274
DPI awareness mode before calling EnsureCoreWebView2Async: SYSTEM_AWARE

BUT: calling the EnsureCoreWebView2Async method fails with System.Runtime.InteropServices.COMException: 'The group or resource is not in the correct state to perform the requested operation. (Exception from HRESULT: 0x8007139F and the process crashes, so I'm not able to get the "after" output.

The weird thing is that when I don't try to access this.Handle before calling the EnsureCoreWebView2Async method, it passes and the results are:

Window handle after EnsureCoreWebView2Async: 5966296
DPI awareness mode after calling EnsureCoreWebView2Async: UNAWARE

Scenario B (OK)

Window handle before EnsureCoreWebView2Async: 1772882
DPI awareness mode before calling EnsureCoreWebView2Async: SYSTEM_AWARE
Window handle after EnsureCoreWebView2Async: 1772882
DPI awareness mode after calling EnsureCoreWebView2Async: SYSTEM_AWARE

@jamesoli
Copy link
Contributor

What it looks like is happening is that in scenario A, since there are two WebViews created by threads with different DPI awarenesses, it will fail the same way SetParent does. That is because we do a cross process SetParent internally which is why when the second WebView is being spun up and we detect that the DPI awareness is different, we send back an error.

The reason that you don't see the issue if you don't reference this.Handle before calling EnsureCoreWebView2Async could be because an HWND is not created until this.Handle is referenced for the first time. And because no window is created, the DPI awareness is not set.

We are working on removing that cross process SetParent dependency which would allow you to create two WebViews with the same user data folder from threads with different DPI Awarenesses. But this will come some time in the future.

@champnic champnic added feature request feature request and removed bug Something isn't working labels Mar 31, 2021
@champnic
Copy link
Member

We can use this issue to track the planned work we have for parenting and will update this thread.

@DominikPalo
Copy link
Author

DominikPalo commented Apr 6, 2021

Thank you, but I would like to mention that even if I use the suggested workaround (use different UserData folders for my WebView2 controls) the issue still persists - I'm not able to change the parent window in scenario A and calling the "SetParent" method fails with ERROR_INVALID_STATE. So, it seems those WebView2 controls somehow still know about each other and the behaviour of the second control depends on the first one.
image

Regarding the threads - I've checked both threads by calling the GetThreadDpiAwarenessContext just before EnsureCoreWebView2Async and you are true - the thread in the PowerPoint TaskPane control (created by ICTPFactory.CreateCTP) is set to UNAWARE and the thread in my custom window is set to SYSTEM_AWARE

@jamesoli
Copy link
Contributor

jamesoli commented Apr 7, 2021

@DominikPalo I'm afraid the SetParent limitation is one from Windows itself. You can't call SetParent with windows of differing DPI awarenesses.

@DominikPalo
Copy link
Author

DominikPalo commented Apr 7, 2021

@DominikPalo I'm afraid the SetParent limitation is one from Windows itself. You can't call SetParent with windows of differing DPI awarenesses.

Yes, that is true - and I'm aware of it. But the question/problem is why calling webView2.EnsureCoreWebView2Async(webView2Environment) inside the TaskPane control (Scenario A) changes the way how subsequent windows are being created:

If I didn't initialize WebView2 in my task pane (commenting out EnsureCoreWebView2Async is enough) or use a different web view control (CefSharp), I'm able to create new WinForm windows with SYSTEM_AWARE DPI context, which is fine and everything works as expected. But, if I initialize a WebView2 control inside the task pane (which is UNAWARE) and then try to create any new WinForms window from the add-in process, all new windows are set to UNAWARE.

I'm calling SetThreadDpiAwarenessContext(PER_MONITOR_AWARE) before creating the window containing the second WebView2 control (otherwise it would be created as UNAWARE in both scenarios), but it seems that in Scenario A this setting is ignored and the new window is created as UNAWARE.

@jozefizso
Copy link

Hi @jamesoli, what options do we have here to make sure the WebView2 runtime will use the same DPI level as the PowerPoint process? This works well in CefSharp Chromium library and we can set our form with Chromium browser control as child of the PowerPoint's slide show window.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature request feature request tracked We are tracking this work internally.
Projects
None yet
Development

No branches or pull requests

6 participants