Browse Source

Fixes several simple TODO12 (#20544)

* Remove obsolete TextInputMethodClient.ShowInputPanel method

* Merge Move and Move private in KeyboardNavigationHandler

* Change Inline.TextDecorations type to AttachedProperty

* Change TextSearch.GetText/SetText parameter to Interactive

* Remove Design.SetPreviewWith overload

* Replace JSWebWorkerClone with JSWebWorker

* Remove StringTokenizer

* Update API suppressions

* Add reflection comment to RenderWorker
pull/20561/head
Julien Lebosquain 1 week ago
committed by GitHub
parent
commit
1ee1509352
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 108
      api/Avalonia.nupkg.xml
  2. 6
      src/Avalonia.Base/Input/IKeyboardNavigationHandler.cs
  3. 22
      src/Avalonia.Base/Input/KeyboardNavigationHandler.cs
  4. 7
      src/Avalonia.Base/Input/TextInput/TextInputMethodClient.cs
  5. 245
      src/Avalonia.Base/Utilities/StringTokenizer.cs
  6. 3
      src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj
  7. 16
      src/Avalonia.Controls/Design.cs
  8. 3
      src/Avalonia.Controls/Documents/Inline.cs
  9. 6
      src/Avalonia.Controls/Primitives/TextSearch.cs
  10. 111
      src/Browser/Avalonia.Browser/Rendering/RenderWorker.cs
  11. 81
      tests/Avalonia.Base.UnitTests/Utilities/StringTokenizerTests.cs

108
api/Avalonia.nupkg.xml

@ -19,6 +19,12 @@
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Utilities.StringTokenizer</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Controls.Primitives.IScrollable</Target>
@ -49,6 +55,12 @@
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Utilities.StringTokenizer</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Controls.Primitives.IScrollable</Target>
@ -73,6 +85,24 @@
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Input.IKeyboardNavigationHandler.Move(Avalonia.Input.IInputElement,Avalonia.Input.NavigationDirection,Avalonia.Input.KeyModifiers)</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Input.KeyboardNavigationHandler.Move(Avalonia.Input.IInputElement,Avalonia.Input.NavigationDirection,Avalonia.Input.KeyModifiers)</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Input.TextInput.TextInputMethodClient.ShowInputPanel</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Media.DrawingImage.get_Viewbox</Target>
@ -181,6 +211,12 @@
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>F:Avalonia.Controls.Documents.Inline.TextDecorationsProperty</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>F:Avalonia.Controls.TextBlock.LetterSpacingProperty</Target>
@ -223,6 +259,12 @@
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Controls.Design.SetPreviewWith(Avalonia.AvaloniaObject,Avalonia.Controls.Control)</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Controls.Design.SetPreviewWith(Avalonia.AvaloniaObject,Avalonia.Controls.ITemplate{Avalonia.Controls.Control})</Target>
@ -259,6 +301,18 @@
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Controls.Primitives.TextSearch.GetText(Avalonia.Controls.Control)</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Controls.Primitives.TextSearch.SetText(Avalonia.Controls.Control,System.String)</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Platform.Screen.#ctor(System.Double,Avalonia.PixelRect,Avalonia.PixelRect,System.Boolean)</Target>
@ -343,6 +397,24 @@
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Input.IKeyboardNavigationHandler.Move(Avalonia.Input.IInputElement,Avalonia.Input.NavigationDirection,Avalonia.Input.KeyModifiers)</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Input.KeyboardNavigationHandler.Move(Avalonia.Input.IInputElement,Avalonia.Input.NavigationDirection,Avalonia.Input.KeyModifiers)</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Input.TextInput.TextInputMethodClient.ShowInputPanel</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Media.DrawingImage.get_Viewbox</Target>
@ -451,6 +523,12 @@
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>F:Avalonia.Controls.Documents.Inline.TextDecorationsProperty</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>F:Avalonia.Controls.TextBlock.LetterSpacingProperty</Target>
@ -493,6 +571,12 @@
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Controls.Design.SetPreviewWith(Avalonia.AvaloniaObject,Avalonia.Controls.Control)</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Controls.Design.SetPreviewWith(Avalonia.AvaloniaObject,Avalonia.Controls.ITemplate{Avalonia.Controls.Control})</Target>
@ -529,6 +613,18 @@
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Controls.Primitives.TextSearch.GetText(Avalonia.Controls.Control)</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Controls.Primitives.TextSearch.SetText(Avalonia.Controls.Control,System.String)</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Platform.Screen.#ctor(System.Double,Avalonia.PixelRect,Avalonia.PixelRect,System.Boolean)</Target>
@ -619,6 +715,12 @@
<Left>baseline/netstandard2.0/Avalonia.Base.dll</Left>
<Right>target/netstandard2.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0006</DiagnosticId>
<Target>M:Avalonia.Input.IKeyboardNavigationHandler.Move(Avalonia.Input.IInputElement,Avalonia.Input.NavigationDirection,Avalonia.Input.KeyModifiers,System.Nullable{Avalonia.Input.KeyDeviceType})</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0006</DiagnosticId>
<Target>M:Avalonia.Platform.IDrawingContextImpl.PopTextOptions</Target>
@ -727,6 +829,12 @@
<Left>baseline/Avalonia/lib/net6.0/Avalonia.OpenGL.dll</Left>
<Right>current/Avalonia/lib/net6.0/Avalonia.OpenGL.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0006</DiagnosticId>
<Target>M:Avalonia.Input.IKeyboardNavigationHandler.Move(Avalonia.Input.IInputElement,Avalonia.Input.NavigationDirection,Avalonia.Input.KeyModifiers,System.Nullable{Avalonia.Input.KeyDeviceType})</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0006</DiagnosticId>
<Target>M:Avalonia.Input.Platform.IClipboard.SetDataAsync(Avalonia.Input.IAsyncDataTransfer)</Target>

6
src/Avalonia.Base/Input/IKeyboardNavigationHandler.cs

@ -24,9 +24,11 @@ namespace Avalonia.Input
/// <param name="element">The current element.</param>
/// <param name="direction">The direction to move.</param>
/// <param name="keyModifiers">Any key modifiers active at the time of focus.</param>
void Move(
/// <param name="deviceType">The device type used to move the focus.</param>
bool Move(
IInputElement element,
NavigationDirection direction,
KeyModifiers keyModifiers = KeyModifiers.None);
KeyModifiers keyModifiers = KeyModifiers.None,
KeyDeviceType? deviceType = null);
}
}

22
src/Avalonia.Base/Input/KeyboardNavigationHandler.cs

@ -98,22 +98,12 @@ namespace Avalonia.Input
return result;
}
/// <summary>
/// Moves the focus in the specified direction.
/// </summary>
/// <param name="element">The current element.</param>
/// <param name="direction">The direction to move.</param>
/// <param name="keyModifiers">Any key modifiers active at the time of focus.</param>
public void Move(
/// <inheritdoc />
public bool Move(
IInputElement? element,
NavigationDirection direction,
KeyModifiers keyModifiers = KeyModifiers.None)
{
MovePrivate(element, direction, keyModifiers, null);
}
// TODO12: remove MovePrivate, and make Move return boolean. Or even remove whole KeyboardNavigationHandler.
private bool MovePrivate(IInputElement? element, NavigationDirection direction, KeyModifiers keyModifiers, KeyDeviceType? deviceType)
KeyModifiers keyModifiers = KeyModifiers.None,
KeyDeviceType? deviceType = null)
{
var next = GetNextPrivate(element, _owner, direction, deviceType);
@ -140,7 +130,7 @@ namespace Avalonia.Input
var current = FocusManager.GetFocusManager(e.Source as IInputElement)?.GetFocusedElement();
var direction = (e.KeyModifiers & KeyModifiers.Shift) == 0 ?
NavigationDirection.Next : NavigationDirection.Previous;
e.Handled = MovePrivate(current, direction, e.KeyModifiers, e.KeyDeviceType);
e.Handled = Move(current, direction, e.KeyModifiers, e.KeyDeviceType);
}
else if (e.Key is Key.Left or Key.Right or Key.Up or Key.Down)
{
@ -153,7 +143,7 @@ namespace Avalonia.Input
Key.Down => NavigationDirection.Down,
_ => throw new ArgumentOutOfRangeException()
};
e.Handled = MovePrivate(current, direction, e.KeyModifiers, e.KeyDeviceType);
e.Handled = Move(current, direction, e.KeyModifiers, e.KeyDeviceType);
}
}

7
src/Avalonia.Base/Input/TextInput/TextInputMethodClient.cs

@ -83,13 +83,6 @@ namespace Avalonia.Input.TextInput
SetPreeditText(preeditText);
}
//TODO12: remove
[Obsolete]
public virtual void ShowInputPanel()
{
RaiseInputPaneActivationRequested();
}
protected virtual void RaiseTextViewVisualChanged()
{
TextViewVisualChanged?.Invoke(this, EventArgs.Empty);

245
src/Avalonia.Base/Utilities/StringTokenizer.cs

@ -1,245 +0,0 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using static System.Char;
namespace Avalonia.Utilities
{
// TODO12: Remove this struct in 12.0 (breaking change)
[Obsolete("This type has been superseded by SpanStringTokenizer.")]
#if !BUILDTASK
public
#endif
record struct StringTokenizer : IDisposable
{
private const char DefaultSeparatorChar = ',';
private readonly string _s;
private readonly int _length;
private readonly char _separator;
private readonly string? _exceptionMessage;
private readonly IFormatProvider _formatProvider;
private int _index;
private int _tokenIndex;
private int _tokenLength;
public StringTokenizer(string s, IFormatProvider formatProvider, string? exceptionMessage = null)
: this(s, GetSeparatorFromFormatProvider(formatProvider), exceptionMessage)
{
_formatProvider = formatProvider;
}
public StringTokenizer(string s, char separator = DefaultSeparatorChar, string? exceptionMessage = null)
{
_s = s ?? throw new ArgumentNullException(nameof(s));
_length = s?.Length ?? 0;
_separator = separator;
_exceptionMessage = exceptionMessage;
_formatProvider = CultureInfo.InvariantCulture;
_index = 0;
_tokenIndex = -1;
_tokenLength = 0;
while (_index < _length && IsWhiteSpace(_s, _index))
{
_index++;
}
}
public string? CurrentToken => _tokenIndex < 0 ? null : _s.Substring(_tokenIndex, _tokenLength);
public ReadOnlySpan<char> CurrentTokenSpan => _tokenIndex < 0 ? ReadOnlySpan<char>.Empty : _s.AsSpan().Slice(_tokenIndex, _tokenLength);
public void Dispose()
{
if (_index != _length)
{
throw GetFormatException();
}
}
public bool TryReadInt32(out Int32 result, char? separator = null)
{
if (TryReadSpan(out var stringResult, separator) &&
SpanHelpers.TryParseInt(stringResult, NumberStyles.Integer, _formatProvider, out result))
{
return true;
}
else
{
result = default;
return false;
}
}
public int ReadInt32(char? separator = null)
{
if (!TryReadInt32(out var result, separator))
{
throw GetFormatException();
}
return result;
}
public bool TryReadDouble(out double result, char? separator = null)
{
if (TryReadSpan(out var stringResult, separator) &&
SpanHelpers.TryParseDouble(stringResult, NumberStyles.Float, _formatProvider, out result))
{
return true;
}
else
{
result = default;
return false;
}
}
public double ReadDouble(char? separator = null)
{
if (!TryReadDouble(out var result, separator))
{
throw GetFormatException();
}
return result;
}
public bool TryReadString([NotNull] out string result, char? separator = null)
{
var success = TryReadToken(separator ?? _separator);
result = CurrentTokenSpan.ToString();
return success;
}
public string ReadString(char? separator = null)
{
if (!TryReadString(out var result, separator))
{
throw GetFormatException();
}
return result;
}
public bool TryReadSpan(out ReadOnlySpan<char> result, char? separator = null)
{
var success = TryReadToken(separator ?? _separator);
result = CurrentTokenSpan;
return success;
}
public ReadOnlySpan<char> ReadSpan(char? separator = null)
{
if (!TryReadSpan(out var result, separator))
{
throw GetFormatException();
}
return result;
}
private bool TryReadToken(char separator)
{
_tokenIndex = -1;
if (_index >= _length)
{
return false;
}
var c = _s[_index];
var index = _index;
var length = 0;
while (_index < _length)
{
c = _s[_index];
if (IsWhiteSpace(c) || c == separator)
{
break;
}
_index++;
length++;
}
SkipToNextToken(separator);
_tokenIndex = index;
_tokenLength = length;
if (_tokenLength < 1)
{
throw GetFormatException();
}
return true;
}
private void SkipToNextToken(char separator)
{
if (_index < _length)
{
var c = _s[_index];
if (c != separator && !IsWhiteSpace(c))
{
throw GetFormatException();
}
var length = 0;
while (_index < _length)
{
c = _s[_index];
if (c == separator)
{
length++;
_index++;
if (length > 1)
{
throw GetFormatException();
}
}
else
{
if (!IsWhiteSpace(c))
{
break;
}
_index++;
}
}
if (length > 0 && _index >= _length)
{
throw GetFormatException();
}
}
}
private FormatException GetFormatException() =>
_exceptionMessage != null ? new FormatException(_exceptionMessage) : new FormatException();
private static char GetSeparatorFromFormatProvider(IFormatProvider provider)
{
var c = DefaultSeparatorChar;
var formatInfo = NumberFormatInfo.GetInstance(provider);
if (formatInfo.NumberDecimalSeparator.Length > 0 && c == formatInfo.NumberDecimalSeparator[0])
{
c = ';';
}
return c;
}
}
}

3
src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj

@ -62,9 +62,6 @@
<Compile Include="../Avalonia.Base/Utilities/IdentifierParser.cs">
<Link>Markup/%(RecursiveDir)%(FileName)%(Extension)</Link>
</Compile>
<Compile Include="../Avalonia.Base/Utilities/StringTokenizer.cs">
<Link>Markup/%(RecursiveDir)%(FileName)%(Extension)</Link>
</Compile>
<Compile Include="../Avalonia.Base/Utilities/MathUtilities.cs">
<Link>Markup/%(RecursiveDir)%(FileName)%(Extension)</Link>
</Compile>

16
src/Avalonia.Controls/Design.cs

@ -128,22 +128,6 @@ namespace Avalonia.Controls
public static readonly AttachedProperty<Control?> PreviewWithProperty = AvaloniaProperty
.RegisterAttached<AvaloniaObject, Control?>("PreviewWith", typeof (Design));
/// <summary>
/// Sets a preview template for the specified <see cref="AvaloniaObject"/> at design-time.
/// </summary>
/// <remarks>
/// This method allows you to specify a substitute control to be rendered in the previewer
/// for a given object.
/// </remarks>
/// <param name="target">The target object.</param>
/// <param name="control">The preview control.</param>
// TODO12: Remove this overload in Avalonia 12
[Obsolete("Use SetPreviewWith(AvaloniaObject, ITemplate<Control>) overload instead. Use <Template></Template> from XAML")]
public static void SetPreviewWith(AvaloniaObject target, Control? control)
{
s_previewWith[target] = control is not null ? new FuncTemplate<Control>(() => control) : null;
}
/// <summary>
/// Sets a preview template for the specified <see cref="AvaloniaObject"/> at design-time.
/// </summary>

3
src/Avalonia.Controls/Documents/Inline.cs

@ -10,11 +10,10 @@ namespace Avalonia.Controls.Documents
/// </summary>
public abstract class Inline : TextElement
{
// TODO12: change the field type to an AttachedProperty for consistency (breaking change)
/// <summary>
/// AvaloniaProperty for <see cref="TextDecorations" /> property.
/// </summary>
public static readonly StyledProperty<TextDecorationCollection?> TextDecorationsProperty =
public static readonly AttachedProperty<TextDecorationCollection?> TextDecorationsProperty =
AvaloniaProperty.RegisterAttached<Inline, Inline, TextDecorationCollection?>(
nameof(TextDecorations),
inherits: true);

6
src/Avalonia.Controls/Primitives/TextSearch.cs

@ -24,22 +24,20 @@ namespace Avalonia.Controls.Primitives
public static readonly AttachedProperty<BindingBase?> TextBindingProperty
= AvaloniaProperty.RegisterAttached<Interactive, BindingBase?>("TextBinding", typeof(TextSearch));
// TODO12: Control should be Interactive to match the property definition.
/// <summary>
/// Sets the value of the <see cref="TextProperty"/> attached property to a given <see cref="Control"/>.
/// </summary>
/// <param name="control">The control.</param>
/// <param name="text">The search text to set.</param>
public static void SetText(Control control, string? text)
public static void SetText(Interactive control, string? text)
=> control.SetValue(TextProperty, text);
// TODO12: Control should be Interactive to match the property definition.
/// <summary>
/// Gets the value of the <see cref="TextProperty"/> attached property from a given <see cref="Control"/>.
/// </summary>
/// <param name="control">The control.</param>
/// <returns>The search text.</returns>
public static string? GetText(Control control)
public static string? GetText(Interactive control)
=> control.GetValue(TextProperty);
/// <summary>

111
src/Browser/Avalonia.Browser/Rendering/RenderWorker.cs

@ -1,10 +1,7 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.JavaScript;
using System.Threading;
using System.Threading.Tasks;
using Avalonia.Browser.Interop;
@ -20,10 +17,13 @@ internal partial class RenderWorker
internal static int WorkerThreadId;
// The worker task needs to be rooted otherwise the web worker will exit.
private static Task? s_workerTask;
public static Task InitializeAsync()
{
var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
var workerTask = JSWebWorkerClone.RunAsync(async () =>
s_workerTask = JSWebWorkerRunAsync(null, async () =>
{
try
{
@ -41,103 +41,18 @@ internal partial class RenderWorker
}
});
workerTask.ContinueWith(_ =>
s_workerTask.ContinueWith(_ =>
{
if (workerTask.IsFaulted)
tcs.TrySetException(workerTask.Exception);
if (s_workerTask.IsFaulted)
tcs.TrySetException(s_workerTask.Exception);
});
return tcs.Task;
}
public static class JSWebWorkerClone
{
private static readonly MethodInfo _setExtLoop;
private static readonly MethodInfo _intallInterop;
[DynamicDependency(DynamicallyAccessedMemberTypes.All, "System.Runtime.InteropServices.JavaScript.JSSynchronizationContext",
"System.Runtime.InteropServices.JavaScript")]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, "System.Runtime.InteropServices.JavaScript.JSHostImplementation",
"System.Runtime.InteropServices.JavaScript")]
[UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Private runtime API")]
[UnconditionalSuppressMessage("Trimming", "IL2036", Justification = "Private runtime API")]
[UnconditionalSuppressMessage("Trimming", "IL2075", Justification = "Private runtime API")]
[UnconditionalSuppressMessage("Trimming", "IL2111", Justification = "Private runtime API")]
static JSWebWorkerClone()
{
var syncContext = typeof(System.Runtime.InteropServices.JavaScript.JSHost)
.Assembly!.GetType("System.Runtime.InteropServices.JavaScript.JSSynchronizationContext")!;
var hostImpl = typeof(System.Runtime.InteropServices.JavaScript.JSHost)
.Assembly!.GetType("System.Runtime.InteropServices.JavaScript.JSHostImplementation")!;
_setExtLoop = hostImpl.GetMethod("SetHasExternalEventLoop")!;
_intallInterop = syncContext.GetMethod("InstallWebWorkerInterop")!;
}
public static Task RunAsync(Func<Task> run)
{
var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
var th = new Thread(_ =>
{
_intallInterop.Invoke(null, [false, CancellationToken.None]);
try
{
run().ContinueWith(t =>
{
if (t.IsFaulted)
tcs.TrySetException(t.Exception);
else if (t.IsCanceled)
tcs.TrySetCanceled();
else
tcs.TrySetResult();
});
}
catch(Exception e)
{
tcs.TrySetException(e);
}
})
{
Name = "Manual JS worker"
};
_setExtLoop.Invoke(null, [th]);
#pragma warning disable CA1416
th.Start();
#pragma warning restore CA1416
return tcs.Task;
}
}
// TODO: Use this class instead of JSWebWorkerClone once https://github.com/dotnet/runtime/issues/102010 is fixed
// TODO12: It was fixed in .NET 10
class JSWebWorkerWrapper
{
[DynamicDependency(DynamicallyAccessedMemberTypes.PublicMethods, "System.Runtime.InteropServices.JavaScript.JSWebWorker",
"System.Runtime.InteropServices.JavaScript")]
[UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Private runtime API")]
[UnconditionalSuppressMessage("Trimming", "IL2036", Justification = "Private runtime API")]
[UnconditionalSuppressMessage("Trimming", "IL2075", Justification = "Private runtime API")]
[UnconditionalSuppressMessage("Trimming", "IL2111", Justification = "Private runtime API")]
static JSWebWorkerWrapper()
{
var type = typeof(System.Runtime.InteropServices.JavaScript.JSHost)
.Assembly!.GetType("System.Runtime.InteropServices.JavaScript.JSWebWorker");
#pragma warning disable IL2075
var m = type!
.GetMethods(BindingFlags.Static | BindingFlags.Public
).First(m => m.Name == "RunAsync"
&& m.ReturnType == typeof(Task)
&& m.GetParameters() is { } parameters
&& parameters.Length == 1
&& parameters[0].ParameterType == typeof(Func<Task>));
#pragma warning restore IL2075
RunAsync = (Func<Func<Task>, Task>) Delegate.CreateDelegate(typeof(Func<Func<Task>, Task>), m);
}
public static Func<Func<Task>, Task> RunAsync { get; set; }
}
// Even though this API is public in the .NET code, it's not part of ref assemblies and is not a stable API.
[UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = "RunAsync")]
private static extern Task JSWebWorkerRunAsync(
[UnsafeAccessorType("System.Runtime.InteropServices.JavaScript.JSWebWorker, System.Runtime.InteropServices.JavaScript")] object? instance,
Func<Task> body);
}

81
tests/Avalonia.Base.UnitTests/Utilities/StringTokenizerTests.cs

@ -1,81 +0,0 @@
using System;
using Avalonia.Utilities;
using Xunit;
#pragma warning disable CS0618 // Type or member is obsolete
namespace Avalonia.Base.UnitTests.Utilities
{
public class StringTokenizerTests
{
[Fact]
public void ReadInt32_Reads_Values()
{
var target = new StringTokenizer("123,456");
Assert.Equal(123, target.ReadInt32());
Assert.Equal(456, target.ReadInt32());
Assert.Throws<FormatException>(() => target.ReadInt32());
}
[Fact]
public void ReadDouble_Reads_Values()
{
var target = new StringTokenizer("12.3,45.6");
Assert.Equal(12.3, target.ReadDouble());
Assert.Equal(45.6, target.ReadDouble());
Assert.Throws<FormatException>(() => target.ReadDouble());
}
[Fact]
public void TryReadInt32_Reads_Values()
{
var target = new StringTokenizer("123,456");
Assert.True(target.TryReadInt32(out var value));
Assert.Equal(123, value);
Assert.True(target.TryReadInt32(out value));
Assert.Equal(456, value);
Assert.False(target.TryReadInt32(out value));
}
[Fact]
public void TryReadInt32_Doesnt_Throw()
{
var target = new StringTokenizer("abc");
Assert.False(target.TryReadInt32(out var value));
}
[Fact]
public void TryReadDouble_Reads_Values()
{
var target = new StringTokenizer("12.3,45.6");
Assert.True(target.TryReadDouble(out var value));
Assert.Equal(12.3, value);
Assert.True(target.TryReadDouble(out value));
Assert.Equal(45.6, value);
Assert.False(target.TryReadDouble(out value));
}
[Fact]
public void TryReadDouble_Doesnt_Throw()
{
var target = new StringTokenizer("abc");
Assert.False(target.TryReadDouble(out var value));
}
[Fact]
public void ReadSpan_And_ReadString_Reads_Same()
{
var target1 = new StringTokenizer("abc,def");
var target2 = new StringTokenizer("abc,def");
Assert.Equal(target1.ReadString(), target2.ReadSpan().ToString());
Assert.True(target1.ReadSpan().SequenceEqual(target2.ReadString()));
}
}
}
Loading…
Cancel
Save