From 3d10001ba8e12336cad392846b1030ba691b784a Mon Sep 17 00:00:00 2001 From: reunion-maestro-bot Date: Tue, 12 Mar 2024 23:21:11 +0000 Subject: [PATCH] Syncing content from committish ad6b41f8923522a5ab752dddbe32ac9f38460afd --- .../CommandBarFlyoutCommandBar.cpp | 74 ++++++-- .../CommandBarFlyoutCommandBar.h | 14 +- controls/dev/ItemsView/ItemsView.cpp | 32 ++-- controls/dev/ItemsView/ItemsView.h | 3 +- controls/dev/MapControl/MapControl.cpp | 34 ++-- controls/dev/MapControl/map.html | 36 ++-- .../dev/NavigationView/NavigationView.cpp | 23 ++- controls/dev/NavigationView/NavigationView.h | 3 +- .../NavigationView/NavigationView.vcxitems | 1 + .../dev/NavigationView/NavigationViewItem.cpp | 66 ++++++- .../dev/NavigationView/NavigationViewItem.h | 3 +- .../NavigationView/NavigationViewItemBase.h | 4 + .../NavigationViewItemPresenter.cpp | 35 +++- .../NavigationViewItemPresenter.h | 6 +- .../dev/NavigationView/NavigationViewTrace.h | 164 ++++++++++++++++++ .../CommonTests.cs | 75 +++++++- .../TestUI/NavigationViewCaseBundle.xaml | 115 ++++++------ .../TestUI/NavigationViewCaseBundle.xaml.cs | 15 ++ controls/dev/Telemetry/MuxcTraceLogging.h | 50 +++++- .../dev/TestHooks/MUXControlsTestHooks.cpp | 11 ++ controls/dev/WebView2/WebView2.cpp | 26 ++- dxaml/xcp/core/core/elements/Popup.cpp | 35 +++- dxaml/xcp/dxaml/lib/InputSiteAdapter.h | 2 +- .../lib/WindowedPopupInputSiteAdapter.cpp | 13 +- dxaml/xcp/win/inc/xcpwindow.h | 39 ++++- dxaml/xcp/win/shared/xcpwindow.cpp | 31 +--- eng/Version.Details.xml | 12 +- 27 files changed, 748 insertions(+), 174 deletions(-) create mode 100644 controls/dev/NavigationView/NavigationViewTrace.h diff --git a/controls/dev/CommandBarFlyout/CommandBarFlyoutCommandBar.cpp b/controls/dev/CommandBarFlyout/CommandBarFlyoutCommandBar.cpp index 6cc8c92f33..95605c1f58 100644 --- a/controls/dev/CommandBarFlyout/CommandBarFlyoutCommandBar.cpp +++ b/controls/dev/CommandBarFlyout/CommandBarFlyoutCommandBar.cpp @@ -128,6 +128,7 @@ CommandBarFlyoutCommandBar::CommandBarFlyoutCommandBar() PopulateAccessibleControls(); UpdateFlowsFromAndFlowsTo(); UpdateUI(!m_commandBarFlyoutIsOpening); + AttachItemEventHandlers(); } }); @@ -141,6 +142,7 @@ CommandBarFlyoutCommandBar::CommandBarFlyoutCommandBar() PopulateAccessibleControls(); UpdateFlowsFromAndFlowsTo(); UpdateUI(!m_commandBarFlyoutIsOpening); + AttachItemEventHandlers(); } }); } @@ -260,7 +262,8 @@ void CommandBarFlyoutCommandBar::OnApplyTemplate() // primary commands's corner radius. BindOwningFlyoutPresenterToCornerRadius(); - AttachEventHandlers(); + AttachControlEventHandlers(); + AttachItemEventHandlers(); PopulateAccessibleControls(); UpdateFlowsFromAndFlowsTo(); UpdateUI(false /* useTransitions */); @@ -273,7 +276,7 @@ void CommandBarFlyoutCommandBar::SetOwningFlyout( m_owningFlyout = owningFlyout; } -void CommandBarFlyoutCommandBar::AttachEventHandlers() +void CommandBarFlyoutCommandBar::AttachControlEventHandlers() { COMMANDBARFLYOUT_TRACE_INFO(*this, TRACE_MSG_METH, METH_NAME, this); @@ -349,6 +352,37 @@ void CommandBarFlyoutCommandBar::AttachEventHandlers() } } +void CommandBarFlyoutCommandBar::AttachItemEventHandlers() +{ + m_itemLoadedRevokerVector.clear(); + + for (auto const& command : PrimaryCommands()) + { + if (auto commandAsFE = command.try_as()) + { + m_itemLoadedRevokerVector.push_back(commandAsFE.Loaded(winrt::auto_revoke, { + [this](winrt::IInspectable const& sender, auto const&) + { + UpdateItemVisualState(sender.as(), true /* isPrimaryControl */); + } + })); + } + } + + for (auto const& command : SecondaryCommands()) + { + if (auto commandAsFE = command.try_as()) + { + m_itemLoadedRevokerVector.push_back(commandAsFE.Loaded(winrt::auto_revoke, { + [this](winrt::IInspectable const& sender, auto const&) + { + UpdateItemVisualState(sender.as(), false /* isPrimaryControl */); + } + })); + } + } +} + void CommandBarFlyoutCommandBar::DetachEventHandlers() { COMMANDBARFLYOUT_TRACE_INFO(*this, TRACE_MSG_METH, METH_NAME, this); @@ -357,6 +391,7 @@ void CommandBarFlyoutCommandBar::DetachEventHandlers() m_secondaryItemsRootPreviewKeyDownRevoker.revoke(); m_secondaryItemsRootSizeChangedRevoker.revoke(); m_firstItemLoadedRevoker.revoke(); + m_itemLoadedRevokerVector.clear(); m_openingStoryboardCompletedRevoker.revoke(); m_closingStoryboardCompletedCallbackRevoker.revoke(); m_expandedUpToCollapsedStoryboardRevoker.revoke(); @@ -677,20 +712,12 @@ void CommandBarFlyoutCommandBar::UpdateVisualState( placementVisual.Clip(rectangleClip); } - auto hasVisibleLabel = [](TCommand const& command) - { - return command && - command.Label().size() > 0 && - command.Visibility() == winrt::Visibility::Visible && - command.LabelPosition() == winrt::CommandBarLabelPosition::Default; - }; - // If no primary command has labels, then we'll shrink down the size of primary commands since the extra space to accommodate labels is unnecessary. bool hasPrimaryCommandLabels = false; for (auto const& primaryCommand : PrimaryCommands()) { - if (hasVisibleLabel(primaryCommand.try_as()) || - hasVisibleLabel(primaryCommand.try_as())) + if (HasVisibleLabel(primaryCommand.try_as()) || + HasVisibleLabel(primaryCommand.try_as())) { hasPrimaryCommandLabels = true; break; @@ -717,6 +744,29 @@ void CommandBarFlyoutCommandBar::UpdateVisualState( winrt::VisualStateManager::GoToState(*this, hasPrimaryCommandLabels ? L"HasPrimaryLabels" : L"NoPrimaryLabels", useTransitions); } +void CommandBarFlyoutCommandBar::UpdateItemVisualState(winrt::Control const& item, bool isPrimaryItem) +{ + if (isPrimaryItem) + { + bool hasPrimaryCommandLabels = false; + for (auto const& primaryCommand : PrimaryCommands()) + { + if (HasVisibleLabel(primaryCommand.try_as()) || + HasVisibleLabel(primaryCommand.try_as())) + { + hasPrimaryCommandLabels = true; + break; + } + } + + winrt::VisualStateManager::GoToState(item, hasPrimaryCommandLabels ? L"HasPrimaryLabels" : L"NoPrimaryLabels", false /* useTransitions */); + } + else + { + winrt::VisualStateManager::GoToState(item, L"NoPrimaryLabels", false /* useTransitions */); + } +} + void CommandBarFlyoutCommandBar::UpdateTemplateSettings() { COMMANDBARFLYOUT_TRACE_INFO(*this, TRACE_MSG_METH_INT, METH_NAME, this, IsOpen()); diff --git a/controls/dev/CommandBarFlyout/CommandBarFlyoutCommandBar.h b/controls/dev/CommandBarFlyout/CommandBarFlyoutCommandBar.h index 2989abc1b7..278ed88d35 100644 --- a/controls/dev/CommandBarFlyout/CommandBarFlyoutCommandBar.h +++ b/controls/dev/CommandBarFlyout/CommandBarFlyoutCommandBar.h @@ -51,12 +51,14 @@ class CommandBarFlyoutCommandBar : void ClearLocalizedStringResourceCache(); private: - void AttachEventHandlers(); + void AttachControlEventHandlers(); + void AttachItemEventHandlers(); void DetachEventHandlers(); void UpdateFlowsFromAndFlowsTo(); void UpdateUI(bool useTransitions = true, bool isForSizeChange = false); void UpdateVisualState(bool useTransitions, bool isForSizeChange = false); + void UpdateItemVisualState(winrt::Control const& item, bool isPrimaryItem); void UpdateTemplateSettings(); void EnsureAutomationSetCountAndPosition(); void EnsureLocalizedControlTypes(); @@ -65,6 +67,15 @@ class CommandBarFlyoutCommandBar : void SetPresenterName(winrt::FlyoutPresenter const& presenter); + template + bool HasVisibleLabel(TCommand const& command) + { + return command && + command.Label().size() > 0 && + command.Visibility() == winrt::Visibility::Visible && + command.LabelPosition() == winrt::CommandBarLabelPosition::Default; + }; + static bool IsControlFocusable( winrt::Control const& control, bool checkTabStop); @@ -95,6 +106,7 @@ class CommandBarFlyoutCommandBar : winrt::UIElement::PreviewKeyDown_revoker m_secondaryItemsRootPreviewKeyDownRevoker{}; winrt::FrameworkElement::SizeChanged_revoker m_secondaryItemsRootSizeChangedRevoker{}; winrt::FrameworkElement::Loaded_revoker m_firstItemLoadedRevoker{}; + std::vector m_itemLoadedRevokerVector{}; // We need to manually connect the end element of the primary items to the start element of the secondary items // for the purposes of UIA items navigation. To ensure that we only have the current start and end elements registered diff --git a/controls/dev/ItemsView/ItemsView.cpp b/controls/dev/ItemsView/ItemsView.cpp index cb1685e243..ddf402ce0f 100644 --- a/controls/dev/ItemsView/ItemsView.cpp +++ b/controls/dev/ItemsView/ItemsView.cpp @@ -2083,32 +2083,42 @@ void ItemsView::SetItemsViewItemContainerRevokers( itemContainer.SetValue(s_ItemsViewItemContainerRevokersProperty, itemContainerRevokers.as()); - m_itemContainersWithRevokers.insert(itemContainer); + m_itemContainersWithRevokers.insert(tracker_ref{ this, itemContainer }); } void ItemsView::ClearItemsViewItemContainerRevokers( const winrt::ItemContainer& itemContainer) { - RevokeItemsViewItemContainerRevokers(itemContainer); - itemContainer.SetValue(s_ItemsViewItemContainerRevokersProperty, nullptr); - m_itemContainersWithRevokers.erase(itemContainer); + const auto& itemContainerRef = tracker_ref{ this, itemContainer }; + const auto& itemContainerSafe = itemContainerRef.safe_get(); + if (itemContainerSafe) + { + RevokeItemsViewItemContainerRevokers(itemContainerSafe); + itemContainerSafe.SetValue(s_ItemsViewItemContainerRevokersProperty, nullptr); + } + const bool removed = static_cast(m_itemContainersWithRevokers.erase(itemContainerRef)); + MUX_ASSERT(removed); } void ItemsView::ClearAllItemsViewItemContainerRevokers() noexcept { - for (const auto& itemContainer : m_itemContainersWithRevokers) + for (const auto& itemContainerTracker : m_itemContainersWithRevokers) { + const auto& itemContainer = itemContainerTracker.safe_get(); // ClearAllItemsViewItemContainerRevokers is only called in the destructor, where exceptions cannot be thrown. // If the associated ItemsView items have not yet been cleaned up, we must detach these revokers or risk a call into freed // memory being made. However if they have been cleaned up these calls will throw. In this case we can ignore // those exceptions. - try - { - RevokeItemsViewItemContainerRevokers(itemContainer); - itemContainer.SetValue(s_ItemsViewItemContainerRevokersProperty, nullptr); - } - catch (...) + if (itemContainer) { + try + { + RevokeItemsViewItemContainerRevokers(itemContainer); + itemContainer.SetValue(s_ItemsViewItemContainerRevokersProperty, nullptr); + } + catch (...) + { + } } } m_itemContainersWithRevokers.clear(); diff --git a/controls/dev/ItemsView/ItemsView.h b/controls/dev/ItemsView/ItemsView.h index 04e0c244e0..a21a5e31a5 100644 --- a/controls/dev/ItemsView/ItemsView.h +++ b/controls/dev/ItemsView/ItemsView.h @@ -399,6 +399,5 @@ class ItemsView : tracker_ref m_bringIntoViewElement{ this }; std::list m_navigationKeysToProcess; - std::set m_itemContainersWithRevokers; - std::map>> m_itemContainersPointerInfos; + std::set> m_itemContainersWithRevokers; }; diff --git a/controls/dev/MapControl/MapControl.cpp b/controls/dev/MapControl/MapControl.cpp index 763468cf5f..791b6c34d1 100644 --- a/controls/dev/MapControl/MapControl.cpp +++ b/controls/dev/MapControl/MapControl.cpp @@ -15,15 +15,14 @@ #include "MapControlMapServiceErrorOccurredEventArgs.h" #include "MapElementsLayer.h" #include "Vector.h" - - +#include "MuxcTraceLogging.h" winrt::hstring MapControl::s_mapHtmlContent{}; MapControl::MapControl() { SetDefaultStyleKey(this); - + // Create layers vector that can hold MapElements to display information and be interacted with on the map auto layers = winrt::make>().as>(); layers.VectorChanged({ this, &MapControl::OnLayersVectorChanged }); @@ -41,14 +40,15 @@ void MapControl::OnApplyTemplate() m_webView.set(webview); m_webviewWebMessageReceivedRevoker = webview.WebMessageReceived(winrt::auto_revoke, { this, &MapControl::WebMessageReceived }); - m_webviewNavigationCompletedRevoker = webview.NavigationCompleted(winrt::auto_revoke,{ + m_webviewNavigationCompletedRevoker = webview.NavigationCompleted(winrt::auto_revoke, { [this](auto const&, winrt::CoreWebView2NavigationCompletedEventArgs const& args) { + XamlTelemetry::MapControl_WebViewNavigationCompleted(reinterpret_cast(this)); InitializeWebMap(); }}); SetUpWebView(); - } + } } winrt::IAsyncOperation MapControl::GetCoreWebView2() @@ -75,6 +75,8 @@ winrt::fire_and_forget MapControl::SetUpWebView() winrt::IAsyncOperation MapControl::InitializeWebMap() { + XamlTelemetry::MapControl_InitializeWebMap(true, reinterpret_cast(this)); + winrt::hstring returnedValue{}; auto center = Center(); winrt::hstring pos = L"0,0"; @@ -100,6 +102,8 @@ winrt::IAsyncOperation MapControl::InitializeWebMap() } } UpdateControlsInWebPage(); + + XamlTelemetry::MapControl_InitializeWebMap(false, reinterpret_cast(this)); co_return returnedValue; } @@ -128,8 +132,11 @@ void MapControl::OnPropertyChanged(const winrt::DependencyPropertyChangedEventAr } } -bool MapControl::IsValidMapServiceToken() { - return !MapServiceToken().empty() && std::all_of(MapServiceToken().begin(), MapServiceToken().end(), ::isalnum); +bool MapControl::IsValidMapServiceToken() +{ + // This token is passed as a JavaScript param in double quotes. Make sure the token itself doesn't contain any. + return !MapServiceToken().empty() + && std::all_of(MapServiceToken().begin(), MapServiceToken().end(), [](wchar_t i){return i != '"';}); } // Updates Azure Maps Authentication Key on WebView @@ -147,7 +154,7 @@ winrt::fire_and_forget MapControl::UpdateMapServiceTokenInWebPage() winrt::MapElementsLayer layer = Layers().GetAt(i).try_as(); OnLayerAdded(layer); } - + UpdateControlsInWebPage(); UpdateCenterInWebPage(); UpdateZoomLevelInWebPage(); @@ -199,8 +206,8 @@ void MapControl::WebMessageReceived(winrt::WebView2 sender, winrt::CoreWebView2W if (type.GetString() == L"pushpinClickEvent") { auto clickedLayer = Layers().GetAt(static_cast(obj.GetNamedObject(L"layer").GetNumber())).try_as(); - auto location = winrt::Geopoint{ winrt::BasicGeoposition{ - obj.GetNamedObject(L"coordinate").GetNamedValue(L"longitude").GetNumber(), + auto location = winrt::Geopoint{ winrt::BasicGeoposition{ + obj.GetNamedObject(L"coordinate").GetNamedValue(L"longitude").GetNumber(), obj.GetNamedObject(L"coordinate").GetNamedValue(L"latitude").GetNumber() }}; auto pointId = obj.GetNamedObject(L"point").GetString(); @@ -223,6 +230,7 @@ void MapControl::WebMessageReceived(winrt::WebView2 sender, winrt::CoreWebView2W else if (type.GetString() == L"javascriptError") { auto errorArgs = winrt::make_self(jsonAsString); + XamlTelemetry::MapControl_WebMessageReceived_Error(reinterpret_cast(this), jsonAsString.data()); m_mapServiceErrorOccurredEventSource(*this, *errorArgs); } } @@ -246,7 +254,7 @@ winrt::fire_and_forget MapControl::ResetLayerCollection() for (uint32_t i = 0; i < Layers().Size(); i++) { - winrt::MapElementsLayer layer = Layers().GetAt(i).as(); + winrt::MapElementsLayer layer = Layers().GetAt(i).as(); auto layerId = winrt::get_self(layer)->Id; co_await core.ExecuteScriptAsync(L"clearLayer(" + winrt::to_hstring(layerId) + L");"); @@ -272,7 +280,7 @@ void MapControl::OnLayersVectorChanged(const winrt::IObservableVector()) { auto layerId = winrt::get_self(elementLayer)->Id; @@ -309,7 +317,7 @@ winrt::IAsyncOperation MapControl::AddMapIcon(winrt::Geopoint ma if (auto webView = m_webView.get()) { auto core = co_await GetCoreWebView2(); - + const auto mapIconPosition = mapIconPoint.Position(); const auto latitude = winrt::to_hstring(mapIconPosition.Latitude); const auto longitude = winrt::to_hstring(mapIconPosition.Longitude); diff --git a/controls/dev/MapControl/map.html b/controls/dev/MapControl/map.html index 64b7b500a1..43a49ace5a 100644 --- a/controls/dev/MapControl/map.html +++ b/controls/dev/MapControl/map.html @@ -16,7 +16,6 @@ margin: 0; overflow: hidden; font-family: segoeui; - background-color: blue; } #mapContainer { @@ -35,7 +34,7 @@ var layersData = {}; var pointsData = []; var dataSource; - + function initializeMap(longitude, latitude, mapServiceToken) { try { //Initialize a map instance. @@ -43,14 +42,15 @@ center: [longitude, latitude], zoom: 3, view: 'Auto', - + //Add authentication details for connecting to Azure Maps. + // Note: an invalid token doesn't seem to throw any errors. authOptions: { authType: 'subscriptionKey', subscriptionKey: mapServiceToken } }); - + map.events.add('ready', function () { dataSource = new atlas.source.DataSource(); map.sources.add(dataSource); @@ -75,14 +75,14 @@ map.layers.add(newLayer); map.events.add('click', newLayer, sendPushpinClickInfo); }); - + } catch (error) { sendErrorInfo(error); } return newLayer.getId(); } - + function addPoint(longitude, latitude, layerId) { var point = new atlas.Shape(new atlas.data.Point([longitude, latitude])); try { @@ -102,7 +102,7 @@ try { map.events.add('ready', function () { pointsData[layerId][pointId].setCoordinates([longitude, latitude]); - + logSymbolLayers(); }); } @@ -162,7 +162,7 @@ map.controls.clear(); } } - + function updateCenter(latitude, longitude) { try { map.setCamera({ @@ -184,7 +184,7 @@ sendErrorInfo(error); } } - + function updateMapServiceToken(mapServiceToken) { try { clearMap(); @@ -197,7 +197,7 @@ sendErrorInfo(error); } } - + function sendPushpinClickInfo(e) { var latitude = e.position[1]; var longitude = e.position[0]; @@ -221,7 +221,7 @@ catch (error) { sendErrorInfo(error); } - + const coordinate = { "latitude": latitude, "longitude": longitude, @@ -233,10 +233,10 @@ "point": pointClicked, "text": "Pushpin clicked!" }; - + window.chrome.webview.postMessage(message); } - + function getMapLayers() { try { layers = map.layers.getLayers(); @@ -253,7 +253,7 @@ sendErrorInfo(error); } } - + function logSymbolLayers() { try { // print out each symbolLayer in symbolLayers by id @@ -266,24 +266,24 @@ sendErrorInfo(error); } } - + function getSymbolLayers() { return symbolLayers; } - + function sendErrorInfo(e) { console.log(e); const coordinate = { "latitude": 0, "longitude": 0, } - + const message = { "type": "javascriptError", "coordinate": coordinate, "text": e.stack }; - + window.chrome.webview.postMessage(message); } diff --git a/controls/dev/NavigationView/NavigationView.cpp b/controls/dev/NavigationView/NavigationView.cpp index d854aadc17..8cc71d3284 100644 --- a/controls/dev/NavigationView/NavigationView.cpp +++ b/controls/dev/NavigationView/NavigationView.cpp @@ -105,6 +105,10 @@ constexpr int s_itemNotFound{ -1 }; static winrt::Size c_infSize{ std::numeric_limits::infinity(), std::numeric_limits::infinity() }; +// Change to 'true' to turn on debugging outputs in Output window +bool NavigationViewTrace::s_IsDebugOutputEnabled{ false }; +bool NavigationViewTrace::s_IsVerboseDebugOutputEnabled{ false }; + NavigationView::~NavigationView() { UnhookEventsAndClearFields(true); @@ -1189,6 +1193,7 @@ void NavigationView::OnRepeaterElementPrepared(const winrt::ItemsRepeater& ir, c auto nvibImpl = winrt::get_self(nvib); nvibImpl->SetNavigationViewParent(*this); nvibImpl->IsTopLevelItem(IsTopLevelItem(nvib)); + nvibImpl->IsInNavigationViewOwnedRepeater(true); // Visual state info propagation auto position = [this, ir]() @@ -1288,6 +1293,7 @@ void NavigationView::OnRepeaterElementClearing(const winrt::ItemsRepeater& ir, c auto const nvibImpl = winrt::get_self(nvib); nvibImpl->Depth(0); nvibImpl->IsTopLevelItem(false); + nvibImpl->IsInNavigationViewOwnedRepeater(false); ClearNavigationViewItemBaseRevokers(nvib); } } @@ -3478,7 +3484,7 @@ void NavigationView::SetNavigationViewItemBaseRevokers(const winrt::NavigationVi auto nvibRevokers = winrt::make_self(); nvibRevokers->visibilityRevoker = RegisterPropertyChanged(nvib, winrt::UIElement::VisibilityProperty(), { this, &NavigationView::OnNavigationViewItemBaseVisibilityPropertyChanged }); nvib.SetValue(s_NavigationViewItemBaseRevokersProperty, nvibRevokers.as()); - m_itemsWithRevokerObjects.insert(nvib); + m_itemsWithRevokerObjects.insert(tracker_ref{ this, nvib }); } void NavigationView::SetNavigationViewItemRevokers(const winrt::NavigationViewItem& nvi) @@ -3497,15 +3503,22 @@ void NavigationView::SetNavigationViewItemRevokers(const winrt::NavigationViewIt void NavigationView::ClearNavigationViewItemBaseRevokers(const winrt::NavigationViewItemBase& nvib) { - RevokeNavigationViewItemBaseRevokers(nvib); - nvib.SetValue(s_NavigationViewItemBaseRevokersProperty, nullptr); - m_itemsWithRevokerObjects.erase(nvib); + const auto& nvibRef = tracker_ref{ this, nvib }; + const auto& nvibSafe = nvibRef.safe_get(); + if (nvibSafe) + { + RevokeNavigationViewItemBaseRevokers(nvibSafe); + nvibSafe.SetValue(s_NavigationViewItemBaseRevokersProperty, nullptr); + } + const bool removed = static_cast(m_itemsWithRevokerObjects.erase(nvibRef)); + MUX_ASSERT(removed); } void NavigationView::ClearAllNavigationViewItemBaseRevokers() noexcept { - for (const auto& nvib : m_itemsWithRevokerObjects) + for (const auto& nvibTracker : m_itemsWithRevokerObjects) { + const auto& nvib = nvibTracker.safe_get(); // ClearAllNavigationViewItemBaseRevokers is only called in the destructor, where exceptions cannot be thrown. // If the associated NV has not yet been cleaned up, we must detach these revokers or risk a call into freed // memory being made. However if they have been cleaned up these calls will throw. In this case we can ignore diff --git a/controls/dev/NavigationView/NavigationView.h b/controls/dev/NavigationView/NavigationView.h index da5618ad87..c41e36c14d 100644 --- a/controls/dev/NavigationView/NavigationView.h +++ b/controls/dev/NavigationView/NavigationView.h @@ -13,6 +13,7 @@ struct bringintoview_event_revoker; #include "NavigationViewHelper.h" #include "NavigationView.properties.h" #include "NavigationViewItemsFactory.h" +#include "NavigationViewTrace.h" enum class TopNavigationViewLayoutState { @@ -193,7 +194,7 @@ class NavigationView : void ClearNavigationViewItemBaseRevokers(const winrt::NavigationViewItemBase& nvib); void ClearAllNavigationViewItemBaseRevokers() noexcept; void RevokeNavigationViewItemBaseRevokers(const winrt::NavigationViewItemBase& nvib); - std::set m_itemsWithRevokerObjects; + std::set> m_itemsWithRevokerObjects; void InvalidateTopNavPrimaryLayout(); // Measure functions for top navigation diff --git a/controls/dev/NavigationView/NavigationView.vcxitems b/controls/dev/NavigationView/NavigationView.vcxitems index 073221337b..fa414ac90e 100644 --- a/controls/dev/NavigationView/NavigationView.vcxitems +++ b/controls/dev/NavigationView/NavigationView.vcxitems @@ -66,6 +66,7 @@ + diff --git a/controls/dev/NavigationView/NavigationViewItem.cpp b/controls/dev/NavigationView/NavigationViewItem.cpp index adc40e1a3f..285413d8f1 100644 --- a/controls/dev/NavigationView/NavigationViewItem.cpp +++ b/controls/dev/NavigationView/NavigationViewItem.cpp @@ -918,10 +918,9 @@ void NavigationViewItem::PropagateDepthToChildren(int depth) } } -void NavigationViewItem::OnExpandCollapseChevronTapped(const winrt::IInspectable& sender, const winrt::TappedRoutedEventArgs& args) +void NavigationViewItem::OnExpandCollapseChevronPointerReleased() { IsExpanded(!IsExpanded()); - args.Handled(true); } void NavigationViewItem::OnFlyoutClosing(const winrt::IInspectable& sender, const winrt::FlyoutBaseClosingEventArgs& args) @@ -982,6 +981,8 @@ void NavigationViewItem::OnLostFocus(winrt::RoutedEventArgs const& e) void NavigationViewItem::ResetTrackedPointerId() { + NAVIGATIONVIEW_TRACE_VERBOSE(*this, TRACE_MSG_METH, METH_NAME, this); + m_trackedPointerId = 0; } @@ -1005,9 +1006,36 @@ bool NavigationViewItem::IgnorePointerId(const winrt::PointerRoutedEventArgs& ar void NavigationViewItem::OnPresenterPointerPressed(const winrt::IInspectable&, const winrt::PointerRoutedEventArgs& args) { - if (IgnorePointerId(args)) + const bool ignorePointerId = IgnorePointerId(args); + NAVIGATIONVIEW_TRACE_VERBOSE(*this, TRACE_MSG_METH_INT, METH_NAME, this, ignorePointerId); + if (ignorePointerId) { - return; + // FUTURE: Remove this workaround, which always switches to a new Touch or Pen input + // in case a previous touch/input got lost due to a missing PointerCaptureLost event. + auto const& pointerDeviceType = args.Pointer().PointerDeviceType(); + if ( + pointerDeviceType == winrt::PointerDeviceType::Touch || + pointerDeviceType == winrt::PointerDeviceType::Pen) + { + m_isPressed = false; + m_isPointerOver = false; + + if (m_capturedPointer) + { + auto presenter = GetPresenterOrItem(); + + MUX_ASSERT(presenter); + + presenter.ReleasePointerCapture(m_capturedPointer); + m_capturedPointer = nullptr; + } + + ResetTrackedPointerId(); + } + else + { + return; + } } auto pointerProperties = args.GetCurrentPoint(*this).Properties(); @@ -1039,7 +1067,9 @@ void NavigationViewItem::OnPresenterPointerPressed(const winrt::IInspectable&, c void NavigationViewItem::OnPresenterPointerReleased(const winrt::IInspectable&, const winrt::PointerRoutedEventArgs& args) { - if (IgnorePointerId(args)) + const bool ignorePointerId = IgnorePointerId(args); + NAVIGATIONVIEW_TRACE_VERBOSE(*this, TRACE_MSG_METH_INT_INT, METH_NAME, this, ignorePointerId, m_isPressed); + if (ignorePointerId) { return; } @@ -1048,7 +1078,8 @@ void NavigationViewItem::OnPresenterPointerReleased(const winrt::IInspectable&, { m_isPressed = false; - if (!IsOutOfControlBounds(args.GetCurrentPoint(*this).Position())) + if (!IsOutOfControlBounds(args.GetCurrentPoint(*this).Position()) && + IsInNavigationViewOwnedRepeater()) { if (auto nvImpl = winrt::get_self(GetNavigationView())) { @@ -1064,6 +1095,7 @@ void NavigationViewItem::OnPresenterPointerReleased(const winrt::IInspectable&, MUX_ASSERT(presenter); presenter.ReleasePointerCapture(m_capturedPointer); + m_capturedPointer = nullptr; } UpdateVisualState(true); @@ -1072,17 +1104,23 @@ void NavigationViewItem::OnPresenterPointerReleased(const winrt::IInspectable&, void NavigationViewItem::OnPresenterPointerEntered(const winrt::IInspectable&, const winrt::PointerRoutedEventArgs& args) { + NAVIGATIONVIEW_TRACE_VERBOSE(*this, TRACE_MSG_METH, METH_NAME, this); + ProcessPointerOver(args); } void NavigationViewItem::OnPresenterPointerMoved(const winrt::IInspectable&, const winrt::PointerRoutedEventArgs& args) { + NAVIGATIONVIEW_TRACE_VERBOSE(*this, TRACE_MSG_METH, METH_NAME, this); + ProcessPointerOver(args); } void NavigationViewItem::OnPresenterPointerExited(const winrt::IInspectable&, const winrt::PointerRoutedEventArgs& args) { - if (IgnorePointerId(args)) + const bool ignorePointerId = IgnorePointerId(args); + NAVIGATIONVIEW_TRACE_VERBOSE(*this, TRACE_MSG_METH_INT_INT, METH_NAME, this, ignorePointerId, !!m_capturedPointer); + if (ignorePointerId) { return; } @@ -1099,16 +1137,22 @@ void NavigationViewItem::OnPresenterPointerExited(const winrt::IInspectable&, co void NavigationViewItem::OnPresenterPointerCanceled(const winrt::IInspectable&, const winrt::PointerRoutedEventArgs& args) { + NAVIGATIONVIEW_TRACE_VERBOSE(*this, TRACE_MSG_METH, METH_NAME, this); + ProcessPointerCanceled(args); } void NavigationViewItem::OnPresenterPointerCaptureLost(const winrt::IInspectable&, const winrt::PointerRoutedEventArgs& args) { + NAVIGATIONVIEW_TRACE_VERBOSE(*this, TRACE_MSG_METH, METH_NAME, this); + ProcessPointerCanceled(args); } void NavigationViewItem::OnIsEnabledChanged(const winrt::IInspectable&, const winrt::DependencyPropertyChangedEventArgs&) { + NAVIGATIONVIEW_TRACE_VERBOSE(*this, TRACE_MSG_METH, METH_NAME, this); + if (!IsEnabled()) { m_isPressed = false; @@ -1140,7 +1184,9 @@ void NavigationViewItem::RotateExpandCollapseChevron(bool isExpanded) void NavigationViewItem::ProcessPointerCanceled(const winrt::PointerRoutedEventArgs& args) { - if (IgnorePointerId(args)) + const bool ignorePointerId = IgnorePointerId(args); + NAVIGATIONVIEW_TRACE_VERBOSE(*this, TRACE_MSG_METH_INT, METH_NAME, this, ignorePointerId); + if (ignorePointerId) { return; } @@ -1176,7 +1222,9 @@ bool NavigationViewItem::IsOutOfControlBounds(const winrt::Point& point) { void NavigationViewItem::ProcessPointerOver(const winrt::PointerRoutedEventArgs& args) { - if (IgnorePointerId(args)) + const bool ignorePointerId = IgnorePointerId(args); + NAVIGATIONVIEW_TRACE_VERBOSE(*this, TRACE_MSG_METH_INT, METH_NAME, this, ignorePointerId); + if (ignorePointerId) { return; } diff --git a/controls/dev/NavigationView/NavigationViewItem.h b/controls/dev/NavigationView/NavigationViewItem.h index f6595eb1c1..0eca30f893 100644 --- a/controls/dev/NavigationView/NavigationViewItem.h +++ b/controls/dev/NavigationView/NavigationViewItem.h @@ -9,6 +9,7 @@ struct bringintoview_event_revoker; #include "NavigationViewItem.g.h" #include "NavigationViewItemPresenter.h" #include "NavigationViewItem.properties.h" +#include "NavigationViewTrace.h" class NavigationViewItem : public winrt::implementation::NavigationViewItemT, @@ -58,7 +59,7 @@ class NavigationViewItem : winrt::ItemsRepeater GetRepeater() const { return m_repeater.get(); }; - void OnExpandCollapseChevronTapped(const winrt::IInspectable& sender, const winrt::TappedRoutedEventArgs& args); + void OnExpandCollapseChevronPointerReleased(); void RotateExpandCollapseChevron(bool isExpanded); bool IsRepeaterVisible() const; void PropagateDepthToChildren(int depth); diff --git a/controls/dev/NavigationView/NavigationViewItemBase.h b/controls/dev/NavigationView/NavigationViewItemBase.h index 66804a3016..638b57780a 100644 --- a/controls/dev/NavigationView/NavigationViewItemBase.h +++ b/controls/dev/NavigationView/NavigationViewItemBase.h @@ -68,6 +68,9 @@ class NavigationViewItemBase : void CreatedByNavigationViewItemsFactory(bool createdByNavigationViewItemsFactory) { m_createdByNavigationViewItemsFactory = createdByNavigationViewItemsFactory; }; bool CreatedByNavigationViewItemsFactory() { return m_createdByNavigationViewItemsFactory; }; + void IsInNavigationViewOwnedRepeater(bool isInNavigationViewOwnedRepeater) { m_isInNavigationViewOwnedRepeater = isInNavigationViewOwnedRepeater; }; + bool IsInNavigationViewOwnedRepeater() const { return m_isInNavigationViewOwnedRepeater; }; + protected: winrt::weak_ref m_navigationView{ nullptr }; @@ -81,4 +84,5 @@ class NavigationViewItemBase : // Flag to keep track of whether this item was created by the custom internal NavigationViewItemsFactory. // This is required in order to achieve proper recycling bool m_createdByNavigationViewItemsFactory{ false }; + bool m_isInNavigationViewOwnedRepeater{ false }; }; diff --git a/controls/dev/NavigationView/NavigationViewItemPresenter.cpp b/controls/dev/NavigationView/NavigationViewItemPresenter.cpp index 6a396ac55a..63e82938e0 100644 --- a/controls/dev/NavigationView/NavigationViewItemPresenter.cpp +++ b/controls/dev/NavigationView/NavigationViewItemPresenter.cpp @@ -21,7 +21,8 @@ NavigationViewItemPresenter::NavigationViewItemPresenter() void NavigationViewItemPresenter::UnhookEventsAndClearFields() { - m_expandCollapseChevronTappedRevoker.revoke(); + m_expandCollapseChevronPointerPressedRevoker.revoke(); + m_expandCollapseChevronPointerReleasedRevoker.revoke(); m_contentGrid.set(nullptr); m_infoBadgePresenter.set(nullptr); @@ -79,12 +80,42 @@ void NavigationViewItemPresenter::LoadChevron() if (auto const expandCollapseChevron = GetTemplateChildT(c_expandCollapseChevron, *this)) { m_expandCollapseChevron.set(expandCollapseChevron); - m_expandCollapseChevronTappedRevoker = expandCollapseChevron.Tapped(winrt::auto_revoke, { navigationViewItem, &NavigationViewItem::OnExpandCollapseChevronTapped }); + + m_expandCollapseChevronPointerPressedRevoker = expandCollapseChevron.PointerPressed(winrt::auto_revoke, { this, &NavigationViewItemPresenter::OnExpandCollapseChevronPointerPressed }); + m_expandCollapseChevronPointerReleasedRevoker = expandCollapseChevron.PointerReleased(winrt::auto_revoke, { this, &NavigationViewItemPresenter::OnExpandCollapseChevronPointerReleased }); } } } } +void NavigationViewItemPresenter::OnExpandCollapseChevronPointerPressed(const winrt::IInspectable& sender, const winrt::PointerRoutedEventArgs& args) +{ + const auto pointerProperties = args.GetCurrentPoint(*this).Properties(); + if (!pointerProperties.IsLeftButtonPressed() || + args.Handled()) + { + // We are only interested in the primary action of the pointer device + // (e.g. left click of a mouse) + // Despite the name, IsLeftButtonPressed covers the primary action regardless of device. + return; + } + + args.Handled(true); +} + +void NavigationViewItemPresenter::OnExpandCollapseChevronPointerReleased(const winrt::IInspectable& sender, const winrt::PointerRoutedEventArgs& args) +{ + const auto navigationViewItem = GetNavigationViewItem(); + const auto pointerProperties = args.GetCurrentPoint(*this).Properties(); + if (!args.Handled() && + pointerProperties.PointerUpdateKind() == winrt::PointerUpdateKind::LeftButtonReleased && + navigationViewItem) + { + navigationViewItem->OnExpandCollapseChevronPointerReleased(); + args.Handled(true); + } +} + void NavigationViewItemPresenter::RotateExpandCollapseChevron(bool isExpanded) { if (isExpanded) diff --git a/controls/dev/NavigationView/NavigationViewItemPresenter.h b/controls/dev/NavigationView/NavigationViewItemPresenter.h index 6a0b5c051e..aa0e257791 100644 --- a/controls/dev/NavigationView/NavigationViewItemPresenter.h +++ b/controls/dev/NavigationView/NavigationViewItemPresenter.h @@ -40,6 +40,9 @@ class NavigationViewItemPresenter: void UpdateMargin(); void UnhookEventsAndClearFields(); + void OnExpandCollapseChevronPointerPressed(const winrt::IInspectable& sender, const winrt::PointerRoutedEventArgs& args); + void OnExpandCollapseChevronPointerReleased(const winrt::IInspectable& sender, const winrt::PointerRoutedEventArgs& args); + double m_compactPaneLengthValue { 40 }; NavigationViewItemHelper m_helper{ this }; @@ -47,7 +50,8 @@ class NavigationViewItemPresenter: tracker_ref m_infoBadgePresenter{ this }; tracker_ref m_expandCollapseChevron{ this }; - winrt::UIElement::Tapped_revoker m_expandCollapseChevronTappedRevoker{}; + winrt::UIElement::PointerPressed_revoker m_expandCollapseChevronPointerPressedRevoker{}; + winrt::UIElement::PointerReleased_revoker m_expandCollapseChevronPointerReleasedRevoker{}; double m_leftIndentation{ 0 }; diff --git a/controls/dev/NavigationView/NavigationViewTrace.h b/controls/dev/NavigationView/NavigationViewTrace.h new file mode 100644 index 0000000000..ffc43f192c --- /dev/null +++ b/controls/dev/NavigationView/NavigationViewTrace.h @@ -0,0 +1,164 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +#pragma once + +#include "common.h" +#include "MuxcTraceLogging.h" +#include "Utils.h" +#include "MUXControlsTestHooks.h" + +inline bool IsNavigationViewTracingEnabled() +{ + return g_IsLoggingProviderEnabled && + g_LoggingProviderLevel >= WINEVENT_LEVEL_INFO && + (g_LoggingProviderMatchAnyKeyword & KEYWORD_NAVIGATIONVIEW || g_LoggingProviderMatchAnyKeyword == 0); +} + +inline bool IsNavigationViewVerboseTracingEnabled() +{ + return g_IsLoggingProviderEnabled && + g_LoggingProviderLevel >= WINEVENT_LEVEL_VERBOSE && + (g_LoggingProviderMatchAnyKeyword & KEYWORD_NAVIGATIONVIEW || g_LoggingProviderMatchAnyKeyword == 0); +} + +inline bool IsNavigationViewPerfTracingEnabled() +{ + return g_IsPerfProviderEnabled && + g_PerfProviderLevel >= WINEVENT_LEVEL_INFO && + (g_PerfProviderMatchAnyKeyword & KEYWORD_NAVIGATIONVIEW || g_PerfProviderMatchAnyKeyword == 0); +} + +#define NAVIGATIONVIEW_TRACE_INFO_ENABLED(includeTraceLogging, sender, message, ...) \ +NavigationViewTrace::TraceInfo(includeTraceLogging, sender, message, __VA_ARGS__); \ + +#define NAVIGATIONVIEW_TRACE_INFO(sender, message, ...) \ +if (IsNavigationViewTracingEnabled()) \ +{ \ + NAVIGATIONVIEW_TRACE_INFO_ENABLED(true /*includeTraceLogging*/, sender, message, __VA_ARGS__); \ +} \ +else if (NavigationViewTrace::s_IsDebugOutputEnabled || NavigationViewTrace::s_IsVerboseDebugOutputEnabled) \ +{ \ + NAVIGATIONVIEW_TRACE_INFO_ENABLED(false /*includeTraceLogging*/, sender, message, __VA_ARGS__); \ +} \ + +#define NAVIGATIONVIEW_TRACE_VERBOSE_ENABLED(includeTraceLogging, sender, message, ...) \ +NavigationViewTrace::TraceVerbose(includeTraceLogging, sender, message, __VA_ARGS__); \ + +#define NAVIGATIONVIEW_TRACE_VERBOSE(sender, message, ...) \ +if (IsNavigationViewVerboseTracingEnabled()) \ +{ \ + NAVIGATIONVIEW_TRACE_VERBOSE_ENABLED(true /*includeTraceLogging*/, sender, message, __VA_ARGS__); \ +} \ +else if (NavigationViewTrace::s_IsVerboseDebugOutputEnabled) \ +{ \ + NAVIGATIONVIEW_TRACE_VERBOSE_ENABLED(false /*includeTraceLogging*/, sender, message, __VA_ARGS__); \ +} \ + +#define NAVIGATIONVIEW_TRACE_PERF(info) \ +if (IsNavigationViewPerfTracingEnabled()) \ +{ \ + NavigationViewTrace::TracePerfInfo(info); \ +} \ + +#ifdef DBG +#define NAVIGATIONVIEW_TRACE_INFO_DBG(sender, message, ...) NAVIGATIONVIEW_TRACE_INFO(sender, message, __VA_ARGS__) +#define NAVIGATIONVIEW_TRACE_VERBOSE_DBG(sender, message, ...) NAVIGATIONVIEW_TRACE_VERBOSE(sender, message, __VA_ARGS__) +#define NAVIGATIONVIEW_TRACE_PERF_DBG(info) NAVIGATIONVIEW_TRACE_PERF(info) +#else +#define NAVIGATIONVIEW_TRACE_INFO_DBG(sender, message, ...) +#define NAVIGATIONVIEW_TRACE_VERBOSE_DBG(sender, message, ...) +#define NAVIGATIONVIEW_TRACE_PERF_DBG(info) +#endif // DBG + +class NavigationViewTrace +{ +public: + static bool s_IsDebugOutputEnabled; + static bool s_IsVerboseDebugOutputEnabled; + + static void TraceInfo(bool includeTraceLogging, const winrt::IInspectable& sender, PCWSTR message, ...) noexcept + { + va_list args; + va_start(args, message); + WCHAR buffer[256]{}; + if (SUCCEEDED(StringCchVPrintfW(buffer, ARRAYSIZE(buffer), message, args))) + { + if (includeTraceLogging) + { + // TraceViewers + // http://toolbox/pef + // http://fastetw/index.aspx + TraceLoggingWrite( + g_hLoggingProvider, + "NavigationViewInfo" /* eventName */, + TraceLoggingLevel(WINEVENT_LEVEL_INFO), + TraceLoggingKeyword(KEYWORD_NAVIGATIONVIEW), + TraceLoggingWideString(buffer, "Message")); + } + + if (s_IsDebugOutputEnabled) + { + OutputDebugStringW(buffer); + } + + com_ptr globalTestHooks = MUXControlsTestHooks::GetGlobalTestHooks(); + + if (globalTestHooks && + (globalTestHooks->GetLoggingLevelForType(L"NavigationView") >= WINEVENT_LEVEL_INFO || globalTestHooks->GetLoggingLevelForInstance(sender) >= WINEVENT_LEVEL_INFO)) + { + globalTestHooks->LogMessage(sender, buffer, false /*isVerboseLevel*/); + } + } + va_end(args); + } + + static void TraceVerbose(bool includeTraceLogging, const winrt::IInspectable& sender, PCWSTR message, ...) noexcept + { + va_list args; + va_start(args, message); + WCHAR buffer[256]{}; + if (SUCCEEDED(StringCchVPrintfW(buffer, ARRAYSIZE(buffer), message, args))) + { + if (includeTraceLogging) + { + // TraceViewers + // http://toolbox/pef + // http://fastetw/index.aspx + TraceLoggingWrite( + g_hLoggingProvider, + "NavigationViewVerbose" /* eventName */, + TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE), + TraceLoggingKeyword(KEYWORD_NAVIGATIONVIEW), + TraceLoggingWideString(buffer, "Message")); + } + + if (s_IsDebugOutputEnabled || s_IsVerboseDebugOutputEnabled) + { + OutputDebugStringW(buffer); + } + + com_ptr globalTestHooks = MUXControlsTestHooks::GetGlobalTestHooks(); + + if (globalTestHooks && + (globalTestHooks->GetLoggingLevelForType(L"NavigationView") >= WINEVENT_LEVEL_VERBOSE || globalTestHooks->GetLoggingLevelForInstance(sender) >= WINEVENT_LEVEL_VERBOSE)) + { + globalTestHooks->LogMessage(sender, buffer, true /*isVerboseLevel*/); + } + } + va_end(args); + } + + static void TracePerfInfo(PCWSTR info) noexcept + { + // TraceViewers + // http://toolbox/pef + // http://fastetw/index.aspx + TraceLoggingWrite( + g_hPerfProvider, + "NavigationViewPerf" /* eventName */, + TraceLoggingLevel(WINEVENT_LEVEL_INFO), + TraceLoggingKeyword(KEYWORD_NAVIGATIONVIEW), + TraceLoggingWideString(info, "Info")); + } +}; diff --git a/controls/dev/NavigationView/NavigationView_InteractionTests/CommonTests.cs b/controls/dev/NavigationView/NavigationView_InteractionTests/CommonTests.cs index ce10e0aa21..ee8879b416 100644 --- a/controls/dev/NavigationView/NavigationView_InteractionTests/CommonTests.cs +++ b/controls/dev/NavigationView/NavigationView_InteractionTests/CommonTests.cs @@ -1552,7 +1552,7 @@ public void EnsureDisplayModeGroupUpdatesOnPaneClosedToMinimalWithBackButton() [TestMethod] [TestProperty("TestSuite", "D")] - public void VerifyExpandCollpaseFunctionality() + public void VerifyExpandCollapseFunctionality() { var testScenarios = RegressionTestScenario.BuildHierarchicalNavRegressionTestScenarios(); foreach (var testScenario in testScenarios) @@ -1630,6 +1630,67 @@ public void VerifyExpandCollpaseFunctionality() } } + [TestMethod] + [TestProperty("TestSuite", "D")] + public void VerifyExpandCollapseFunctionalityUsingChevron() + { + using (var setup = new TestSetupHelper(new[] { "NavigationView Tests", "HierarchicalNavigationView Markup Test" })) + { + + TextBlock textblockExpandingItemAndContainerMatch = new TextBlock(FindElement.ByName("TextblockExpandingItemAndContainerMatch")); + TextBlock textblockCollapsedItemAndContainerMatch = new TextBlock(FindElement.ByName("TextblockCollapsedItemAndContainerMatch")); + TextBlock textBlockExpandingItem = new TextBlock(FindElement.ByName("TextBlockExpandingItem")); + TextBlock textBlockCollapsedItem = new TextBlock(FindElement.ByName("TextBlockCollapsedItem")); + + Log.Comment("Verify that Menu Item 6 is not expanded."); + // Verify through test page elements + Verify.AreEqual(textblockExpandingItemAndContainerMatch.DocumentText, "N/A"); + Verify.AreEqual(textblockCollapsedItemAndContainerMatch.DocumentText, "N/A"); + Verify.AreEqual(textBlockExpandingItem.DocumentText, "N/A"); + Verify.AreEqual(textBlockCollapsedItem.DocumentText, "N/A"); + // Verify that child menu item is not in tree + UIObject parentItem = FindElement.ByName("Menu Item 6 (Selectable)"); + UIObject childItem = FindElement.ByName("Menu Item 7 (Selectable)"); + Verify.IsNull(childItem); + + Log.Comment("Expand Menu Item 6."); + InputHelper.LeftClick(parentItem, parentItem.BoundingRectangle.Width - 20 , parentItem.BoundingRectangle.Height / 2 + 10); + Wait.ForIdle(); + + Log.Comment("Verify that Menu Item 6 was expanded correctly."); + // Verify through test page elements + Verify.AreEqual(textblockExpandingItemAndContainerMatch.DocumentText, "true"); + Verify.AreEqual(textblockCollapsedItemAndContainerMatch.DocumentText, "N/A"); + Verify.AreEqual(textBlockExpandingItem.DocumentText, "Menu Item 6 (Selectable)"); + Verify.AreEqual(textBlockCollapsedItem.DocumentText, "N/A"); + // Verify that child menu item is in tree + childItem = FindElement.ByName("Menu Item 7 (Selectable)"); + Verify.IsNotNull(childItem, "Child item should be visible after expanding parent item."); + + Log.Comment("Try collapse Menu Item 6 using an invalid right click."); + InputHelper.RightClick(parentItem, parentItem.BoundingRectangle.Width - 25, 25); + Wait.ForIdle(); + + Log.Comment("Verify that Menu Item 6 was not collapsed."); + // Verify through test page elements + Verify.AreEqual(textblockExpandingItemAndContainerMatch.DocumentText, "true"); + Verify.AreEqual(textblockCollapsedItemAndContainerMatch.DocumentText, "N/A"); + Verify.AreEqual(textBlockExpandingItem.DocumentText, "Menu Item 6 (Selectable)"); + Verify.AreEqual(textBlockCollapsedItem.DocumentText, "N/A"); + + Log.Comment("Collapse Menu Item 6 using left click."); + InputHelper.LeftClick(parentItem, parentItem.BoundingRectangle.Width - 25, 25); + Wait.ForIdle(); + + Log.Comment("Verify that Menu Item 6 was collapsed correctly."); + // Verify through test page elements + Verify.AreEqual(textblockExpandingItemAndContainerMatch.DocumentText, "true"); + Verify.AreEqual(textblockCollapsedItemAndContainerMatch.DocumentText, "true"); + Verify.AreEqual(textBlockExpandingItem.DocumentText, "Menu Item 6 (Selectable)"); + Verify.AreEqual(textBlockCollapsedItem.DocumentText, "Menu Item 6 (Selectable)"); + } + } + [TestMethod] [TestProperty("TestSuite", "D")] public void VerifyNoCrashWhenSwitchingPaneDisplayModeWithAutoWrappedElements() @@ -1830,6 +1891,18 @@ public void VerifyNavigationViewItemInPaneFooterHasTemplateSettingBindings() Verify.AreEqual("88", paneFooterNavViewItemWidthTextBlock.DocumentText); } } + + [TestMethod] + public void VerifyClickingNavigationViewItemInPaneFooterDoesNotCrash() + { + using (var setup = new TestSetupHelper(new[] { "NavigationView Tests", "NavigationView Test" })) + { + Log.Comment("Click on PaneFooterNavigationViewItem."); + var paneFooterNavigationViewItem = FindElement.ByName("PaneFooterNavigationViewItem"); + InputHelper.LeftClick(paneFooterNavigationViewItem); + Wait.ForIdle(); + } + } [TestMethod] public void VerifyAddingNewItemsFromIndexZero() diff --git a/controls/dev/NavigationView/TestUI/NavigationViewCaseBundle.xaml b/controls/dev/NavigationView/TestUI/NavigationViewCaseBundle.xaml index f630141587..2ce3a92959 100644 --- a/controls/dev/NavigationView/TestUI/NavigationViewCaseBundle.xaml +++ b/controls/dev/NavigationView/TestUI/NavigationViewCaseBundle.xaml @@ -9,58 +9,75 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"> - - - - + + + + + + + + + + + + None + Info + Verbose + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + - - - - + + + + - - - - + + + + + - - - - - + + - diff --git a/controls/dev/NavigationView/TestUI/NavigationViewCaseBundle.xaml.cs b/controls/dev/NavigationView/TestUI/NavigationViewCaseBundle.xaml.cs index 84a739990e..8240852bf1 100644 --- a/controls/dev/NavigationView/TestUI/NavigationViewCaseBundle.xaml.cs +++ b/controls/dev/NavigationView/TestUI/NavigationViewCaseBundle.xaml.cs @@ -1,7 +1,13 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. +using Common; using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Private.Controls; + +using WEX.TestExecution; +using WEX.TestExecution.Markup; +using WEX.Logging.Interop; namespace MUXControlsTestApp { @@ -10,6 +16,7 @@ public sealed partial class NavigationViewCaseBundle : TestPage { public NavigationViewCaseBundle() { + LogController.InitializeLogging(); this.InitializeComponent(); NavigationViewPage.Click += delegate { Frame.NavigateWithoutAnimation(typeof(NavigationViewPage), 0); }; NavigationViewInfoBadgePage.Click += delegate { Frame.NavigateWithoutAnimation(typeof(NavigationViewInfoBadgeTestPage), 0); }; @@ -38,5 +45,13 @@ public NavigationViewCaseBundle() PaneLayoutTestPageButton.Click += delegate { Frame.NavigateWithoutAnimation(typeof(PaneLayoutTestPage), 0); }; PaneFooterTestPageButton.Click += delegate { Frame.NavigateWithoutAnimation(typeof(PaneFooterTestPage), 0); }; } + + private void CmbNavigationViewOutputDebugStringLevel_SelectionChanged(object sender, SelectionChangedEventArgs e) + { + MUXControlsTestHooks.SetOutputDebugStringLevelForType( + "NavigationView", + cmbNavigationViewOutputDebugStringLevel.SelectedIndex == 1 || cmbNavigationViewOutputDebugStringLevel.SelectedIndex == 2, + cmbNavigationViewOutputDebugStringLevel.SelectedIndex == 2); + } } } diff --git a/controls/dev/Telemetry/MuxcTraceLogging.h b/controls/dev/Telemetry/MuxcTraceLogging.h index fb689e3ee4..e3d8abbdb8 100644 --- a/controls/dev/Telemetry/MuxcTraceLogging.h +++ b/controls/dev/Telemetry/MuxcTraceLogging.h @@ -29,7 +29,8 @@ #define KEYWORD_ITEMCONTAINER 0x0000000000000200 #define KEYWORD_LINEDFLOWLAYOUT 0x0000000000000400 #define KEYWORD_ANNOTATEDSCROLLBAR 0x0000000000000800 -#define KEYWORD_SELECTORBAR 0x0000000000001000 +#define KEYWORD_SELECTORBAR 0x0000000000001000 +#define KEYWORD_NAVIGATIONVIEW 0x0000000000002000 // Common output formats #define TRACE_MSG_METH L"%s[0x%p]()\n" @@ -162,6 +163,35 @@ class XamlTelemetry final : public TelemetryBase PCSTR, EventName, bool, IsInteresting, TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE)); + + DEFINE_TRACELOGGING_EVENT_PARAM2(WebView2_FireNavigationCompleted, + uint64_t, ObjectPointer, + bool, hasEventHandlers, + TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE)); + + DEFINE_TRACELOGGING_EVENT_PARAM2(WebView2_CreateCoreObjects, + bool, IsStart, + uint64_t, ObjectPointer, + TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE)); + + DEFINE_TRACELOGGING_EVENT_PARAM2(WebView2_TryCompleteInitialization, + bool, IsStart, + uint64_t, ObjectPointer, + TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE)); + + DEFINE_TRACELOGGING_EVENT_PARAM2(MapControl_InitializeWebMap, + bool, IsStart, + uint64_t, ObjectPointer, + TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE)); + + DEFINE_TRACELOGGING_EVENT_PARAM1(MapControl_WebViewNavigationCompleted, + uint64_t, ObjectPointer, + TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE)); + + DEFINE_TRACELOGGING_EVENT_PARAM2(MapControl_WebMessageReceived_Error, + uint64_t, ObjectPointer, + PCWSTR, Message, + TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE)); }; #pragma warning(pop) @@ -192,3 +222,21 @@ struct PerfXamlEvent_RAII PCSTR m_eventName; bool m_isInteresting; }; + +struct SimpleXamlStartStopEvent_RAII +{ +public: + // Disallow copying/moving + SimpleXamlStartStopEvent_RAII(const SimpleXamlStartStopEvent_RAII&) = delete; + SimpleXamlStartStopEvent_RAII(SimpleXamlStartStopEvent_RAII&&) = delete; + SimpleXamlStartStopEvent_RAII& operator=(const SimpleXamlStartStopEvent_RAII&) = delete; + SimpleXamlStartStopEvent_RAII& operator=(SimpleXamlStartStopEvent_RAII&&) = delete; + +protected: + SimpleXamlStartStopEvent_RAII(uint64_t objectPointer) + : m_objectPointer(objectPointer) + { + } + + uint64_t m_objectPointer; +}; diff --git a/controls/dev/TestHooks/MUXControlsTestHooks.cpp b/controls/dev/TestHooks/MUXControlsTestHooks.cpp index e5822592ce..6560e1e7fd 100644 --- a/controls/dev/TestHooks/MUXControlsTestHooks.cpp +++ b/controls/dev/TestHooks/MUXControlsTestHooks.cpp @@ -21,6 +21,10 @@ #include "ItemsViewTrace.h" #endif +#ifdef NAVIGATIONVIEW_INCLUDED +#include "NavigationViewTrace.h" +#endif + #ifdef SCROLLPRESENTER_INCLUDED #include "ScrollPresenterTrace.h" #endif @@ -117,6 +121,13 @@ if (type == L"AnnotatedScrollBar" || type.empty()) ItemsViewTrace::s_IsVerboseDebugOutputEnabled = isLoggingVerboseLevel; } #endif +#ifdef NAVIGATIONVIEW_INCLUDED + if (type == L"NavigationView" || type.empty()) + { + NavigationViewTrace::s_IsDebugOutputEnabled = isLoggingInfoLevel || isLoggingVerboseLevel; + NavigationViewTrace::s_IsVerboseDebugOutputEnabled = isLoggingVerboseLevel; + } +#endif #ifdef SCROLLPRESENTER_INCLUDED if (type == L"ScrollPresenter" || type.empty()) { diff --git a/controls/dev/WebView2/WebView2.cpp b/controls/dev/WebView2/WebView2.cpp index d5446a31f6..0a7f2213af 100644 --- a/controls/dev/WebView2/WebView2.cpp +++ b/controls/dev/WebView2/WebView2.cpp @@ -18,6 +18,7 @@ #include "WebView2Utility.h" #include "Windows.Globalization.h" #include +#include "MuxcTraceLogging.h" using namespace Microsoft::WRL; @@ -740,6 +741,8 @@ winrt::IAsyncAction WebView2::CreateCoreObjects(winrt::CoreWebView2Environment e } else { + XamlTelemetry::WebView2_CreateCoreObjects(true, reinterpret_cast(this)); + m_creationInProgressAsync = std::make_unique(); RegisterXamlEventHandlers(); @@ -765,6 +768,8 @@ winrt::IAsyncAction WebView2::CreateCoreObjects(winrt::CoreWebView2Environment e { CreateMissingAnaheimWarning(); } + + XamlTelemetry::WebView2_CreateCoreObjects(false, reinterpret_cast(this)); } co_return; @@ -777,7 +782,7 @@ winrt::IAsyncOperation WebView2::CreateDefaultCo winrt::CoreWebView2Environment returnedValue = nullptr; auto strongThis = get_strong(); // ensure object lifetime during coroutines - + // NOTE: To enable Anaheim logging, add: environmentOptions.AdditionalBrowserArguments(L"--enable-logging=stderr --v=1"); winrt::CoreWebView2EnvironmentOptions environmentOptions = winrt::CoreWebView2EnvironmentOptions(); @@ -1374,7 +1379,7 @@ winrt::IAsyncAction WebView2::EnsureCoreWebView2Async(winrt::CoreWebView2Environ } // If CWV2 exists already, return immediately provided that current call to EnsureCWV2Async is compatible with original creation - // Specifically, if current call has: + // Specifically, if current call has: // a. Null args : Always compatible [i.e. E() == E(nullptr) == E(nullptr, nullptr))] // b. Non-null arg(s) : Compatible iff called with same args as call that created current core objecs. // Note comparison is via reference equality for both args. @@ -1382,7 +1387,7 @@ winrt::IAsyncAction WebView2::EnsureCoreWebView2Async(winrt::CoreWebView2Environ { if (environment != nullptr || controllerOptions != nullptr) { - if (m_coreWebViewEnvironment != environment) + if (m_coreWebViewEnvironment != environment) { throw winrt::hresult_invalid_argument( L"WebView2 was already initialized with a different CoreWebView2Environment. " @@ -1450,6 +1455,15 @@ winrt::AccessibilitySettings WebView2::GetAccessibilitySettings() return m_accessibilitySettings; } +struct WebView2_TryCompleteInitialization_RAII : public SimpleXamlStartStopEvent_RAII +{ +public: + WebView2_TryCompleteInitialization_RAII(uint64_t objectPointer) : SimpleXamlStartStopEvent_RAII(objectPointer) + { XamlTelemetry::WebView2_TryCompleteInitialization(true, m_objectPointer); } + ~WebView2_TryCompleteInitialization_RAII() + { XamlTelemetry::WebView2_TryCompleteInitialization(false, m_objectPointer); } +}; + void WebView2::TryCompleteInitialization() { // If proper Anaheim not present, no further initialization is necessary @@ -1477,6 +1491,8 @@ void WebView2::TryCompleteInitialization() return; } + WebView2_TryCompleteInitialization_RAII etwEvents(reinterpret_cast(this)); + // In a desktop scenario, we may have created the CoreWebView2 with a dummy hwnd as its parent // (see EnsureTemporaryHostHwnd()), in which case we need to update to use the real parent here. // If we used a CoreWindow parent, that hwnd has not changed. The CoreWebView2 does not allow us to switch @@ -1578,7 +1594,7 @@ void WebView2::ConvertXamlArgsToOle32Args(const winrt::DragEventArgs& args, DWOR // Get the drag drop modifiers and convert to key state winrt::DragDropModifiers dragDropModifiers = args.Modifiers(); if ((dragDropModifiers & winrt::DragDropModifiers::LeftButton) == - winrt::DragDropModifiers::LeftButton) + winrt::DragDropModifiers::LeftButton) { keyState |= MK_LBUTTON; } @@ -1608,7 +1624,7 @@ void WebView2::ConvertXamlArgsToOle32Args(const winrt::DragEventArgs& args, DWOR keyState |= MK_ALT; } - // point parameter must be modified to include the WebView's offset and be + // point parameter must be modified to include the WebView's offset and be // in the WebView's client coordinates (Similar to how SendMouseInput works). winrt::Point cursorPosition_winrt{args.GetPosition(*this /* relativeTo */)}; cursorPosition = {static_cast(cursorPosition_winrt.X), static_cast(cursorPosition_winrt.Y)}; diff --git a/dxaml/xcp/core/core/elements/Popup.cpp b/dxaml/xcp/core/core/elements/Popup.cpp index eed80edea4..fe4cf6638d 100644 --- a/dxaml/xcp/core/core/elements/Popup.cpp +++ b/dxaml/xcp/core/core/elements/Popup.cpp @@ -42,6 +42,7 @@ #include #include "WinRTExpressionConversionContext.h" #include "VisualDebugTags.h" +#include "xcpwindow.h" using namespace DirectUI; using namespace Focus; @@ -1445,7 +1446,22 @@ _Check_return_ HRESULT CPopup::PositionAndSizeWindowForWindowedPopup() m_windowedPopupMoveAndResizeRect.Width = popupWindowWidth; m_windowedPopupMoveAndResizeRect.Height = popupWindowHeight; - IFC_RETURN(m_desktopBridge->MoveAndResize(m_windowedPopupMoveAndResizeRect)); + { + // This MoveAndResize call results in a WM_WINDOWPOSCHANGED message, and we're seeing cases where a + // third-party library is hooking into the wndproc, responding to WM_WINDOWPOSCHANGED, and is calling + // SendMessage in response. That causes messages to be pumped, which causes reentrancy in to Xaml and a + // crash. For now, disable lifted CoreMessaging's dispatching for the duration of the MoveAndResize call. + // In the future, we'll want a mechanism that can protect against reentrancy without messing with the lifted + // CoreMessaging dispatcher. See https://task.ms/49218302. + CXcpDispatcher* dispatcher = nullptr; + auto hostSite = GetContext()->GetHostSite(); + if (hostSite) + { + dispatcher = static_cast(hostSite->GetXcpDispatcher()); + } + PauseNewDispatch deferReentrancy(dispatcher ? dispatcher->GetMessageLoopExtensionsNoRef() : nullptr); + IFC_RETURN(m_desktopBridge->MoveAndResize(m_windowedPopupMoveAndResizeRect)); + } } // @@ -1899,7 +1915,22 @@ _Check_return_ HRESULT CPopup::AdjustWindowedPopupBoundsForDropShadow(_In_ const m_windowedPopupMoveAndResizeRect.Width = XcpCeiling(expandedBounds.Width); m_windowedPopupMoveAndResizeRect.Height = XcpCeiling(expandedBounds.Height); - IFC_RETURN(m_desktopBridge->MoveAndResize(m_windowedPopupMoveAndResizeRect)); + { + // This MoveAndResize call results in a WM_WINDOWPOSCHANGED message, and we're seeing cases where a + // third-party library is hooking into the wndproc, responding to WM_WINDOWPOSCHANGED, and is calling + // SendMessage in response. That causes messages to be pumped, which causes reentrancy in to Xaml and a + // crash. For now, disable lifted CoreMessaging's dispatching for the duration of the MoveAndResize call. + // In the future, we'll want a mechanism that can protect against reentrancy without messing with the lifted + // CoreMessaging dispatcher. See https://task.ms/49218302. + CXcpDispatcher* dispatcher = nullptr; + auto hostSite = GetContext()->GetHostSite(); + if (hostSite) + { + dispatcher = static_cast(hostSite->GetXcpDispatcher()); + } + PauseNewDispatch deferReentrancy(dispatcher ? dispatcher->GetMessageLoopExtensionsNoRef() : nullptr); + IFC_RETURN(m_desktopBridge->MoveAndResize(m_windowedPopupMoveAndResizeRect)); + } } return S_OK; diff --git a/dxaml/xcp/dxaml/lib/InputSiteAdapter.h b/dxaml/xcp/dxaml/lib/InputSiteAdapter.h index c174b8cdd6..51903f66a1 100644 --- a/dxaml/xcp/dxaml/lib/InputSiteAdapter.h +++ b/dxaml/xcp/dxaml/lib/InputSiteAdapter.h @@ -21,7 +21,7 @@ class InputSiteAdapter InputSiteAdapter(); virtual ~InputSiteAdapter(); - void Initialize(_In_ ixp::IContentIsland* contentIsland, _In_ CContentRoot* contentRoot, _In_ CJupiterWindow* jupiterWindow); + void Initialize(_In_ ixp::IContentIsland* contentIsland, _In_ CContentRoot* contentRoot, _In_ CJupiterWindow* jupiterWindow, bool connectActivationListener = true); wrl::ComPtr GetIslandInputSite() const { return m_islandInputSite; } diff --git a/dxaml/xcp/dxaml/lib/WindowedPopupInputSiteAdapter.cpp b/dxaml/xcp/dxaml/lib/WindowedPopupInputSiteAdapter.cpp index 40f248bc06..2098dff352 100644 --- a/dxaml/xcp/dxaml/lib/WindowedPopupInputSiteAdapter.cpp +++ b/dxaml/xcp/dxaml/lib/WindowedPopupInputSiteAdapter.cpp @@ -11,7 +11,18 @@ void WindowedPopupInputSiteAdapter::Initialize(_In_ CPopup* popup, _In_ ixp::IContentIsland* contentIsland, _In_ CContentRoot* contentRoot, _In_ CJupiterWindow* jupiterWindow) { m_windowedPopupNoRef = popup; - __super::Initialize(contentIsland, contentRoot, jupiterWindow); + + // + // Do not hook a handler to InputActivationChanged. The windowed popup does not ever take activation for itself. The + // contentRoot we're passing in here is the main Xaml island, which has its own InputSiteAdapter already that has + // its own InputActivationChanged handler hooked. + // + // This matters for desktop acrylic backdrops inside this windowed popup, which depend on activation state of the + // main Xaml island, which just looks at the top-level hwnd. The windowed popup is a separate top-level hwnd with + // its own activation state (as far as Windows is concerned), but that activation state shouldn't affect anything. + // + __super::Initialize(contentIsland, contentRoot, jupiterWindow, false /* connectActivationListener */); + IFCFAILFAST(ctl::make(&m_pointerPointTransformFromContentRoot)); // Windowed popups do not ever take activation. diff --git a/dxaml/xcp/win/inc/xcpwindow.h b/dxaml/xcp/win/inc/xcpwindow.h index 23b98caf2e..1c5e122eac 100644 --- a/dxaml/xcp/win/inc/xcpwindow.h +++ b/dxaml/xcp/win/inc/xcpwindow.h @@ -6,8 +6,7 @@ // Abstract: // Implement top level control model -__interface IMessageSession; -__interface IMessageLoopExtensions; +#include class CompositorScheduler; @@ -130,6 +129,8 @@ class CXcpDispatcher final : void ProcessMessage(UINT msg, WPARAM wParam, LPARAM lParam); + IMessageLoopExtensions* GetMessageLoopExtensionsNoRef() const { return m_messageLoopExtensions.get(); } + private: _Check_return_ HRESULT ReloadSource(); @@ -194,5 +195,39 @@ class CXcpDispatcher final : // We skip some kinds of messages in this state. }; State m_state { State::Running }; +}; + +// RAII wrapper around IMessageLoopExtensions::PauseNewDispatch and +// ResumeDispatch. +// Used to prevent Xaml reentrancy by making CoreMessaging stop dispatching, +// including its private window messages. Those messages will be rescheduled +// once the deferral stops (i.e. this object falls out of scope). +class PauseNewDispatch +{ +public: + PauseNewDispatch(_In_opt_ IMessageLoopExtensions* messageLoopExtensions) + : m_messageLoopExtensions(messageLoopExtensions) + { + if (m_messageLoopExtensions) + { + IFCFAILFAST(m_messageLoopExtensions->PauseNewDispatch()); + } + } + + ~PauseNewDispatch() + { + if (m_messageLoopExtensions) + { + IFCFAILFAST(m_messageLoopExtensions->ResumeDispatch()); + } + } + + // Disallow copying + PauseNewDispatch(const PauseNewDispatch&) = delete; + PauseNewDispatch(PauseNewDispatch&&) = delete; + PauseNewDispatch& operator=(const PauseNewDispatch&) = delete; + PauseNewDispatch& operator=(PauseNewDispatch&&) = delete; +private: + xref_ptr m_messageLoopExtensions; }; diff --git a/dxaml/xcp/win/shared/xcpwindow.cpp b/dxaml/xcp/win/shared/xcpwindow.cpp index bd31dc08b9..ce0459c8b5 100644 --- a/dxaml/xcp/win/shared/xcpwindow.cpp +++ b/dxaml/xcp/win/shared/xcpwindow.cpp @@ -14,35 +14,6 @@ #include "GraphicsTelemetry.h" #include "DXamlCoreTipTests.h" -// RAII wrapper around IMessageLoopExtensions::PauseNewDispatch and -// ResumeDispatch. -// Used to prevent Xaml reentrancy by making CoreMessaging stop dispatching, -// including its private window messages. Those messages will be rescheduled -// once the deferral stops (i.e. this object falls out of scope). -class PauseNewDispatch -{ -public: - PauseNewDispatch(_In_ IMessageLoopExtensions* messageLoopExtensions) - : m_messageLoopExtensions(messageLoopExtensions) - { - IFCFAILFAST(m_messageLoopExtensions->PauseNewDispatch()); - } - - ~PauseNewDispatch() - { - IFCFAILFAST(m_messageLoopExtensions->ResumeDispatch()); - } - - // Disallow copying/moving - PauseNewDispatch(const PauseNewDispatch&) = delete; - PauseNewDispatch(PauseNewDispatch&&) = delete; - PauseNewDispatch& operator=(const PauseNewDispatch&) = delete; - - PauseNewDispatch& operator=(PauseNewDispatch&&) = delete; -private: - xref_ptr m_messageLoopExtensions; -}; - //------------------------------------------------------------------------ // // Synopsis: @@ -613,7 +584,7 @@ void CXcpDispatcher::SendMessage( void CXcpDispatcher::ProcessMessage(UINT msg, WPARAM wParam, LPARAM lParam) { ASSERT((msg >= WM_FIRST) && (msg <= WM_LAST)); - + if (!m_bStarted) { return; diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 662f14ff3c..98c77fa6db 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -2,17 +2,17 @@ - + https://dev.azure.com/microsoft/ProjectReunion/_git/WindowsAppSDK - f729ae6e9a9d1db8022c4a2be28b93c399f95b8f + 6717f9fc2c0da67b14f74e7854ade9d22933ee63 - + https://dev.azure.com/microsoft/LiftedIXP/_git/DCPP - ae2b1fa0e5de26d1f5cc3cdbc7ad03a96c110781 + c2021b41a2c423f3ab83d1de51218b9df521fb8f - + https://dev.azure.com/microsoft/LiftedIXP/_git/DCPP - ae2b1fa0e5de26d1f5cc3cdbc7ad03a96c110781 + c2021b41a2c423f3ab83d1de51218b9df521fb8f