Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove trailing slash from void HTML elements #51646

Open
wants to merge 16 commits into
base: main
Choose a base branch
from

Conversation

ahukkanen
Copy link

Motivation / Background

This Pull Request has been created because W3C HTML validator gives a lot of the following type of notices about the trailing slashes on void HTML elements:

Trailing slash on void elements has no effect and interacts badly with unquoted attribute values.

Detail

This Pull Request changes the trailing slash /> on void HTML elements to > as suggested by the W3C validator.

This changes also the default value of the tag helper's open argument from false to nil and reflects this change in the documentation of the method.

The trailing slashes are removed from all void elements but preserved for the self-closing elements within SVGs, i.e. animate, animateMotion, animateTransform, circle, ellipse, line, path, polygon, polyline, rect, set, stop, use and view as defined here:

define_self_closing_element :animate, code_generator: code_generator
define_self_closing_element :animateMotion, code_generator: code_generator
define_self_closing_element :animateTransform, code_generator: code_generator
define_self_closing_element :circle, code_generator: code_generator
define_self_closing_element :ellipse, code_generator: code_generator
define_self_closing_element :line, code_generator: code_generator
define_self_closing_element :path, code_generator: code_generator
define_self_closing_element :polygon, code_generator: code_generator
define_self_closing_element :polyline, code_generator: code_generator
define_self_closing_element :rect, code_generator: code_generator
define_self_closing_element :set, code_generator: code_generator
define_self_closing_element :stop, code_generator: code_generator
define_self_closing_element :use, code_generator: code_generator
define_self_closing_element :view, code_generator: code_generator

This is to prevent any SVGs breaking due to this change.

In addition, a new configuration value config.action_view.void_element_trailing_slash is added that can be used to revert to the legacy behavior, i.e. keeping the trailing slashes on the void elements. This is to help migrating to newer versions and to provide this as an option in case some implementations rely on the current behavior.

Additional information

Fix #51643

Checklist

Before submitting the PR make sure the following are checked:

  • This Pull Request is related to one change. Unrelated changes should be opened in separate PRs.
  • Commit message has a detailed description of what changed and why. If this PR fixes a related issue include it in the commit message. Ex: [Fix #issue-number]
  • Tests are added or updated if you fix a bug or add a feature.
  • CHANGELOG files are updated for the changed libraries if there is a behavior change or additional feature. Minor bug fixes and documentation changes should not be included.

@rails-bot rails-bot bot added the actionpack label Apr 23, 2024
Copy link
Member

@rafaelfranca rafaelfranca left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the pull request. There are a few things that need to change in order for this to be merged. Can you take a look?

actionview/app/assets/javascripts/rails-ujs.esm.js Outdated Show resolved Hide resolved
actionview/app/assets/javascripts/rails-ujs.js Outdated Show resolved Hide resolved
actionview/lib/action_view/helpers/form_tag_helper.rb Outdated Show resolved Hide resolved
actionview/lib/action_view/railtie.rb Outdated Show resolved Hide resolved
@@ -245,7 +247,7 @@ def tag_string(name, content = nil, options, escape: true, &block)
content_tag_string(name, content, options, escape)
end

def self_closing_tag_string(name, options, escape = true, tag_suffix = " />")
def self_closing_tag_string(name, options, escape = true, tag_suffix = (void_element_trailing_slash == false ? ">" : " />"))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I understand why this change is being made. Is this method being used somewhere outside of define_self_closing_element and define_void_element? The idea in 3438979 was that this method is for self closing elements but can be specialized for void elements by passing in the suffix.

Since omitting the / is not valid for self-closing elements, I'm not sure it should ever be the default for this method.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method is used for both: void elements and the self closing SVG tags.

I moved the configuration check to the void element definition method and changed the default value to the tag closing without the ending slash at dcf7b87. This is because going forward, the ending slash should be rather an exception (i.e. SVGs) than the default.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is because going forward, the ending slash should be rather an exception (i.e. SVGs) than the default.

Right, I don't think this is a good idea. Void elements are a special case of self-closing tags where the / is optional instead of required. If the default is > then every caller wanting a self-closing tag must pass in /> to get a valid tag. If the default is /> then passing in > is just an optimization for void tags but the method won't generate invalid HTML.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The default can be changed back as per this request but I'll wait for the answer to the other question before doing that.

@ahukkanen
Copy link
Author

@rafaelfranca @skipkayhil Thank you both for your kind reviews and valuable feedback.

The issues pointed out in the reviews are now resolved.

Comment on lines +77 to +78
tag_suffix = ActionView::Helpers::TagHelper.void_element_trailing_slash ? " />" : ">"
self_closing_tag_string("#{name}", options, escape, tag_suffix)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are there negative performance impacts from introducing this conditional into each individual call?

Is it possible to extract the suffix String definition so that it's outside the batch.push(<<~RUBY) string, then interpolated back into the method String?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is not a real difference between these two the implementations.

The difference is one if statement which is linear at runtime. The string generation is done regardless of this if statement being there or not (i.e. when passing the last parameter as a constant string).

Note that the same if statement is also run at the tag helper every time.

Of course, there is an impact on performance every time more conditionals are added but at least the time complexity of both implementations is the same (linear).

If you would prefer, I can implement the change but it won't affect the performance in any way.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still don't understand why this method is being changed. It already does what you want by generating tags with > instead of />.

To me, the new config is about transitioning code that currently uses /> to use > instead. But since this already uses > I don't think there's any reason to make it suddenly do the wrong thing even if temporarily (and in my opinion changing it like this is actually backwards incompatible).

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@skipkayhil You are correct that it changes current behavior.

But without this change, e.g. these elements won't have the closing slash even if config.action_view.void_element_trailing_slash is set to true:
Void elements without closing slash

This source is generated by following the getting started guide of Rails.

It is fine by me to revert the change but then we are not respecting the configuration option in all cases, which was part of the fixes implemented after the previous review.

It's just a question what we want here:

  1. Keeping the current behavior of having mixed void HTML tags (what is requested here)
  2. Respecting the new configuration option in all cases (what was requested in the review)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Depending on how https://github.com/rails/rails/pull/51646/files#r1578010728 is resolved, could the tag_suffix definition be inlined into the interpolation?

Then, that value could be interpolated directly into the evaluated Ruby method:

Suggested change
tag_suffix = ActionView::Helpers::TagHelper.void_element_trailing_slash ? " />" : ">"
self_closing_tag_string("#{name}", options, escape, tag_suffix)
self_closing_tag_string("#{name}", options, escape, "#{
ActionView::Helpers::TagHelper.void_element_trailing_slash ? " />" : ">"
}")

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Furthermore, is there a reason that the void_element_trailing_slash attribute need to be defined on ActionView::Helpers::TagHelper? Could it be defined on the ActionView::Helpers::TagHelper::TagBuilder class directly?

If the mattr_accessor :void_element_trailing_slash, default: false line were moved to the TagBuilder class, that configuration could be accessed directly from the class method, instead of as a fully-qualified class variable:

Suggested change
tag_suffix = ActionView::Helpers::TagHelper.void_element_trailing_slash ? " />" : ">"
self_closing_tag_string("#{name}", options, escape, tag_suffix)
self_closing_tag_string("#{name}", options, escape, "#{void_element_trailing_slash ? " />" : ">"}")

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Depending on how https://github.com/rails/rails/pull/51646/files#r1578010728 is resolved, could the tag_suffix definition be inlined into the interpolation?

Yes this is of course possible. But I'll just stress that it has no implication on performance at least to my knowledge.

I can do this change if it is insisted but I will wait for the feedback regarding the question on how to proceed with this, as there is the question above which is the desired direction with this.

Furthermore, is there a reason that the void_element_trailing_slash attribute need to be defined on ActionView::Helpers::TagHelper? Could it be defined on the ActionView::Helpers::TagHelper::TagBuilder class directly?

It is also needed at TagHelper#tag method.

I don't have a preference where the configuration option is defined, I just thought it would be natural at the lowest nesting level where it is needed.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@skipkayhil @rafaelfranca Could you chime in which would be the preferred direction forward, so that we could finalize this.

So there are two alternatives, as pointed out above:

  1. Not to change this method which would make the HTML inconsistent regarding the introduced configuration flag, i.e. when the ending slashes are preferred, the HTML would be inconsistent and not respect the configuration option in all places.
  2. Following the current direction where the configuration option is respected everywhere, but implement the changes requested here

Which one both of you think is the desired approach?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Remove trailing slash from void HTML elements
4 participants