| File: Rendering\RenderTreeUpdater.cs | Web Access |
| Project: src\aspnetcore\src\Components\Components\src\Microsoft.AspNetCore.Components.csproj (Microsoft.AspNetCore.Components) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using Microsoft.AspNetCore.Components.RenderTree; namespace Microsoft.AspNetCore.Components.Rendering; internal sealed class RenderTreeUpdater { public static void UpdateToMatchClientState(RenderTreeBuilder renderTreeBuilder, ulong eventHandlerId, object newFieldValue) { // We only allow the client to supply string or bool currently, since those are the only kinds of // values we output on attributes that go to the client if (newFieldValue is bool boolValue) { if (OperatingSystem.IsBrowser()) { newFieldValue = boolValue ? "" : null!; } } else if (!(newFieldValue is string)) { return; } // Find the element that contains the event handler var frames = renderTreeBuilder.GetFrames(); var framesArray = frames.Array; var framesLength = frames.Count; var closestElementFrameIndex = -1; for (var frameIndex = 0; frameIndex < framesLength; frameIndex++) { ref var frame = ref framesArray[frameIndex]; switch (frame.FrameTypeField) { case RenderTreeFrameType.Element: closestElementFrameIndex = frameIndex; break; case RenderTreeFrameType.Attribute: if (frame.AttributeEventHandlerIdField == eventHandlerId) { if (!string.IsNullOrEmpty(frame.AttributeEventUpdatesAttributeNameField)) { UpdateFrameToMatchClientState( renderTreeBuilder, framesArray, closestElementFrameIndex, frame.AttributeEventUpdatesAttributeNameField, newFieldValue); } // Whether or not we did update the frame, that was the one that matches // the event handler ID, so no need to look any further return; } break; } } } private static void UpdateFrameToMatchClientState(RenderTreeBuilder renderTreeBuilder, RenderTreeFrame[] framesArray, int elementFrameIndex, string attributeName, object attributeValue) { // Find the attribute frame ref var elementFrame = ref framesArray[elementFrameIndex]; var elementSubtreeEndIndexExcl = elementFrameIndex + elementFrame.ElementSubtreeLengthField; for (var attributeFrameIndex = elementFrameIndex + 1; attributeFrameIndex < elementSubtreeEndIndexExcl; attributeFrameIndex++) { ref var attributeFrame = ref framesArray[attributeFrameIndex]; if (attributeFrame.FrameTypeField != RenderTreeFrameType.Attribute) { // We're now looking at the descendants not attributes, so the search is over break; } if (attributeFrame.AttributeNameField == attributeName) { // Found an existing attribute we can update attributeFrame.AttributeValueField = attributeValue; return; } } // If we get here, we didn't find the desired attribute, so we have to insert a new frame for it var insertAtIndex = elementFrameIndex + 1; var didInsertFrame = renderTreeBuilder.InsertAttributeExpensive(insertAtIndex, RenderTreeDiffBuilder.SystemAddedAttributeSequenceNumber, attributeName, attributeValue); if (!didInsertFrame) { // The builder decided to omit the new frame, e.g., because it's a false-valued bool // In this case there's nothing else to update return; } framesArray = renderTreeBuilder.GetFrames().Array; // Refresh in case it mutated due to the expansion // Update subtree length for this and all ancestor containers // Ancestors can only be regions or other elements, since components can't "contain" elements inline // We only have to walk backwards, since later entries in the frames array can't contain an earlier one for (var otherFrameIndex = elementFrameIndex; otherFrameIndex >= 0; otherFrameIndex--) { ref var otherFrame = ref framesArray[otherFrameIndex]; switch (otherFrame.FrameTypeField) { case RenderTreeFrameType.Element: { var otherFrameSubtreeLength = otherFrame.ElementSubtreeLengthField; var otherFrameEndIndexExcl = otherFrameIndex + otherFrameSubtreeLength; if (otherFrameEndIndexExcl > elementFrameIndex) // i.e., contains the element we're inserting into { otherFrame.ElementSubtreeLengthField = otherFrameSubtreeLength + 1; } break; } case RenderTreeFrameType.Region: { var otherFrameSubtreeLength = otherFrame.RegionSubtreeLengthField; var otherFrameEndIndexExcl = otherFrameIndex + otherFrameSubtreeLength; if (otherFrameEndIndexExcl > elementFrameIndex) // i.e., contains the element we're inserting into { otherFrame.RegionSubtreeLengthField = otherFrameSubtreeLength + 1; } break; } } } } }