diff --git a/src/Microsoft.AspNetCore.Blazor.Browser.JS/src/Rendering/BrowserRenderer.ts b/src/Microsoft.AspNetCore.Blazor.Browser.JS/src/Rendering/BrowserRenderer.ts
index 09d13db7b9..164c63c4c7 100644
--- a/src/Microsoft.AspNetCore.Blazor.Browser.JS/src/Rendering/BrowserRenderer.ts
+++ b/src/Microsoft.AspNetCore.Blazor.Browser.JS/src/Rendering/BrowserRenderer.ts
@@ -153,7 +153,7 @@ export class BrowserRenderer {
return this.insertFrameRange(batch, componentId, parent, childIndex, frames, frameIndex + 1, frameIndex + frameReader.subtreeLength(frame));
case FrameType.elementReferenceCapture:
if (parent instanceof Element) {
- applyCaptureIdToElement(parent, frameReader.elementReferenceCaptureId(frame));
+ applyCaptureIdToElement(parent, frameReader.elementReferenceCaptureId(frame)!);
return 0; // A "capture" is a child in the diff, but has no node in the DOM
} else {
throw new Error('Reference capture frames can only be children of element frames.');
diff --git a/src/Microsoft.AspNetCore.Blazor.Browser.JS/src/Rendering/ElementReferenceCapture.ts b/src/Microsoft.AspNetCore.Blazor.Browser.JS/src/Rendering/ElementReferenceCapture.ts
index 27ea7b0ba2..5d898eaa52 100644
--- a/src/Microsoft.AspNetCore.Blazor.Browser.JS/src/Rendering/ElementReferenceCapture.ts
+++ b/src/Microsoft.AspNetCore.Blazor.Browser.JS/src/Rendering/ElementReferenceCapture.ts
@@ -1,20 +1,20 @@
-export function applyCaptureIdToElement(element: Element, referenceCaptureId: number) {
+export function applyCaptureIdToElement(element: Element, referenceCaptureId: string) {
element.setAttribute(getCaptureIdAttributeName(referenceCaptureId), '');
}
-function getElementByCaptureId(referenceCaptureId: number) {
+function getElementByCaptureId(referenceCaptureId: string) {
const selector = `[${getCaptureIdAttributeName(referenceCaptureId)}]`;
return document.querySelector(selector);
}
-function getCaptureIdAttributeName(referenceCaptureId: number) {
+function getCaptureIdAttributeName(referenceCaptureId: string) {
return `_bl_${referenceCaptureId}`;
}
// Support receiving ElementRef instances as args in interop calls
const elementRefKey = '_blazorElementRef'; // Keep in sync with ElementRef.cs
DotNet.attachReviver((key, value) => {
- if (value && typeof value === 'object' && value.hasOwnProperty(elementRefKey) && typeof value[elementRefKey] === 'number') {
+ if (value && typeof value === 'object' && value.hasOwnProperty(elementRefKey) && typeof value[elementRefKey] === 'string') {
return getElementByCaptureId(value[elementRefKey]);
} else {
return value;
diff --git a/src/Microsoft.AspNetCore.Blazor.Browser.JS/src/Rendering/RenderBatch/RenderBatch.ts b/src/Microsoft.AspNetCore.Blazor.Browser.JS/src/Rendering/RenderBatch/RenderBatch.ts
index e18b54af12..3f95e9f5f7 100644
--- a/src/Microsoft.AspNetCore.Blazor.Browser.JS/src/Rendering/RenderBatch/RenderBatch.ts
+++ b/src/Microsoft.AspNetCore.Blazor.Browser.JS/src/Rendering/RenderBatch/RenderBatch.ts
@@ -43,7 +43,7 @@ export interface RenderTreeEditReader {
export interface RenderTreeFrameReader {
frameType(frame: RenderTreeFrame): FrameType;
subtreeLength(frame: RenderTreeFrame): number;
- elementReferenceCaptureId(frame: RenderTreeFrame): number;
+ elementReferenceCaptureId(frame: RenderTreeFrame): string | null;
componentId(frame: RenderTreeFrame): number;
elementName(frame: RenderTreeFrame): string | null;
textContent(frame: RenderTreeFrame): string | null;
diff --git a/src/Microsoft.AspNetCore.Blazor.Browser.JS/src/Rendering/RenderBatch/SharedMemoryRenderBatch.ts b/src/Microsoft.AspNetCore.Blazor.Browser.JS/src/Rendering/RenderBatch/SharedMemoryRenderBatch.ts
index 7ad2186e0a..d6dad6cfbc 100644
--- a/src/Microsoft.AspNetCore.Blazor.Browser.JS/src/Rendering/RenderBatch/SharedMemoryRenderBatch.ts
+++ b/src/Microsoft.AspNetCore.Blazor.Browser.JS/src/Rendering/RenderBatch/SharedMemoryRenderBatch.ts
@@ -77,7 +77,7 @@ const frameReader = {
structLength: 28,
frameType: (frame: RenderTreeFrame) => platform.readInt32Field(frame as any, 4) as FrameType,
subtreeLength: (frame: RenderTreeFrame) => platform.readInt32Field(frame as any, 8),
- elementReferenceCaptureId: (frame: RenderTreeFrame) => platform.readInt32Field(frame as any, 8),
+ elementReferenceCaptureId: (frame: RenderTreeFrame) => platform.readStringField(frame as any, 16),
componentId: (frame: RenderTreeFrame) => platform.readInt32Field(frame as any, 12),
elementName: (frame: RenderTreeFrame) => platform.readStringField(frame as any, 16),
textContent: (frame: RenderTreeFrame) => platform.readStringField(frame as any, 16),
diff --git a/src/Microsoft.AspNetCore.Blazor.Server/Circuits/RenderBatchWriter.cs b/src/Microsoft.AspNetCore.Blazor.Server/Circuits/RenderBatchWriter.cs
index 4554018f65..1e892d7b1d 100644
--- a/src/Microsoft.AspNetCore.Blazor.Server/Circuits/RenderBatchWriter.cs
+++ b/src/Microsoft.AspNetCore.Blazor.Server/Circuits/RenderBatchWriter.cs
@@ -158,7 +158,7 @@ namespace Microsoft.AspNetCore.Blazor.Server.Circuits
WritePadding(_binaryWriter, 4);
break;
case RenderTreeFrameType.ElementReferenceCapture:
- _binaryWriter.Write(frame.ElementReferenceCaptureId);
+ WriteString(frame.ElementReferenceCaptureId);
WritePadding(_binaryWriter, 8);
break;
case RenderTreeFrameType.Region:
diff --git a/src/Microsoft.AspNetCore.Blazor/ElementRef.cs b/src/Microsoft.AspNetCore.Blazor/ElementRef.cs
index 135853a8ae..011d71e523 100644
--- a/src/Microsoft.AspNetCore.Blazor/ElementRef.cs
+++ b/src/Microsoft.AspNetCore.Blazor/ElementRef.cs
@@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.JSInterop.Internal;
+using System;
using System.Collections.Generic;
using System.Threading;
@@ -12,25 +13,18 @@ namespace Microsoft.AspNetCore.Blazor
///
public readonly struct ElementRef : ICustomJsonSerializer
{
- // Static to ensure uniqueness even if there are multiple Renderer instances
- // This would not be necessary if the JS-side code maintained a lookup from capureId to Element instances,
- // but we're not doing that presently as it causes more work during disposal to remove those entries
- // WARNING: Once we support server-side rendering, we should check if running on the server and avoid
- // populating element reference capture IDs at all, because doing so could (a) eventually
- // overflow the static int, and (b) disclose information to clients about how many other
- // requests the server is handling, etc. In general, as part of implementing SSR, we need to
- // audit the code it calls for any use of statics.
- private static int _nextId = 0;
+ static long _nextIdForWebAssemblyOnly = 1;
- internal int Id { get; }
+ // The Id is unique at least within the scope of a given user/circuit
+ internal string Id { get; }
- private ElementRef(int id)
+ private ElementRef(string id)
{
Id = id;
}
internal static ElementRef CreateWithUniqueId()
- => new ElementRef(Interlocked.Increment(ref _nextId));
+ => new ElementRef(CreateUniqueId());
object ICustomJsonSerializer.ToJsonPrimitive()
{
@@ -39,5 +33,26 @@ namespace Microsoft.AspNetCore.Blazor
{ "_blazorElementRef", Id }
};
}
+
+ static string CreateUniqueId()
+ {
+ if (PlatformInfo.IsWebAssembly)
+ {
+ // On WebAssembly there's only one user, so it's fine to expose the number
+ // of IDs that have been assigned, and this is cheaper than creating a GUID.
+ // It's unfortunate that this still involves a heap allocation. If that becomes
+ // a problem we could extend RenderTreeFrame to have both "string" and "long"
+ // fields for ElementRefCaptureId, of which only one would be in use depending
+ // on the platform.
+ var id = Interlocked.Increment(ref _nextIdForWebAssemblyOnly);
+ return id.ToString();
+ }
+ else
+ {
+ // For remote rendering, it's important not to disclose any cross-user state,
+ // such as the number of IDs that have been assigned.
+ return Guid.NewGuid().ToString("D");
+ }
+ }
}
}
diff --git a/src/Microsoft.AspNetCore.Blazor/PlatformInfo.cs b/src/Microsoft.AspNetCore.Blazor/PlatformInfo.cs
new file mode 100644
index 0000000000..1297811490
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Blazor/PlatformInfo.cs
@@ -0,0 +1,17 @@
+// 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 System.Runtime.InteropServices;
+
+namespace Microsoft.AspNetCore.Blazor
+{
+ internal static class PlatformInfo
+ {
+ public static bool IsWebAssembly { get; }
+
+ static PlatformInfo()
+ {
+ IsWebAssembly = RuntimeInformation.IsOSPlatform(OSPlatform.Create("WEBASSEMBLY"));
+ }
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Blazor/RenderTree/RenderTreeFrame.cs b/src/Microsoft.AspNetCore.Blazor/RenderTree/RenderTreeFrame.cs
index 8c679a543a..9fc1ea870e 100644
--- a/src/Microsoft.AspNetCore.Blazor/RenderTree/RenderTreeFrame.cs
+++ b/src/Microsoft.AspNetCore.Blazor/RenderTree/RenderTreeFrame.cs
@@ -137,13 +137,13 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
/// If the property equals ,
/// gets the ID of the reference capture. Otherwise, the value is undefined.
///
- [FieldOffset(8)] public readonly int ElementReferenceCaptureId;
+ [FieldOffset(16)] public readonly string ElementReferenceCaptureId;
///
/// If the property equals ,
/// gets the action that writes the reference to its target. Otherwise, the value is undefined.
///
- [FieldOffset(16)] public readonly Action ElementReferenceCaptureAction;
+ [FieldOffset(24)] public readonly Action ElementReferenceCaptureAction;
// --------------------------------------------------------------------------------
// RenderTreeFrameType.ComponentReferenceCapture
@@ -227,7 +227,7 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
RegionSubtreeLength = regionSubtreeLength;
}
- private RenderTreeFrame(int sequence, Action elementReferenceCaptureAction, int elementReferenceCaptureId)
+ private RenderTreeFrame(int sequence, Action elementReferenceCaptureAction, string elementReferenceCaptureId)
: this()
{
FrameType = RenderTreeFrameType.ElementReferenceCapture;
@@ -264,7 +264,7 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
=> new RenderTreeFrame(sequence, regionSubtreeLength: 0);
internal static RenderTreeFrame ElementReferenceCapture(int sequence, Action elementReferenceCaptureAction)
- => new RenderTreeFrame(sequence, elementReferenceCaptureAction: elementReferenceCaptureAction, elementReferenceCaptureId: 0);
+ => new RenderTreeFrame(sequence, elementReferenceCaptureAction: elementReferenceCaptureAction, elementReferenceCaptureId: null);
internal static RenderTreeFrame ComponentReferenceCapture(int sequence, Action