From 177d8188c5964685a8a21d60aac9430c033443c6 Mon Sep 17 00:00:00 2001 From: Gordon MacMaster <31481849+gmacmaster@users.noreply.github.com> Date: Mon, 18 May 2026 15:08:00 -0400 Subject: [PATCH 1/2] fix: correct CompositionHwndHost initialization order for DPI-aware second windows (#7) * fix: correct CompositionHwndHost initialization order for DPI-aware second windows CompositionHwndHost::Initialize() connected the ContentIsland to the DesktopChildSiteBridge before setting the ResizePolicy, causing the island's coordinate space to be configured without proper DPI awareness. This led to InputPointerSource delivering touch coordinates that did not account for the display's scale factor on secondary windows at non-100% zoom. Reorders initialization to match ReactNativeWindow::ContentSiteBridge(): ResizePolicy is now set before Connect, and ScaleFactor is set before the island is connected. Also adds WM_DPICHANGED handling to TranslateMessage so the island's scale factor and layout are updated if the display DPI changes at runtime. * Update CompositionHwndHost.cpp * Update CompositionHwndHost.cpp * host lost on function return * Update CompositionHwndHost.cpp * Create react-native-windows-c628c673-e01f-4887-9fcf-b1e5c468d311.json --- ...-c628c673-e01f-4887-9fcf-b1e5c468d311.json | 7 ++++ .../Composition/CompositionHwndHost.cpp | 35 +++++++++++++++---- .../Fabric/Composition/CompositionHwndHost.h | 2 ++ 3 files changed, 38 insertions(+), 6 deletions(-) create mode 100644 change/react-native-windows-c628c673-e01f-4887-9fcf-b1e5c468d311.json diff --git a/change/react-native-windows-c628c673-e01f-4887-9fcf-b1e5c468d311.json b/change/react-native-windows-c628c673-e01f-4887-9fcf-b1e5c468d311.json new file mode 100644 index 00000000000..f3b0fa9830a --- /dev/null +++ b/change/react-native-windows-c628c673-e01f-4887-9fcf-b1e5c468d311.json @@ -0,0 +1,7 @@ +{ + "type": "prerelease", + "comment": "Fix CompositionHwndHost initialization order and add WM_DPICHANGED handling: ResizePolicy is now set before Connect and ScaleFactor before the island is connected, matching the ReactNativeWindow reference pattern, so that secondary windows render with correct DPI-aware touch coordinates. A new WM_DPICHANGED handler updates the island's scale factor and invalidates cached pixel dimensions before SetWindowPos, ensuring layout re-arranges immediately on runtime DPI changes — including same-monitor scale changes where the pixel rect is unchanged.", + "packageName": "react-native-windows", + "email": "gordomacmaster@gmail.com", + "dependentChangeType": "patch" +} diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionHwndHost.cpp b/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionHwndHost.cpp index c095f1d4dad..c0b72dcc860 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionHwndHost.cpp +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionHwndHost.cpp @@ -34,16 +34,19 @@ void CompositionHwndHost::Initialize(uint64_t hwnd) noexcept { ReactViewHost().ReactNativeHost().InstanceSettings().Properties()); m_compRootView = winrt::Microsoft::ReactNative::ReactNativeIsland(compositor); - auto bridge = winrt::Microsoft::UI::Content::DesktopChildSiteBridge::Create( + m_bridge = winrt::Microsoft::UI::Content::DesktopChildSiteBridge::Create( compositor, winrt::Microsoft::UI::GetWindowIdFromWindow(m_hwnd)); - auto island = m_compRootView.Island(); - - bridge.Connect(island); - bridge.Show(); + // ResizePolicy must be set before Connect so the bridge configures the + // island's coordinate space with correct DPI awareness (matches + // ReactNativeWindow::ContentSiteBridge initialization order). + m_bridge.ResizePolicy(winrt::Microsoft::UI::Content::ContentSizePolicy::ResizeContentToParentWindow); + auto island = m_compRootView.Island(); m_compRootView.ScaleFactor(ScaleFactor()); - bridge.ResizePolicy(winrt::Microsoft::UI::Content::ContentSizePolicy::ResizeContentToParentWindow); + + m_bridge.Connect(island); + m_bridge.Show(); m_compRootView.ReactViewHost(std::move(m_reactViewHost)); m_compRootView.ScaleFactor(ScaleFactor()); @@ -82,6 +85,26 @@ LRESULT CompositionHwndHost::TranslateMessage(int msg, uint64_t wParam, int64_t UpdateSize(); return 0; } + case WM_DPICHANGED: { + m_compRootView.ScaleFactor(ScaleFactor()); + // Invalidate cached pixel dimensions so the WM_WINDOWPOSCHANGED that + // follows SetWindowPos always passes UpdateSize's dimension guard and + // re-runs Arrange with the new DIP scale. Without this, a same-monitor + // scale change (identical pixel rect) would short-circuit UpdateSize and + // leave layout stale. + m_width = 0; + m_height = 0; + auto *suggestedRect = reinterpret_cast(lParam); + SetWindowPos( + m_hwnd, + nullptr, + suggestedRect->left, + suggestedRect->top, + suggestedRect->right - suggestedRect->left, + suggestedRect->bottom - suggestedRect->top, + SWP_NOZORDER | SWP_NOACTIVATE); + return 0; + } } if (m_compRootView) { diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionHwndHost.h b/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionHwndHost.h index f0dfda5dbfd..7f3ea7f04a6 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionHwndHost.h +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionHwndHost.h @@ -5,6 +5,7 @@ #include "CompositionHwndHost.g.h" #include +#include #include #include "ReactHost/React.h" @@ -34,6 +35,7 @@ struct CompositionHwndHost : CompositionHwndHostT { HWND m_hwnd; winrt::Microsoft::ReactNative::ReactNativeIsland m_compRootView{nullptr}; + winrt::Microsoft::UI::Content::DesktopChildSiteBridge m_bridge{nullptr}; LONG m_height{0}; LONG m_width{0}; From 2df334481aa16bb3dc4ce27778b16f98df18df8a Mon Sep 17 00:00:00 2001 From: Gordon MacMaster Date: Mon, 18 May 2026 16:45:01 -0400 Subject: [PATCH 2/2] pr comments --- ...-c628c673-e01f-4887-9fcf-b1e5c468d311.json | 2 +- .../Composition/CompositionHwndHost.cpp | 20 ------------------- 2 files changed, 1 insertion(+), 21 deletions(-) diff --git a/change/react-native-windows-c628c673-e01f-4887-9fcf-b1e5c468d311.json b/change/react-native-windows-c628c673-e01f-4887-9fcf-b1e5c468d311.json index f3b0fa9830a..57c9f9fa255 100644 --- a/change/react-native-windows-c628c673-e01f-4887-9fcf-b1e5c468d311.json +++ b/change/react-native-windows-c628c673-e01f-4887-9fcf-b1e5c468d311.json @@ -1,6 +1,6 @@ { "type": "prerelease", - "comment": "Fix CompositionHwndHost initialization order and add WM_DPICHANGED handling: ResizePolicy is now set before Connect and ScaleFactor before the island is connected, matching the ReactNativeWindow reference pattern, so that secondary windows render with correct DPI-aware touch coordinates. A new WM_DPICHANGED handler updates the island's scale factor and invalidates cached pixel dimensions before SetWindowPos, ensuring layout re-arranges immediately on runtime DPI changes — including same-monitor scale changes where the pixel rect is unchanged.", + "comment": "Fix CompositionHwndHost initialization order: ResizePolicy is now set before Connect and ScaleFactor before the island is connected, matching the ReactNativeWindow reference pattern, so that secondary windows render with correct DPI-aware touch coordinates.", "packageName": "react-native-windows", "email": "gordomacmaster@gmail.com", "dependentChangeType": "patch" diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionHwndHost.cpp b/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionHwndHost.cpp index c0b72dcc860..eaf49396132 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionHwndHost.cpp +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionHwndHost.cpp @@ -85,26 +85,6 @@ LRESULT CompositionHwndHost::TranslateMessage(int msg, uint64_t wParam, int64_t UpdateSize(); return 0; } - case WM_DPICHANGED: { - m_compRootView.ScaleFactor(ScaleFactor()); - // Invalidate cached pixel dimensions so the WM_WINDOWPOSCHANGED that - // follows SetWindowPos always passes UpdateSize's dimension guard and - // re-runs Arrange with the new DIP scale. Without this, a same-monitor - // scale change (identical pixel rect) would short-circuit UpdateSize and - // leave layout stale. - m_width = 0; - m_height = 0; - auto *suggestedRect = reinterpret_cast(lParam); - SetWindowPos( - m_hwnd, - nullptr, - suggestedRect->left, - suggestedRect->top, - suggestedRect->right - suggestedRect->left, - suggestedRect->bottom - suggestedRect->top, - SWP_NOZORDER | SWP_NOACTIVATE); - return 0; - } } if (m_compRootView) {