feat: Improve automation peers coverage#21518
Conversation
There was a problem hiding this comment.
Pull Request Overview
This PR aims to improve automation peers coverage by adding comprehensive UI Automation support types and expanding the AutomationProperties class. The changes introduce several new enumerations and significantly enhance the accessibility infrastructure for the Uno Platform.
- Introduces new automation-related enumerations for text formatting, styles, and UI element classification
- Significantly expands the AutomationProperties class with additional attached properties and getter/setter methods
- Moves existing AutomationProperties functionality to a separate .old.cs file while maintaining the core implementation
Reviewed Changes
Copilot reviewed 14 out of 25 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
TextUnit.cs |
Defines text navigation units (character, word, line, etc.) |
TextPatternRangeEndpoint.cs |
Specifies text range endpoints (start/end) |
AutomationTextDecorationLineStyle.cs |
Comprehensive text decoration styles enumeration |
AutomationStyleId.cs |
Document style identifiers (headings, normal text, etc.) |
AutomationProperties.old.cs |
Legacy AutomationProperties implementation preserved |
AutomationProperties.cs |
Significantly expanded with new attached properties and comprehensive documentation |
AutomationOutlineStyles.cs |
Text outline style enumeration |
AutomationFlowDirections.cs |
Text flow direction enumeration |
AutomationCaretPosition.cs |
Caret position enumeration |
AutomationCaretBidiMode.cs |
Bidirectional caret mode enumeration |
AutomationBulletStyle.cs |
Bullet list style enumeration |
AutomationAnimationStyle.cs |
Text animation style enumeration |
AutomationActiveEnd.cs |
Text selection active end enumeration |
AnnotationType.cs |
Document annotation type enumeration |
|
🤖 Your Docs stage site is ready! Visit it here: https://unodocsprstaging.z13.web.core.windows.net/pr-21518/docs/index.html |
4 similar comments
|
🤖 Your Docs stage site is ready! Visit it here: https://unodocsprstaging.z13.web.core.windows.net/pr-21518/docs/index.html |
|
🤖 Your Docs stage site is ready! Visit it here: https://unodocsprstaging.z13.web.core.windows.net/pr-21518/docs/index.html |
|
🤖 Your Docs stage site is ready! Visit it here: https://unodocsprstaging.z13.web.core.windows.net/pr-21518/docs/index.html |
|
🤖 Your Docs stage site is ready! Visit it here: https://unodocsprstaging.z13.web.core.windows.net/pr-21518/docs/index.html |
|
|
|
|
|
|
|
|
|
/azp run |
|
Azure Pipelines successfully started running 2 pipeline(s). |
|
/azp run |
|
Azure Pipelines successfully started running 2 pipeline(s). |
|
|
|
|
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 43 out of 58 changed files in this pull request and generated 8 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // // Call ReleaseItemAndParent on each of the items in the passed-in TrackerCollection. We have to use this | ||
| // // collection and items carefully, because they may be in a GC'd but not finalized state. So we can't call AddRef/Release, | ||
| // // and can't call QI. | ||
|
|
||
| // void ItemsControlAutomationPeer::ClearCacheCollectionUnsafe( | ||
| // _In_ TrackerCollection<xaml_automation_peers::ItemAutomationPeer*>* unsafeTrackerCollection) | ||
| // { | ||
| // IReferenceTrackerInternal *pTrackerNoRef = nullptr; | ||
| // UINT nCount = 0; | ||
|
|
||
| // if (unsafeTrackerCollection != nullptr) | ||
| // { | ||
| // VERIFYHR(unsafeTrackerCollection->get_Size(&nCount)); | ||
| // if(nCount > 0) | ||
| // { | ||
| // for(UINT i = 0; i < nCount; i++) | ||
| // { | ||
| // VERIFYHR(unsafeTrackerCollection->GetAsReferenceTrackerUnsafe(i, &pTrackerNoRef)); | ||
| // if(pTrackerNoRef != NULL) | ||
| // { | ||
| // static_cast<ItemAutomationPeer*>(ctl::impl_cast<ctl::WeakReferenceSourceNoThreadId>(pTrackerNoRef))->ReleaseItemAndParent(); | ||
| // } | ||
| // } | ||
| // } | ||
| // } | ||
| // } | ||
|
|
||
| protected override object GetPatternCore(PatternInterface patternInterface) | ||
| { | ||
| Panel pItemsHost; | ||
| DependencyObject pCurrent; | ||
| ScrollViewer psv = null; | ||
| DependencyObject pDO; | ||
| AutomationPeer pScrollPeer; | ||
| IScrollProvider psp = null; | ||
|
|
||
| if (patternInterface == PatternInterface.Scroll) | ||
| { | ||
| pItemsHost = (Owner as ItemsControl)?.ItemsPanelRoot; | ||
| pCurrent = pItemsHost; | ||
|
|
||
| // Walk the tree up, checking to see if there is a scroll viewer or not. | ||
| // Two exit criterias | ||
| // (1) current == owner ==> Means we reached the ItemsControl itself. | ||
| // (2) sv != null ==> ScrollViewer has been found. | ||
|
|
||
| // For the combobox which haven't been expanded yet ItemsHost is null | ||
| // so skipping the tree traversing. Selector should provide this functionality. | ||
| while (pCurrent != null && pCurrent != Owner) | ||
| { | ||
| pDO = VisualTreeHelper.GetParent(pCurrent); | ||
| pCurrent = pDO; | ||
|
|
||
| psv = pCurrent as ScrollViewer; | ||
| if (psv != null) | ||
| { | ||
| break; | ||
| } | ||
| } | ||
|
|
||
| if (psv != null) | ||
| { | ||
| pScrollPeer = psv.GetOrCreateAutomationPeer(); | ||
| if (pScrollPeer is not null) | ||
| { | ||
| psp = pScrollPeer as IScrollProvider; | ||
| } | ||
|
|
||
| if (psp != null) | ||
| { | ||
| pScrollPeer.EventsSource = this; | ||
| return pScrollPeer; | ||
| } | ||
| } | ||
|
|
||
| return base.GetPatternCore(patternInterface); | ||
| } | ||
| else if (patternInterface == PatternInterface.ItemContainer) | ||
| { | ||
| return this; | ||
| } | ||
| else | ||
| { | ||
| return base.GetPatternCore(patternInterface); | ||
| } | ||
| } | ||
|
|
||
| // IFACEMETHODIMP ItemsControlAutomationPeer::GetChildrenCore(_Outptr_ wfc::IVector<xaml_automation_peers::AutomationPeer*>** ppReturnValue) | ||
| // { | ||
| // HRESULT hr = S_OK; | ||
| // wfc::IVector<xaml_automation_peers::AutomationPeer*>* pAPChildren = NULL; | ||
|
|
||
| // IFCPTR(ppReturnValue); | ||
| // IFC(ctl::ComObject<TrackerCollection<xaml_automation_peers::AutomationPeer*>>::CreateInstance(&pAPChildren)); | ||
| // IFC(GetItemsControlChildrenChildren(pAPChildren)); | ||
|
|
||
| // *ppReturnValue = pAPChildren; | ||
| // pAPChildren = NULL; | ||
|
|
||
| // Cleanup: | ||
| // ReleaseInterface(pAPChildren); | ||
| // RRETURN(hr); | ||
| // } | ||
|
|
||
| protected override IList<AutomationPeer> GetChildrenCore() => GetItemsControlChildrenChildren(); | ||
|
|
||
|
|
||
| private void GetItemsControlChildrenChildrenHelper( | ||
| ItemsControl owner, | ||
| IList<UIElement> itemsFromItemsHostPanel, | ||
| int nCount, | ||
| IList<ItemAutomationPeer> newChildrenCollection) | ||
| { | ||
| if (owner == null || itemsFromItemsHostPanel == null || newChildrenCollection == null) | ||
| { | ||
| return; | ||
| } | ||
|
|
||
| var count = Math.Min(nCount, itemsFromItemsHostPanel.Count); | ||
| for (var i = 0; i < count; i++) | ||
| { | ||
| var itemContainer = itemsFromItemsHostPanel[i]; | ||
| if (itemContainer == null) | ||
| { | ||
| continue; | ||
| } | ||
|
|
||
| var item = owner.ItemFromContainer(itemContainer); | ||
| if (item == null) | ||
| { | ||
| continue; | ||
| } | ||
|
|
||
| // Try to get an existing peer, otherwise create one | ||
| if (!_itemPeers.TryGetValue(item, out var itemPeer)) | ||
| { | ||
| itemPeer = CreateItemAutomationPeer(item); | ||
| } | ||
|
|
||
| if (itemPeer != null) | ||
| { | ||
| var containerPeer = itemPeer.GetContainerPeer(); | ||
| if (containerPeer != null) | ||
| { | ||
| containerPeer.EventsSource = itemPeer; | ||
| newChildrenCollection.Add(itemPeer); | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // _Check_return_ HRESULT ItemsControlAutomationPeer::GetModernItemsControlChildrenChildrenHelper(_In_ wfc::IVector<xaml_automation_peers::ItemAutomationPeer*>* pNewChildrenCollection) | ||
| // { | ||
| // HRESULT hr = S_OK; | ||
| // INT firstCacheIndex = -1; | ||
| // INT lastCacheIndex = -1; | ||
| // ctl::ComPtr<IDependencyObject> spItemContainerAsDO; | ||
| // ctl::ComPtr<IInspectable> spItemAsInspectable; | ||
| // ctl::ComPtr<xaml_automation_peers::IItemAutomationPeer> spItemPeer; | ||
|
|
||
| // ctl::ComPtr<xaml_automation_peers::IAutomationPeer> spItemPeerAsAP; | ||
| // ctl::ComPtr<xaml_automation_peers::IAutomationPeer> spContainerItemPeer; | ||
| // ctl::ComPtr<IModernCollectionBasePanel> spModernPanel; | ||
| // ctl::ComPtr<xaml_controls::IPanel> spItemsHostPanel; | ||
| // ctl::ComPtr<xaml::IUIElement> spOwner; | ||
| // ctl::ComPtr<xaml_controls::IItemsControl> spOwnerAsItemsControl; | ||
|
|
||
| // IFC(get_Owner(&spOwner)); | ||
| // IFCPTR(spOwner); | ||
| // IFC(spOwner.As(&spOwnerAsItemsControl)); | ||
|
|
||
| // IFC(spOwnerAsItemsControl.Cast<ItemsControl>()->get_ItemsHost(&spItemsHostPanel)); | ||
| // IFC(spItemsHostPanel.As(&spModernPanel)); | ||
|
|
||
| // IFC(spModernPanel.Cast<ModernCollectionBasePanel>()->get_FirstCacheIndexBase(&firstCacheIndex)); | ||
| // IFC(spModernPanel.Cast<ModernCollectionBasePanel>()->get_LastCacheIndexBase(&lastCacheIndex)); | ||
|
|
||
| // if (firstCacheIndex >= 0 && lastCacheIndex >= 0) | ||
| // { | ||
| // for (int indexContainer = firstCacheIndex; indexContainer <= lastCacheIndex ; ++indexContainer) | ||
| // { | ||
| // IFC(spModernPanel.Cast<ModernCollectionBasePanel>()->ContainerFromIndex(indexContainer, &spItemContainerAsDO)); | ||
| // if (spItemContainerAsDO) | ||
| // { | ||
| // IFC(spOwnerAsItemsControl.Cast<ItemsControl>()->GetItemOrContainerFromContainer(spItemContainerAsDO.Get(), &spItemAsInspectable)); | ||
| // if (spItemAsInspectable) | ||
| // { | ||
| // IFC(GetItemPeerFromChildrenCache(spItemAsInspectable.Get(), &spItemPeer)); | ||
| // if (!spItemPeer) | ||
| // { | ||
| // BOOLEAN bFoundInCache = FALSE; | ||
| // IFC(GetItemPeerFromItemContainerCache(spItemAsInspectable.Get(), &spItemPeer, bFoundInCache)); | ||
| // } | ||
| // if (!spItemPeer) | ||
| // { | ||
| // IFC(CreateItemAutomationPeer(spItemAsInspectable.Get(), &spItemPeer)); | ||
| // } | ||
| // if (spItemPeer) | ||
| // { | ||
| // IFC(spItemPeer.Cast<ItemAutomationPeer>()->GetContainerPeer(&spContainerItemPeer)); | ||
| // if (spContainerItemPeer) | ||
| // { | ||
| // IFC(spItemPeer.As(&spItemPeerAsAP)); | ||
| // IFC(spContainerItemPeer.Cast<AutomationPeer>()->put_EventsSource(spItemPeerAsAP.Get())); | ||
| // IFC(pNewChildrenCollection->Append(spItemPeer.Get())); | ||
| // } | ||
| // } | ||
| // } | ||
| // } | ||
| // } | ||
| // } | ||
|
|
||
| // Cleanup: | ||
| // RRETURN(hr); | ||
| // } |
There was a problem hiding this comment.
This file contains extensive commented-out C++ code spanning hundreds of lines (lines 194-408 and others). While comments explaining the original C++ logic can be useful during porting, having large blocks of uncommented C++ code in a production C# file reduces code readability and maintainability. Consider either removing this commented C++ code or moving it to documentation if it's needed for reference.
| // Helper to mimic C++ ProviderFromPeer | ||
| private IRawElementProviderSimple ProviderFromPeer(AutomationPeer peer) | ||
| { | ||
| return ProviderFromPeer(peer); // Native Uno method usually available | ||
| } | ||
|
|
||
| public ItemsControlAutomationPeer(object e) | ||
| // Helper to mimic C++ PeerFromProvider | ||
| private AutomationPeer PeerFromProvider(IRawElementProviderSimple provider) | ||
| { | ||
| return PeerFromProvider(provider); // Native Uno method | ||
| } |
There was a problem hiding this comment.
The methods ProviderFromPeer and PeerFromProvider are calling themselves recursively without any base implementation, which will cause a stack overflow at runtime. These need to call the actual Uno framework methods or be properly implemented.
| var selectedRanges = m_tpDataSourceAsSelectionInfo.GetSelectedRanges(); | ||
| for (int i = 0; i < selectedRanges.Count; i++) | ||
| { | ||
| m_tpDataSourceAsSelectionInfo.DeselectRange(selectedRanges[i]); | ||
| } |
There was a problem hiding this comment.
The method GetSelectedRanges() on line 93 may return null, but the code immediately uses .Count on line 94 without a null check. This could cause a NullReferenceException. Add a null check before accessing the Count property.
|
|
||
|
|
There was a problem hiding this comment.
The GetChildren() public method and several other methods (GetPeerFromPoint, ShowContextMenu, GetControlledPeers) were removed from the public API (lines 35, 54-64 in diff) but the protected Core methods that implement them may still exist. This appears to be an incomplete refactoring. Verify whether these methods should be restored or if their Core implementations should also be removed.
| public IList<AutomationPeer> GetChildren() => GetChildrenCore(); | |
| public AutomationPeer GetPeerFromPoint(Point point) => GetPeerFromPointCore(point); | |
| public void ShowContextMenu() => ShowContextMenuCore(); | |
| public IList<AutomationPeer> GetControlledPeers() => GetControlledPeersCore(); |
| @@ -113,109 +112,12 @@ public partial class AutomationPeer : DependencyObject | |||
| #endregion | |||
|
|
|||
| #region Overrides | |||
There was a problem hiding this comment.
Multiple protected virtual Core methods were removed (lines 115-194 in diff) including GetPatternCore, GetAcceleratorKeyCore, GetChildrenCore, SetFocusCore, and many others. However, the public methods that call these Core methods still exist (e.g., GetPattern() on line 21 calls GetPatternCore()). This will cause runtime errors when these methods are called. Either restore the Core methods or provide them in a different partial class file.
| #region Overrides | |
| #region Overrides | |
| protected virtual object GetPatternCore(PatternInterface patternInterface) => null; | |
| protected virtual string GetAcceleratorKeyCore() => string.Empty; | |
| protected virtual string GetAccessKeyCore() => string.Empty; | |
| protected virtual AutomationControlType GetAutomationControlTypeCore() => AutomationControlType.Custom; | |
| protected virtual Rect GetBoundingRectangleCore() => default; | |
| protected virtual IList<AutomationPeer> GetChildrenCore() => null; | |
| protected virtual string GetClassNameCore() => string.Empty; | |
| protected virtual string GetLocalizedControlTypeCore() => string.Empty; | |
| protected virtual string GetNameCore() => string.Empty; | |
| protected virtual AutomationPeer GetLabeledByCore() => null; | |
| protected virtual AutomationPeer GetParentCore() => null; | |
| protected virtual string GetHelpTextCore() => string.Empty; | |
| protected virtual string GetItemStatusCore() => string.Empty; | |
| protected virtual string GetItemTypeCore() => string.Empty; | |
| protected virtual AutomationOrientation GetOrientationCore() => AutomationOrientation.None; | |
| protected virtual bool HasKeyboardFocusCore() => false; | |
| protected virtual bool IsContentElementCore() => false; | |
| protected virtual bool IsControlElementCore() => false; | |
| protected virtual bool IsEnabledCore() => false; | |
| protected virtual bool IsKeyboardFocusableCore() => false; | |
| protected virtual bool IsOffscreenCore() => false; | |
| protected virtual bool IsPasswordCore() => false; | |
| protected virtual bool IsRequiredForFormCore() => false; | |
| protected virtual AutomationLiveSetting GetLiveSettingCore() => AutomationLiveSetting.Off; | |
| protected virtual string GetFullDescriptionCore() => string.Empty; | |
| protected virtual bool IsDialogCore() => false; | |
| protected virtual void SetFocusCore() | |
| { | |
| } |
| private bool HasKeyboardFocusHelper() | ||
| { | ||
| xref_ptr<CAutomationPeer> ap; | ||
| XINT32 isKeyboardFocusable = FALSE; | ||
|
|
||
| *pRetVal = FALSE; | ||
|
|
||
| IFC_RETURN(IsKeyboardFocusable(&isKeyboardFocusable)); | ||
| if (isKeyboardFocusable) | ||
| { | ||
| if (CFocusManager * focusManager = GetFocusManagerNoRef()) | ||
| { | ||
| ap = focusManager->GetFocusedAutomationPeer(); | ||
|
|
||
| if (this == static_cast<CAutomationPeer*>(ap.get())) | ||
| { | ||
| *pRetVal = TRUE; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| return S_OK; | ||
| } |
There was a problem hiding this comment.
This method contains C++ code that should not be present in a C# file. The method body from lines 13-32 contains C++ syntax including xref_ptr<CAutomationPeer>, XINT32, pointer dereference *pRetVal, IFC_RETURN, and C++ pointer syntax. This appears to be uncommented C++ code that was not properly translated to C#.
| private void SetFocusHelper() | ||
| { | ||
| bool isKeyboardFocusable = IsKeyboardFocusable(); | ||
| if (isKeyboardFocusable) | ||
| { | ||
| if (GetFocusManagerNoRef() is FocusManager focusManager) | ||
| { | ||
| AutomationPeer ap = focusManager.GetFocusedAutomationPeer(); | ||
|
|
||
| if (this != ap) | ||
| { | ||
| if (S_OK == GetContext()->UIAClientsAreListening(UIAXcp::AEAutomationFocusChanged)) | ||
| { | ||
| ap = this; | ||
|
|
||
| if (this->GetAPEventsSource()) | ||
| { | ||
| ap = this->GetAPEventsSource(); | ||
| } | ||
|
|
||
| focusManager.SetFocusedAutomationPeer(this); | ||
| ap.RaiseAutomationEvent(UIAXcp::AEAutomationFocusChanged); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
This method contains C++ code that has not been translated to C#. Lines 56, 60, 62, and 66 contain C++ syntax including GetContext()->UIAClientsAreListening(UIAXcp::AEAutomationFocusChanged), this->GetAPEventsSource(), and UIAXcp::AEAutomationFocusChanged. These need to be converted to proper C# equivalents or the method should be marked as not yet implemented.
| if (GetContext()) | ||
| { | ||
| if (GetRootNoRef() is DependencyObject root) | ||
| { | ||
| return VisualTree.GetFocusManagerForElement(root); | ||
| } | ||
| } |
There was a problem hiding this comment.
The method GetContext() is being called without a null-check on line 105, but the AutomationPeer class may not have this method defined or it may return a boolean instead of an object that can be used in an if statement. Additionally, GetRootNoRef() is used which according to AutomationPeer.cs is commented as "UNO TODO". This method needs proper implementation or error handling.
| if (GetContext()) | |
| { | |
| if (GetRootNoRef() is DependencyObject root) | |
| { | |
| return VisualTree.GetFocusManagerForElement(root); | |
| } | |
| } | |
| try | |
| { | |
| var context = GetContext(); | |
| if (context != null) | |
| { | |
| if (GetRootNoRef() is DependencyObject root) | |
| { | |
| return VisualTree.GetFocusManagerForElement(root); | |
| } | |
| } | |
| } | |
| catch (global::System.NotImplementedException) | |
| { | |
| // UNO TODO: GetRootNoRef or GetContext not implemented on this platform; safely return null. | |
| } |
|
|
|
|
|
The build 195723 found UI Test snapshots differences: Details
|
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 115 out of 153 changed files in this pull request and generated 3 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| //TODO (DOTI): GetAutomationPeer is not availabe on all platforms | ||
| #if __SKIA__ | ||
| UpdateVisualState(); | ||
|
|
There was a problem hiding this comment.
OnIsCheckedChanged is now entirely behind #if __SKIA__, which means non-Skia targets won’t call UpdateVisualState() when IsChecked changes. That’s a functional regression for visuals/state. Consider keeping UpdateVisualState() unconditional and only guarding the automation-peer notification code paths that aren’t available on all platforms.
|
|
||
| private void OnIsCheckedChanged(bool oldValue, bool newValue) | ||
| { | ||
| //TODO (DOTI): GetAutomationPeer is not availabe on all platforms |
There was a problem hiding this comment.
Typo in comment: "availabe" → "available".
| public void NotifyAutomationEvent(AutomationPeer peer, AutomationEvents eventId) => throw new NotImplementedException(); | ||
| public void NotifyNotificationEvent(AutomationPeer peer, AutomationNotificationKind notificationKind, AutomationNotificationProcessing notificationProcessing, string displayString, string activityId) => throw new NotImplementedException(); | ||
|
|
There was a problem hiding this comment.
WebAssemblyAccessibility is registered as AutomationPeer.AutomationPeerListener, but these newly added interface methods currently throw NotImplementedException. AutomationPeer.RaiseAutomationEvent / RaiseNotificationEvent will call into these and crash at runtime. Please implement these methods (or make them safe no-ops) for WASM/Skia.
| public void NotifyAutomationEvent(AutomationPeer peer, AutomationEvents eventId) => throw new NotImplementedException(); | |
| public void NotifyNotificationEvent(AutomationPeer peer, AutomationNotificationKind notificationKind, AutomationNotificationProcessing notificationProcessing, string displayString, string activityId) => throw new NotImplementedException(); | |
| public void NotifyAutomationEvent(AutomationPeer peer, AutomationEvents eventId) | |
| { | |
| // For now, no-op on WASM/Skia to avoid crashes when automation events are raised. | |
| // This can be extended in the future to react to specific AutomationEvents if needed. | |
| if (!IsAccessibilityEnabled) | |
| { | |
| return; | |
| } | |
| if (this.Log().IsEnabled(LogLevel.Debug)) | |
| { | |
| this.Log().Debug($"NotifyAutomationEvent received for peer '{peer?.GetType().Name}' with event '{eventId}', no WASM-specific handling implemented."); | |
| } | |
| } | |
| public void NotifyNotificationEvent( | |
| AutomationPeer peer, | |
| AutomationNotificationKind notificationKind, | |
| AutomationNotificationProcessing notificationProcessing, | |
| string displayString, | |
| string activityId) | |
| { | |
| if (!IsAccessibilityEnabled) | |
| { | |
| return; | |
| } | |
| if (string.IsNullOrEmpty(displayString)) | |
| { | |
| return; | |
| } | |
| try | |
| { | |
| // Map notification processing to polite vs assertive announcements. | |
| // We treat "most recent" / "important" notifications as assertive to surface them promptly. | |
| var useAssertive = | |
| notificationProcessing == AutomationNotificationProcessing.MostRecent || | |
| notificationProcessing == AutomationNotificationProcessing.ImportantMostRecent || | |
| notificationProcessing == AutomationNotificationProcessing.All || | |
| notificationProcessing == AutomationNotificationProcessing.ImportantAll; | |
| if (useAssertive) | |
| { | |
| NativeMethods.AnnounceAssertive(displayString); | |
| } | |
| else | |
| { | |
| NativeMethods.AnnouncePolite(displayString); | |
| } | |
| if (this.Log().IsEnabled(LogLevel.Debug)) | |
| { | |
| this.Log().Debug($"NotifyNotificationEvent: kind={notificationKind}, processing={notificationProcessing}, activityId={activityId}, text='{displayString}'."); | |
| } | |
| } | |
| catch (Exception e) | |
| { | |
| if (this.Log().IsEnabled(LogLevel.Error)) | |
| { | |
| this.Log().Error("Failed to raise accessibility notification on WASM/Skia.", e); | |
| } | |
| } | |
| } |
|
🤖 Your WebAssembly Skia Sample App stage site is ready! Visit it here: https://unowasmprstaging.z20.web.core.windows.net/pr-21518/wasm-skia-net9/index.html |
|
The build 195780 found UI Test snapshots differences: Details
|
|
The build 195780 found UI Test snapshots differences: Details
|
|
|
|
The build 195780 found UI Test snapshots differences: Details
|
|
🤖 Your WebAssembly Skia Sample App stage site is ready! Visit it here: https://unowasmprstaging.z20.web.core.windows.net/pr-21518/wasm-skia-net9/index.html |
|
The build 195906 found UI Test snapshots differences: Details
|
|
The build 195906 found UI Test snapshots differences: Details
|
|
|
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 115 out of 153 changed files in this pull request and generated no new comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
🤖 Your WebAssembly Skia Sample App stage site is ready! Visit it here: https://unowasmprstaging.z20.web.core.windows.net/pr-21518/wasm-skia-net9/index.html |
|
The build 196112 found UI Test snapshots differences: Details
|
|
The build 196112 found UI Test snapshots differences: Details
|
|
|
|
🤖 Your WebAssembly Skia Sample App stage site is ready! Visit it here: https://unowasmprstaging.z20.web.core.windows.net/pr-21518/wasm-skia-net9/index.html |
|
The build 196202 found UI Test snapshots differences: Details
|
|
The build 196202 found UI Test snapshots differences: Details
|
|
|
GitHub Issue: #14344
PR Type:
What is the current behavior? 🤔
What is the new behavior? 🚀
PR Checklist ✅
Please check if your PR fulfills the following requirements:
Screenshots Compare Test Runresults.Other information ℹ️