Browse Source

Store event handler IDs as longs. Fixes #12058 (#12305)

* Change event handler IDs to be longs

* Update unit tests

* Enable detailed errors for test app

* CR: Explicitly check if we exceed Number.MAX_SAFE_INTEGER

* Update ref assemblies
Steve Sanderson 6 years ago
parent
commit
4e04b81415
27 changed files with 177 additions and 116 deletions
  1. 1 1
      src/Components/Blazor/Blazor/ref/Microsoft.AspNetCore.Blazor.netstandard2.0.cs
  2. 3 3
      src/Components/Blazor/Blazor/src/Rendering/WebAssemblyRenderer.cs
  3. 2 2
      src/Components/Components/ref/Microsoft.AspNetCore.Components.netstandard2.0.cs
  4. 4 4
      src/Components/Components/src/RenderTree/RenderTreeFrame.cs
  5. 3 3
      src/Components/Components/src/Rendering/RenderBatch.cs
  6. 1 1
      src/Components/Components/src/Rendering/RenderBatchBuilder.cs
  7. 1 1
      src/Components/Components/src/Rendering/RenderTreeUpdater.cs
  8. 3 3
      src/Components/Components/src/Rendering/Renderer.Log.cs
  9. 9 9
      src/Components/Components/src/Rendering/Renderer.cs
  10. 3 3
      src/Components/Components/test/RenderTreeDiffBuilderTest.cs
  11. 2 2
      src/Components/Components/test/RendererTest.cs
  12. 26 12
      src/Components/Server/src/Circuits/RenderBatchWriter.cs
  13. 25 21
      src/Components/Server/test/Circuits/RenderBatchWriterTest.cs
  14. 2 2
      src/Components/Shared/test/TestRenderer.cs
  15. 0 0
      src/Components/Web.JS/dist/Release/blazor.server.js
  16. 0 0
      src/Components/Web.JS/dist/Release/blazor.webassembly.js
  17. 15 0
      src/Components/Web.JS/src/Platform/Mono/MonoPlatform.ts
  18. 1 0
      src/Components/Web.JS/src/Platform/Platform.ts
  19. 24 4
      src/Components/Web.JS/src/Rendering/RenderBatch/OutOfProcessRenderBatch.ts
  20. 3 3
      src/Components/Web.JS/src/Rendering/RenderBatch/SharedMemoryRenderBatch.ts
  21. 1 1
      src/Components/Web/ref/Microsoft.AspNetCore.Components.Web.netstandard2.0.cs
  22. 1 1
      src/Components/Web/src/RendererRegistryEventDispatcher.cs
  23. 21 17
      src/Components/test/Ignitor.Test/RenderBatchReaderTest.cs
  24. 4 1
      src/Components/test/testassets/ComponentsApp.Server/Startup.cs
  25. 1 1
      src/Components/test/testassets/Ignitor/ElementHive.cs
  26. 2 2
      src/Components/test/testassets/Ignitor/ElementNode.cs
  27. 19 19
      src/Components/test/testassets/Ignitor/RenderBatchReader.cs

+ 1 - 1
src/Components/Blazor/Blazor/ref/Microsoft.AspNetCore.Blazor.netstandard2.0.cs

@@ -62,7 +62,7 @@ namespace Microsoft.AspNetCore.Blazor.Rendering
         public override Microsoft.AspNetCore.Components.Dispatcher Dispatcher { get { throw null; } }
         public System.Threading.Tasks.Task AddComponentAsync(System.Type componentType, string domElementSelector) { throw null; }
         public System.Threading.Tasks.Task AddComponentAsync<TComponent>(string domElementSelector) where TComponent : Microsoft.AspNetCore.Components.IComponent { throw null; }
-        public override System.Threading.Tasks.Task DispatchEventAsync(int eventHandlerId, Microsoft.AspNetCore.Components.Rendering.EventFieldInfo eventFieldInfo, Microsoft.AspNetCore.Components.UIEventArgs eventArgs) { throw null; }
+        public override System.Threading.Tasks.Task DispatchEventAsync(ulong eventHandlerId, Microsoft.AspNetCore.Components.Rendering.EventFieldInfo eventFieldInfo, Microsoft.AspNetCore.Components.UIEventArgs eventArgs) { throw null; }
         protected override void Dispose(bool disposing) { }
         protected override void HandleException(System.Exception exception) { }
         protected override System.Threading.Tasks.Task UpdateDisplayAsync(in Microsoft.AspNetCore.Components.Rendering.RenderBatch batch) { throw null; }

+ 3 - 3
src/Components/Blazor/Blazor/src/Rendering/WebAssemblyRenderer.cs

@@ -120,7 +120,7 @@ namespace Microsoft.AspNetCore.Blazor.Rendering
         }
 
         /// <inheritdoc />
-        public override Task DispatchEventAsync(int eventHandlerId, EventFieldInfo eventFieldInfo, UIEventArgs eventArgs)
+        public override Task DispatchEventAsync(ulong eventHandlerId, EventFieldInfo eventFieldInfo, UIEventArgs eventArgs)
         {
             // Be sure we only run one event handler at once. Although they couldn't run
             // simultaneously anyway (there's only one thread), they could run nested on
@@ -183,12 +183,12 @@ namespace Microsoft.AspNetCore.Blazor.Rendering
 
         readonly struct IncomingEventInfo
         {
-            public readonly int EventHandlerId;
+            public readonly ulong EventHandlerId;
             public readonly EventFieldInfo EventFieldInfo;
             public readonly UIEventArgs EventArgs;
             public readonly TaskCompletionSource<object> TaskCompletionSource;
 
-            public IncomingEventInfo(int eventHandlerId, EventFieldInfo eventFieldInfo, UIEventArgs eventArgs)
+            public IncomingEventInfo(ulong eventHandlerId, EventFieldInfo eventFieldInfo, UIEventArgs eventArgs)
             {
                 EventHandlerId = eventHandlerId;
                 EventFieldInfo = eventFieldInfo;

+ 2 - 2
src/Components/Components/ref/Microsoft.AspNetCore.Components.netstandard2.0.cs

@@ -724,7 +724,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
     {
         private readonly object _dummy;
         public Microsoft.AspNetCore.Components.RenderTree.ArrayRange<int> DisposedComponentIDs { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
-        public Microsoft.AspNetCore.Components.RenderTree.ArrayRange<int> DisposedEventHandlerIDs { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
+        public Microsoft.AspNetCore.Components.RenderTree.ArrayRange<ulong> DisposedEventHandlerIDs { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
         public Microsoft.AspNetCore.Components.RenderTree.ArrayRange<Microsoft.AspNetCore.Components.RenderTree.RenderTreeFrame> ReferenceFrames { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
         public Microsoft.AspNetCore.Components.RenderTree.ArrayRange<Microsoft.AspNetCore.Components.RenderTree.RenderTreeDiff> UpdatedComponents { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
     }
@@ -735,7 +735,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
         public event System.UnhandledExceptionEventHandler UnhandledSynchronizationException { add { } remove { } }
         protected internal virtual void AddToRenderQueue(int componentId, Microsoft.AspNetCore.Components.RenderFragment renderFragment) { }
         protected internal int AssignRootComponentId(Microsoft.AspNetCore.Components.IComponent component) { throw null; }
-        public virtual System.Threading.Tasks.Task DispatchEventAsync(int eventHandlerId, Microsoft.AspNetCore.Components.Rendering.EventFieldInfo fieldInfo, Microsoft.AspNetCore.Components.UIEventArgs eventArgs) { throw null; }
+        public virtual System.Threading.Tasks.Task DispatchEventAsync(ulong eventHandlerId, Microsoft.AspNetCore.Components.Rendering.EventFieldInfo fieldInfo, Microsoft.AspNetCore.Components.UIEventArgs eventArgs) { throw null; }
         public void Dispose() { }
         protected virtual void Dispose(bool disposing) { }
         protected abstract void HandleException(System.Exception exception);

+ 4 - 4
src/Components/Components/src/RenderTree/RenderTreeFrame.cs

@@ -10,7 +10,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree
     /// <summary>
     /// Represents an entry in a tree of user interface (UI) items.
     /// </summary>
-    [StructLayout(LayoutKind.Explicit)]
+    [StructLayout(LayoutKind.Explicit, Pack = 4)]
     public readonly struct RenderTreeFrame
     {
         // Note that the struct layout has to be valid in both 32-bit and 64-bit runtime platforms,
@@ -90,7 +90,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree
         /// If the <see cref="FrameType"/> property equals <see cref="RenderTreeFrameType.Attribute"/>
         /// gets the ID of the corresponding event handler, if any.
         /// </summary>
-        [FieldOffset(8)] public readonly int AttributeEventHandlerId;
+        [FieldOffset(8)] public readonly ulong AttributeEventHandlerId;
 
         /// <summary>
         /// If the <see cref="FrameType"/> property equals <see cref="RenderTreeFrameType.Attribute"/>,
@@ -267,7 +267,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree
         }
 
         // Attribute constructor
-        private RenderTreeFrame(int sequence, string attributeName, object attributeValue, int attributeEventHandlerId, string attributeEventUpdatesAttributeName)
+        private RenderTreeFrame(int sequence, string attributeName, object attributeValue, ulong attributeEventHandlerId, string attributeEventUpdatesAttributeName)
             : this()
         {
             FrameType = RenderTreeFrameType.Attribute;
@@ -337,7 +337,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree
         internal RenderTreeFrame WithComponent(ComponentState componentState)
             => new RenderTreeFrame(Sequence, componentSubtreeLength: ComponentSubtreeLength, ComponentType, componentState, ComponentKey);
 
-        internal RenderTreeFrame WithAttributeEventHandlerId(int eventHandlerId)
+        internal RenderTreeFrame WithAttributeEventHandlerId(ulong eventHandlerId)
             => new RenderTreeFrame(Sequence, attributeName: AttributeName, AttributeValue, eventHandlerId, AttributeEventUpdatesAttributeName);
 
         internal RenderTreeFrame WithAttributeValue(object attributeValue)

+ 3 - 3
src/Components/Components/src/Rendering/RenderBatch.cs

@@ -1,4 +1,4 @@
-// Copyright (c) .NET Foundation. All rights reserved.
+// Copyright (c) .NET Foundation. All rights reserved.
 // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
 
 using Microsoft.AspNetCore.Components.RenderTree;
@@ -30,13 +30,13 @@ namespace Microsoft.AspNetCore.Components.Rendering
         /// <summary>
         /// Gets the IDs of the event handlers that were disposed.
         /// </summary>
-        public ArrayRange<int> DisposedEventHandlerIDs { get; }
+        public ArrayRange<ulong> DisposedEventHandlerIDs { get; }
 
         internal RenderBatch(
             ArrayRange<RenderTreeDiff> updatedComponents,
             ArrayRange<RenderTreeFrame> referenceFrames,
             ArrayRange<int> disposedComponentIDs,
-            ArrayRange<int> disposedEventHandlerIDs)
+            ArrayRange<ulong> disposedEventHandlerIDs)
         {
             UpdatedComponents = updatedComponents;
             ReferenceFrames = referenceFrames;

+ 1 - 1
src/Components/Components/src/Rendering/RenderBatchBuilder.cs

@@ -18,7 +18,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
         // Primary result data
         public ArrayBuilder<RenderTreeDiff> UpdatedComponentDiffs { get; } = new ArrayBuilder<RenderTreeDiff>();
         public ArrayBuilder<int> DisposedComponentIds { get; } = new ArrayBuilder<int>();
-        public ArrayBuilder<int> DisposedEventHandlerIds { get; } = new ArrayBuilder<int>();
+        public ArrayBuilder<ulong> DisposedEventHandlerIds { get; } = new ArrayBuilder<ulong>();
 
         // Buffers referenced by UpdatedComponentDiffs
         public ArrayBuilder<RenderTreeEdit> EditsBuffer { get; } = new ArrayBuilder<RenderTreeEdit>(64);

+ 1 - 1
src/Components/Components/src/Rendering/RenderTreeUpdater.cs

@@ -7,7 +7,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
 {
     internal class RenderTreeUpdater
     {
-        public static void UpdateToMatchClientState(RenderTreeBuilder renderTreeBuilder, int eventHandlerId, object newFieldValue)
+        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

+ 3 - 3
src/Components/Components/src/Rendering/Renderer.Log.cs

@@ -22,8 +22,8 @@ namespace Microsoft.AspNetCore.Components.Rendering
             private static readonly Action<ILogger, int, Type, Exception> _disposingComponent =
                 LoggerMessage.Define<int, Type>(LogLevel.Debug, new EventId(4, "DisposingComponent"), "Disposing component {ComponentId} of type {ComponentType}");
 
-            private static readonly Action<ILogger, int, string, Exception> _handlingEvent =
-                LoggerMessage.Define<int, string>(LogLevel.Debug, new EventId(5, "HandlingEvent"), "Handling event {EventId} of type '{EventType}'");
+            private static readonly Action<ILogger, ulong, string, Exception> _handlingEvent =
+                LoggerMessage.Define<ulong, string>(LogLevel.Debug, new EventId(5, "HandlingEvent"), "Handling event {EventId} of type '{EventType}'");
 
             public static void InitializingComponent(ILogger logger, ComponentState componentState, ComponentState parentComponentState)
             {
@@ -56,7 +56,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
                 }
             }
 
-            internal static void HandlingEvent(ILogger<Renderer> logger, int eventHandlerId, UIEventArgs eventArgs)
+            internal static void HandlingEvent(ILogger<Renderer> logger, ulong eventHandlerId, UIEventArgs eventArgs)
             {
                 _handlingEvent(logger, eventHandlerId, eventArgs?.Type ?? "null", null);
             }

+ 9 - 9
src/Components/Components/src/Rendering/Renderer.cs

@@ -20,13 +20,13 @@ namespace Microsoft.AspNetCore.Components.Rendering
         private readonly IServiceProvider _serviceProvider;
         private readonly Dictionary<int, ComponentState> _componentStateById = new Dictionary<int, ComponentState>();
         private readonly RenderBatchBuilder _batchBuilder = new RenderBatchBuilder();
-        private readonly Dictionary<int, EventCallback> _eventBindings = new Dictionary<int, EventCallback>();
-        private readonly Dictionary<int, int> _eventHandlerIdReplacements = new Dictionary<int, int>();
+        private readonly Dictionary<ulong, EventCallback> _eventBindings = new Dictionary<ulong, EventCallback>();
+        private readonly Dictionary<ulong, ulong> _eventHandlerIdReplacements = new Dictionary<ulong, ulong>();
         private readonly ILogger<Renderer> _logger;
 
         private int _nextComponentId = 0; // TODO: change to 'long' when Mono .NET->JS interop supports it
         private bool _isBatchInProgress;
-        private int _lastEventHandlerId = 0;
+        private ulong _lastEventHandlerId;
         private List<Task> _pendingTasks;
 
         /// <summary>
@@ -206,7 +206,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
         /// A <see cref="Task"/> which will complete once all asynchronous processing related to the event
         /// has completed.
         /// </returns>
-        public virtual Task DispatchEventAsync(int eventHandlerId, EventFieldInfo fieldInfo, UIEventArgs eventArgs)
+        public virtual Task DispatchEventAsync(ulong eventHandlerId, EventFieldInfo fieldInfo, UIEventArgs eventArgs)
         {
             EnsureSynchronizationContext();
 
@@ -354,7 +354,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
             }
         }
 
-        internal void TrackReplacedEventHandlerId(int oldEventHandlerId, int newEventHandlerId)
+        internal void TrackReplacedEventHandlerId(ulong oldEventHandlerId, ulong newEventHandlerId)
         {
             // Tracking the chain of old->new replacements allows us to interpret incoming EventFieldInfo
             // values even if they refer to an event handler ID that's since been superseded. This is essential
@@ -362,7 +362,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
             _eventHandlerIdReplacements.Add(oldEventHandlerId, newEventHandlerId);
         }
 
-        private int FindLatestEventHandlerIdInChain(int eventHandlerId)
+        private ulong FindLatestEventHandlerIdInChain(ulong eventHandlerId)
         {
             while (_eventHandlerIdReplacements.TryGetValue(eventHandlerId, out var replacementEventHandlerId))
             {
@@ -573,7 +573,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
             }
         }
 
-        private void RemoveEventHandlerIds(ArrayRange<int> eventHandlerIds, Task afterTaskIgnoreErrors)
+        private void RemoveEventHandlerIds(ArrayRange<ulong> eventHandlerIds, Task afterTaskIgnoreErrors)
         {
             if (eventHandlerIds.Count == 0)
             {
@@ -598,7 +598,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
 
             // Factor out the async part into a separate local method purely so, in the
             // synchronous case, there's no state machine or task construction
-            async Task ContinueAfterTask(ArrayRange<int> eventHandlerIds, Task afterTaskIgnoreErrors)
+            async Task ContinueAfterTask(ArrayRange<ulong> eventHandlerIds, Task afterTaskIgnoreErrors)
             {
                 // We need to delay the actual removal (e.g., until we've confirmed the client
                 // has processed the batch and hence can be sure not to reuse the handler IDs
@@ -637,7 +637,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
             }
         }
 
-        private void UpdateRenderTreeToMatchClientState(int eventHandlerId, EventFieldInfo fieldInfo)
+        private void UpdateRenderTreeToMatchClientState(ulong eventHandlerId, EventFieldInfo fieldInfo)
         {
             var componentState = GetOptionalComponentState(fieldInfo.ComponentId);
             if (componentState != null)

+ 3 - 3
src/Components/Components/test/RenderTreeDiffBuilderTest.cs

@@ -859,7 +859,7 @@ namespace Microsoft.AspNetCore.Components.Test
                     Assert.Equal(0, entry.ReferenceFrameIndex);
                 });
             AssertFrame.Attribute(referenceFrames[0], "onbar", addedHandler);
-            Assert.NotEqual(0, removedEventHandlerFrame.AttributeEventHandlerId);
+            Assert.NotEqual(default, removedEventHandlerFrame.AttributeEventHandlerId);
             Assert.Equal(
                 new[] { removedEventHandlerFrame.AttributeEventHandlerId },
                 batchBuilder.DisposedEventHandlerIDs.AsEnumerable());
@@ -1592,7 +1592,7 @@ namespace Microsoft.AspNetCore.Components.Test
             Assert.Empty(result.Edits);
             AssertFrame.Attribute(oldAttributeFrame, "ontest", retainedHandler);
             AssertFrame.Attribute(newAttributeFrame, "ontest", retainedHandler);
-            Assert.NotEqual(0, oldAttributeFrame.AttributeEventHandlerId);
+            Assert.NotEqual(default, oldAttributeFrame.AttributeEventHandlerId);
             Assert.Equal(oldAttributeFrame.AttributeEventHandlerId, newAttributeFrame.AttributeEventHandlerId);
             Assert.Empty(batchBuilder.DisposedEventHandlerIDs.AsEnumerable());
         }
@@ -1619,7 +1619,7 @@ namespace Microsoft.AspNetCore.Components.Test
             Assert.Single(result.Edits);
             AssertFrame.Attribute(oldAttributeFrame, "ontest", retainedHandler);
             AssertFrame.Attribute(newAttributeFrame, "ontest", retainedHandler);
-            Assert.NotEqual(0, oldAttributeFrame.AttributeEventHandlerId);
+            Assert.NotEqual(default, oldAttributeFrame.AttributeEventHandlerId);
             Assert.Equal(oldAttributeFrame.AttributeEventHandlerId, newAttributeFrame.AttributeEventHandlerId);
             Assert.Empty(batchBuilder.DisposedEventHandlerIDs.AsEnumerable());
         }

+ 2 - 2
src/Components/Components/test/RendererTest.cs

@@ -3313,7 +3313,7 @@ namespace Microsoft.AspNetCore.Components.Test
                 Assert.Equal(RenderTreeEditType.SetAttribute, edit.Type);
                 var attributeFrame = batch2.ReferenceFrames[edit.ReferenceFrameIndex];
                 AssertFrame.Attribute(attributeFrame, "ontestevent", typeof(Action<UIChangeEventArgs>));
-                Assert.NotEqual(0, attributeFrame.AttributeEventHandlerId);
+                Assert.NotEqual(default, attributeFrame.AttributeEventHandlerId);
                 Assert.NotEqual(eventHandlerId, attributeFrame.AttributeEventHandlerId);
             });
         }
@@ -3365,7 +3365,7 @@ namespace Microsoft.AspNetCore.Components.Test
                     Assert.Equal(RenderTreeEditType.SetAttribute, edit.Type);
                     var attributeFrame = latestBatch.ReferenceFrames[edit.ReferenceFrameIndex];
                     AssertFrame.Attribute(attributeFrame, "ontestevent", typeof(Action<UIChangeEventArgs>));
-                    Assert.NotEqual(0, attributeFrame.AttributeEventHandlerId);
+                    Assert.NotEqual(default, attributeFrame.AttributeEventHandlerId);
                     Assert.NotEqual(eventHandlerId, attributeFrame.AttributeEventHandlerId);
                 });
             }

+ 26 - 12
src/Components/Server/src/Circuits/RenderBatchWriter.cs

@@ -130,16 +130,15 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
 
         void Write(in RenderTreeFrame frame)
         {
+            // TODO: Change this to write as a short, saving 2 bytes per frame
             _binaryWriter.Write((int)frame.FrameType);
 
             // We want each frame to take up the same number of bytes, so that the
             // recipient can index into the array directly instead of having to
             // walk through it.
-            // Since we can fit every frame type into 3 ints, use that as the
+            // Since we can fit every frame type into 16 bytes, use that as the
             // common size. For smaller frames, we add padding to expand it to
-            // 12 bytes (i.e., 3 x 4-byte ints).
-            // The total size then for each frame is 16 bytes (frame type, then
-            // 3 other ints).
+            // 16 bytes.
             switch (frame.FrameType)
             {
                 case RenderTreeFrameType.Attribute:
@@ -160,41 +159,41 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
                         var attributeValueString = frame.AttributeValue as string;
                         WriteString(attributeValueString, allowDeduplication: string.IsNullOrEmpty(attributeValueString));
                     }
-                    _binaryWriter.Write(frame.AttributeEventHandlerId);
+                    _binaryWriter.Write(frame.AttributeEventHandlerId); // 8 bytes
                     break;
                 case RenderTreeFrameType.Component:
                     _binaryWriter.Write(frame.ComponentSubtreeLength);
                     _binaryWriter.Write(frame.ComponentId);
-                    WritePadding(_binaryWriter, 4);
+                    WritePadding(_binaryWriter, 8);
                     break;
                 case RenderTreeFrameType.ComponentReferenceCapture:
                     // The client doesn't need to know about these. But we still have
                     // to include them in the array otherwise the ReferenceFrameIndex
                     // values in the edits data would be wrong.
-                    WritePadding(_binaryWriter, 12);
+                    WritePadding(_binaryWriter, 16);
                     break;
                 case RenderTreeFrameType.Element:
                     _binaryWriter.Write(frame.ElementSubtreeLength);
                     WriteString(frame.ElementName, allowDeduplication: true);
-                    WritePadding(_binaryWriter, 4);
+                    WritePadding(_binaryWriter, 8);
                     break;
                 case RenderTreeFrameType.ElementReferenceCapture:
                     WriteString(frame.ElementReferenceCaptureId, allowDeduplication: false);
-                    WritePadding(_binaryWriter, 8);
+                    WritePadding(_binaryWriter, 12);
                     break;
                 case RenderTreeFrameType.Region:
                     _binaryWriter.Write(frame.RegionSubtreeLength);
-                    WritePadding(_binaryWriter, 8);
+                    WritePadding(_binaryWriter, 12);
                     break;
                 case RenderTreeFrameType.Text:
                     WriteString(
                         frame.TextContent,
                         allowDeduplication: string.IsNullOrWhiteSpace(frame.TextContent));
-                    WritePadding(_binaryWriter, 8);
+                    WritePadding(_binaryWriter, 12);
                     break;
                 case RenderTreeFrameType.Markup:
                     WriteString(frame.MarkupContent, allowDeduplication: false);
-                    WritePadding(_binaryWriter, 8);
+                    WritePadding(_binaryWriter, 12);
                     break;
                 default:
                     throw new ArgumentException($"Unsupported frame type: {frame.FrameType}");
@@ -216,6 +215,21 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
             return startPos;
         }
 
+        int Write(in ArrayRange<ulong> numbers)
+        {
+            var startPos = (int)_binaryWriter.BaseStream.Position;
+            _binaryWriter.Write(numbers.Count);
+
+            var array = numbers.Array;
+            var count = numbers.Count;
+            for (var index = 0; index < count; index++)
+            {
+                _binaryWriter.Write(array[index]);
+            }
+
+            return startPos;
+        }
+
         void WriteString(string value, bool allowDeduplication)
         {
             if (value == null)

+ 25 - 21
src/Components/Server/test/Circuits/RenderBatchWriterTest.cs

@@ -76,7 +76,7 @@ namespace Microsoft.AspNetCore.Components.Server
                 new ArrayRange<RenderTreeDiff>(),
                 new ArrayRange<RenderTreeFrame>(),
                 new ArrayRange<int>(),
-                new ArrayRange<int>(new[] { 123, int.MaxValue, int.MinValue, 456 }, 3) // Only use first 3 to show that param is respected
+                new ArrayRange<ulong>(new ulong[] { 123, ulong.MaxValue, ulong.MinValue, 456 }, 3) // Only use first 3 to show that param is respected
                 ));
 
             // Assert
@@ -84,15 +84,15 @@ namespace Microsoft.AspNetCore.Components.Server
                 0,  // Length of UpdatedComponents
                 0,  // Length of ReferenceFrames
                 0,  // Length of DisposedComponentIds
-                3, 123, int.MaxValue, int.MinValue, // DisposedEventHandlerIds as length-prefixed array
+                3, (ulong)123, ulong.MaxValue, ulong.MinValue, // DisposedEventHandlerIds as length-prefixed array
 
                 0,  // Index of UpdatedComponents
                 4,  // Index of ReferenceFrames
                 8,  // Index of DisposedComponentIds
                 12, // Index of DisposedEventHandlerIds
-                28  // Index of strings
+                40  // Index of strings
             );
-            Assert.Equal(48, bytes.Length); // No other data
+            Assert.Equal(60, bytes.Length); // No other data
         }
 
         [Fact]
@@ -200,7 +200,7 @@ namespace Microsoft.AspNetCore.Components.Server
                     RenderTreeFrame.Attribute(123, "Attribute with string value", "String value"),
                     RenderTreeFrame.Attribute(124, "Attribute with nonstring value", 1),
                     RenderTreeFrame.Attribute(125, "Attribute with delegate value", new Action(() => { }))
-                        .WithAttributeEventHandlerId(789),
+                        .WithAttributeEventHandlerId(((ulong)uint.MaxValue) + 1),
                     RenderTreeFrame.ChildComponent(126, typeof(object))
                         .WithComponentSubtreeLength(5678)
                         .WithComponent(new ComponentState(renderer, 2000, new FakeComponent(), null)),
@@ -230,22 +230,22 @@ namespace Microsoft.AspNetCore.Components.Server
             var referenceFramesStartIndex = ReadInt(bytes, bytes.Length - 16);
             AssertBinaryContents(bytes, referenceFramesStartIndex,
                 16, // Number of frames
-                RenderTreeFrameType.Attribute, "Attribute with string value", "String value", 0,
-                RenderTreeFrameType.Attribute, "Attribute with nonstring value", NullStringMarker, 0,
-                RenderTreeFrameType.Attribute, "Attribute with delegate value", NullStringMarker, 789,
-                RenderTreeFrameType.Component, 5678, 2000, 0,
-                RenderTreeFrameType.ComponentReferenceCapture, 0, 0, 0,
-                RenderTreeFrameType.Element, 1234, "Some element", 0,
-                RenderTreeFrameType.ElementReferenceCapture, "my unique ID", 0, 0,
-                RenderTreeFrameType.Region, 1234, 0, 0,
-                RenderTreeFrameType.Text, "Some text", 0, 0,
-                RenderTreeFrameType.Markup, "Some markup", 0, 0,
-                RenderTreeFrameType.Text, "\n\t  ", 0, 0,
-                RenderTreeFrameType.Attribute, "Attribute with string value", "String value", 0,
-                RenderTreeFrameType.Element, 999, "Some element", 0,
-                RenderTreeFrameType.Text, "Some text", 0, 0,
-                RenderTreeFrameType.Markup, "Some markup", 0, 0,
-                RenderTreeFrameType.Text, "\n\t  ", 0, 0
+                RenderTreeFrameType.Attribute, "Attribute with string value", "String value", 0, 0,
+                RenderTreeFrameType.Attribute, "Attribute with nonstring value", NullStringMarker, 0, 0,
+                RenderTreeFrameType.Attribute, "Attribute with delegate value", NullStringMarker, ((ulong)uint.MaxValue) + 1,
+                RenderTreeFrameType.Component, 5678, 2000, 0, 0,
+                RenderTreeFrameType.ComponentReferenceCapture, 0, 0, 0, 0,
+                RenderTreeFrameType.Element, 1234, "Some element", 0, 0,
+                RenderTreeFrameType.ElementReferenceCapture, "my unique ID", 0, 0, 0,
+                RenderTreeFrameType.Region, 1234, 0, 0, 0,
+                RenderTreeFrameType.Text, "Some text", 0, 0, 0,
+                RenderTreeFrameType.Markup, "Some markup", 0, 0, 0,
+                RenderTreeFrameType.Text, "\n\t  ", 0, 0, 0,
+                RenderTreeFrameType.Attribute, "Attribute with string value", "String value", 0, 0,
+                RenderTreeFrameType.Element, 999, "Some element", 0, 0,
+                RenderTreeFrameType.Text, "Some text", 0, 0, 0,
+                RenderTreeFrameType.Markup, "Some markup", 0, 0, 0,
+                RenderTreeFrameType.Text, "\n\t  ", 0, 0, 0
             );
 
             Assert.Equal(new[]
@@ -320,6 +320,10 @@ namespace Microsoft.AspNetCore.Components.Server
                     {
                         Assert.Equal(expectedInt, reader.ReadInt32());
                     }
+                    else if (expectedEntry is ulong expectedUlong)
+                    {
+                        Assert.Equal(expectedUlong, reader.ReadUInt64());
+                    }
                     else if (expectedEntry is string || expectedEntry == NullStringMarker)
                     {
                         // For strings, we have to look up the value in the table of strings

+ 2 - 2
src/Components/Shared/test/TestRenderer.cs

@@ -60,10 +60,10 @@ namespace Microsoft.AspNetCore.Components.Test.Helpers
         public new Task RenderRootComponentAsync(int componentId, ParameterCollection parameters)
             => Dispatcher.InvokeAsync(() => base.RenderRootComponentAsync(componentId, parameters));
 
-        public Task DispatchEventAsync(int eventHandlerId, UIEventArgs args)
+        public Task DispatchEventAsync(ulong eventHandlerId, UIEventArgs args)
             => Dispatcher.InvokeAsync(() => base.DispatchEventAsync(eventHandlerId, null, args));
 
-        public new Task DispatchEventAsync(int eventHandlerId, EventFieldInfo eventFieldInfo, UIEventArgs args)
+        public new Task DispatchEventAsync(ulong eventHandlerId, EventFieldInfo eventFieldInfo, UIEventArgs args)
             => Dispatcher.InvokeAsync(() => base.DispatchEventAsync(eventHandlerId, eventFieldInfo, args));
 
         private static Task UnwrapTask(Task task)

File diff suppressed because it is too large
+ 0 - 0
src/Components/Web.JS/dist/Release/blazor.server.js


File diff suppressed because it is too large
+ 0 - 0
src/Components/Web.JS/dist/Release/blazor.webassembly.js


+ 15 - 0
src/Components/Web.JS/src/Platform/Mono/MonoPlatform.ts

@@ -13,6 +13,8 @@ let invoke_method: (method: MethodHandle, target: System_Object, argsArrayPtr: n
 let mono_string_get_utf8: (managedString: System_String) => Mono.Utf8Ptr;
 let mono_string: (jsString: string) => System_String;
 const appBinDirName = 'appBinDir';
+const uint64HighOrderShift = Math.pow(2, 32);
+const maxSafeNumberHighPart = Math.pow(2, 21) - 1; // The high-order int32 from Number.MAX_SAFE_INTEGER
 
 export const monoPlatform: Platform = {
   start: function start(loadAssemblyUrls: string[]) {
@@ -122,6 +124,19 @@ export const monoPlatform: Platform = {
     return Module.getValue((baseAddress as any as number) + (fieldOffset || 0), 'i32');
   },
 
+  readUint64Field: function readHeapUint64(baseAddress: Pointer, fieldOffset?: number): number {
+    // Module.getValue(..., 'i64') doesn't work because the implementation treats 'i64' as
+    // being the same as 'i32'. Also we must take care to read both halves as unsigned.
+    const address = (baseAddress as any as number) + (fieldOffset || 0);
+    const heapU32Index = address >> 2;
+    const highPart = Module.HEAPU32[heapU32Index + 1];
+    if (highPart > maxSafeNumberHighPart) {
+      throw new Error(`Cannot read uint64 with high order part ${highPart}, because the result would exceed Number.MAX_SAFE_INTEGER.`);
+    }
+
+    return (highPart * uint64HighOrderShift) + Module.HEAPU32[heapU32Index];
+  },
+
   readFloatField: function readHeapFloat(baseAddress: Pointer, fieldOffset?: number): number {
     return Module.getValue((baseAddress as any as number) + (fieldOffset || 0), 'float');
   },

+ 1 - 0
src/Components/Web.JS/src/Platform/Platform.ts

@@ -16,6 +16,7 @@ export interface Platform {
   getObjectFieldsBaseAddress(referenceTypedObject: System_Object): Pointer;
   readInt16Field(baseAddress: Pointer, fieldOffset?: number): number;
   readInt32Field(baseAddress: Pointer, fieldOffset?: number): number;
+  readUint64Field(baseAddress: Pointer, fieldOffset?: number): number;
   readFloatField(baseAddress: Pointer, fieldOffset?: number): number;
   readObjectField<T extends System_Object>(baseAddress: Pointer, fieldOffset?: number): T;
   readStringField(baseAddress: Pointer, fieldOffset?: number): string | null;

+ 24 - 4
src/Components/Web.JS/src/Rendering/RenderBatch/OutOfProcessRenderBatch.ts

@@ -2,11 +2,13 @@ import { RenderBatch, ArrayRange, RenderTreeDiff, ArrayValues, RenderTreeEdit, E
 import { decodeUtf8 } from './Utf8Decoder';
 
 const updatedComponentsEntryLength = 4; // Each is a single int32 giving the location of the data
-const referenceFramesEntryLength = 16; // 1 byte for frame type, then 3 bytes for type-specific data
+const referenceFramesEntryLength = 20; // 1 int for frame type, then 16 bytes for type-specific data
 const disposedComponentIdsEntryLength = 4; // Each is an int32 giving the ID
-const disposedEventHandlerIdsEntryLength = 4; // Each is an int32 giving the ID
+const disposedEventHandlerIdsEntryLength = 8; // Each is an int64 giving the ID
 const editsEntryLength = 16; // 4 ints
 const stringTableEntryLength = 4; // Each is an int32 giving the string data location, or -1 for null
+const uint64HighPartShift = Math.pow(2, 32);
+const maxSafeNumberHighPart = Math.pow(2, 21) - 1; // The high-order int32 from Number.MAX_SAFE_INTEGER
 
 export class OutOfProcessRenderBatch implements RenderBatch {
   constructor(private batchData: Uint8Array) {
@@ -51,7 +53,7 @@ export class OutOfProcessRenderBatch implements RenderBatch {
 
   disposedEventHandlerIdsEntry(values: ArrayValues<number>, index: number): number {
     const entryPos = (values as any) + index * disposedEventHandlerIdsEntryLength;
-    return readInt32LE(this.batchData, entryPos);
+    return readUint64LE(this.batchData, entryPos);
   }
 
   diffReader: RenderTreeDiffReader;
@@ -160,7 +162,7 @@ class OutOfProcessRenderTreeFrameReader implements RenderTreeFrameReader {
   }
 
   attributeEventHandlerId(frame: RenderTreeFrame) {
-    return readInt32LE(this.batchDataUint8, frame as any + 12); // 4th int
+    return readUint64LE(this.batchDataUint8, frame as any + 12); // Bytes 12-19
   }
 }
 
@@ -235,6 +237,24 @@ function readInt32LE(buffer: Uint8Array, position: number): any {
     | (buffer[position + 3] << 24);
 }
 
+function readUint32LE(buffer: Uint8Array, position: number): any {
+  return (buffer[position])
+    + (buffer[position + 1] << 8)
+    + (buffer[position + 2] << 16)
+    + ((buffer[position + 3] << 24) >>> 0); // The >>> 0 coerces the value to unsigned
+}
+
+function readUint64LE(buffer: Uint8Array, position: number): any {
+  // This cannot be done using bit-shift operators in JavaScript, because
+  // those all implicitly convert to int32
+  const highPart = readUint32LE(buffer, position + 4);
+  if (highPart > maxSafeNumberHighPart) {
+    throw new Error(`Cannot read uint64 with high order part ${highPart}, because the result would exceed Number.MAX_SAFE_INTEGER.`);
+  }
+
+  return (highPart * uint64HighPartShift) + readUint32LE(buffer, position);
+}
+
 function readLEB128(buffer: Uint8Array, position: number) {
   let result = 0;
   let shift = 0;

+ 3 - 3
src/Components/Web.JS/src/Rendering/RenderBatch/SharedMemoryRenderBatch.ts

@@ -43,8 +43,8 @@ export class SharedMemoryRenderBatch implements RenderBatch {
   }
 
   disposedEventHandlerIdsEntry(values: ArrayValues<number>, index: number) {
-    const pointer = arrayValuesEntry(values, index, /* int length */ 4);
-    return platform.readInt32Field(pointer as any as Pointer);
+    const pointer = arrayValuesEntry(values, index, /* long length */ 8);
+    return platform.readUint64Field(pointer as any as Pointer);
   }
 
   arrayRangeReader = arrayRangeReader;
@@ -108,7 +108,7 @@ const frameReader = {
   markupContent: (frame: RenderTreeFrame) => platform.readStringField(frame as any, 16)!,
   attributeName: (frame: RenderTreeFrame) => platform.readStringField(frame as any, 16),
   attributeValue: (frame: RenderTreeFrame) => platform.readStringField(frame as any, 24),
-  attributeEventHandlerId: (frame: RenderTreeFrame) => platform.readInt32Field(frame as any, 8),
+  attributeEventHandlerId: (frame: RenderTreeFrame) => platform.readUint64Field(frame as any, 8),
 };
 
 function arrayValuesEntry<T>(arrayValues: ArrayValues<T>, index: number, itemSize: number): T {

+ 1 - 1
src/Components/Web/ref/Microsoft.AspNetCore.Components.Web.netstandard2.0.cs

@@ -13,7 +13,7 @@ namespace Microsoft.AspNetCore.Components.Web
             public int BrowserRendererId { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
             public string EventArgsType { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
             public Microsoft.AspNetCore.Components.Rendering.EventFieldInfo EventFieldInfo { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
-            public int EventHandlerId { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
+            public ulong EventHandlerId { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
         }
     }
 }

+ 1 - 1
src/Components/Web/src/RendererRegistryEventDispatcher.cs

@@ -128,7 +128,7 @@ namespace Microsoft.AspNetCore.Components.Web
             /// <summary>
             /// For framework use only.
             /// </summary>
-            public int EventHandlerId { get; set; }
+            public ulong EventHandlerId { get; set; }
 
             /// <summary>
             /// For framework use only.

+ 21 - 17
src/Components/test/Ignitor.Test/RenderBatchReaderTest.cs

@@ -150,7 +150,7 @@ namespace Ignitor
                     RenderTreeFrame.Attribute(123, "Attribute with string value", "String value"),
                     RenderTreeFrame.Attribute(124, "Attribute with nonstring value", 1),
                     RenderTreeFrame.Attribute(125, "Attribute with delegate value", new Action(() => { }))
-                        .WithAttributeEventHandlerId(789),
+                        .WithAttributeEventHandlerId((ulong)uint.MaxValue + 1),
                     RenderTreeFrame.ChildComponent(126, typeof(object))
                         .WithComponentSubtreeLength(5678)
                         .WithComponent(new ComponentState(renderer, 2000, new FakeComponent(), null)),
@@ -180,22 +180,22 @@ namespace Ignitor
             var referenceFramesStartIndex = ReadInt(bytes, bytes.Length - 16);
             AssertBinaryContents(bytes, referenceFramesStartIndex,
                 16, // Number of frames
-                RenderTreeFrameType.Attribute, "Attribute with string value", "String value", 0,
-                RenderTreeFrameType.Attribute, "Attribute with nonstring value", NullStringMarker, 0,
-                RenderTreeFrameType.Attribute, "Attribute with delegate value", NullStringMarker, 789,
-                RenderTreeFrameType.Component, 5678, 2000, 0,
-                RenderTreeFrameType.ComponentReferenceCapture, 0, 0, 0,
-                RenderTreeFrameType.Element, 1234, "Some element", 0,
-                RenderTreeFrameType.ElementReferenceCapture, "my unique ID", 0, 0,
-                RenderTreeFrameType.Region, 1234, 0, 0,
-                RenderTreeFrameType.Text, "Some text", 0, 0,
-                RenderTreeFrameType.Markup, "Some markup", 0, 0,
-                RenderTreeFrameType.Text, "\n\t  ", 0, 0,
-                RenderTreeFrameType.Attribute, "Attribute with string value", "String value", 0,
-                RenderTreeFrameType.Element, 999, "Some element", 0,
-                RenderTreeFrameType.Text, "Some text", 0, 0,
-                RenderTreeFrameType.Markup, "Some markup", 0, 0,
-                RenderTreeFrameType.Text, "\n\t  ", 0, 0
+                RenderTreeFrameType.Attribute, "Attribute with string value", "String value", 0, 0,
+                RenderTreeFrameType.Attribute, "Attribute with nonstring value", NullStringMarker, 0, 0,
+                RenderTreeFrameType.Attribute, "Attribute with delegate value", NullStringMarker, (ulong)uint.MaxValue + 1,
+                RenderTreeFrameType.Component, 5678, 2000, 0, 0,
+                RenderTreeFrameType.ComponentReferenceCapture, 0, 0, 0, 0,
+                RenderTreeFrameType.Element, 1234, "Some element", 0, 0,
+                RenderTreeFrameType.ElementReferenceCapture, "my unique ID", 0, 0, 0,
+                RenderTreeFrameType.Region, 1234, 0, 0, 0,
+                RenderTreeFrameType.Text, "Some text", 0, 0, 0,
+                RenderTreeFrameType.Markup, "Some markup", 0, 0, 0,
+                RenderTreeFrameType.Text, "\n\t  ", 0, 0, 0,
+                RenderTreeFrameType.Attribute, "Attribute with string value", "String value", 0, 0,
+                RenderTreeFrameType.Element, 999, "Some element", 0, 0,
+                RenderTreeFrameType.Text, "Some text", 0, 0, 0,
+                RenderTreeFrameType.Markup, "Some markup", 0, 0, 0,
+                RenderTreeFrameType.Text, "\n\t  ", 0, 0, 0
             );
 
             Assert.Equal(new[]
@@ -279,6 +279,10 @@ namespace Ignitor
                     {
                         Assert.Equal(expectedInt, reader.ReadInt32());
                     }
+                    else if (expectedEntry is ulong expectedUlong)
+                    {
+                        Assert.Equal(expectedUlong, reader.ReadUInt64());
+                    }
                     else if (expectedEntry is string || expectedEntry == NullStringMarker)
                     {
                         // For strings, we have to look up the value in the table of strings

+ 4 - 1
src/Components/test/testassets/ComponentsApp.Server/Startup.cs

@@ -17,7 +17,10 @@ namespace ComponentsApp.Server
         {
             services.AddMvc();
             services.AddSingleton<CircuitHandler, LoggingCircuitHandler>();
-            services.AddServerSideBlazor();
+            services.AddServerSideBlazor(options =>
+            {
+                options.JSInteropDetailedErrors = true;
+            });
 
             services.AddSingleton<WeatherForecastService, DefaultWeatherForecastService>();
         }

+ 1 - 1
src/Components/test/testassets/Ignitor/ElementHive.cs

@@ -93,7 +93,7 @@ namespace Ignitor
 
         }
 
-        private void DisposeEventHandler(int eventHandlerId)
+        private void DisposeEventHandler(ulong eventHandlerId)
         {
 
         }

+ 2 - 2
src/Components/test/testassets/Ignitor/ElementNode.cs

@@ -101,7 +101,7 @@ namespace Ignitor
 
         public class ElementEventDescriptor
         {
-            public ElementEventDescriptor(string eventName, int eventId)
+            public ElementEventDescriptor(string eventName, ulong eventId)
             {
                 EventName = eventName ?? throw new ArgumentNullException(nameof(eventName));
                 EventId = eventId;
@@ -109,7 +109,7 @@ namespace Ignitor
 
             public string EventName { get; }
 
-            public int EventId { get; }
+            public ulong EventId { get; }
         }
 
         public async Task ClickAsync(HubConnection connection)

+ 19 - 19
src/Components/test/testassets/Ignitor/RenderBatchReader.cs

@@ -14,6 +14,8 @@ namespace Ignitor
 {
     public static class RenderBatchReader
     {
+        private const int ReferenceFrameSize = 20;
+
         private static readonly Renderer Renderer = new FakeRenderer();
 
         public static RenderBatch Read(ReadOnlySpan<byte> data)
@@ -131,69 +133,67 @@ namespace Ignitor
 
         private static ArrayRange<RenderTreeFrame> ReadReferenceFrames(ReadOnlySpan<byte> data, string[] strings)
         {
-            var result = new RenderTreeFrame[data.Length / 16];
+            var result = new RenderTreeFrame[data.Length / ReferenceFrameSize];
 
-            for (var i = 0; i < data.Length; i += 16)
+            for (var i = 0; i < data.Length; i += ReferenceFrameSize)
             {
-                var frameData = data.Slice(i, 16);
+                var frameData = data.Slice(i, ReferenceFrameSize);
 
                 var type = (RenderTreeFrameType)BitConverter.ToInt32(frameData.Slice(0, 4));
 
                 // We want each frame to take up the same number of bytes, so that the
                 // recipient can index into the array directly instead of having to
                 // walk through it.
-                // Since we can fit every frame type into 3 ints, use that as the
+                // Since we can fit every frame type into 16 bytes, use that as the
                 // common size. For smaller frames, we add padding to expand it to
-                // 12 bytes (i.e., 3 x 4-byte ints).
-                // The total size then for each frame is 16 bytes (frame type, then
-                // 3 other ints).
+                // 16 bytes.
                 switch (type)
                 {
                     case RenderTreeFrameType.Attribute:
                         var attributeName = ReadString(frameData.Slice(4, 4), strings);
                         var attributeValue = ReadString(frameData.Slice(8, 4), strings);
-                        var attributeEventHandlerId = BitConverter.ToInt32(frameData.Slice(12, 4));
-                        result[i / 16] = RenderTreeFrame.Attribute(0, attributeName, attributeValue).WithAttributeEventHandlerId(attributeEventHandlerId);
+                        var attributeEventHandlerId = BitConverter.ToUInt64(frameData.Slice(12, 8));
+                        result[i / ReferenceFrameSize] = RenderTreeFrame.Attribute(0, attributeName, attributeValue).WithAttributeEventHandlerId(attributeEventHandlerId);
                         break;
 
                     case RenderTreeFrameType.Component:
                         var componentSubtreeLength = BitConverter.ToInt32(frameData.Slice(4, 4));
                         var componentId = BitConverter.ToInt32(frameData.Slice(8, 4)); // Nowhere to put this without creating a ComponentState
-                        result[i / 16] = RenderTreeFrame.ChildComponent(0, componentType: null)
+                        result[i / ReferenceFrameSize] = RenderTreeFrame.ChildComponent(0, componentType: null)
                             .WithComponentSubtreeLength(componentSubtreeLength)
                             .WithComponent(new ComponentState(Renderer, componentId, new FakeComponent(), null));
                         break;
 
                     case RenderTreeFrameType.ComponentReferenceCapture:
                         // Client doesn't process these, skip.
-                        result[i / 16] = RenderTreeFrame.ComponentReferenceCapture(0, null, 0);
+                        result[i / ReferenceFrameSize] = RenderTreeFrame.ComponentReferenceCapture(0, null, 0);
                         break;
 
                     case RenderTreeFrameType.Element:
                         var elementSubtreeLength = BitConverter.ToInt32(frameData.Slice(4, 4));
                         var elementName = ReadString(frameData.Slice(8, 4), strings);
-                        result[i / 16] = RenderTreeFrame.Element(0, elementName).WithElementSubtreeLength(elementSubtreeLength);
+                        result[i / ReferenceFrameSize] = RenderTreeFrame.Element(0, elementName).WithElementSubtreeLength(elementSubtreeLength);
                         break;
 
                     case RenderTreeFrameType.ElementReferenceCapture:
                         var referenceCaptureId = ReadString(frameData.Slice(4, 4), strings);
-                        result[i / 16] = RenderTreeFrame.ElementReferenceCapture(0, null)
+                        result[i / ReferenceFrameSize] = RenderTreeFrame.ElementReferenceCapture(0, null)
                             .WithElementReferenceCaptureId(referenceCaptureId);
                         break;
 
                     case RenderTreeFrameType.Region:
                         var regionSubtreeLength = BitConverter.ToInt32(frameData.Slice(4, 4));
-                        result[i / 16] = RenderTreeFrame.Region(0).WithRegionSubtreeLength(regionSubtreeLength);
+                        result[i / ReferenceFrameSize] = RenderTreeFrame.Region(0).WithRegionSubtreeLength(regionSubtreeLength);
                         break;
 
                     case RenderTreeFrameType.Text:
                         var text = ReadString(frameData.Slice(4, 4), strings);
-                        result[i / 16] = RenderTreeFrame.Text(0, text);
+                        result[i / ReferenceFrameSize] = RenderTreeFrame.Text(0, text);
                         break;
 
                     case RenderTreeFrameType.Markup:
                         var markup = ReadString(frameData.Slice(4, 4), strings);
-                        result[i / 16] = RenderTreeFrame.Markup(0, markup);
+                        result[i / ReferenceFrameSize] = RenderTreeFrame.Markup(0, markup);
                         break;
 
                     default:
@@ -209,9 +209,9 @@ namespace Ignitor
             return new ArrayRange<int>(Array.Empty<int>(), 0);
         }
 
-        private static ArrayRange<int> ReadDisposedEventHandlerIds(ReadOnlySpan<byte> data)
+        private static ArrayRange<ulong> ReadDisposedEventHandlerIds(ReadOnlySpan<byte> data)
         {
-            return new ArrayRange<int>(Array.Empty<int>(), 0);
+            return new ArrayRange<ulong>(Array.Empty<ulong>(), 0);
         }
 
         private static string ReadString(ReadOnlySpan<byte> data, string[] strings)
@@ -276,7 +276,7 @@ namespace Ignitor
             {
                 // This is a count-prefixed contiguous array of RenderTreeFrame.
                 var count = BitConverter.ToInt32(data.Slice(_referenceFrames, 4));
-                return data.Slice(_referenceFrames + 4, count * 16);
+                return data.Slice(_referenceFrames + 4, count * ReferenceFrameSize);
             }
 
             public ReadOnlySpan<byte> GetStringTableIndexes(ReadOnlySpan<byte> data)

Some files were not shown because too many files changed in this diff