Skip to content

[Problem/Bug]: Switching to Window-to-Visual hosting mode causes context menu and <select> popups display offset #5448

@kohno97

Description

@kohno97

What happened?

Our legacy Win32 app is configured as DPI unaware. We switched from Windowed to Window-to-Visual hosting in order to resolve an issue where two instances would freeze each other if one was busy. The WebView2 control renders and receives mouse input correctly, however the context menu on right click and popups from <select> form input fields appear at an offset if rendered on a screen with scale other than 100%.

Importance

Moderate. My app's user experience is affected, but still usable.

Runtime Channel

Stable release (WebView2 Runtime)

Runtime Version

142.0.3595.94

SDK Version

1.0.3595.46

Framework

Win32

Operating System

Windows 11

OS Version

26100.6584

Repro steps

This is a minimal example, generated with CoPilot, that reproduces the problem. If executed on a monitor with scale larger than 100%, popups appear offset as in the attached images (in this case, scale = 125%). Popups appear normally on a screen with 100% scaling. Attached Wv2PopupIssue.zip.

// WebView2 Window-to-Visual issue on DPI-unaware app
#include <windows.h>
#include <wrl.h>
#include <wil/com.h>
#include <WebView2.h>
#include <WebView2EnvironmentOptions.h>
#include <ShellScalingApi.h>

using namespace Microsoft::WRL;

#pragma comment(lib, "user32.lib")
#pragma comment(lib, "gdi32.lib")
#pragma comment(lib, "shcore.lib")

HINSTANCE g_hInstance;
HWND g_hwndMain = nullptr;
HWND g_hwndLeft = nullptr;
HWND g_hwndWebView = nullptr;
wil::com_ptr<ICoreWebView2Controller> g_webViewController;

const wchar_t* CLASS_NAME = L"WV2TestWindow";
const wchar_t* LEFT_CLASS = L"LeftPanel";
const wchar_t* WEBVIEW_CLASS = L"WebViewPanel";

LRESULT CALLBACK WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
LRESULT CALLBACK LeftPanelProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
LRESULT CALLBACK WebViewPanelProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
void UpdateLayout(HWND hwnd);
void CreateWebView2();

int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR pCmdLine, int nCmdShow)
{
    g_hInstance = hInstance;

    // Set process to DPI unaware
    SetProcessDpiAwareness(PROCESS_DPI_UNAWARE);

    // Set environment variable for WebView2 to use visual hosting
    SetEnvironmentVariable(
        L"COREWEBVIEW2_FORCED_HOSTING_MODE",
        L"COREWEBVIEW2_HOSTING_MODE_WINDOW_TO_VISUAL");

    // Register main window class
    WNDCLASS wc = {};
    wc.lpfnWndProc = WndProc;
    wc.hInstance = hInstance;
    wc.lpszClassName = CLASS_NAME;
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
    wc.hCursor = LoadCursor(nullptr, IDC_ARROW);
    RegisterClass(&wc);

    // Register left panel class
    WNDCLASS wcLeft = {};
    wcLeft.lpfnWndProc = LeftPanelProc;
    wcLeft.hInstance = hInstance;
    wcLeft.lpszClassName = LEFT_CLASS;
    wcLeft.hbrBackground = CreateSolidBrush(RGB(255, 255, 224)); // Pale yellow
    wcLeft.hCursor = LoadCursor(nullptr, IDC_ARROW);
    RegisterClass(&wcLeft);

    // Register webview panel class
    WNDCLASS wcWebView = {};
    wcWebView.lpfnWndProc = WebViewPanelProc;
    wcWebView.hInstance = hInstance;
    wcWebView.lpszClassName = WEBVIEW_CLASS;
    wcWebView.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
    wcWebView.hCursor = LoadCursor(nullptr, IDC_ARROW);
    RegisterClass(&wcWebView);

    // Create main window
    g_hwndMain = CreateWindowEx(
        0,
        CLASS_NAME,
        L"WebView2 DPI Issue Test",
        WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT, 1024, 768,
        nullptr,
        nullptr,
        hInstance,
        nullptr
    );

    if (g_hwndMain == nullptr)
    {
        return 0;
    }

    // Create left panel
    g_hwndLeft = CreateWindowEx(
        0,
        LEFT_CLASS,
        nullptr,
        WS_CHILD | WS_VISIBLE,
        0, 0, 0, 0,
        g_hwndMain,
        nullptr,
        hInstance,
        nullptr
    );

    // Create webview panel
    g_hwndWebView = CreateWindowEx(
        0,
        WEBVIEW_CLASS,
        nullptr,
        WS_CHILD | WS_VISIBLE,
        0, 0, 0, 0,
        g_hwndMain,
        nullptr,
        hInstance,
        nullptr
    );

    ShowWindow(g_hwndMain, nCmdShow);
    UpdateLayout(g_hwndMain);

    // Create WebView2
    CreateWebView2();

    // Message loop
    MSG msg = {};
    while (GetMessage(&msg, nullptr, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return 0;
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg)
    {
    case WM_SIZE:
        UpdateLayout(hwnd);
        return 0;

    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    }
    return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

LRESULT CALLBACK LeftPanelProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

LRESULT CALLBACK WebViewPanelProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

void UpdateLayout(HWND hwnd)
{
    RECT rc;
    GetClientRect(hwnd, &rc);
    int width = rc.right - rc.left;
    int height = rc.bottom - rc.top;

    if (width > 800)
    {
        // Show left panel (200px wide)
        MoveWindow(g_hwndLeft, 0, 0, 200, height, TRUE);
        MoveWindow(g_hwndWebView, 200, 0, width - 200, height, TRUE);
        ShowWindow(g_hwndLeft, SW_SHOW);
    }
    else
    {
        // Hide left panel, full width for webview
        ShowWindow(g_hwndLeft, SW_HIDE);
        MoveWindow(g_hwndWebView, 0, 0, width, height, TRUE);
    }

    // Update WebView2 bounds if it exists
    if (g_webViewController)
    {
        RECT rcWebView;
        GetClientRect(g_hwndWebView, &rcWebView);
        g_webViewController->put_Bounds(rcWebView);
    }
}

void CreateWebView2()
{
    // Get user data folder path
    wchar_t userDataFolder[MAX_PATH];
    GetEnvironmentVariable(L"TEMP", userDataFolder, MAX_PATH);
    wcscat_s(userDataFolder, L"\\WV2TestUserData");

    CreateCoreWebView2EnvironmentWithOptions(
        nullptr,
        userDataFolder,
        nullptr,
        Callback<ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler>(
            [](HRESULT result, ICoreWebView2Environment* env) -> HRESULT
            {
                if (FAILED(result))
                {
                    return result;
                }

                // Create controller
                env->CreateCoreWebView2Controller(
                    g_hwndWebView,
                    Callback<ICoreWebView2CreateCoreWebView2ControllerCompletedHandler>(
                        [](HRESULT result, ICoreWebView2Controller* controller) -> HRESULT
                        {
                            if (FAILED(result))
                            {
                                return result;
                            }

                            g_webViewController = controller;

                            // Get WebView2 core
                            wil::com_ptr<ICoreWebView2> webView;
                            g_webViewController->get_CoreWebView2(&webView);

                            // Set bounds
                            RECT rc;
                            GetClientRect(g_hwndWebView, &rc);
                            g_webViewController->put_Bounds(rc);

                            // Navigate to URL
                            webView->Navigate(L"https://ace.c9.io/build/kitchen-sink.html");

                            return S_OK;
                        }
                    ).Get()
                );

                return S_OK;
            }
        ).Get()
    );
}
Image Image

Repros in Edge Browser

No, issue does not reproduce in the corresponding Edge version

Regression

Don't know

Last working version (if regression)

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions