This commit is contained in:
parent
285110de91
commit
7f4dd27551
|
|
@ -79,7 +79,7 @@ namespace Microsoft.AspNetCore.Components.Performance
|
||||||
[Benchmark(Description = "RenderTreeDiffBuilder: Input and validation on a single form field.", Baseline = true)]
|
[Benchmark(Description = "RenderTreeDiffBuilder: Input and validation on a single form field.", Baseline = true)]
|
||||||
public void ComputeDiff_SingleFormField()
|
public void ComputeDiff_SingleFormField()
|
||||||
{
|
{
|
||||||
builder.Clear();
|
builder.ClearStateForCurrentBatch();
|
||||||
var diff = RenderTreeDiffBuilder.ComputeDiff(renderer, builder, 0, original.GetFrames(), modified.GetFrames());
|
var diff = RenderTreeDiffBuilder.ComputeDiff(renderer, builder, 0, original.GetFrames(), modified.GetFrames());
|
||||||
GC.KeepAlive(diff);
|
GC.KeepAlive(diff);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
@ -30,11 +30,17 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
||||||
// Scratch data structure for understanding attribute diffs.
|
// Scratch data structure for understanding attribute diffs.
|
||||||
public Dictionary<string, int> AttributeDiffSet { get; } = new Dictionary<string, int>();
|
public Dictionary<string, int> AttributeDiffSet { get; } = new Dictionary<string, int>();
|
||||||
|
|
||||||
public void Clear()
|
public void ClearStateForCurrentBatch()
|
||||||
{
|
{
|
||||||
|
// This method is used to reset the builder back to a default state so it can
|
||||||
|
// begin building the next batch. That means clearing all the tracked state, but
|
||||||
|
// *not* clearing ComponentRenderQueue because that may hold information about
|
||||||
|
// the next batch we want to build. We shouldn't ever need to clear
|
||||||
|
// ComponentRenderQueue explicitly, because it gets cleared as an aspect of
|
||||||
|
// processing the render queue.
|
||||||
|
|
||||||
EditsBuffer.Clear();
|
EditsBuffer.Clear();
|
||||||
ReferenceFramesBuffer.Clear();
|
ReferenceFramesBuffer.Clear();
|
||||||
ComponentRenderQueue.Clear();
|
|
||||||
UpdatedComponentDiffs.Clear();
|
UpdatedComponentDiffs.Clear();
|
||||||
DisposedComponentIds.Clear();
|
DisposedComponentIds.Clear();
|
||||||
DisposedEventHandlerIds.Clear();
|
DisposedEventHandlerIds.Clear();
|
||||||
|
|
|
||||||
|
|
@ -447,9 +447,18 @@ namespace Microsoft.AspNetCore.Components.Rendering
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
RemoveEventHandlerIds(_batchBuilder.DisposedEventHandlerIds.ToRange(), updateDisplayTask);
|
RemoveEventHandlerIds(_batchBuilder.DisposedEventHandlerIds.ToRange(), updateDisplayTask);
|
||||||
_batchBuilder.Clear();
|
_batchBuilder.ClearStateForCurrentBatch();
|
||||||
_isBatchInProgress = false;
|
_isBatchInProgress = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// An OnAfterRenderAsync callback might have queued more work synchronously.
|
||||||
|
// Note: we do *not* re-render implicitly after the OnAfterRenderAsync-returned
|
||||||
|
// task (that would be an infinite loop). We only render after an explicit render
|
||||||
|
// request (e.g., StateHasChanged()).
|
||||||
|
if (_batchBuilder.ComponentRenderQueue.Count > 0)
|
||||||
|
{
|
||||||
|
ProcessRenderQueue();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Task InvokeRenderCompletedCalls(ArrayRange<RenderTreeDiff> updatedComponents)
|
private Task InvokeRenderCompletedCalls(ArrayRange<RenderTreeDiff> updatedComponents)
|
||||||
|
|
|
||||||
|
|
@ -2507,6 +2507,31 @@ namespace Microsoft.AspNetCore.Components.Test
|
||||||
Assert.Equal(1, childComponents[2].OnAfterRenderCallCount); // Disposed
|
Assert.Equal(1, childComponents[2].OnAfterRenderCallCount); // Disposed
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CanTriggerRenderingSynchronouslyFromInsideAfterRenderCallback()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
AfterRenderCaptureComponent component = null;
|
||||||
|
component = new AfterRenderCaptureComponent
|
||||||
|
{
|
||||||
|
OnAfterRenderLogic = () =>
|
||||||
|
{
|
||||||
|
if (component.OnAfterRenderCallCount < 10)
|
||||||
|
{
|
||||||
|
component.TriggerRender();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var renderer = new TestRenderer();
|
||||||
|
renderer.AssignRootComponentId(component);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
component.TriggerRender();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(10, component.OnAfterRenderCallCount);
|
||||||
|
}
|
||||||
|
|
||||||
[ConditionalFact]
|
[ConditionalFact]
|
||||||
[SkipOnHelix] // https://github.com/aspnet/AspNetCore/issues/7487
|
[SkipOnHelix] // https://github.com/aspnet/AspNetCore/issues/7487
|
||||||
public async Task CanTriggerEventHandlerDisposedInEarlierPendingBatchAsync()
|
public async Task CanTriggerEventHandlerDisposedInEarlierPendingBatchAsync()
|
||||||
|
|
@ -3414,11 +3439,14 @@ namespace Microsoft.AspNetCore.Components.Test
|
||||||
|
|
||||||
private class AfterRenderCaptureComponent : AutoRenderComponent, IComponent, IHandleAfterRender
|
private class AfterRenderCaptureComponent : AutoRenderComponent, IComponent, IHandleAfterRender
|
||||||
{
|
{
|
||||||
|
public Action OnAfterRenderLogic { get; set; }
|
||||||
|
|
||||||
public int OnAfterRenderCallCount { get; private set; }
|
public int OnAfterRenderCallCount { get; private set; }
|
||||||
|
|
||||||
public Task OnAfterRenderAsync()
|
public Task OnAfterRenderAsync()
|
||||||
{
|
{
|
||||||
OnAfterRenderCallCount++;
|
OnAfterRenderCallCount++;
|
||||||
|
OnAfterRenderLogic?.Invoke();
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,21 @@ namespace Microsoft.AspNetCore.Components.E2ETests.ServerExecutionTests
|
||||||
Browser.Equal("1", () => Browser.FindElement(By.Id("count")).Text);
|
Browser.Equal("1", () => Browser.FindElement(By.Id("count")).Text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CanUseJSInteropFromOnAfterRenderAsync()
|
||||||
|
{
|
||||||
|
Navigate("/prerendered/prerendered-interop");
|
||||||
|
|
||||||
|
// Prerendered output can't use JSInterop
|
||||||
|
Browser.Equal("No value yet", () => Browser.FindElement(By.Id("val-get-by-interop")).Text);
|
||||||
|
Browser.Equal(string.Empty, () => Browser.FindElement(By.Id("val-set-by-interop")).GetAttribute("value"));
|
||||||
|
|
||||||
|
// Once connected, we can
|
||||||
|
BeginInteractivity();
|
||||||
|
Browser.Equal("Hello from interop call", () => Browser.FindElement(By.Id("val-get-by-interop")).Text);
|
||||||
|
Browser.Equal("Hello from interop call", () => Browser.FindElement(By.Id("val-set-by-interop")).GetAttribute("value"));
|
||||||
|
}
|
||||||
|
|
||||||
private void BeginInteractivity()
|
private void BeginInteractivity()
|
||||||
{
|
{
|
||||||
Browser.FindElement(By.Id("load-boot-script")).Click();
|
Browser.FindElement(By.Id("load-boot-script")).Click();
|
||||||
|
|
|
||||||
|
|
@ -590,6 +590,14 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
||||||
Browser.Equal("First Second Third Fourth Fifth", () => result.Text);
|
Browser.Equal("First Second Third Fourth Fifth", () => result.Text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CanPerformInteropImmediatelyOnComponentInsertion()
|
||||||
|
{
|
||||||
|
var appElement = MountTestComponent<InteropOnInitializationComponent>();
|
||||||
|
Browser.Equal("Hello from interop call", () => appElement.FindElement(By.Id("val-get-by-interop")).Text);
|
||||||
|
Browser.Equal("Hello from interop call", () => appElement.FindElement(By.Id("val-set-by-interop")).GetAttribute("value"));
|
||||||
|
}
|
||||||
|
|
||||||
static IAlert SwitchToAlert(IWebDriver driver)
|
static IAlert SwitchToAlert(IWebDriver driver)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,7 @@
|
||||||
<option value="BasicTestApp.FormsTest.SimpleValidationComponent">Simple validation</option>
|
<option value="BasicTestApp.FormsTest.SimpleValidationComponent">Simple validation</option>
|
||||||
<option value="BasicTestApp.FormsTest.TypicalValidationComponent">Typical validation</option>
|
<option value="BasicTestApp.FormsTest.TypicalValidationComponent">Typical validation</option>
|
||||||
<option value="BasicTestApp.FormsTest.NotifyPropertyChangedValidationComponent">INotifyPropertyChanged validation</option>
|
<option value="BasicTestApp.FormsTest.NotifyPropertyChangedValidationComponent">INotifyPropertyChanged validation</option>
|
||||||
|
<option value="BasicTestApp.InteropOnInitializationComponent">Interop on initialization</option>
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
@if (SelectedComponentType != null)
|
@if (SelectedComponentType != null)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,52 @@
|
||||||
|
@page "/prerendered-interop"
|
||||||
|
@using Microsoft.AspNetCore.Components.Services
|
||||||
|
@using Microsoft.JSInterop
|
||||||
|
@inject IComponentContext ComponentContext
|
||||||
|
@inject IJSRuntime JSRuntime
|
||||||
|
|
||||||
|
<p>
|
||||||
|
This component shows it's possible to use JSInterop as part of the initialization
|
||||||
|
logic of a component, and have that be compatible with prerendering. It also shows
|
||||||
|
that it's possible to trigger a rendering update from inside OnAfterRenderAsync,
|
||||||
|
though it's the developer's own responsibility to avoid an infinite loop when
|
||||||
|
doing that.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Value get via JS interop call:
|
||||||
|
<strong id="val-get-by-interop">@(infoFromJs ?? "No value yet")</strong>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Value set via JS interop call:
|
||||||
|
<input id="val-set-by-interop" ref="@myElem" />
|
||||||
|
</p>
|
||||||
|
|
||||||
|
@functions {
|
||||||
|
string infoFromJs;
|
||||||
|
ElementRef myElem;
|
||||||
|
|
||||||
|
protected override async Task OnAfterRenderAsync()
|
||||||
|
{
|
||||||
|
// TEMPORARY: Currently we need this guard to avoid making the interop
|
||||||
|
// call during prerendering. Soon this will be unnecessary because we
|
||||||
|
// will change OnAfterRenderAsync not to run during the prerendering phase.
|
||||||
|
if (!ComponentContext.IsConnected)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (infoFromJs == null)
|
||||||
|
{
|
||||||
|
// We can only use the ElementRef in OnAfterRenderAsync (and not any
|
||||||
|
// earlier lifecycle method), because there is no JS element until
|
||||||
|
// the component has been rendered.
|
||||||
|
infoFromJs = await JSRuntime.InvokeAsync<string>(
|
||||||
|
"setElementValue", myElem, "Hello from interop call");
|
||||||
|
|
||||||
|
// Now we can re-render with the new state obtained from the interop call.
|
||||||
|
// Not an infinite loop, because we only call this when "infoFromJs == null"
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -16,6 +16,7 @@
|
||||||
// Used by ElementRefComponent
|
// Used by ElementRefComponent
|
||||||
function setElementValue(element, newValue) {
|
function setElementValue(element, newValue) {
|
||||||
element.value = newValue;
|
element.value = newValue;
|
||||||
|
return element.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
(function () {
|
(function () {
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,12 @@
|
||||||
scriptElem.src = '_framework/components.server.js';
|
scriptElem.src = '_framework/components.server.js';
|
||||||
document.body.appendChild(scriptElem);
|
document.body.appendChild(scriptElem);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Used by InteropOnInitializationComponent
|
||||||
|
function setElementValue(element, newValue) {
|
||||||
|
element.value = newValue;
|
||||||
|
return element.value;
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue