Search code examples
htmlruby-on-railsformsw3c-validation

Rails button_to fails with W3C validator


Outputs of a Ruby-on-Rails button_to have started failing in W3C validation recently, with the returned message of:

An input element with a type attribute whose value is hidden must not have an autocomplete attribute whose value is on or off.

So I would like to get rid of the error.

The corresponding button_to statement in View (ERB) in Rails-7.0.4 is

<%= button_to "Add Entry", new_article_path, form_class: "inline_form button_to",
  method: :get, params: { a_token: "abc" } %>

which produces an HTML (simplified!):

<!DOCTYPE html>
<html lang="en"> <head> <meta charset="utf-8"> <title>My Title</title> </head>
<body>
 <form class="inline_form button_to" method="get" action="/articles/new?locale=en">
  <input type="submit" value="Add Entry" />
  <input type="hidden" name="a_token" value="abc" autocomplete="off" />
 </form>
</body>
</html>

If I paste this in the W3C validator form, it fails with the above-mentioned message.

Apparently, the W3C validator does not like the presence of the attribute autocomplete in the input tag with type="hidden". But that is the default output of button_to (in Rails 7.0).

It seems the Rails team added autocomplete=off last year to make the HTML output Firefox-proof according to Issue #42610 in Rails repository. In the associated discussion, user jpwynn says (2021-06-27) the change would not break the W3C validation. But seemingly it does now……


As far as I am aware, I have modified no relevant part in my Rails app. Yet, I notice routine testing has started to fail at W3C validations. So, I suspect W3C has changed the specification very recently (but I may be wrong!).

My Rails test is pretty simple as follows, using Gem w3c_validators Ver.1.3.7; it just detects any errors and fails if there is any:

 # /test/test_helper.rb
require 'w3c_validators'

def my_w3c_validate
  arerr = @validator.validate_text(response.body).errors
  assert_equal 0, arerr.size,
    "Failed in W3C-validation: ("+arerr.map(&:to_s).join(") (")+")"
end

with Gemfile:

group :development, :test do
  gem 'w3c_validators', '~> 1', '>= 1.3.6'  # => 1.3.7 in reality
end

So, is the output of Rails button_to a valid HTML? If not, how can I modify it so that the output HTML will be a valid HTML? I am using the most recent stable version, Rails-7.0.4.


Solution

  • The maintainer of W3C HTML checker (validator) @sideshowbarker kindly gave information (see comments in the question).

    Here is a summary:

    1. W3C HTML checker is doing definitely right:
      <input type="hidden" name="abc" autocomplete="off"> is invalid.
      • According to the HTML Form specification,

        When wearing the autofill anchor mantle, the autocomplete attribute, if specified, must have a value that is an ordered set of space-separated tokens consisting of just autofill detail tokens (i.e. the "on" and "off" keywords are not allowed)” — where “wearing the autofill anchor mantle

        which is basically applied to the <input type=hidden> tag.

    2. The new check item was very recently (2022-10-26) implemented on the checker, hence the recent change in the result of my Rails W3C tests. See Git pull request
    3. The HTML generated by Rails' button_to method violates the spec of the <input> tag.
      • Ironically, the HTML generation by button_to changed recently (last year) to deal with Firefox's mal-behaviour; it did pass the W3C HTML validation at that time! See Rails Github Issue #42610

    Now, since the W3C validator is doing right, you have two options:

    1. Change the way of HTML generation by Rails' button_to
    2. Modify the result of W3C validations in this respect so that the validation would not fail just because of this error and you can still use all the other validations.

    Here is the measure in the second policy. You pass the Array of W3CValidators::Message and this method returns the same Array but with the said-errors removed. The original relevant error message can be recorded in Logger. I put the script (but with more extensive description) in Github Gist, too.

    # @example Usage, maybe in /test/test_helper.rb
    #   # Make sure to write in /config/environments/test.rb
    #   #    config.ignore_w3c_validate_hidden_autocomplete = true
    #   #
    #   #require 'w3c_validators'
    #   errors = @validator.validate_text(response.body).errors
    #   errors = _may_ignore_autocomplete_errors_for_hidden(errors, "W3C validaiton failed: ")
    #   assert_empty errors, "Failed in W3C validation: "+errors.map(&:to_s).inspect
    #
    # @param errs [Array<W3CValidators::Message>] Output of +@validator.validate_text(response.body).errors+
    # @param prefix [String] Prefix of the warning message recorded with Logger.
    #    If empty, no message is recorded in Logger.
    # @return [Array<String>]
    def may_ignore_autocomplete_errors_for_hidden(errs, prefix="")
      removeds = []
      return errs if !Rails.configuration.ignore_w3c_validate_hidden_autocomplete
      errs.map{ |es|
        # Example of an Error:
        #   ERROR; line 165: An “input” element with a “type” attribute whose value is “hidden” must not have an “autocomplete” attribute whose value is “on” or “off”
        if /\AERROR\b.+\binput\b[^a-z]+\belement.+\btype\b.+\bhidden\b.+\bautocomplete\b[^a-z]+\battribute\b/i =~ es.to_s
          removeds << es
          nil
        else
          es
        end
      }.compact
    
    ensure
      # Records it in Logger
      if !removeds.empty? && !prefix.blank?
        Rails.logger.warn(prefix + removeds.map(&:to_s).uniq.inspect)
      end
    end