Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/Controls/src/Core/ImageBrush.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
namespace Microsoft.Maui.Controls
{
[ContentProperty(nameof(ImageSource))]
class ImageBrush : Brush
public class ImageBrush : Brush

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 AI-Generated Review (multi-model)

[moderate] Public API Surface Design / Logic and Correctness — This line makes ImageBrush public (previously internal), and the accompanying PublicAPI.Unshipped.txt entries in this PR ship Equals/GetHashCode as public API. Equals (line 28-29) performs value comparison on ImageSource, while GetHashCode() (line 31) returns base.GetHashCode() — reference-identity, since neither Brush nor Element overrides it. This violates the .NET contract that equal objects must produce equal hash codes: two distinct ImageBrush instances referencing the same ImageSource compare Equal but hash differently, breaking correctness for Dictionary<ImageBrush,_>/HashSet<ImageBrush> usage. Since this is now public/shipped API, the defect can no longer be fixed without a breaking change.

Comment thread
HarishwaranVijayakumar marked this conversation as resolved.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 AI-Generated Review (multi-model)

[major] Public API Surface — Making ImageBrush public exposes an equality/hash-code contract violation: Equals treats two brushes with the same ImageSource as equal, but GetHashCode() still delegates to base.GetHashCode(), so equal public instances can hash differently in Dictionary/HashSet. Please make the hash code use the same ImageSource state (or change equality) before shipping this API.

{
public ImageBrush()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,15 @@ Microsoft.Maui.Controls.Handlers.ShellSectionWrapperFragment.ShellSectionWrapper
Microsoft.Maui.Controls.HybridWebView.Invoker.get -> Microsoft.Maui.HybridWebViewInvoker!
Microsoft.Maui.Controls.HybridWebView.Invoker.set -> void
Microsoft.Maui.Controls.HybridWebView.SetInvokeJavaScriptTarget<T>(T! target, System.Text.Json.Serialization.JsonSerializerContext! jsonSerializerContext) -> void
Microsoft.Maui.Controls.ImageBrush
Microsoft.Maui.Controls.ImageBrush.ImageBrush() -> void
Microsoft.Maui.Controls.ImageBrush.ImageBrush(Microsoft.Maui.Controls.ImageSource! imageSource) -> void
override Microsoft.Maui.Controls.ImageBrush.Equals(object? obj) -> bool
override Microsoft.Maui.Controls.ImageBrush.GetHashCode() -> int
override Microsoft.Maui.Controls.ImageBrush.IsEmpty.get -> bool
static readonly Microsoft.Maui.Controls.ImageBrush.ImageSourceProperty -> Microsoft.Maui.Controls.BindableProperty!
virtual Microsoft.Maui.Controls.ImageBrush.ImageSource.get -> Microsoft.Maui.Controls.ImageSource?
virtual Microsoft.Maui.Controls.ImageBrush.ImageSource.set -> void
Microsoft.Maui.Controls.ImageSource.InvalidateStyle() -> void
Microsoft.Maui.Controls.Label.~Label() -> void
Microsoft.Maui.Controls.LongPressGestureRecognizer
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
#nullable enable
Microsoft.Maui.Controls.ImageBrush
Microsoft.Maui.Controls.ImageBrush.ImageBrush() -> void
Microsoft.Maui.Controls.ImageBrush.ImageBrush(Microsoft.Maui.Controls.ImageSource! imageSource) -> void
override Microsoft.Maui.Controls.ImageBrush.Equals(object? obj) -> bool
override Microsoft.Maui.Controls.ImageBrush.GetHashCode() -> int
override Microsoft.Maui.Controls.ImageBrush.IsEmpty.get -> bool
static readonly Microsoft.Maui.Controls.ImageBrush.ImageSourceProperty -> Microsoft.Maui.Controls.BindableProperty!
virtual Microsoft.Maui.Controls.ImageBrush.ImageSource.get -> Microsoft.Maui.Controls.ImageSource?
virtual Microsoft.Maui.Controls.ImageBrush.ImageSource.set -> void
*REMOVED*~override Microsoft.Maui.Controls.Platform.Compatibility.ShellSectionRootRenderer.TraitCollectionDidChange(UIKit.UITraitCollection previousTraitCollection) -> void
*REMOVED*~static Microsoft.Maui.Controls.VisualStateManager.GetVisualStateGroups(Microsoft.Maui.Controls.VisualElement visualElement) -> System.Collections.Generic.IList<Microsoft.Maui.Controls.VisualStateGroup>
Microsoft.Maui.Controls.AppThemeBinding
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
#nullable enable
Microsoft.Maui.Controls.ImageBrush
Microsoft.Maui.Controls.ImageBrush.ImageBrush() -> void
Microsoft.Maui.Controls.ImageBrush.ImageBrush(Microsoft.Maui.Controls.ImageSource! imageSource) -> void
override Microsoft.Maui.Controls.ImageBrush.Equals(object? obj) -> bool
override Microsoft.Maui.Controls.ImageBrush.GetHashCode() -> int
override Microsoft.Maui.Controls.ImageBrush.IsEmpty.get -> bool
static readonly Microsoft.Maui.Controls.ImageBrush.ImageSourceProperty -> Microsoft.Maui.Controls.BindableProperty!
virtual Microsoft.Maui.Controls.ImageBrush.ImageSource.get -> Microsoft.Maui.Controls.ImageSource?
virtual Microsoft.Maui.Controls.ImageBrush.ImageSource.set -> void
*REMOVED*~override Microsoft.Maui.Controls.Platform.Compatibility.ShellSectionRootRenderer.TraitCollectionDidChange(UIKit.UITraitCollection previousTraitCollection) -> void
*REMOVED*~static Microsoft.Maui.Controls.VisualStateManager.GetVisualStateGroups(Microsoft.Maui.Controls.VisualElement visualElement) -> System.Collections.Generic.IList<Microsoft.Maui.Controls.VisualStateGroup>
Microsoft.Maui.Controls.AppThemeBinding
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@
Microsoft.Maui.Controls.Window.StatusBarTheme.get -> Microsoft.Maui.StatusBarTheme
Microsoft.Maui.Controls.Window.StatusBarTheme.set -> void
static readonly Microsoft.Maui.Controls.Window.StatusBarThemeProperty -> Microsoft.Maui.Controls.BindableProperty!
Microsoft.Maui.Controls.ImageBrush
Microsoft.Maui.Controls.ImageBrush.ImageBrush() -> void
Microsoft.Maui.Controls.ImageBrush.ImageBrush(Microsoft.Maui.Controls.ImageSource! imageSource) -> void
override Microsoft.Maui.Controls.ImageBrush.Equals(object? obj) -> bool
override Microsoft.Maui.Controls.ImageBrush.GetHashCode() -> int
override Microsoft.Maui.Controls.ImageBrush.IsEmpty.get -> bool
static readonly Microsoft.Maui.Controls.ImageBrush.ImageSourceProperty -> Microsoft.Maui.Controls.BindableProperty!
virtual Microsoft.Maui.Controls.ImageBrush.ImageSource.get -> Microsoft.Maui.Controls.ImageSource?
virtual Microsoft.Maui.Controls.ImageBrush.ImageSource.set -> void
Microsoft.Maui.Controls.HybridWebView.Invoker.get -> Microsoft.Maui.HybridWebViewInvoker!
Microsoft.Maui.Controls.HybridWebView.Invoker.set -> void
Microsoft.Maui.Controls.HybridWebView.SetInvokeJavaScriptTarget<T>(T! target, System.Text.Json.Serialization.JsonSerializerContext! jsonSerializerContext) -> void
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,15 @@ Microsoft.Maui.Controls.HybridWebView.SetInvokeJavaScriptTarget<T>(T! target, Sy
Microsoft.Maui.Controls.AppThemeBinding
Microsoft.Maui.Controls.AppThemeBinding.AppThemeBinding() -> void
Microsoft.Maui.Controls.BoxView.~BoxView() -> void
Microsoft.Maui.Controls.ImageBrush
Microsoft.Maui.Controls.ImageBrush.ImageBrush() -> void
Microsoft.Maui.Controls.ImageBrush.ImageBrush(Microsoft.Maui.Controls.ImageSource! imageSource) -> void
override Microsoft.Maui.Controls.ImageBrush.Equals(object? obj) -> bool
override Microsoft.Maui.Controls.ImageBrush.GetHashCode() -> int
override Microsoft.Maui.Controls.ImageBrush.IsEmpty.get -> bool
static readonly Microsoft.Maui.Controls.ImageBrush.ImageSourceProperty -> Microsoft.Maui.Controls.BindableProperty!
virtual Microsoft.Maui.Controls.ImageBrush.ImageSource.get -> Microsoft.Maui.Controls.ImageSource?
virtual Microsoft.Maui.Controls.ImageBrush.ImageSource.set -> void
Microsoft.Maui.Controls.ImageSource.InvalidateStyle() -> void
Microsoft.Maui.Controls.LongPressGestureRecognizer
Microsoft.Maui.Controls.LongPressGestureRecognizer.AllowableMovement.get -> double
Expand Down
9 changes: 9 additions & 0 deletions src/Controls/src/Core/PublicAPI/net/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,15 @@ Microsoft.Maui.Controls.ImageSource.InvalidateStyle() -> void
~Microsoft.Maui.Controls.Internals.ResourcesChangedEventArgs.ResourcesChangedEventArgs(System.Collections.Generic.IEnumerable<string> keys, System.Func<string, object> resolver) -> void
~Microsoft.Maui.Controls.Internals.TypedBindingBase.ConverterCulture.get -> System.Globalization.CultureInfo
~Microsoft.Maui.Controls.Internals.TypedBindingBase.ConverterCulture.set -> void
Microsoft.Maui.Controls.ImageBrush
Microsoft.Maui.Controls.ImageBrush.ImageBrush() -> void
Microsoft.Maui.Controls.ImageBrush.ImageBrush(Microsoft.Maui.Controls.ImageSource! imageSource) -> void
override Microsoft.Maui.Controls.ImageBrush.Equals(object? obj) -> bool
override Microsoft.Maui.Controls.ImageBrush.GetHashCode() -> int
override Microsoft.Maui.Controls.ImageBrush.IsEmpty.get -> bool
static readonly Microsoft.Maui.Controls.ImageBrush.ImageSourceProperty -> Microsoft.Maui.Controls.BindableProperty!
virtual Microsoft.Maui.Controls.ImageBrush.ImageSource.get -> Microsoft.Maui.Controls.ImageSource?
virtual Microsoft.Maui.Controls.ImageBrush.ImageSource.set -> void
Microsoft.Maui.Controls.LongPressGestureRecognizer
Microsoft.Maui.Controls.LongPressGestureRecognizer.AllowableMovement.get -> double
Microsoft.Maui.Controls.LongPressGestureRecognizer.AllowableMovement.set -> void
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,15 @@ Microsoft.Maui.Controls.ImageSource.InvalidateStyle() -> void
~Microsoft.Maui.Controls.Internals.ResourcesChangedEventArgs.ResourcesChangedEventArgs(System.Collections.Generic.IEnumerable<string> keys, System.Func<string, object> resolver) -> void
~Microsoft.Maui.Controls.Internals.TypedBindingBase.ConverterCulture.get -> System.Globalization.CultureInfo
~Microsoft.Maui.Controls.Internals.TypedBindingBase.ConverterCulture.set -> void
Microsoft.Maui.Controls.ImageBrush
Microsoft.Maui.Controls.ImageBrush.ImageBrush() -> void
Microsoft.Maui.Controls.ImageBrush.ImageBrush(Microsoft.Maui.Controls.ImageSource! imageSource) -> void
override Microsoft.Maui.Controls.ImageBrush.Equals(object? obj) -> bool
override Microsoft.Maui.Controls.ImageBrush.GetHashCode() -> int
override Microsoft.Maui.Controls.ImageBrush.IsEmpty.get -> bool
static readonly Microsoft.Maui.Controls.ImageBrush.ImageSourceProperty -> Microsoft.Maui.Controls.BindableProperty!
virtual Microsoft.Maui.Controls.ImageBrush.ImageSource.get -> Microsoft.Maui.Controls.ImageSource?
virtual Microsoft.Maui.Controls.ImageBrush.ImageSource.set -> void
Microsoft.Maui.Controls.LongPressGestureRecognizer
Microsoft.Maui.Controls.LongPressGestureRecognizer.AllowableMovement.get -> double
Microsoft.Maui.Controls.LongPressGestureRecognizer.AllowableMovement.set -> void
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
43 changes: 43 additions & 0 deletions src/Controls/tests/TestCases.HostApp/Issues/Issue12928.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Maui.Controls.Sample.Issues.Issue12928"
Title="ImageBrush Background">

<Grid RowDefinitions="Auto, *, *, Auto, *, *, Auto"
Padding="15"
RowSpacing="8">

<Label x:Name="Control1Label"
Text="Label"
FontAttributes="Bold"
AutomationId="Control1Label"/>

<ContentView x:Name="Control1ColorContainer"
Grid.Row="1"
AutomationId="Control1ColorContainer"/>

<ContentView x:Name="Control1ImageContainer"
Grid.Row="2"
AutomationId="Control1Container"/>

<Label x:Name="Control2Label"
Grid.Row="3"
Text="Button"
FontAttributes="Bold"
AutomationId="Control2Label"/>

<ContentView x:Name="Control2ColorContainer"
Grid.Row="4"
AutomationId="Control2ColorContainer"/>

<ContentView x:Name="Control2ImageContainer"
Grid.Row="5"
AutomationId="Control2Container"/>

<Button Grid.Row="6"
Text="Select Controls"
Clicked="OnOptionsClicked"
AutomationId="OptionsButton"/>
</Grid>
</ContentPage>
Loading
Loading