diff --git a/samples/HostedInAspNet.Client/Program.cs b/samples/HostedInAspNet.Client/Program.cs index c5ae7fe9b2..f1e4227acc 100644 --- a/samples/HostedInAspNet.Client/Program.cs +++ b/samples/HostedInAspNet.Client/Program.cs @@ -22,18 +22,18 @@ namespace HostedInAspNet.Client { public void BuildRenderTree(RenderTreeBuilder builder) { - builder.OpenElement("h1"); - builder.AddText("Hello from RenderTree"); + builder.OpenElement(0, "h1"); + builder.AddText(1, "Hello from RenderTree"); builder.CloseElement(); - builder.OpenElement("ul"); + builder.OpenElement(2, "ul"); - builder.OpenElement("li"); - builder.AddText("First item"); + builder.OpenElement(3, "li"); + builder.AddText(4, "First item"); builder.CloseElement(); - builder.OpenElement("li"); - builder.AddText("Second item"); + builder.OpenElement(5, "li"); + builder.AddText(6, "Second item"); builder.CloseElement(); builder.CloseElement(); diff --git a/src/Microsoft.Blazor.Browser.JS/src/Rendering/RenderTreeNode.ts b/src/Microsoft.Blazor.Browser.JS/src/Rendering/RenderTreeNode.ts index 36f227edac..8078d31835 100644 --- a/src/Microsoft.Blazor.Browser.JS/src/Rendering/RenderTreeNode.ts +++ b/src/Microsoft.Blazor.Browser.JS/src/Rendering/RenderTreeNode.ts @@ -1,6 +1,6 @@ import { System_String, System_Array, Pointer } from '../Platform/Platform'; import { platform } from '../Environment'; -const renderTreeNodeStructLength = 40; +const renderTreeNodeStructLength = 44; // To minimise GC pressure, instead of instantiating a JS object to represent each tree node, // we work in terms of pointers to the structs on the .NET heap, and use static functions that @@ -12,15 +12,15 @@ export function getTreeNodePtr(renderTreeEntries: System_Array, index: number): export const renderTreeNode = { // The properties and memory layout must be kept in sync with the .NET equivalent in RenderTreeNode.cs - nodeType: (node: RenderTreeNodePointer) => platform.readInt32Field(node, 0) as NodeType, - elementName: (node: RenderTreeNodePointer) => platform.readStringField(node, 4), - descendantsEndIndex: (node: RenderTreeNodePointer) => platform.readInt32Field(node, 8) as NodeType, - textContent: (node: RenderTreeNodePointer) => platform.readStringField(node, 12), - attributeName: (node: RenderTreeNodePointer) => platform.readStringField(node, 16), - attributeValue: (node: RenderTreeNodePointer) => platform.readStringField(node, 20), - attributeEventHandlerValue: (node: RenderTreeNodePointer) => platform.readObjectField(node, 24), - componentId: (node: RenderTreeNodePointer) => platform.readInt32Field(node, 32), - component: (node: RenderTreeNodePointer) => platform.readObjectField(node, 36), + nodeType: (node: RenderTreeNodePointer) => platform.readInt32Field(node, 4) as NodeType, + elementName: (node: RenderTreeNodePointer) => platform.readStringField(node, 8), + descendantsEndIndex: (node: RenderTreeNodePointer) => platform.readInt32Field(node, 12) as NodeType, + textContent: (node: RenderTreeNodePointer) => platform.readStringField(node, 16), + attributeName: (node: RenderTreeNodePointer) => platform.readStringField(node, 20), + attributeValue: (node: RenderTreeNodePointer) => platform.readStringField(node, 24), + attributeEventHandlerValue: (node: RenderTreeNodePointer) => platform.readObjectField(node, 28), + componentId: (node: RenderTreeNodePointer) => platform.readInt32Field(node, 36), + component: (node: RenderTreeNodePointer) => platform.readObjectField(node, 40), }; export enum NodeType { diff --git a/src/Microsoft.Blazor.Build/Core/RazorCompilation/Engine/BlazorIntermediateNodeWriter.cs b/src/Microsoft.Blazor.Build/Core/RazorCompilation/Engine/BlazorIntermediateNodeWriter.cs index cd7e0135eb..57768306a7 100644 --- a/src/Microsoft.Blazor.Build/Core/RazorCompilation/Engine/BlazorIntermediateNodeWriter.cs +++ b/src/Microsoft.Blazor.Build/Core/RazorCompilation/Engine/BlazorIntermediateNodeWriter.cs @@ -32,6 +32,7 @@ namespace Microsoft.Blazor.Build.Core.RazorCompilation.Engine private IList _currentAttributeValues; private IDictionary _currentElementAttributes = new Dictionary(); private IList _currentElementAttributeTokens = new List(); + private int _sourceSequence = 0; public override void BeginWriterScope(CodeRenderingContext context, string writer) { @@ -106,7 +107,9 @@ namespace Microsoft.Blazor.Build.Core.RazorCompilation.Engine // Since we're not in the middle of writing an element, this must evaluate as some // text to display context.CodeWriter - .WriteStartMethodInvocation($"{builderVarName}.{nameof(RenderTreeBuilder.AddText)}"); + .WriteStartMethodInvocation($"{builderVarName}.{nameof(RenderTreeBuilder.AddText)}") + .Write((_sourceSequence++).ToString()) + .WriteParameterSeparator(); for (var i = 0; i < node.Children.Count; i++) { @@ -187,6 +190,8 @@ namespace Microsoft.Blazor.Build.Core.RazorCompilation.Engine // Text node codeWriter .WriteStartMethodInvocation($"{builderVarName}.{nameof(RenderTreeBuilder.AddText)}") + .Write((_sourceSequence++).ToString()) + .WriteParameterSeparator() .WriteStringLiteral(nextToken.Data) .WriteEndMethodInvocation(); break; @@ -200,6 +205,8 @@ namespace Microsoft.Blazor.Build.Core.RazorCompilation.Engine { codeWriter .WriteStartMethodInvocation($"{builderVarName}.{nameof(RenderTreeBuilder.OpenElement)}") + .Write((_sourceSequence++).ToString()) + .WriteParameterSeparator() .WriteStringLiteral(nextTag.Data) .WriteEndMethodInvocation(); } diff --git a/src/Microsoft.Blazor/RenderTree/RenderTreeBuilder.cs b/src/Microsoft.Blazor/RenderTree/RenderTreeBuilder.cs index 15f8a3098d..adb314d904 100644 --- a/src/Microsoft.Blazor/RenderTree/RenderTreeBuilder.cs +++ b/src/Microsoft.Blazor/RenderTree/RenderTreeBuilder.cs @@ -35,11 +35,12 @@ namespace Microsoft.Blazor.RenderTree /// also call immediately after appending the /// new element's child nodes. /// + /// An integer that represents the position of the instruction in the source code. /// A value representing the type of the element. - public void OpenElement(string elementName) + public void OpenElement(int sequence, string elementName) { _openElementIndices.Push(_entriesInUse); - Append(RenderTreeNode.Element(elementName)); + Append(RenderTreeNode.Element(sequence, elementName)); } /// @@ -55,16 +56,18 @@ namespace Microsoft.Blazor.RenderTree /// /// Appends a node representing text content. /// + /// An integer that represents the position of the instruction in the source code. /// Content for the new text node. - public void AddText(string textContent) - => Append(RenderTreeNode.Text(textContent)); + public void AddText(int sequence, string textContent) + => Append(RenderTreeNode.Text(sequence, textContent)); /// /// Appends a node representing text content. /// + /// An integer that represents the position of the instruction in the source code. /// Content for the new text node. - public void AddText(object textContent) - => AddText(textContent?.ToString()); + public void AddText(int sequence, object textContent) + => AddText(sequence, textContent?.ToString()); /// /// Appends a node representing a string-valued attribute. @@ -123,8 +126,9 @@ namespace Microsoft.Blazor.RenderTree /// Appends a node representing a child component. /// /// The type of the child component. - public void AddComponent() where TComponent: IComponent - => Append(RenderTreeNode.ChildComponent()); + /// An integer that represents the position of the instruction in the source code. + public void AddComponent(int sequence) where TComponent: IComponent + => Append(RenderTreeNode.ChildComponent(sequence)); private void AssertCanAddAttribute() { diff --git a/src/Microsoft.Blazor/RenderTree/RenderTreeNode.cs b/src/Microsoft.Blazor/RenderTree/RenderTreeNode.cs index a91865af43..0d0cf04230 100644 --- a/src/Microsoft.Blazor/RenderTree/RenderTreeNode.cs +++ b/src/Microsoft.Blazor/RenderTree/RenderTreeNode.cs @@ -15,6 +15,15 @@ namespace Microsoft.Blazor.RenderTree /// public struct RenderTreeNode { + /// + /// If the property equals , + /// , or , + /// gets the sequence number of the node. Sequence numbers indicate the relative source + /// positions of the instructions that inserted the nodes. Sequence numbers are only + /// comparable within the same sequence (typically, the same source method). + /// + public int Sequence { get; private set; } + /// /// Describes the type of this node. /// @@ -75,14 +84,16 @@ namespace Microsoft.Blazor.RenderTree /// public IComponent Component { get; private set; } - internal static RenderTreeNode Element(string elementName) => new RenderTreeNode + internal static RenderTreeNode Element(int sequence, string elementName) => new RenderTreeNode { + Sequence = sequence, NodeType = RenderTreeNodeType.Element, ElementName = elementName, }; - internal static RenderTreeNode Text(string textContent) => new RenderTreeNode + internal static RenderTreeNode Text(int sequence, string textContent) => new RenderTreeNode { + Sequence = sequence, NodeType = RenderTreeNodeType.Text, TextContent = textContent ?? string.Empty, }; @@ -101,8 +112,9 @@ namespace Microsoft.Blazor.RenderTree AttributeEventHandlerValue = value }; - internal static RenderTreeNode ChildComponent() where T: IComponent => new RenderTreeNode + internal static RenderTreeNode ChildComponent(int sequence) where T: IComponent => new RenderTreeNode { + Sequence = sequence, NodeType = RenderTreeNodeType.Component, ComponentType = typeof(T) }; diff --git a/test/Microsoft.Blazor.Test/RenderTreeBuilderTest.cs b/test/Microsoft.Blazor.Test/RenderTreeBuilderTest.cs index 1cc0228812..70d41b4b73 100644 --- a/test/Microsoft.Blazor.Test/RenderTreeBuilderTest.cs +++ b/test/Microsoft.Blazor.Test/RenderTreeBuilderTest.cs @@ -43,9 +43,9 @@ namespace Microsoft.Blazor.Test var nullString = (string)null; // Act - builder.AddText("First item"); - builder.AddText(nullString); - builder.AddText("Second item"); + builder.AddText(0, "First item"); + builder.AddText(0, nullString); + builder.AddText(0, "Second item"); // Assert var nodes = builder.GetNodes(); @@ -64,8 +64,8 @@ namespace Microsoft.Blazor.Test var nullObject = (object)null; // Act - builder.AddText(1234); - builder.AddText(nullObject); + builder.AddText(0, 1234); + builder.AddText(0, nullObject); // Assert var nodes = builder.GetNodes(); @@ -82,7 +82,7 @@ namespace Microsoft.Blazor.Test var builder = new RenderTreeBuilder(new TestRenderer()); // Act - builder.OpenElement("my element"); + builder.OpenElement(0, "my element"); // Assert var node = builder.GetNodes().Single(); @@ -96,8 +96,8 @@ namespace Microsoft.Blazor.Test var builder = new RenderTreeBuilder(new TestRenderer()); // Act - builder.AddText("some node so that the element isn't at position zero"); - builder.OpenElement("my element"); + builder.AddText(0, "some node so that the element isn't at position zero"); + builder.OpenElement(0, "my element"); builder.CloseElement(); // Assert @@ -113,11 +113,11 @@ namespace Microsoft.Blazor.Test var builder = new RenderTreeBuilder(new TestRenderer()); // Act - builder.OpenElement("my element"); - builder.AddText("child 1"); - builder.AddText("child 2"); + builder.OpenElement(0, "my element"); + builder.AddText(0, "child 1"); + builder.AddText(0, "child 2"); builder.CloseElement(); - builder.AddText("unrelated item"); + builder.AddText(0, "unrelated item"); // Assert var nodes = builder.GetNodes(); @@ -132,22 +132,22 @@ namespace Microsoft.Blazor.Test var builder = new RenderTreeBuilder(new TestRenderer()); // Act - builder.AddText("standalone text 1"); // 0: standalone text 1 - builder.OpenElement("root"); // 1: - builder.AddText("root text 1"); // 2: root text 1 - builder.AddText("root text 2"); // 3: root text 2 - builder.OpenElement("child"); // 4: - builder.AddText("child text"); // 5: child text - builder.OpenElement("grandchild"); // 6: - builder.AddText("grandchild text 1"); // 7: grandchild text 1 - builder.AddText("grandchild text 2"); // 8: grandchild text 2 - builder.CloseElement(); // - builder.CloseElement(); // - builder.AddText("root text 3"); // 9: root text 3 - builder.OpenElement("child 2"); // 10: - builder.CloseElement(); // - builder.CloseElement(); // - builder.AddText("standalone text 2"); // 11: standalone text 2 + builder.AddText(0, "standalone text 1"); // 0: standalone text 1 + builder.OpenElement(0, "root"); // 1: + builder.AddText(0, "root text 1"); // 2: root text 1 + builder.AddText(0, "root text 2"); // 3: root text 2 + builder.OpenElement(0, "child"); // 4: + builder.AddText(0, "child text"); // 5: child text + builder.OpenElement(0, "grandchild"); // 6: + builder.AddText(0, "grandchild text 1"); // 7: grandchild text 1 + builder.AddText(0, "grandchild text 2"); // 8: grandchild text 2 + builder.CloseElement(); // + builder.CloseElement(); // + builder.AddText(0, "root text 3"); // 9: root text 3 + builder.OpenElement(0, "child 2"); // 10: + builder.CloseElement(); // + builder.CloseElement(); // + builder.AddText(0, "standalone text 2"); // 11: standalone text 2 // Assert Assert.Collection(builder.GetNodes(), @@ -173,12 +173,12 @@ namespace Microsoft.Blazor.Test UIEventHandler eventHandler = eventInfo => { }; // Act - builder.OpenElement("myelement"); // 0: - builder.OpenElement("child"); // 3: - builder.AddText("some text"); // 5: some text + builder.AddText(0, "some text"); // 5: some text builder.CloseElement(); // builder.CloseElement(); // @@ -227,8 +227,8 @@ namespace Microsoft.Blazor.Test // Act/Assert Assert.Throws(() => { - builder.OpenElement("some element"); - builder.AddText("hello"); + builder.OpenElement(0, "some element"); + builder.AddText(1, "hello"); builder.AddAttribute("name", "value"); }); } @@ -242,8 +242,8 @@ namespace Microsoft.Blazor.Test // Act/Assert Assert.Throws(() => { - builder.OpenElement("some element"); - builder.AddText("hello"); + builder.OpenElement(0, "some element"); + builder.AddText(1, "hello"); builder.AddAttribute("name", eventInfo => { }); }); } @@ -255,11 +255,11 @@ namespace Microsoft.Blazor.Test var builder = new RenderTreeBuilder(new TestRenderer()); // Act - builder.OpenElement("parent"); // 0: - builder.AddComponent(); // 1: + builder.AddComponent(1); // 1: - builder.AddComponent(); // 4: (2); // 4: builder.CloseElement(); // @@ -280,9 +280,9 @@ namespace Microsoft.Blazor.Test var builder = new RenderTreeBuilder(new TestRenderer()); // Act - builder.AddText("some text"); - builder.OpenElement("elem"); - builder.AddText("more text"); + builder.AddText(0, "some text"); + builder.OpenElement(1, "elem"); + builder.AddText(2, "more text"); builder.CloseElement(); builder.Clear(); diff --git a/test/Microsoft.Blazor.Test/RendererTest.cs b/test/Microsoft.Blazor.Test/RendererTest.cs index 4faf92a5bb..0fd27ff48f 100644 --- a/test/Microsoft.Blazor.Test/RendererTest.cs +++ b/test/Microsoft.Blazor.Test/RendererTest.cs @@ -21,8 +21,8 @@ namespace Microsoft.Blazor.Test var renderer = new TestRenderer(); var component = new TestComponent(builder => { - builder.OpenElement("my element"); - builder.AddText("some text"); + builder.OpenElement(0, "my element"); + builder.AddText(1, "some text"); builder.CloseElement(); }); @@ -43,8 +43,8 @@ namespace Microsoft.Blazor.Test var renderer = new TestRenderer(); var component = new TestComponent(builder => { - builder.AddText("Hello"); - builder.AddComponent(); + builder.AddText(0, "Hello"); + builder.AddComponent(1); }); // Act/Assert @@ -94,7 +94,7 @@ namespace Microsoft.Blazor.Test var renderer = new TestRenderer(); var parentComponent = new TestComponent(builder => { - builder.AddComponent(); + builder.AddComponent(0); }); var parentComponentId = renderer.AssignComponentId(parentComponent); renderer.RenderComponent(parentComponentId); @@ -152,7 +152,7 @@ namespace Microsoft.Blazor.Test var renderer = new TestRenderer(); var parentComponent = new TestComponent(builder => { - builder.AddComponent(); + builder.AddComponent(0); }); var parentComponentId = renderer.AssignComponentId(parentComponent); renderer.RenderComponent(parentComponentId); @@ -350,7 +350,7 @@ namespace Microsoft.Blazor.Test public void BuildRenderTree(RenderTreeBuilder builder) { - builder.AddText(Message); + builder.AddText(0, Message); } } @@ -360,7 +360,7 @@ namespace Microsoft.Blazor.Test public void BuildRenderTree(RenderTreeBuilder builder) { - builder.OpenElement("some element"); + builder.OpenElement(0, "some element"); builder.AddAttribute("some event", Handler); builder.CloseElement(); } diff --git a/test/testapps/BasicTestApp/ParentChildComponent.cs b/test/testapps/BasicTestApp/ParentChildComponent.cs index 5da38a4f30..58220f7890 100644 --- a/test/testapps/BasicTestApp/ParentChildComponent.cs +++ b/test/testapps/BasicTestApp/ParentChildComponent.cs @@ -10,11 +10,11 @@ namespace BasicTestApp { public void BuildRenderTree(RenderTreeBuilder builder) { - builder.OpenElement("fieldset"); - builder.OpenElement("legend"); - builder.AddText("Parent component"); + builder.OpenElement(0, "fieldset"); + builder.OpenElement(1, "legend"); + builder.AddText(2, "Parent component"); builder.CloseElement(); - builder.AddComponent(); + builder.AddComponent(3); builder.CloseElement(); } @@ -22,7 +22,7 @@ namespace BasicTestApp { public void BuildRenderTree(RenderTreeBuilder builder) { - builder.AddText("Child component"); + builder.AddText(0, "Child component"); } } }