Skip to content

Commit e5bb36d

Browse files
Forms behavior clarifications (#49340)
1 parent d24da32 commit e5bb36d

File tree

77 files changed

+1226
-958
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

77 files changed

+1226
-958
lines changed

src/Components/Components/src/CascadingParameterState.cs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using System.Reflection;
88
using Microsoft.AspNetCore.Components.Reflection;
99
using Microsoft.AspNetCore.Components.Rendering;
10+
using Microsoft.AspNetCore.Components.RenderTree;
1011
using static Microsoft.AspNetCore.Internal.LinkerFlags;
1112

1213
namespace Microsoft.AspNetCore.Components;
@@ -43,7 +44,7 @@ public static IReadOnlyList<CascadingParameterState> FindCascadingParameters(Com
4344
for (var infoIndex = 0; infoIndex < numInfos; infoIndex++)
4445
{
4546
ref var info = ref infos[infoIndex];
46-
var supplier = GetMatchingCascadingValueSupplier(info, componentState);
47+
var supplier = GetMatchingCascadingValueSupplier(info, componentState.Renderer, componentState.LogicalParentComponentState);
4748
if (supplier != null)
4849
{
4950
// Although not all parameters might be matched, we know the maximum number
@@ -55,10 +56,10 @@ public static IReadOnlyList<CascadingParameterState> FindCascadingParameters(Com
5556
return resultStates ?? (IReadOnlyList<CascadingParameterState>)Array.Empty<CascadingParameterState>();
5657
}
5758

58-
private static ICascadingValueSupplier? GetMatchingCascadingValueSupplier(in CascadingParameterInfo info, ComponentState componentState)
59+
internal static ICascadingValueSupplier? GetMatchingCascadingValueSupplier(in CascadingParameterInfo info, Renderer renderer, ComponentState? componentState)
5960
{
6061
// First scan up through the component hierarchy
61-
var candidate = componentState.LogicalParentComponentState;
62+
var candidate = componentState;
6263
while (candidate is not null)
6364
{
6465
if (candidate.Component is ICascadingValueSupplier valueSupplier && valueSupplier.CanSupplyValue(info))
@@ -70,7 +71,7 @@ public static IReadOnlyList<CascadingParameterState> FindCascadingParameters(Com
7071
}
7172

7273
// We got to the root and found no match, so now look at the providers registered in DI
73-
foreach (var valueSupplier in componentState.Renderer.ServiceProviderCascadingValueSuppliers)
74+
foreach (var valueSupplier in renderer.ServiceProviderCascadingValueSuppliers)
7475
{
7576
if (valueSupplier.CanSupplyValue(info))
7677
{

src/Components/Components/src/PublicAPI.Unshipped.txt

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -32,15 +32,18 @@ Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder.AddComponentRenderMo
3232
Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder.AddNamedEvent(int sequence, string! eventType, string! assignedName) -> void
3333
Microsoft.AspNetCore.Components.RenderTree.ComponentFrameFlags
3434
Microsoft.AspNetCore.Components.RenderTree.ComponentFrameFlags.HasCallerSpecifiedRenderMode = 1 -> Microsoft.AspNetCore.Components.RenderTree.ComponentFrameFlags
35-
Microsoft.AspNetCore.Components.RenderTree.NamedEvent
36-
Microsoft.AspNetCore.Components.RenderTree.NamedEvent.AssignedName.get -> string!
37-
Microsoft.AspNetCore.Components.RenderTree.NamedEvent.ComponentId.get -> int
38-
Microsoft.AspNetCore.Components.RenderTree.NamedEvent.EventType.get -> string!
39-
Microsoft.AspNetCore.Components.RenderTree.NamedEvent.FrameIndex.get -> int
40-
Microsoft.AspNetCore.Components.RenderTree.NamedEvent.NamedEvent() -> void
41-
Microsoft.AspNetCore.Components.RenderTree.NamedEvent.NamedEvent(int componentId, int frameIndex, string! eventType, string! assignedName) -> void
42-
Microsoft.AspNetCore.Components.RenderTree.RenderBatch.AddedNamedEvents.get -> Microsoft.AspNetCore.Components.RenderTree.ArrayRange<Microsoft.AspNetCore.Components.RenderTree.NamedEvent>?
43-
Microsoft.AspNetCore.Components.RenderTree.RenderBatch.RemovedNamedEvents.get -> Microsoft.AspNetCore.Components.RenderTree.ArrayRange<Microsoft.AspNetCore.Components.RenderTree.NamedEvent>?
35+
Microsoft.AspNetCore.Components.RenderTree.NamedEventChange
36+
Microsoft.AspNetCore.Components.RenderTree.NamedEventChange.AssignedName.get -> string!
37+
Microsoft.AspNetCore.Components.RenderTree.NamedEventChange.ChangeType.get -> Microsoft.AspNetCore.Components.RenderTree.NamedEventChangeType
38+
Microsoft.AspNetCore.Components.RenderTree.NamedEventChange.ComponentId.get -> int
39+
Microsoft.AspNetCore.Components.RenderTree.NamedEventChange.EventType.get -> string!
40+
Microsoft.AspNetCore.Components.RenderTree.NamedEventChange.FrameIndex.get -> int
41+
Microsoft.AspNetCore.Components.RenderTree.NamedEventChange.NamedEventChange() -> void
42+
Microsoft.AspNetCore.Components.RenderTree.NamedEventChange.NamedEventChange(Microsoft.AspNetCore.Components.RenderTree.NamedEventChangeType changeType, int componentId, int frameIndex, string! eventType, string! assignedName) -> void
43+
Microsoft.AspNetCore.Components.RenderTree.NamedEventChangeType
44+
Microsoft.AspNetCore.Components.RenderTree.NamedEventChangeType.Added = 0 -> Microsoft.AspNetCore.Components.RenderTree.NamedEventChangeType
45+
Microsoft.AspNetCore.Components.RenderTree.NamedEventChangeType.Removed = 1 -> Microsoft.AspNetCore.Components.RenderTree.NamedEventChangeType
46+
Microsoft.AspNetCore.Components.RenderTree.RenderBatch.NamedEventChanges.get -> Microsoft.AspNetCore.Components.RenderTree.ArrayRange<Microsoft.AspNetCore.Components.RenderTree.NamedEventChange>?
4447
Microsoft.AspNetCore.Components.RenderTree.RenderTreeFrame.ComponentFrameFlags.get -> Microsoft.AspNetCore.Components.RenderTree.ComponentFrameFlags
4548
Microsoft.AspNetCore.Components.RenderTree.RenderTreeFrameType.ComponentRenderMode = 9 -> Microsoft.AspNetCore.Components.RenderTree.RenderTreeFrameType
4649
Microsoft.AspNetCore.Components.RenderTree.RenderTreeFrameType.NamedEvent = 10 -> Microsoft.AspNetCore.Components.RenderTree.RenderTreeFrameType

src/Components/Components/src/RenderTree/NamedEvent.cs renamed to src/Components/Components/src/RenderTree/NamedEventChange.cs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,20 @@ namespace Microsoft.AspNetCore.Components.RenderTree;
88
/// of the Blazor framework. These types will change in a future release.
99
/// </summary>
1010
/// <remarks>
11-
/// Constructs an instance of <see cref="NamedEvent"/>.
11+
/// Constructs an instance of <see cref="NamedEventChange"/>.
1212
/// </remarks>
13+
/// <param name="changeType">The type of the change.</param>
1314
/// <param name="componentId">The ID of the component holding the named value.</param>
1415
/// <param name="frameIndex">The index of the <see cref="RenderTreeFrameType.NamedEvent"/> frame within the component's current render output.</param>
1516
/// <param name="eventType">The event type.</param>
1617
/// <param name="assignedName">The application-assigned name.</param>
17-
public readonly struct NamedEvent(int componentId, int frameIndex, string eventType, string assignedName)
18+
public readonly struct NamedEventChange(NamedEventChangeType changeType, int componentId, int frameIndex, string eventType, string assignedName)
1819
{
20+
/// <summary>
21+
/// Describes the type of the change.
22+
/// </summary>
23+
public readonly NamedEventChangeType ChangeType { get; } = changeType;
24+
1925
/// <summary>
2026
/// The ID of the component holding the named event.
2127
/// </summary>
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
namespace Microsoft.AspNetCore.Components.RenderTree;
5+
6+
/// <summary>
7+
/// Describes a change to a named event.
8+
/// </summary>
9+
public enum NamedEventChangeType : int
10+
{
11+
/// <summary>
12+
/// Indicates that the item was added.
13+
/// </summary>
14+
Added,
15+
16+
/// <summary>
17+
/// Indicates that the item was removed.
18+
/// </summary>
19+
Removed,
20+
}

src/Components/Components/src/RenderTree/RenderBatch.cs

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -34,29 +34,22 @@ public readonly struct RenderBatch
3434
public ArrayRange<ulong> DisposedEventHandlerIDs { get; }
3535

3636
/// <summary>
37-
/// Gets the named events that were added, or null.
37+
/// Gets the named events that were changed, or null.
3838
/// </summary>
39-
public ArrayRange<NamedEvent>? AddedNamedEvents { get; }
40-
41-
/// <summary>
42-
/// Gets the named events that were removed, or null.
43-
/// </summary>
44-
public ArrayRange<NamedEvent>? RemovedNamedEvents { get; }
39+
public ArrayRange<NamedEventChange>? NamedEventChanges { get; }
4540

4641
internal RenderBatch(
4742
ArrayRange<RenderTreeDiff> updatedComponents,
4843
ArrayRange<RenderTreeFrame> referenceFrames,
4944
ArrayRange<int> disposedComponentIDs,
5045
ArrayRange<ulong> disposedEventHandlerIDs,
51-
ArrayRange<NamedEvent>? addedNamedEvents,
52-
ArrayRange<NamedEvent>? removedNamedEvents)
46+
ArrayRange<NamedEventChange>? changedNamedEvents)
5347
{
5448
UpdatedComponents = updatedComponents;
5549
ReferenceFrames = referenceFrames;
5650
DisposedComponentIDs = disposedComponentIDs;
5751
DisposedEventHandlerIDs = disposedEventHandlerIDs;
58-
AddedNamedEvents = addedNamedEvents;
59-
RemovedNamedEvents = removedNamedEvents;
52+
NamedEventChanges = changedNamedEvents;
6053
}
6154
}
6255

src/Components/Components/src/Rendering/RenderBatchBuilder.cs

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,7 @@ internal sealed class RenderBatchBuilder : IDisposable
2222
public ArrayBuilder<RenderTreeDiff> UpdatedComponentDiffs { get; } = new ArrayBuilder<RenderTreeDiff>();
2323
public ArrayBuilder<int> DisposedComponentIds { get; } = new ArrayBuilder<int>();
2424
public ArrayBuilder<ulong> DisposedEventHandlerIds { get; } = new ArrayBuilder<ulong>();
25-
public ArrayBuilder<NamedEvent>? AddedNamedEvents;
26-
public ArrayBuilder<NamedEvent>? RemovedNamedEvents;
25+
public ArrayBuilder<NamedEventChange>? NamedEventChanges;
2726

2827
// Buffers referenced by UpdatedComponentDiffs
2928
public ArrayBuilder<RenderTreeEdit> EditsBuffer { get; } = new ArrayBuilder<RenderTreeEdit>(64);
@@ -56,8 +55,7 @@ public void ClearStateForCurrentBatch()
5655
DisposedComponentIds.Clear();
5756
DisposedEventHandlerIds.Clear();
5857
AttributeDiffSet.Clear();
59-
AddedNamedEvents?.Clear();
60-
RemovedNamedEvents?.Clear();
58+
NamedEventChanges?.Clear();
6159
}
6260

6361
public RenderBatch ToBatch()
@@ -66,8 +64,7 @@ public RenderBatch ToBatch()
6664
ReferenceFramesBuffer.ToRange(),
6765
DisposedComponentIds.ToRange(),
6866
DisposedEventHandlerIds.ToRange(),
69-
AddedNamedEvents?.ToRange(),
70-
RemovedNamedEvents?.ToRange());
67+
NamedEventChanges?.ToRange());
7168

7269
public void InvalidateParameterViews()
7370
{
@@ -87,14 +84,14 @@ public void InvalidateParameterViews()
8784

8885
public void AddNamedEvent(int componentId, int frameIndex, ref RenderTreeFrame frame)
8986
{
90-
AddedNamedEvents ??= new();
91-
AddedNamedEvents.Append(new NamedEvent(componentId, frameIndex, frame.NamedEventType, frame.NamedEventAssignedName));
87+
NamedEventChanges ??= new();
88+
NamedEventChanges.Append(new NamedEventChange(NamedEventChangeType.Added, componentId, frameIndex, frame.NamedEventType, frame.NamedEventAssignedName));
9289
}
9390

9491
public void RemoveNamedEvent(int componentId, int frameIndex, ref RenderTreeFrame frame)
9592
{
96-
RemovedNamedEvents ??= new();
97-
RemovedNamedEvents.Append(new NamedEvent(componentId, frameIndex, frame.NamedEventType, frame.NamedEventAssignedName));
93+
NamedEventChanges ??= new();
94+
NamedEventChanges.Append(new NamedEventChange(NamedEventChangeType.Removed, componentId, frameIndex, frame.NamedEventType, frame.NamedEventAssignedName));
9895
}
9996

10097
public void Dispose()
@@ -104,7 +101,6 @@ public void Dispose()
104101
UpdatedComponentDiffs.Dispose();
105102
DisposedComponentIds.Dispose();
106103
DisposedEventHandlerIds.Dispose();
107-
AddedNamedEvents?.Dispose();
108-
RemovedNamedEvents?.Dispose();
104+
NamedEventChanges?.Dispose();
109105
}
110106
}

src/Components/Components/src/Rendering/RenderTreeBuilder.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public sealed class RenderTreeBuilder : IDisposable
2828
private bool _hasSeenAddMultipleAttributes;
2929
private Dictionary<string, int>? _seenAttributeNames;
3030
private IComponentRenderMode? _pendingComponentCallSiteRenderMode; // TODO: Remove when Razor compiler supports call-site @rendermode
31-
private (int Sequence, string AssignedName)? _pendingNamedSubmitEvent; // TODO: Remove when Razor compiler supports @onsubmit:name
31+
private (int Sequence, string AssignedName)? _pendingNamedSubmitEvent; // TODO: Remove when Razor compiler supports @formname
3232

3333
/// <summary>
3434
/// The reserved parameter name used for supplying child content.
@@ -80,7 +80,7 @@ public void CloseElement()
8080
_entries.Buffer[indexOfEntryBeingClosed].ElementSubtreeLengthField = _entries.Count - indexOfEntryBeingClosed;
8181
}
8282

83-
// TODO: Remove this once Razor supports @onsubmit:name
83+
// TODO: Remove this once Razor supports @formname
8484
private void CompletePendingNamedSubmitEvent()
8585
{
8686
if (_pendingNamedSubmitEvent is { } pendingNamedSubmitEvent)
@@ -237,9 +237,9 @@ public void AddAttribute(int sequence, string name, string? value)
237237
AssertCanAddAttribute();
238238
if (value != null || _lastNonAttributeFrameType == RenderTreeFrameType.Component)
239239
{
240-
// TODO: Remove this once the Razor compiler is updated to support @onsubmit:name
241-
// That should compile directly as a call to AddNamedValue.
242-
if (string.Equals(name, "@onsubmit:name", StringComparison.Ordinal) && _lastNonAttributeFrameType == RenderTreeFrameType.Element)
240+
// TODO: Remove this once the Razor compiler is updated to support @formname
241+
// That should compile directly as a call to AddNamedEvent.
242+
if (string.Equals(name, "@formname", StringComparison.Ordinal) && _lastNonAttributeFrameType == RenderTreeFrameType.Element)
243243
{
244244
_pendingNamedSubmitEvent = (sequence, value!);
245245
}
@@ -722,7 +722,7 @@ public void AddComponentRenderMode(int sequence, IComponentRenderMode renderMode
722722
public void AddNamedEvent(int sequence, string eventType, string assignedName)
723723
{
724724
ArgumentNullException.ThrowIfNull(eventType);
725-
ArgumentNullException.ThrowIfNull(assignedName);
725+
ArgumentException.ThrowIfNullOrEmpty(assignedName);
726726

727727
// Note that we could trivially extend this to a generic concept of "named values" that exist within the rendertree
728728
// and are tracked when added, removed, or updated. Currently we don't need that generality, but if we ever do, we

src/Components/Components/test/RenderTreeDiffBuilderTest.cs

Lines changed: 16 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2237,10 +2237,9 @@ public void RecognizesNamedEventBeingAdded()
22372237
Assert.Equal(0, entry.ReferenceFrameIndex);
22382238
Assert.Equal("new element", referenceFrames[entry.ReferenceFrameIndex].ElementName);
22392239
});
2240-
Assert.Collection(batch.AddedNamedEvents.Value.AsEnumerable(),
2241-
entry => AssertNamedEvent(entry, 123, 2, "someevent1", "added to existing element"),
2242-
entry => AssertNamedEvent(entry, 123, 4, "someevent2", "added with new element"));
2243-
Assert.False(batch.RemovedNamedEvents.HasValue);
2240+
Assert.Collection(batch.NamedEventChanges.Value.AsEnumerable(),
2241+
entry => AssertNamedEventChange(entry, NamedEventChangeType.Added, 123, 2, "someevent1", "added to existing element"),
2242+
entry => AssertNamedEventChange(entry, NamedEventChangeType.Added, 123, 4, "someevent2", "added with new element"));
22442243
}
22452244

22462245
[Fact]
@@ -2264,10 +2263,9 @@ public void RecognizesNamedEventBeingRemoved()
22642263
// Assert
22652264
Assert.Collection(result.Edits,
22662265
entry => AssertEdit(entry, RenderTreeEditType.RemoveFrame, 1));
2267-
Assert.False(batch.AddedNamedEvents.HasValue);
2268-
Assert.Collection(batch.RemovedNamedEvents.Value.AsEnumerable(),
2269-
entry => AssertNamedEvent(entry, 123, 2, "someevent1", "removing from retained element"),
2270-
entry => AssertNamedEvent(entry, 123, 4, "someevent2", "removed because element was removed"));
2266+
Assert.Collection(batch.NamedEventChanges.Value.AsEnumerable(),
2267+
entry => AssertNamedEventChange(entry, NamedEventChangeType.Removed, 123, 2, "someevent1", "removing from retained element"),
2268+
entry => AssertNamedEventChange(entry, NamedEventChangeType.Removed, 123, 4, "someevent2", "removed because element was removed"));
22712269
}
22722270

22732271
[Fact]
@@ -2293,10 +2291,9 @@ public void RecognizesNamedEventBeingMoved()
22932291
Assert.Equal(0, entry.ReferenceFrameIndex);
22942292
Assert.Equal("attr1", referenceFrames[entry.ReferenceFrameIndex].AttributeName);
22952293
});
2296-
Assert.Collection(batch.RemovedNamedEvents.Value.AsEnumerable(),
2297-
entry => AssertNamedEvent(entry, 123, 1, "eventname", "assigned name"));
2298-
Assert.Collection(batch.AddedNamedEvents.Value.AsEnumerable(),
2299-
entry => AssertNamedEvent(entry, 123, 2, "eventname", "assigned name"));
2294+
Assert.Collection(batch.NamedEventChanges.Value.AsEnumerable(),
2295+
entry => AssertNamedEventChange(entry, NamedEventChangeType.Removed, 123, 1, "eventname", "assigned name"),
2296+
entry => AssertNamedEventChange(entry, NamedEventChangeType.Added, 123, 2, "eventname", "assigned name"));
23002297
}
23012298

23022299
[Fact]
@@ -2317,10 +2314,9 @@ public void RecognizesNamedEventChangingAssignedName()
23172314

23182315
// Assert
23192316
Assert.Empty(result.Edits);
2320-
Assert.Collection(batch.RemovedNamedEvents.Value.AsEnumerable(),
2321-
entry => AssertNamedEvent(entry, 123, 1, "eventname1", "original name"));
2322-
Assert.Collection(batch.AddedNamedEvents.Value.AsEnumerable(),
2323-
entry => AssertNamedEvent(entry, 123, 1, "eventname1", "changed name"));
2317+
Assert.Collection(batch.NamedEventChanges.Value.AsEnumerable(),
2318+
entry => AssertNamedEventChange(entry, NamedEventChangeType.Removed, 123, 1, "eventname1", "original name"),
2319+
entry => AssertNamedEventChange(entry, NamedEventChangeType.Added, 123, 1, "eventname1", "changed name"));
23242320
}
23252321

23262322
private (RenderTreeDiff, RenderTreeFrame[]) GetSingleUpdatedComponent(bool initializeFromFrames = false)
@@ -2486,13 +2482,15 @@ private static void AssertPermutationListEntry(
24862482
Assert.Equal(toSiblingIndex, edit.MoveToSiblingIndex);
24872483
}
24882484

2489-
private static void AssertNamedEvent(
2490-
NamedEvent namedEvent,
2485+
private static void AssertNamedEventChange(
2486+
NamedEventChange namedEvent,
2487+
NamedEventChangeType type,
24912488
int componentId,
24922489
int frameIndex,
24932490
string eventType,
24942491
string assignedName)
24952492
{
2493+
Assert.Equal(type, namedEvent.ChangeType);
24962494
Assert.Equal(componentId, namedEvent.ComponentId);
24972495
Assert.Equal(frameIndex, namedEvent.FrameIndex);
24982496
Assert.Equal(eventType, namedEvent.EventType);

src/Components/Components/test/Rendering/RenderTreeBuilderTest.cs

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2270,6 +2270,21 @@ public void CannotAddNamedEventWithNullAssignedName()
22702270
Assert.Equal("assignedName", ex.ParamName);
22712271
}
22722272

2273+
[Fact]
2274+
public void CannotAddNamedEventWithEmptyAssignedName()
2275+
{
2276+
// Arrange
2277+
var builder = new RenderTreeBuilder();
2278+
builder.OpenElement(0, "elem");
2279+
2280+
// Act/Assert
2281+
var ex = Assert.Throws<ArgumentException>(() =>
2282+
{
2283+
builder.AddNamedEvent(1, "eventtype", "");
2284+
});
2285+
Assert.Equal("assignedName", ex.ParamName);
2286+
}
2287+
22732288
[Fact]
22742289
public void CannotAddAttributesAfterNamedEvent()
22752290
{
@@ -2287,9 +2302,9 @@ public void CannotAddAttributesAfterNamedEvent()
22872302
}
22882303

22892304
[Fact]
2290-
public void TemporaryApiForNamedSubmitEventsWorksEvenIfAttributesAddedAfter()
2305+
public void TemporaryApiForFormNameEventsWorksEvenIfAttributesAddedAfter()
22912306
{
2292-
// TODO: Remove this once the Razor compiler is updated to support @onsubmit:name directly
2307+
// TODO: Remove this once the Razor compiler is updated to support @formname directly
22932308

22942309
// Arrange
22952310
var builder = new RenderTreeBuilder();
@@ -2298,7 +2313,7 @@ public void TemporaryApiForNamedSubmitEventsWorksEvenIfAttributesAddedAfter()
22982313
// Act
22992314
builder.OpenElement(0, "div");
23002315
builder.AddAttribute(1, "attr1", 123);
2301-
builder.AddAttribute(2, "@onsubmit:name", "some custom name");
2316+
builder.AddAttribute(2, "@formname", "some custom name");
23022317
builder.AddAttribute(3, "attr2", 456);
23032318
builder.OpenElement(4, "other");
23042319
builder.CloseElement();

0 commit comments

Comments
 (0)