Skip to content

Commit

Permalink
Add support for PreventDefault and StopPropagation in event handlers
Browse files Browse the repository at this point in the history
\n\nCommit migrated from ae5be3e

Commit migrated from dotnet/aspnetcore@5badd9418263
  • Loading branch information
ajaybhargavb authored and NTaylorMullen committed Oct 18, 2019
1 parent a0f0c32 commit 7635bba
Show file tree
Hide file tree
Showing 216 changed files with 1,439 additions and 413 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,12 @@
<data name="EventHandlerTagHelper_Documentation" xml:space="preserve">
<value>Sets the '{0}' attribute to the provided string or delegate value. A delegate value should be of type '{1}'.</value>
</data>
<data name="EventHandlerTagHelper_PreventDefault_Documentation" xml:space="preserve">
<value>Specifies whether to cancel(if cancelable) the default action that belongs to the '{0}' event.</value>
</data>
<data name="EventHandlerTagHelper_StopPropagation_Documentation" xml:space="preserve">
<value>Specifies whether to prevent further propagation of the '{0}' event in the capturing and bubbling phases.</value>
</data>
<data name="ImplementsDirective_Description" xml:space="preserve">
<value>Declares an interface implementation for the current class.</value>
</data>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -700,10 +700,11 @@ private void WriteComponentAttributeInnards(CodeRenderingContext context, Compon
}
}

bool NeedsTypeCheck(ComponentAttributeIntermediateNode n)
static bool NeedsTypeCheck(ComponentAttributeIntermediateNode n)
{
return n.BoundAttribute != null && !n.BoundAttribute.IsWeaklyTyped();
}
// Weakly typed attributes will have their TypeName set to null.
return n.BoundAttribute != null && n.TypeName != null;
}
}

private IReadOnlyList<IntermediateToken> GetCSharpTokens(IntermediateNode node)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -438,5 +438,21 @@ public static RazorDiagnostic Create_InconsistentStartAndEndTagName(string start
startTagName,
endTagName);
}

public static readonly RazorDiagnosticDescriptor EventHandlerParameter_Duplicates =
new RazorDiagnosticDescriptor(
$"{DiagnosticPrefix}10014",
() => "The attribute '{0}' was matched by multiple event handlers parameter attributes. Duplicates:{1}",
RazorDiagnosticSeverity.Error);

public static RazorDiagnostic CreateEventHandlerParameter_Duplicates(SourceSpan? source, string attribute, TagHelperDirectiveAttributeParameterIntermediateNode[] attributes)
{
var diagnostic = RazorDiagnostic.Create(
EventHandlerParameter_Duplicates,
source ?? SourceSpan.Undefined,
attribute,
Environment.NewLine + string.Join(Environment.NewLine, attributes.Select(p => p.TagHelper.DisplayName)));
return diagnostic;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// 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;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Razor.Language.Extensions;
Expand Down Expand Up @@ -31,12 +32,17 @@ protected override void ExecuteCore(RazorCodeDocument codeDocument, DocumentInte
// Each usage will be represented by a tag helper property that is a descendant of either
// a component or element.
var references = documentNode.FindDescendantReferences<TagHelperDirectiveAttributeIntermediateNode>();
var parameterReferences = documentNode.FindDescendantReferences<TagHelperDirectiveAttributeParameterIntermediateNode>();

var parents = new HashSet<IntermediateNode>();
for (var i = 0; i < references.Count; i++)
{
parents.Add(references[i].Parent);
}
for (var i = 0; i < parameterReferences.Count; i++)
{
parents.Add(parameterReferences[i].Parent);
}

foreach (var parent in parents)
{
Expand All @@ -59,6 +65,23 @@ protected override void ExecuteCore(RazorCodeDocument codeDocument, DocumentInte
reference.Replace(RewriteUsage(reference.Parent, node));
}
}

for (var i = 0; i < parameterReferences.Count; i++)
{
var reference = parameterReferences[i];
var node = (TagHelperDirectiveAttributeParameterIntermediateNode)reference.Node;

if (!reference.Parent.Children.Contains(node))
{
// This node was removed as a duplicate, skip it.
continue;
}

if (node.TagHelper.IsEventHandlerTagHelper())
{
reference.Replace(RewriteParameterUsage(reference.Parent, node));
}
}
}

private void ProcessDuplicates(IntermediateNode parent)
Expand Down Expand Up @@ -109,6 +132,24 @@ private void ProcessDuplicates(IntermediateNode parent)
parent.Children.Remove(property);
}
}

var parameterDuplicates = parent.Children
.OfType<TagHelperDirectiveAttributeParameterIntermediateNode>()
.Where(p => p.TagHelper?.IsEventHandlerTagHelper() ?? false)
.GroupBy(p => p.AttributeName)
.Where(g => g.Count() > 1);

foreach (var duplicate in parameterDuplicates)
{
parent.Diagnostics.Add(ComponentDiagnosticFactory.CreateEventHandlerParameter_Duplicates(
parent.Source,
duplicate.Key,
duplicate.ToArray()));
foreach (var property in duplicate)
{
parent.Children.Remove(property);
}
}
}

private IntermediateNode RewriteUsage(IntermediateNode parent, TagHelperDirectiveAttributeIntermediateNode node)
Expand Down Expand Up @@ -198,7 +239,7 @@ private IntermediateNode RewriteUsage(IntermediateNode parent, TagHelperDirectiv
}
}

private static IReadOnlyList<IntermediateToken> GetAttributeContent(TagHelperDirectiveAttributeIntermediateNode node)
private static IReadOnlyList<IntermediateToken> GetAttributeContent(IntermediateNode node)
{
var template = node.FindDescendantNodes<TemplateIntermediateNode>().FirstOrDefault();
if (template != null)
Expand All @@ -222,5 +263,49 @@ private static IReadOnlyList<IntermediateToken> GetAttributeContent(TagHelperDir
return node.FindDescendantNodes<IntermediateToken>();
}
}

private IntermediateNode RewriteParameterUsage(IntermediateNode parent, TagHelperDirectiveAttributeParameterIntermediateNode node)
{
// Now rewrite the node to look like:
//
// builder.AddEventPreventDefaultAttribute(2, "onclick", true); // If minimized.
// or
// builder.AddEventPreventDefaultAttribute(2, "onclick", someBoolExpression); // If a bool expression is provided in the value.

string eventHandlerMethod;
if (node.BoundAttributeParameter.Name == "preventDefault")
{
eventHandlerMethod = ComponentsApi.RenderTreeBuilder.AddEventPreventDefaultAttribute;
}
else if (node.BoundAttributeParameter.Name == "stopPropagation")
{
eventHandlerMethod = ComponentsApi.RenderTreeBuilder.AddEventStopPropagationAttribute;
}
else
{
// Unsupported event handler attribute parameter. This can only happen if bound attribute descriptor
// is configured to expect a parameter other than 'preventDefault' and 'stopPropagation'.
return node;
}

var result = new ComponentAttributeIntermediateNode(node)
{
Annotations =
{
[ComponentMetadata.Common.OriginalAttributeName] = node.OriginalAttributeName,
[ComponentMetadata.Common.AddAttributeMethodName] = eventHandlerMethod,
},
};

result.Children.Clear();
if (node.AttributeStructure != AttributeStructure.Minimized)
{
var tokens = GetAttributeContent(node);
result.Children.Add(new CSharpExpressionIntermediateNode());
result.Children[0].Children.AddRange(tokens);
}

return result;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ public static class Common
public static readonly string OriginalAttributeName = "Common.OriginalAttributeName";

public static readonly string DirectiveAttribute = "Common.DirectiveAttribute";

public static readonly string AddAttributeMethodName = "Common.AddAttributeMethodName";
}

public static class Bind
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -509,10 +509,12 @@ public override void WriteComponentAttribute(CodeRenderingContext context, Compo
throw new ArgumentNullException(nameof(node));
}

var addAttributeMethod = node.Annotations[ComponentMetadata.Common.AddAttributeMethodName] as string ?? ComponentsApi.RenderTreeBuilder.AddAttribute;

// _builder.AddAttribute(1, "Foo", 42);
context.CodeWriter.Write(_scopeStack.BuilderVarName);
context.CodeWriter.Write(".");
context.CodeWriter.Write(ComponentsApi.RenderTreeBuilder.AddAttribute);
context.CodeWriter.Write(addAttributeMethod);
context.CodeWriter.Write("(");
context.CodeWriter.Write((_sourceSequence++).ToString());
context.CodeWriter.Write(", ");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,10 @@ public static class RenderTreeBuilder
public static readonly string SetKey = nameof(SetKey);

public static readonly string SetUpdatesAttributeName = nameof(SetUpdatesAttributeName);

public static readonly string AddEventPreventDefaultAttribute = nameof(AddEventPreventDefaultAttribute);

public static readonly string AddEventStopPropagationAttribute = nameof(AddEventStopPropagationAttribute);
}

public static class RuntimeHelpers
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,32 @@ public ComponentAttributeIntermediateNode(TagHelperDirectiveAttributeIntermediat
}
}

public ComponentAttributeIntermediateNode(TagHelperDirectiveAttributeParameterIntermediateNode directiveAttributeParameterNode)
{
if (directiveAttributeParameterNode == null)
{
throw new ArgumentNullException(nameof(directiveAttributeParameterNode));
}

AttributeName = directiveAttributeParameterNode.AttributeNameWithoutParameter;
AttributeStructure = directiveAttributeParameterNode.AttributeStructure;
BoundAttribute = directiveAttributeParameterNode.BoundAttribute;
PropertyName = directiveAttributeParameterNode.BoundAttributeParameter.GetPropertyName();
Source = directiveAttributeParameterNode.Source;
TagHelper = directiveAttributeParameterNode.TagHelper;
TypeName = directiveAttributeParameterNode.BoundAttributeParameter.TypeName;

for (var i = 0; i < directiveAttributeParameterNode.Children.Count; i++)
{
Children.Add(directiveAttributeParameterNode.Children[i]);
}

for (var i = 0; i < directiveAttributeParameterNode.Diagnostics.Count; i++)
{
Diagnostics.Add(directiveAttributeParameterNode.Diagnostics[i]);
}
}

public override IntermediateNodeCollection Children { get; } = new IntermediateNodeCollection();

public string AttributeName { get; set; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2848,6 +2848,7 @@ public void EventHandler_OnElement_WithLambdaDelegate()

// Act
var generated = CompileToCSharp(@"
@using Microsoft.AspNetCore.Components.Web
<input @onclick=""x => { }"" />");

// Assert
Expand Down Expand Up @@ -2896,6 +2897,78 @@ void OnClick(MouseEventArgs e) {
CompileToAssembly(generated);
}

[Fact]
public void EventHandler_PreventDefault_StopPropagation_Minimized()
{
// Arrange

// Act
var generated = CompileToCSharp(@"
@using Microsoft.AspNetCore.Components.Web
<input @onclick:preventDefault @onclick:stopPropagation />");

// Assert
AssertDocumentNodeMatchesBaseline(generated.CodeDocument);
AssertCSharpDocumentMatchesBaseline(generated.CodeDocument);
CompileToAssembly(generated);
}

[Fact]
public void EventHandler_PreventDefault_StopPropagation()
{
// Arrange

// Act
var generated = CompileToCSharp(@"
@using Microsoft.AspNetCore.Components.Web
<input @onfocus:preventDefault=""true"" @onclick:stopPropagation=""Foo"" @onfocus:stopPropagation=""false"" />
@code {
bool Foo { get; set; }
}");

// Assert
AssertDocumentNodeMatchesBaseline(generated.CodeDocument);
AssertCSharpDocumentMatchesBaseline(generated.CodeDocument);
CompileToAssembly(generated);
}

[Fact]
public void EventHandler_WithDelegate_PreventDefault()
{
// Arrange

// Act
var generated = CompileToCSharp(@"
@using Microsoft.AspNetCore.Components.Web
<input @onfocus=""OnFocus"" @onfocus:preventDefault=""ShouldPreventDefault()"" />
@code {
void OnFocus(FocusEventArgs e) { }
bool ShouldPreventDefault() { return false; }
}");

// Assert
AssertDocumentNodeMatchesBaseline(generated.CodeDocument);
AssertCSharpDocumentMatchesBaseline(generated.CodeDocument);
CompileToAssembly(generated);
}

[Fact]
public void EventHandler_PreventDefault_Duplicates()
{
// Arrange

// Act
var generated = CompileToCSharp(@"
@using Microsoft.AspNetCore.Components.Web
<input @onclick:preventDefault=""true"" @onclick:preventDefault />");

// Assert
AssertDocumentNodeMatchesBaseline(generated.CodeDocument);
AssertCSharpDocumentMatchesBaseline(generated.CodeDocument);
CompileToAssembly(generated);
}

#endregion

#region Generics
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@ Document -
IntermediateToken - - CSharp - #pragma warning restore 0414
MethodDeclaration - - protected override - void - BuildRenderTree
Component - (0:0,0 [41] x:\dir\subdir\Test\TestComponent.cshtml) - MyComponent
ComponentAttribute - (26:0,26 [11] x:\dir\subdir\Test\TestComponent.cshtml) - Value - AttributeStructure.DoubleQuotes
ComponentAttribute - (26:0,26 [11] x:\dir\subdir\Test\TestComponent.cshtml) - Value - Value - AttributeStructure.DoubleQuotes
CSharpExpression -
IntermediateToken - (26:0,26 [11] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - ParentValue
ComponentAttribute - (26:0,26 [11] x:\dir\subdir\Test\TestComponent.cshtml) - ValueChanged - AttributeStructure.DoubleQuotes
ComponentAttribute - (26:0,26 [11] x:\dir\subdir\Test\TestComponent.cshtml) - ValueChanged - ValueChanged - AttributeStructure.DoubleQuotes
CSharpExpression -
IntermediateToken - - CSharp - Microsoft.AspNetCore.Components.CompilerServices.RuntimeHelpers.CreateInferredEventCallback(this, __value => ParentValue = __value, ParentValue)
ComponentAttribute - (26:0,26 [11] x:\dir\subdir\Test\TestComponent.cshtml) - ValueExpression - AttributeStructure.DoubleQuotes
ComponentAttribute - (26:0,26 [11] x:\dir\subdir\Test\TestComponent.cshtml) - ValueExpression - ValueExpression - AttributeStructure.DoubleQuotes
CSharpExpression -
IntermediateToken - - CSharp - () => ParentValue
HtmlContent - (41:0,41 [2] x:\dir\subdir\Test\TestComponent.cshtml)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@ Document -
IntermediateToken - - CSharp - #pragma warning restore 0414
MethodDeclaration - - protected override - void - BuildRenderTree
Component - (0:0,0 [45] x:\dir\subdir\Test\TestComponent.cshtml) - MyComponent
ComponentAttribute - (30:0,30 [11] x:\dir\subdir\Test\TestComponent.cshtml) - SomeParam - AttributeStructure.DoubleQuotes
ComponentAttribute - (30:0,30 [11] x:\dir\subdir\Test\TestComponent.cshtml) - SomeParam - SomeParam - AttributeStructure.DoubleQuotes
CSharpExpression -
IntermediateToken - (30:0,30 [11] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - ParentValue
ComponentAttribute - (30:0,30 [11] x:\dir\subdir\Test\TestComponent.cshtml) - SomeParamChanged - AttributeStructure.DoubleQuotes
ComponentAttribute - (30:0,30 [11] x:\dir\subdir\Test\TestComponent.cshtml) - SomeParamChanged - SomeParamChanged - AttributeStructure.DoubleQuotes
CSharpExpression -
IntermediateToken - - CSharp - Microsoft.AspNetCore.Components.CompilerServices.RuntimeHelpers.CreateInferredEventCallback(this, __value => ParentValue = __value, ParentValue)
ComponentAttribute - (30:0,30 [11] x:\dir\subdir\Test\TestComponent.cshtml) - SomeParamExpression - AttributeStructure.DoubleQuotes
ComponentAttribute - (30:0,30 [11] x:\dir\subdir\Test\TestComponent.cshtml) - SomeParamExpression - SomeParamExpression - AttributeStructure.DoubleQuotes
CSharpExpression -
IntermediateToken - - CSharp - () => ParentValue
HtmlContent - (45:0,45 [2] x:\dir\subdir\Test\TestComponent.cshtml)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ Document -
IntermediateToken - - CSharp - #pragma warning restore 0414
MethodDeclaration - - protected override - void - BuildRenderTree
Component - (0:0,0 [41] x:\dir\subdir\Test\TestComponent.cshtml) - MyComponent
ComponentAttribute - (26:0,26 [11] x:\dir\subdir\Test\TestComponent.cshtml) - Value - AttributeStructure.DoubleQuotes
ComponentAttribute - (26:0,26 [11] x:\dir\subdir\Test\TestComponent.cshtml) - Value - Value - AttributeStructure.DoubleQuotes
CSharpExpression -
IntermediateToken - (26:0,26 [11] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - ParentValue
ComponentAttribute - (26:0,26 [11] x:\dir\subdir\Test\TestComponent.cshtml) - ValueChanged - AttributeStructure.DoubleQuotes
ComponentAttribute - (26:0,26 [11] x:\dir\subdir\Test\TestComponent.cshtml) - ValueChanged - ValueChanged - AttributeStructure.DoubleQuotes
CSharpExpression -
IntermediateToken - - CSharp - Microsoft.AspNetCore.Components.CompilerServices.RuntimeHelpers.CreateInferredEventCallback(this, __value => ParentValue = __value, ParentValue)
HtmlContent - (41:0,41 [2] x:\dir\subdir\Test\TestComponent.cshtml)
Expand Down
Loading

0 comments on commit 7635bba

Please sign in to comment.