diff --git a/src/Components/Components/src/Microsoft.AspNetCore.Components.csproj b/src/Components/Components/src/Microsoft.AspNetCore.Components.csproj
index 7565534a51d108b7140c7538f844309da992cf55..1e7f881ed501a40a664ca7ef5fc2a82dd59170b2 100644
--- a/src/Components/Components/src/Microsoft.AspNetCore.Components.csproj
+++ b/src/Components/Components/src/Microsoft.AspNetCore.Components.csproj
@@ -10,7 +10,6 @@
 
   <ItemGroup>
     <Compile Include="$(ComponentsSharedSourceRoot)src\ArrayBuilder.cs" LinkBase="RenderTree" />
-    <Compile Include="$(ComponentsSharedSourceRoot)src\WebAssemblyJSInteropInternalCalls.cs" />
   </ItemGroup>
 
   <ItemGroup>
diff --git a/src/Components/Components/src/Profiling/ComponentsProfiling.cs b/src/Components/Components/src/Profiling/ComponentsProfiling.cs
deleted file mode 100644
index f47a0c917c8e325fa48698d86c0c0d8417734424..0000000000000000000000000000000000000000
--- a/src/Components/Components/src/Profiling/ComponentsProfiling.cs
+++ /dev/null
@@ -1,23 +0,0 @@
-// 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.CompilerServices;
-using System.Runtime.InteropServices;
-
-namespace Microsoft.AspNetCore.Components.Profiling
-{
-    internal abstract class ComponentsProfiling
-    {
-        // For now, this is only intended for use on Blazor WebAssembly, and will have no effect
-        // when running on Blazor Server. The reason for having the ComponentsProfiling abstraction
-        // is so that if we later have two different implementations (one for WebAssembly, one for
-        // Server), the execution characteristics of calling Start/End will be unchanged and historical
-        // perf data will still be comparable to newer data.
-        public static readonly ComponentsProfiling Instance = PlatformInfo.IsWebAssembly
-            ? new WebAssemblyComponentsProfiling()
-            : (ComponentsProfiling)new NoOpComponentsProfiling();
-
-        public abstract void Start([CallerMemberName] string? name = null);
-        public abstract void End([CallerMemberName] string? name = null);
-    }
-}
diff --git a/src/Components/Components/src/Profiling/NoOpComponentsProfiling.cs b/src/Components/Components/src/Profiling/NoOpComponentsProfiling.cs
deleted file mode 100644
index 74037e7de7446ae6e3c07934e63d0ffaf557dd3e..0000000000000000000000000000000000000000
--- a/src/Components/Components/src/Profiling/NoOpComponentsProfiling.cs
+++ /dev/null
@@ -1,16 +0,0 @@
-// 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.
-
-namespace Microsoft.AspNetCore.Components.Profiling
-{
-    internal class NoOpComponentsProfiling : ComponentsProfiling
-    {
-        public override void Start(string? name)
-        {
-        }
-
-        public override void End(string? name)
-        {
-        }
-    }
-}
diff --git a/src/Components/Components/src/Profiling/WebAssemblyComponentsProfiling.cs b/src/Components/Components/src/Profiling/WebAssemblyComponentsProfiling.cs
deleted file mode 100644
index 4e0b6dbd745473f5245010409f2172c7c9093834..0000000000000000000000000000000000000000
--- a/src/Components/Components/src/Profiling/WebAssemblyComponentsProfiling.cs
+++ /dev/null
@@ -1,41 +0,0 @@
-// 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 WebAssembly.JSInterop;
-
-namespace Microsoft.AspNetCore.Components.Profiling
-{
-    // Later on, we will likely want to move this into the WebAssembly package. However it needs to
-    // be inlined into the Components package directly until we're ready to make the underlying
-    // ComponentsProfile abstraction into a public API. It's possible that this API will never become
-    // public, or that it will be replaced by something more standard for .NET, if it's possible to
-    // make that work performantly on WebAssembly.
-
-    internal class WebAssemblyComponentsProfiling : ComponentsProfiling
-    {
-        static bool IsCapturing = false;
-
-        public static void SetCapturing(bool isCapturing)
-        {
-            IsCapturing = isCapturing;
-        }
-
-        public override void Start(string? name)
-        {
-            if (IsCapturing)
-            {
-                InternalCalls.InvokeJSUnmarshalled<string, object, object, object>(
-                    out _, "_blazorProfileStart", name, null, null);
-            }
-        }
-
-        public override void End(string? name)
-        {
-            if (IsCapturing)
-            {
-                InternalCalls.InvokeJSUnmarshalled<string, object, object, object>(
-                    out _, "_blazorProfileEnd", name, null, null);
-            }
-        }
-    }
-}
diff --git a/src/Components/Components/src/RenderTree/RenderTreeDiffBuilder.cs b/src/Components/Components/src/RenderTree/RenderTreeDiffBuilder.cs
index a7892991f45f0d35e95d0924b916c62cf76ff135..7429cf7d7418a1d0735b8653b11e9d36e83b76c5 100644
--- a/src/Components/Components/src/RenderTree/RenderTreeDiffBuilder.cs
+++ b/src/Components/Components/src/RenderTree/RenderTreeDiffBuilder.cs
@@ -7,7 +7,6 @@ using System;
 using System.Collections.Generic;
 using System.Diagnostics;
 using System.Runtime.CompilerServices;
-using Microsoft.AspNetCore.Components.Profiling;
 using Microsoft.AspNetCore.Components.Rendering;
 
 namespace Microsoft.AspNetCore.Components.RenderTree
@@ -28,7 +27,6 @@ namespace Microsoft.AspNetCore.Components.RenderTree
             ArrayRange<RenderTreeFrame> oldTree,
             ArrayRange<RenderTreeFrame> newTree)
         {
-            ComponentsProfiling.Instance.Start();
             var editsBuffer = batchBuilder.EditsBuffer;
             var editsBufferStartLength = editsBuffer.Count;
 
@@ -37,7 +35,6 @@ namespace Microsoft.AspNetCore.Components.RenderTree
 
             var editsSegment = editsBuffer.ToSegment(editsBufferStartLength, editsBuffer.Count);
             var result = new RenderTreeDiff(componentId, editsSegment);
-            ComponentsProfiling.Instance.End();
             return result;
         }
 
@@ -49,7 +46,6 @@ namespace Microsoft.AspNetCore.Components.RenderTree
             int oldStartIndex, int oldEndIndexExcl,
             int newStartIndex, int newEndIndexExcl)
         {
-            ProfilingStart();
             // This is deliberately a very large method. Parts of it could be factored out
             // into other private methods, but doing so comes at a consequential perf cost,
             // because it involves so much parameter passing. You can think of the code here
@@ -300,12 +296,10 @@ namespace Microsoft.AspNetCore.Components.RenderTree
                     diffContext.KeyedItemInfoDictionaryPool.Return(keyedItemInfos);
                 }
             }
-            ProfilingEnd();
         }
 
         private static Dictionary<object, KeyedItemInfo> BuildKeyToInfoLookup(DiffContext diffContext, int oldStartIndex, int oldEndIndexExcl, int newStartIndex, int newEndIndexExcl)
         {
-            ProfilingStart();
             var result = diffContext.KeyedItemInfoDictionaryPool.Get();
             var oldTree = diffContext.OldTree;
             var newTree = diffContext.NewTree;
@@ -351,7 +345,6 @@ namespace Microsoft.AspNetCore.Components.RenderTree
                 newStartIndex = NextSiblingIndex(frame, newStartIndex);
             }
 
-            ProfilingEnd();
             return result;
         }
 
@@ -384,7 +377,6 @@ namespace Microsoft.AspNetCore.Components.RenderTree
             int oldStartIndex, int oldEndIndexExcl,
             int newStartIndex, int newEndIndexExcl)
         {
-            ProfilingStart();
             // The overhead of the dictionary used by AppendAttributeDiffEntriesForRangeSlow is
             // significant, so we want to try and do a merge-join if possible, but fall back to
             // a hash-join if not. We'll do a merge join until we hit a case we can't handle and
@@ -433,7 +425,6 @@ namespace Microsoft.AspNetCore.Components.RenderTree
                             ref diffContext,
                             oldStartIndex, oldEndIndexExcl,
                             newStartIndex, newEndIndexExcl);
-                        ProfilingEnd();
                         return;
                     }
 
@@ -459,12 +450,9 @@ namespace Microsoft.AspNetCore.Components.RenderTree
                         ref diffContext,
                         oldStartIndex, oldEndIndexExcl,
                         newStartIndex, newEndIndexExcl);
-                    ProfilingEnd();
                     return;
                 }
             }
-
-            ProfilingEnd();
         }
 
         private static void AppendAttributeDiffEntriesForRangeSlow(
@@ -472,7 +460,6 @@ namespace Microsoft.AspNetCore.Components.RenderTree
             int oldStartIndex, int oldEndIndexExcl,
             int newStartIndex, int newEndIndexExcl)
         {
-            ProfilingStart();
             var oldTree = diffContext.OldTree;
             var newTree = diffContext.NewTree;
 
@@ -511,7 +498,6 @@ namespace Microsoft.AspNetCore.Components.RenderTree
 
             // We should have processed any additions at this point. Reset for the next batch.
             diffContext.AttributeDiffSet.Clear();
-            ProfilingEnd();
         }
 
         private static void UpdateRetainedChildComponent(
@@ -519,7 +505,6 @@ namespace Microsoft.AspNetCore.Components.RenderTree
             int oldComponentIndex,
             int newComponentIndex)
         {
-            ProfilingStart();
             var oldTree = diffContext.OldTree;
             var newTree = diffContext.NewTree;
             ref var oldComponentFrame = ref oldTree[oldComponentIndex];
@@ -546,8 +531,6 @@ namespace Microsoft.AspNetCore.Components.RenderTree
             {
                 componentState.SetDirectParameters(newParameters);
             }
-
-            ProfilingEnd();
         }
 
         private static int NextSiblingIndex(in RenderTreeFrame frame, int frameIndex)
@@ -570,7 +553,6 @@ namespace Microsoft.AspNetCore.Components.RenderTree
             int oldFrameIndex,
             int newFrameIndex)
         {
-            ProfilingStart();
             var oldTree = diffContext.OldTree;
             var newTree = diffContext.NewTree;
             ref var oldFrame = ref oldTree[oldFrameIndex];
@@ -583,7 +565,6 @@ namespace Microsoft.AspNetCore.Components.RenderTree
             {
                 InsertNewFrame(ref diffContext, newFrameIndex);
                 RemoveOldFrame(ref diffContext, oldFrameIndex);
-                ProfilingEnd();
                 return;
             }
 
@@ -709,8 +690,6 @@ namespace Microsoft.AspNetCore.Components.RenderTree
                 default:
                     throw new NotImplementedException($"Encountered unsupported frame type during diffing: {newTree[newFrameIndex].FrameType}");
             }
-
-            ProfilingEnd();
         }
 
         // This should only be called for attributes that have the same name. This is an
@@ -720,7 +699,6 @@ namespace Microsoft.AspNetCore.Components.RenderTree
             int oldFrameIndex,
             int newFrameIndex)
         {
-            ProfilingStart();
             var oldTree = diffContext.OldTree;
             var newTree = diffContext.NewTree;
             ref var oldFrame = ref oldTree[oldFrameIndex];
@@ -749,13 +727,10 @@ namespace Microsoft.AspNetCore.Components.RenderTree
                 // since it was unchanged.
                 newFrame = oldFrame;
             }
-
-            ProfilingEnd();
         }
 
         private static void InsertNewFrame(ref DiffContext diffContext, int newFrameIndex)
         {
-            ProfilingStart();
             var newTree = diffContext.NewTree;
             ref var newFrame = ref newTree[newFrameIndex];
             switch (newFrame.FrameType)
@@ -808,12 +783,10 @@ namespace Microsoft.AspNetCore.Components.RenderTree
                 default:
                     throw new NotImplementedException($"Unexpected frame type during {nameof(InsertNewFrame)}: {newFrame.FrameType}");
             }
-            ProfilingEnd();
         }
 
         private static void RemoveOldFrame(ref DiffContext diffContext, int oldFrameIndex)
         {
-            ProfilingStart();
             var oldTree = diffContext.OldTree;
             ref var oldFrame = ref oldTree[oldFrameIndex];
             switch (oldFrame.FrameType)
@@ -855,7 +828,6 @@ namespace Microsoft.AspNetCore.Components.RenderTree
                 default:
                     throw new NotImplementedException($"Unexpected frame type during {nameof(RemoveOldFrame)}: {oldFrame.FrameType}");
             }
-            ProfilingEnd();
         }
 
         private static int GetAttributesEndIndexExclusive(RenderTreeFrame[] tree, int rootIndex)
@@ -889,7 +861,6 @@ namespace Microsoft.AspNetCore.Components.RenderTree
 
         private static void InitializeNewSubtree(ref DiffContext diffContext, int frameIndex)
         {
-            ProfilingStart();
             var frames = diffContext.NewTree;
             var endIndexExcl = frameIndex + frames[frameIndex].ElementSubtreeLength;
             for (var i = frameIndex; i < endIndexExcl; i++)
@@ -911,12 +882,10 @@ namespace Microsoft.AspNetCore.Components.RenderTree
                         break;
                 }
             }
-            ProfilingEnd();
         }
 
         private static void InitializeNewComponentFrame(ref DiffContext diffContext, int frameIndex)
         {
-            ProfilingStart();
             var frames = diffContext.NewTree;
             ref var frame = ref frames[frameIndex];
 
@@ -933,7 +902,6 @@ namespace Microsoft.AspNetCore.Components.RenderTree
             var initialParametersLifetime = new ParameterViewLifetime(diffContext.BatchBuilder);
             var initialParameters = new ParameterView(initialParametersLifetime, frames, frameIndex);
             childComponentState.SetDirectParameters(initialParameters);
-            ProfilingEnd();
         }
 
         private static void InitializeNewAttributeFrame(ref DiffContext diffContext, ref RenderTreeFrame newFrame)
@@ -978,7 +946,6 @@ namespace Microsoft.AspNetCore.Components.RenderTree
 
         private static void DisposeFramesInRange(RenderBatchBuilder batchBuilder, RenderTreeFrame[] frames, int startIndex, int endIndexExcl)
         {
-            ProfilingStart();
             for (var i = startIndex; i < endIndexExcl; i++)
             {
                 ref var frame = ref frames[i];
@@ -991,7 +958,6 @@ namespace Microsoft.AspNetCore.Components.RenderTree
                     batchBuilder.DisposedEventHandlerIds.Append(frame.AttributeEventHandlerId);
                 }
             }
-            ProfilingEnd();
         }
 
         /// <summary>
@@ -1033,18 +999,5 @@ namespace Microsoft.AspNetCore.Components.RenderTree
                 SiblingIndex = 0;
             }
         }
-
-        // Having too many calls to ComponentsProfiling.Instance.Start/End has a measurable perf impact
-        // even when capturing is disabled. So, to enable detailed profiling for this class, define the
-        // Profile_RenderTreeDiffBuilder compiler symbol, otherwise the calls are compiled out entirely.
-        // Enabling detailed profiling adds about 5% to rendering benchmark times.
-
-        [Conditional("Profile_RenderTreeDiffBuilder")]
-        private static void ProfilingStart([CallerMemberName] string? name = null)
-            => ComponentsProfiling.Instance.Start(name);
-
-        [Conditional("Profile_RenderTreeDiffBuilder")]
-        private static void ProfilingEnd([CallerMemberName] string? name = null)
-            => ComponentsProfiling.Instance.End(name);
     }
 }
diff --git a/src/Components/Components/src/RenderTree/Renderer.cs b/src/Components/Components/src/RenderTree/Renderer.cs
index 446243201ff987c075bddb484f4aaa24bd540c3f..2f1e74a95cdfff068c8e8d57379cfc1fef6ec386 100644
--- a/src/Components/Components/src/RenderTree/Renderer.cs
+++ b/src/Components/Components/src/RenderTree/Renderer.cs
@@ -8,7 +8,6 @@ using System.Collections.Generic;
 using System.Diagnostics;
 using System.Threading;
 using System.Threading.Tasks;
-using Microsoft.AspNetCore.Components.Profiling;
 using Microsoft.AspNetCore.Components.Rendering;
 using Microsoft.Extensions.DependencyInjection;
 using Microsoft.Extensions.Logging;
@@ -247,7 +246,6 @@ namespace Microsoft.AspNetCore.Components.RenderTree
         /// </returns>
         public virtual Task DispatchEventAsync(ulong eventHandlerId, EventFieldInfo fieldInfo, EventArgs eventArgs)
         {
-            ComponentsProfiling.Instance.Start();
             Dispatcher.AssertAccess();
 
             if (!_eventBindings.TryGetValue(eventHandlerId, out var callback))
@@ -275,7 +273,6 @@ namespace Microsoft.AspNetCore.Components.RenderTree
             catch (Exception e)
             {
                 HandleException(e);
-                ComponentsProfiling.Instance.End();
                 return Task.CompletedTask;
             }
             finally
@@ -290,7 +287,6 @@ namespace Microsoft.AspNetCore.Components.RenderTree
             // Task completed synchronously or is still running. We already processed all of the rendering
             // work that was queued so let our error handler deal with it.
             var result = GetErrorHandledTask(task);
-            ComponentsProfiling.Instance.End();
             return result;
         }
 
@@ -441,7 +437,6 @@ namespace Microsoft.AspNetCore.Components.RenderTree
 
         private void ProcessRenderQueue()
         {
-            ComponentsProfiling.Instance.Start();
             Dispatcher.AssertAccess();
 
             if (_isBatchInProgress)
@@ -456,7 +451,6 @@ namespace Microsoft.AspNetCore.Components.RenderTree
             {
                 if (_batchBuilder.ComponentRenderQueue.Count == 0)
                 {
-                    ComponentsProfiling.Instance.End();
                     return;
                 }
 
@@ -468,9 +462,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree
                 }
 
                 var batch = _batchBuilder.ToBatch();
-                ComponentsProfiling.Instance.Start(nameof(UpdateDisplayAsync));
                 updateDisplayTask = UpdateDisplayAsync(batch);
-                ComponentsProfiling.Instance.End(nameof(UpdateDisplayAsync));
 
                 // Fire off the execution of OnAfterRenderAsync, but don't wait for it
                 // if there is async work to be done.
@@ -480,7 +472,6 @@ namespace Microsoft.AspNetCore.Components.RenderTree
             {
                 // Ensure we catch errors while running the render functions of the components.
                 HandleException(e);
-                ComponentsProfiling.Instance.End();
                 return;
             }
             finally
@@ -498,7 +489,6 @@ namespace Microsoft.AspNetCore.Components.RenderTree
             {
                 ProcessRenderQueue();
             }
-            ComponentsProfiling.Instance.End();
         }
 
         private Task InvokeRenderCompletedCalls(ArrayRange<RenderTreeDiff> updatedComponents, Task updateDisplayTask)
diff --git a/src/Components/Components/src/Rendering/ComponentState.cs b/src/Components/Components/src/Rendering/ComponentState.cs
index 7b755efd5dfc30fc0dcf482039a9d78f89426766..3655d493350b5a3aa23ca95b6d9636384134801f 100644
--- a/src/Components/Components/src/Rendering/ComponentState.cs
+++ b/src/Components/Components/src/Rendering/ComponentState.cs
@@ -5,7 +5,6 @@ using System;
 using System.Collections.Generic;
 using System.Diagnostics.CodeAnalysis;
 using System.Threading.Tasks;
-using Microsoft.AspNetCore.Components.Profiling;
 using Microsoft.AspNetCore.Components.RenderTree;
 
 namespace Microsoft.AspNetCore.Components.Rendering
@@ -57,7 +56,6 @@ namespace Microsoft.AspNetCore.Components.Rendering
 
         public void RenderIntoBatch(RenderBatchBuilder batchBuilder, RenderFragment renderFragment)
         {
-            ComponentsProfiling.Instance.Start();
             // A component might be in the render queue already before getting disposed by an
             // earlier entry in the render queue. In that case, rendering is a no-op.
             if (_componentWasDisposed)
@@ -69,9 +67,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
             (CurrentRenderTree, _renderTreeBuilderPrevious) = (_renderTreeBuilderPrevious, CurrentRenderTree);
 
             CurrentRenderTree.Clear();
-            ComponentsProfiling.Instance.Start("BuildRenderTree");
             renderFragment(CurrentRenderTree);
-            ComponentsProfiling.Instance.End("BuildRenderTree");
 
             var diff = RenderTreeDiffBuilder.ComputeDiff(
                 _renderer,
@@ -81,7 +77,6 @@ namespace Microsoft.AspNetCore.Components.Rendering
                 CurrentRenderTree.GetFrames());
             batchBuilder.UpdatedComponentDiffs.Append(diff);
             batchBuilder.InvalidateParameterViews();
-            ComponentsProfiling.Instance.End();
         }
 
         public bool TryDisposeInBatch(RenderBatchBuilder batchBuilder, [NotNullWhen(false)] out Exception? exception)
diff --git a/src/Components/Components/src/Rendering/RenderTreeBuilder.cs b/src/Components/Components/src/Rendering/RenderTreeBuilder.cs
index 4e9280c70f4b9654bc4d18961a8e538c4cdb1d83..566265c98880d96deb3e2d6b701e9e63c80e641d 100644
--- a/src/Components/Components/src/Rendering/RenderTreeBuilder.cs
+++ b/src/Components/Components/src/Rendering/RenderTreeBuilder.cs
@@ -5,7 +5,6 @@ using System;
 using System.Collections.Generic;
 using System.Diagnostics;
 using System.Runtime.CompilerServices;
-using Microsoft.AspNetCore.Components.Profiling;
 using Microsoft.AspNetCore.Components.RenderTree;
 
 namespace Microsoft.AspNetCore.Components.Rendering
@@ -45,7 +44,6 @@ namespace Microsoft.AspNetCore.Components.Rendering
         /// <param name="elementName">A value representing the type of the element.</param>
         public void OpenElement(int sequence, string elementName)
         {
-            ProfilingStart();
             // We are entering a new scope, since we track the "duplicate attributes" per
             // element/component we might need to clean them up now.
             if (_hasSeenAddMultipleAttributes)
@@ -56,7 +54,6 @@ namespace Microsoft.AspNetCore.Components.Rendering
 
             _openElementIndices.Push(_entries.Count);
             Append(RenderTreeFrame.Element(sequence, elementName));
-            ProfilingEnd();
         }
 
         /// <summary>
@@ -65,7 +62,6 @@ namespace Microsoft.AspNetCore.Components.Rendering
         /// </summary>
         public void CloseElement()
         {
-            ProfilingStart();
             var indexOfEntryBeingClosed = _openElementIndices.Pop();
 
             // We might be closing an element with only attributes, run the duplicate cleanup pass
@@ -77,7 +73,6 @@ namespace Microsoft.AspNetCore.Components.Rendering
 
             ref var entry = ref _entries.Buffer[indexOfEntryBeingClosed];
             entry = entry.WithElementSubtreeLength(_entries.Count - indexOfEntryBeingClosed);
-            ProfilingEnd();
         }
 
         /// <summary>
@@ -87,9 +82,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
         /// <param name="markupContent">Content for the new markup frame.</param>
         public void AddMarkupContent(int sequence, string? markupContent)
         {
-            ProfilingStart();
             Append(RenderTreeFrame.Markup(sequence, markupContent ?? string.Empty));
-            ProfilingEnd();
         }
 
         /// <summary>
@@ -99,9 +92,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
         /// <param name="textContent">Content for the new text frame.</param>
         public void AddContent(int sequence, string? textContent)
         {
-            ProfilingStart();
             Append(RenderTreeFrame.Text(sequence, textContent ?? string.Empty));
-            ProfilingEnd();
         }
 
         /// <summary>
@@ -111,7 +102,6 @@ namespace Microsoft.AspNetCore.Components.Rendering
         /// <param name="fragment">Content to append.</param>
         public void AddContent(int sequence, RenderFragment? fragment)
         {
-            ProfilingStart();
             if (fragment != null)
             {
                 // We surround the fragment with a region delimiter to indicate that the
@@ -122,7 +112,6 @@ namespace Microsoft.AspNetCore.Components.Rendering
                 fragment(this);
                 CloseRegion();
             }
-            ProfilingEnd();
         }
 
         /// <summary>
@@ -133,12 +122,10 @@ namespace Microsoft.AspNetCore.Components.Rendering
         /// <param name="value">The value used by <paramref name="fragment"/>.</param>
         public void AddContent<TValue>(int sequence, RenderFragment<TValue>? fragment, TValue value)
         {
-            ProfilingStart();
             if (fragment != null)
             {
                 AddContent(sequence, fragment(value));
             }
-            ProfilingEnd();
         }
 
         /// <summary>
@@ -169,15 +156,12 @@ namespace Microsoft.AspNetCore.Components.Rendering
         /// <param name="name">The name of the attribute.</param>
         public void AddAttribute(int sequence, string name)
         {
-            ProfilingStart();
-
             if (_lastNonAttributeFrameType != RenderTreeFrameType.Element)
             {
                 throw new InvalidOperationException($"Valueless attributes may only be added immediately after frames of type {RenderTreeFrameType.Element}");
             }
 
             Append(RenderTreeFrame.Attribute(sequence, name, BoxedTrue));
-            ProfilingEnd();
         }
 
         /// <summary>
@@ -194,7 +178,6 @@ namespace Microsoft.AspNetCore.Components.Rendering
         /// <param name="value">The value of the attribute.</param>
         public void AddAttribute(int sequence, string name, bool value)
         {
-            ProfilingStart();
             AssertCanAddAttribute();
             if (_lastNonAttributeFrameType == RenderTreeFrameType.Component)
             {
@@ -210,7 +193,6 @@ namespace Microsoft.AspNetCore.Components.Rendering
             {
                 TrackAttributeName(name);
             }
-            ProfilingEnd();
         }
 
         /// <summary>
@@ -227,7 +209,6 @@ namespace Microsoft.AspNetCore.Components.Rendering
         /// <param name="value">The value of the attribute.</param>
         public void AddAttribute(int sequence, string name, string? value)
         {
-            ProfilingStart();
             AssertCanAddAttribute();
             if (value != null || _lastNonAttributeFrameType == RenderTreeFrameType.Component)
             {
@@ -237,7 +218,6 @@ namespace Microsoft.AspNetCore.Components.Rendering
             {
                 TrackAttributeName(name);
             }
-            ProfilingEnd();
         }
 
         /// <summary>
@@ -254,7 +234,6 @@ namespace Microsoft.AspNetCore.Components.Rendering
         /// <param name="value">The value of the attribute.</param>
         public void AddAttribute(int sequence, string name, MulticastDelegate? value)
         {
-            ProfilingStart();
             AssertCanAddAttribute();
             if (value != null || _lastNonAttributeFrameType == RenderTreeFrameType.Component)
             {
@@ -264,7 +243,6 @@ namespace Microsoft.AspNetCore.Components.Rendering
             {
                 TrackAttributeName(name);
             }
-            ProfilingEnd();
         }
 
         /// <summary>
@@ -285,7 +263,6 @@ namespace Microsoft.AspNetCore.Components.Rendering
         /// </remarks>
         public void AddAttribute(int sequence, string name, EventCallback value)
         {
-            ProfilingStart();
             AssertCanAddAttribute();
             if (_lastNonAttributeFrameType == RenderTreeFrameType.Component)
             {
@@ -310,7 +287,6 @@ namespace Microsoft.AspNetCore.Components.Rendering
                 // Track the attribute name if needed since we elided the frame.
                 TrackAttributeName(name);
             }
-            ProfilingEnd();
         }
 
         /// <summary>
@@ -331,7 +307,6 @@ namespace Microsoft.AspNetCore.Components.Rendering
         /// </remarks>
         public void AddAttribute<TArgument>(int sequence, string name, EventCallback<TArgument> value)
         {
-            ProfilingStart();
             AssertCanAddAttribute();
             if (_lastNonAttributeFrameType == RenderTreeFrameType.Component)
             {
@@ -356,7 +331,6 @@ namespace Microsoft.AspNetCore.Components.Rendering
                 // Track the attribute name if needed since we elided the frame.
                 TrackAttributeName(name);
             }
-            ProfilingEnd();
         }
 
         /// <summary>
@@ -370,7 +344,6 @@ namespace Microsoft.AspNetCore.Components.Rendering
         /// <param name="value">The value of the attribute.</param>
         public void AddAttribute(int sequence, string name, object? value)
         {
-            ProfilingStart();
             // This looks a bit daunting because we need to handle the boxed/object version of all of the
             // types that AddAttribute special cases.
             if (_lastNonAttributeFrameType == RenderTreeFrameType.Element)
@@ -423,7 +396,6 @@ namespace Microsoft.AspNetCore.Components.Rendering
                 // This is going to throw. Calling it just to get a consistent exception message.
                 AssertCanAddAttribute();
             }
-            ProfilingEnd();
         }
 
         /// <summary>
@@ -438,7 +410,6 @@ namespace Microsoft.AspNetCore.Components.Rendering
         /// <param name="frame">A <see cref="RenderTreeFrame"/> holding the name and value of the attribute.</param>
         public void AddAttribute(int sequence, in RenderTreeFrame frame)
         {
-            ProfilingStart();
             if (frame.FrameType != RenderTreeFrameType.Attribute)
             {
                 throw new ArgumentException($"The {nameof(frame.FrameType)} must be {RenderTreeFrameType.Attribute}.");
@@ -446,7 +417,6 @@ namespace Microsoft.AspNetCore.Components.Rendering
 
             AssertCanAddAttribute();
             Append(frame.WithAttributeSequence(sequence));
-            ProfilingEnd();
         }
 
         /// <summary>
@@ -456,7 +426,6 @@ namespace Microsoft.AspNetCore.Components.Rendering
         /// <param name="attributes">A collection of key-value pairs representing attributes.</param>
         public void AddMultipleAttributes(int sequence, IEnumerable<KeyValuePair<string, object>>? attributes)
         {
-            ProfilingStart();
             // Calling this up-front just to make sure we validate before mutating anything.
             AssertCanAddAttribute();
 
@@ -473,7 +442,6 @@ namespace Microsoft.AspNetCore.Components.Rendering
                     AddAttribute(sequence, attribute.Key, attribute.Value);
                 }
             }
-            ProfilingEnd();
         }
 
         /// <summary>
@@ -490,7 +458,6 @@ namespace Microsoft.AspNetCore.Components.Rendering
         /// <param name="updatesAttributeName">The name of another attribute whose value can be updated when the event handler is executed.</param>
         public void SetUpdatesAttributeName(string updatesAttributeName)
         {
-            ProfilingStart();
             if (_entries.Count == 0)
             {
                 throw new InvalidOperationException("No preceding attribute frame exists.");
@@ -503,7 +470,6 @@ namespace Microsoft.AspNetCore.Components.Rendering
             }
 
             prevFrame = prevFrame.WithAttributeEventUpdatesAttributeName(updatesAttributeName);
-            ProfilingEnd();
         }
 
         /// <summary>
@@ -535,12 +501,10 @@ namespace Microsoft.AspNetCore.Components.Rendering
         /// <param name="value">The value for the key.</param>
         public void SetKey(object? value)
         {
-            ProfilingStart();
             if (value == null)
             {
                 // Null is equivalent to not having set a key, which is valuable because Razor syntax doesn't have an
                 // easy way to have conditional directive attributes
-                ProfilingEnd();
                 return;
             }
 
@@ -563,12 +527,10 @@ namespace Microsoft.AspNetCore.Components.Rendering
                 default:
                     throw new InvalidOperationException($"Cannot set a key on a frame of type {parentFrame.FrameType}.");
             }
-            ProfilingEnd();
         }
 
         private void OpenComponentUnchecked(int sequence, Type componentType)
         {
-            ProfilingStart();
             // We are entering a new scope, since we track the "duplicate attributes" per
             // element/component we might need to clean them up now.
             if (_hasSeenAddMultipleAttributes)
@@ -579,7 +541,6 @@ namespace Microsoft.AspNetCore.Components.Rendering
 
             _openElementIndices.Push(_entries.Count);
             Append(RenderTreeFrame.ChildComponent(sequence, componentType));
-            ProfilingEnd();
         }
 
         /// <summary>
@@ -588,7 +549,6 @@ namespace Microsoft.AspNetCore.Components.Rendering
         /// </summary>
         public void CloseComponent()
         {
-            ProfilingStart();
             var indexOfEntryBeingClosed = _openElementIndices.Pop();
 
             // We might be closing a component with only attributes. Run the attribute cleanup pass
@@ -600,7 +560,6 @@ namespace Microsoft.AspNetCore.Components.Rendering
 
             ref var entry = ref _entries.Buffer[indexOfEntryBeingClosed];
             entry = entry.WithComponentSubtreeLength(_entries.Count - indexOfEntryBeingClosed);
-            ProfilingEnd();
         }
 
         /// <summary>
@@ -610,14 +569,12 @@ namespace Microsoft.AspNetCore.Components.Rendering
         /// <param name="elementReferenceCaptureAction">An action to be invoked whenever the reference value changes.</param>
         public void AddElementReferenceCapture(int sequence, Action<ElementReference> elementReferenceCaptureAction)
         {
-            ProfilingStart();
             if (GetCurrentParentFrameType() != RenderTreeFrameType.Element)
             {
                 throw new InvalidOperationException($"Element reference captures may only be added as children of frames of type {RenderTreeFrameType.Element}");
             }
 
             Append(RenderTreeFrame.ElementReferenceCapture(sequence, elementReferenceCaptureAction));
-            ProfilingEnd();
         }
 
         /// <summary>
@@ -627,7 +584,6 @@ namespace Microsoft.AspNetCore.Components.Rendering
         /// <param name="componentReferenceCaptureAction">An action to be invoked whenever the reference value changes.</param>
         public void AddComponentReferenceCapture(int sequence, Action<object?> componentReferenceCaptureAction)
         {
-            ProfilingStart();
             var parentFrameIndex = GetCurrentParentFrameIndex();
             if (!parentFrameIndex.HasValue)
             {
@@ -641,7 +597,6 @@ namespace Microsoft.AspNetCore.Components.Rendering
             }
 
             Append(RenderTreeFrame.ComponentReferenceCapture(sequence, componentReferenceCaptureAction, parentFrameIndexValue));
-            ProfilingEnd();
         }
 
         /// <summary>
@@ -650,7 +605,6 @@ namespace Microsoft.AspNetCore.Components.Rendering
         /// <param name="sequence">An integer that represents the position of the instruction in the source code.</param>
         public void OpenRegion(int sequence)
         {
-            ProfilingStart();
             // We are entering a new scope, since we track the "duplicate attributes" per
             // element/component we might need to clean them up now.
             if (_hasSeenAddMultipleAttributes)
@@ -661,7 +615,6 @@ namespace Microsoft.AspNetCore.Components.Rendering
 
             _openElementIndices.Push(_entries.Count);
             Append(RenderTreeFrame.Region(sequence));
-            ProfilingEnd();
         }
 
         /// <summary>
@@ -670,11 +623,9 @@ namespace Microsoft.AspNetCore.Components.Rendering
         /// </summary>
         public void CloseRegion()
         {
-            ProfilingStart();
             var indexOfEntryBeingClosed = _openElementIndices.Pop();
             ref var entry = ref _entries.Buffer[indexOfEntryBeingClosed];
             entry = entry.WithRegionSubtreeLength(_entries.Count - indexOfEntryBeingClosed);
-            ProfilingEnd();
         }
 
         private void AssertCanAddAttribute()
@@ -702,29 +653,24 @@ namespace Microsoft.AspNetCore.Components.Rendering
         /// </summary>
         public void Clear()
         {
-            ProfilingStart();
             _entries.Clear();
             _openElementIndices.Clear();
             _lastNonAttributeFrameType = null;
             _hasSeenAddMultipleAttributes = false;
             _seenAttributeNames?.Clear();
-            ProfilingEnd();
         }
 
         // internal because this should only be used during the post-event tree patching logic
         // It's expensive because it involves copying all the subsequent memory in the array
         internal void InsertAttributeExpensive(int insertAtIndex, int sequence, string attributeName, object? attributeValue)
         {
-            ProfilingStart();
             // Replicate the same attribute omission logic as used elsewhere
             if ((attributeValue == null) || (attributeValue is bool boolValue && !boolValue))
             {
-                ProfilingEnd();
                 return;
             }
 
             _entries.InsertExpensive(insertAtIndex, RenderTreeFrame.Attribute(sequence, attributeName, attributeValue));
-            ProfilingEnd();
         }
 
         /// <summary>
@@ -748,7 +694,6 @@ namespace Microsoft.AspNetCore.Components.Rendering
         // Internal for testing
         internal void ProcessDuplicateAttributes(int first)
         {
-            ProfilingStart();
             Debug.Assert(_hasSeenAddMultipleAttributes);
 
             // When AddMultipleAttributes method has been called, we need to postprocess attributes while closing
@@ -830,7 +775,6 @@ namespace Microsoft.AspNetCore.Components.Rendering
 
             seenAttributeNames.Clear();
             _hasSeenAddMultipleAttributes = false;
-            ProfilingEnd();
         }
 
         // Internal for testing
@@ -847,22 +791,7 @@ namespace Microsoft.AspNetCore.Components.Rendering
 
         void IDisposable.Dispose()
         {
-            ProfilingStart();
             _entries.Dispose();
-            ProfilingEnd();
         }
-
-        // Having too many calls to ComponentsProfiling.Instance.Start/End has a measurable perf impact
-        // even when capturing is disabled. So, to enable detailed profiling for this class, define the
-        // Profile_RenderTreeBuilder compiler symbol, otherwise the calls are compiled out entirely.
-        // Enabling detailed profiling adds about 5% to rendering benchmark times.
-
-        [Conditional("Profile_RenderTreeBuilder")]
-        private static void ProfilingStart([CallerMemberName] string? name = null)
-            => ComponentsProfiling.Instance.Start(name);
-
-        [Conditional("Profile_RenderTreeBuilder")]
-        private static void ProfilingEnd([CallerMemberName] string? name = null)
-            => ComponentsProfiling.Instance.End(name);
     }
 }
diff --git a/src/Components/Web.JS/dist/Release/blazor.server.js b/src/Components/Web.JS/dist/Release/blazor.server.js
index 32c6455b2002826fbd11c96111043d917ecb96ab..97da9d5ed9498ef67c205008e9f2b51dbdecc0e9 100644
Binary files a/src/Components/Web.JS/dist/Release/blazor.server.js and b/src/Components/Web.JS/dist/Release/blazor.server.js differ
diff --git a/src/Components/Web.JS/dist/Release/blazor.webassembly.js b/src/Components/Web.JS/dist/Release/blazor.webassembly.js
index cd99a3089a22c368fc35f4e72cab4227fa267207..c335f6c5649a4aa07b6637179fcc2199e5aa1049 100644
Binary files a/src/Components/Web.JS/dist/Release/blazor.webassembly.js and b/src/Components/Web.JS/dist/Release/blazor.webassembly.js differ
diff --git a/src/Components/Web.JS/src/Boot.Server.ts b/src/Components/Web.JS/src/Boot.Server.ts
index c781d52aacc8e300ea0bfd2fd40617a3b95a0e9f..ed5647dd128eecff705e58278bf058f957239944 100644
--- a/src/Components/Web.JS/src/Boot.Server.ts
+++ b/src/Components/Web.JS/src/Boot.Server.ts
@@ -12,7 +12,6 @@ import { setEventDispatcher } from './Rendering/RendererEventDispatcher';
 import { resolveOptions, CircuitStartOptions } from './Platform/Circuits/CircuitStartOptions';
 import { DefaultReconnectionHandler } from './Platform/Circuits/DefaultReconnectionHandler';
 import { attachRootComponentToLogicalElement } from './Rendering/Renderer';
-import { initializeProfiling } from './Platform/Profiling';
 
 let renderingFailed = false;
 let started = false;
@@ -22,7 +21,6 @@ async function boot(userOptions?: Partial<CircuitStartOptions>): Promise<void> {
     throw new Error('Blazor has already started.');
   }
   started = true;
-  initializeProfiling(null);
 
   // Establish options to be used
   const options = resolveOptions(userOptions);
diff --git a/src/Components/Web.JS/src/Boot.WebAssembly.ts b/src/Components/Web.JS/src/Boot.WebAssembly.ts
index 99b117ad78d50305a8571622f80946aca3f81409..b0a08fe2b4b6820331b5fe0e422a354783c76f86 100644
--- a/src/Components/Web.JS/src/Boot.WebAssembly.ts
+++ b/src/Components/Web.JS/src/Boot.WebAssembly.ts
@@ -11,7 +11,6 @@ import { WebAssemblyConfigLoader } from './Platform/WebAssemblyConfigLoader';
 import { BootConfigResult } from './Platform/BootConfig';
 import { Pointer } from './Platform/Platform';
 import { WebAssemblyStartOptions } from './Platform/WebAssemblyStartOptions';
-import { profileStart, profileEnd } from './Platform/Profiling';
 
 let started = false;
 
@@ -34,8 +33,6 @@ async function boot(options?: Partial<WebAssemblyStartOptions>): Promise<void> {
   const platform = Environment.setPlatform(monoPlatform);
   window['Blazor'].platform = platform;
   window['Blazor']._internal.renderBatch = (browserRendererId: number, batchAddress: Pointer) => {
-    profileStart('renderBatch');
-
     // We're going to read directly from the .NET memory heap, so indicate to the platform
     // that we don't want anything to modify the memory contents during this time. Currently this
     // is only guaranteed by the fact that .NET code doesn't run during this time, but in the
@@ -47,8 +44,6 @@ async function boot(options?: Partial<WebAssemblyStartOptions>): Promise<void> {
     } finally {
       heapLock.release();
     }
-
-    profileEnd('renderBatch');
   };
 
   // Configure navigation via JS Interop
diff --git a/src/Components/Web.JS/src/GlobalExports.ts b/src/Components/Web.JS/src/GlobalExports.ts
index 08f7557ba510f8e3f11d516b1b5b398ff8f5030d..df7f9b18a8425af1fab0281c842d67f1b5c573be 100644
--- a/src/Components/Web.JS/src/GlobalExports.ts
+++ b/src/Components/Web.JS/src/GlobalExports.ts
@@ -1,7 +1,6 @@
 import { navigateTo, internalFunctions as navigationManagerInternalFunctions } from './Services/NavigationManager';
 import { attachRootComponentToElement } from './Rendering/Renderer';
 import { domFunctions } from './DomWrapper';
-import { setProfilingEnabled } from './Platform/Profiling';
 
 // Make the following APIs available in global scope for invocation from JS
 window['Blazor'] = {
@@ -11,6 +10,5 @@ window['Blazor'] = {
     attachRootComponentToElement,
     navigationManager: navigationManagerInternalFunctions,
     domWrapper: domFunctions,
-    setProfilingEnabled: setProfilingEnabled,
   },
 };
diff --git a/src/Components/Web.JS/src/Platform/Mono/MonoPlatform.ts b/src/Components/Web.JS/src/Platform/Mono/MonoPlatform.ts
index d2abd2a70edf03bccae334316b6bc7c23694b87e..5a5eda94224352a31bb8b659033fca19d448484e 100644
--- a/src/Components/Web.JS/src/Platform/Mono/MonoPlatform.ts
+++ b/src/Components/Web.JS/src/Platform/Mono/MonoPlatform.ts
@@ -5,7 +5,6 @@ import { WebAssemblyResourceLoader, LoadingResource } from '../WebAssemblyResour
 import { Platform, System_Array, Pointer, System_Object, System_String, HeapLock } from '../Platform';
 import { loadTimezoneData } from './TimezoneDataFile';
 import { WebAssemblyBootResourceType } from '../WebAssemblyStartOptions';
-import { initializeProfiling } from '../Profiling';
 
 let mono_wasm_add_assembly: (name: string, heapAddress: number, length: number) => void;
 const appBinDirName = 'appBinDir';
@@ -36,10 +35,6 @@ export const monoPlatform: Platform = {
   start: function start(resourceLoader: WebAssemblyResourceLoader) {
     return new Promise<void>((resolve, reject) => {
       attachDebuggerHotkey(resourceLoader);
-      initializeProfiling(isCapturing => {
-        const setCapturingMethod = bindStaticMethod('Microsoft.AspNetCore.Components', 'Microsoft.AspNetCore.Components.Profiling.WebAssemblyComponentsProfiling', 'SetCapturing');
-        setCapturingMethod(isCapturing);
-      });
 
       // dotnet.js assumes the existence of this
       window['Browser'] = {
diff --git a/src/Components/Web.JS/src/Platform/Profiling.ts b/src/Components/Web.JS/src/Platform/Profiling.ts
deleted file mode 100644
index 9c4f0f220b445ad39deed852ea93d43e255c9cfb..0000000000000000000000000000000000000000
--- a/src/Components/Web.JS/src/Platform/Profiling.ts
+++ /dev/null
@@ -1,134 +0,0 @@
-import { System_String } from './Platform';
-
-interface TimingEntry {
-    // To minimize overhead, don't even decode the strings that arrive from .NET. Assume they are compile-time constants
-    // and hence the memory address will be fixed, so we can just store the pointer value.
-    name: string | System_String;
-    type: 'start' | 'end';
-    timestamp: number;
-}
-
-interface TraceEvent {
-    // https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/preview
-    name: string;
-    cat: string; // Category
-    ph: 'B' | 'E'; // Phase
-    ts: number; // Timestamp in microseconds
-    pid: number; // Process ID
-    tid: number; // Thread ID
-}
-
-let updateCapturingStateInHost: (isCapturing: boolean) => void;
-let captureStartTime = 0;
-const blazorProfilingEnabledKey = 'blazorProfilingEnabled';
-const profilingEnabled = !!localStorage[blazorProfilingEnabledKey];
-const entryLog: TimingEntry[] = [];
-const openRegionsStack: (string | System_String)[] = [];
-
-export function setProfilingEnabled(enabled: boolean) {
-    // We only wire up the hotkeys etc. if the following localStorage entry is present during startup
-    // This is to ensure we're not interfering with any other hotkeys that developers might want to
-    // use for different purposes, plus it gives us a single point where we can notify .NET code during
-    // startup about whether profiling should be enabled.
-    localStorage[blazorProfilingEnabledKey] = (enabled !== false);
-    location.reload();
-}
-
-export function initializeProfiling(setCapturingCallback: ((isCapturing: boolean) => void) | null) {
-    if (!profilingEnabled) {
-        return;
-    }
-
-    updateCapturingStateInHost = setCapturingCallback || (() => {});
-
-    // Attach hotkey (alt/cmd)+shift+p
-    const altKeyName = navigator.platform.match(/^Mac/i) ? 'Cmd' : 'Alt';
-    console.info(`Profiling hotkey: Shift+${altKeyName}+P (when application has focus)`);
-    document.addEventListener('keydown', evt => {
-        if (evt.shiftKey && (evt.metaKey || evt.altKey) && evt.code === 'KeyP') {
-            toggleCaptureEnabled();
-        }
-    });
-}
-
-export function profileStart(name: System_String | string) {
-    if (!captureStartTime) {
-        return;
-    }
-
-    const startTime = performance.now();
-    openRegionsStack.push(name);
-    entryLog.push({ name: name, type: 'start', timestamp: startTime });
-}
-
-export function profileEnd(name: System_String | string) {
-    if (!captureStartTime) {
-        return;
-    }
-
-    const endTime = performance.now();
-    const poppedRegionName = openRegionsStack.pop();
-    if (!poppedRegionName) {
-        throw new Error(`Profiling mismatch: tried to end profiling for '${readJsString(name)}', but the stack was empty.`);
-    } else if (poppedRegionName !== name) {
-        throw new Error(`Profiling mismatch: tried to end profiling for '${readJsString(name)}', but the top stack item was '${readJsString(poppedRegionName)}'.`);
-    }
-
-    entryLog.push({ name: name, type: 'end', timestamp: endTime });
-}
-
-function profileReset() {
-    openRegionsStack.length = 0;
-    entryLog.length = 0;
-    captureStartTime = 0;
-    updateCapturingStateInHost(false);
-}
-
-function profileExport() {
-    const traceEvents: TraceEvent[] = entryLog.map(entry => ({
-        name: readJsString(entry.name)!,
-        cat: 'PERF',
-        ph: entry.type === 'start' ? 'B': 'E',
-        ts: (entry.timestamp - captureStartTime) * 1000,
-        pid: 0,
-        tid: 0,
-    }));
-    const traceEventsJson = JSON.stringify(traceEvents);
-    const traceEventsBuffer = new TextEncoder().encode(traceEventsJson);
-    const anchorElement = document.createElement('a');
-    anchorElement.href = URL.createObjectURL(new Blob([traceEventsBuffer]));
-    anchorElement.setAttribute('download', 'trace.json');
-    anchorElement.click();
-    URL.revokeObjectURL(anchorElement.href);
-}
-
-function toggleCaptureEnabled() {
-    if (!captureStartTime) {
-        displayOverlayMessage('Started capturing performance profile...');
-        updateCapturingStateInHost(true);
-        captureStartTime = performance.now();
-    } else {
-        displayOverlayMessage('Finished capturing performance profile');
-        profileExport();
-        profileReset();
-    }
-}
-
-function displayOverlayMessage(message: string) {
-    const elem = document.createElement('div');
-    elem.textContent = message;
-    elem.setAttribute('style', 'position: absolute; z-index: 99999; font-family: \'Sans Serif\'; top: 0; left: 0; padding: 4px; font-size: 12px; background-color: purple; color: white;');
-    document.body.appendChild(elem);
-    setTimeout(() => document.body.removeChild(elem), 3000);
-}
-
-function readJsString(str: string | System_String) {
-    // This is expensive, so don't do it while capturing timings. Only do it as part of the export process.
-    return typeof str === 'string' ? str : BINDING.conv_string(str);
-}
-
-// These globals deliberately differ from our normal conventions for attaching functions inside Blazor.*
-// because the intention is to minimize overhead in all reasonable ways. Having any dot-separators in the
-// name would cause extra string allocations on every invocation.
-window['_blazorProfileStart'] = profileStart;
-window['_blazorProfileEnd'] = profileEnd;
diff --git a/src/Components/Web.JS/src/Rendering/BrowserRenderer.ts b/src/Components/Web.JS/src/Rendering/BrowserRenderer.ts
index 0c9c3ea2b96adb82869d52af84a5e1144211dc91..14faf703291c9dd6eeca511f6870f09cc602f17d 100644
--- a/src/Components/Web.JS/src/Rendering/BrowserRenderer.ts
+++ b/src/Components/Web.JS/src/Rendering/BrowserRenderer.ts
@@ -6,7 +6,6 @@ import { applyCaptureIdToElement } from './ElementReferenceCapture';
 import { EventFieldInfo } from './EventFieldInfo';
 import { dispatchEvent } from './RendererEventDispatcher';
 import { attachToEventDelegator as attachNavigationManagerToEventDelegator } from '../Services/NavigationManager';
-import { profileEnd, profileStart } from '../Platform/Profiling';
 const selectValuePropname = '_blazorSelectValue';
 const sharedTemplateElemForParsing = document.createElement('template');
 const sharedSvgElemForParsing = document.createElementNS('http://www.w3.org/2000/svg', 'g');
@@ -41,8 +40,6 @@ export class BrowserRenderer {
   }
 
   public updateComponent(batch: RenderBatch, componentId: number, edits: ArrayBuilderSegment<RenderTreeEdit>, referenceFrames: ArrayValues<RenderTreeFrame>): void {
-    profileStart('updateComponent');
-
     const element = this.childComponentLocations[componentId];
     if (!element) {
       throw new Error(`No element is currently associated with component ${componentId}`);
@@ -70,8 +67,6 @@ export class BrowserRenderer {
     if ((activeElementBefore instanceof HTMLElement) && ownerDocument && ownerDocument.activeElement !== activeElementBefore) {
       activeElementBefore.focus();
     }
-
-    profileEnd('updateComponent');
   }
 
   public disposeComponent(componentId: number) {
diff --git a/src/Components/Shared/src/WebAssemblyJSInteropInternalCalls.cs b/src/Components/WebAssembly/JSInterop/src/InternalCalls.cs
similarity index 100%
rename from src/Components/Shared/src/WebAssemblyJSInteropInternalCalls.cs
rename to src/Components/WebAssembly/JSInterop/src/InternalCalls.cs
diff --git a/src/Components/WebAssembly/JSInterop/src/Microsoft.JSInterop.WebAssembly.csproj b/src/Components/WebAssembly/JSInterop/src/Microsoft.JSInterop.WebAssembly.csproj
index 64d0f52e10829e1a042792eafffd77288bbdc5d4..8b58eb02dd684e0421c5ed823004d5eab13d6518 100644
--- a/src/Components/WebAssembly/JSInterop/src/Microsoft.JSInterop.WebAssembly.csproj
+++ b/src/Components/WebAssembly/JSInterop/src/Microsoft.JSInterop.WebAssembly.csproj
@@ -9,7 +9,6 @@
   </PropertyGroup>
 
   <ItemGroup>
-    <Compile Include="$(ComponentsSharedSourceRoot)src\WebAssemblyJSInteropInternalCalls.cs" />
     <Reference Include="Microsoft.JSInterop" />
   </ItemGroup>