actionview 5.2.7.1 → 6.1.4.6

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of actionview might be problematic. Click here for more details.

Files changed (92) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +250 -112
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +5 -3
  5. data/lib/action_view/base.rb +81 -15
  6. data/lib/action_view/buffers.rb +15 -0
  7. data/lib/action_view/cache_expiry.rb +52 -0
  8. data/lib/action_view/context.rb +5 -9
  9. data/lib/action_view/dependency_tracker.rb +10 -4
  10. data/lib/action_view/digestor.rb +15 -22
  11. data/lib/action_view/flows.rb +0 -1
  12. data/lib/action_view/gem_version.rb +4 -4
  13. data/lib/action_view/helpers/active_model_helper.rb +0 -1
  14. data/lib/action_view/helpers/asset_tag_helper.rb +64 -47
  15. data/lib/action_view/helpers/asset_url_helper.rb +9 -6
  16. data/lib/action_view/helpers/atom_feed_helper.rb +2 -1
  17. data/lib/action_view/helpers/cache_helper.rb +23 -22
  18. data/lib/action_view/helpers/capture_helper.rb +4 -0
  19. data/lib/action_view/helpers/csp_helper.rb +4 -2
  20. data/lib/action_view/helpers/csrf_helper.rb +1 -1
  21. data/lib/action_view/helpers/date_helper.rb +73 -30
  22. data/lib/action_view/helpers/form_helper.rb +305 -37
  23. data/lib/action_view/helpers/form_options_helper.rb +23 -23
  24. data/lib/action_view/helpers/form_tag_helper.rb +19 -16
  25. data/lib/action_view/helpers/javascript_helper.rb +12 -11
  26. data/lib/action_view/helpers/number_helper.rb +14 -8
  27. data/lib/action_view/helpers/output_safety_helper.rb +1 -1
  28. data/lib/action_view/helpers/rendering_helper.rb +17 -7
  29. data/lib/action_view/helpers/sanitize_helper.rb +12 -18
  30. data/lib/action_view/helpers/tag_helper.rb +100 -55
  31. data/lib/action_view/helpers/tags/base.rb +18 -11
  32. data/lib/action_view/helpers/tags/check_box.rb +0 -1
  33. data/lib/action_view/helpers/tags/collection_check_boxes.rb +0 -1
  34. data/lib/action_view/helpers/tags/collection_helpers.rb +0 -1
  35. data/lib/action_view/helpers/tags/collection_radio_buttons.rb +0 -1
  36. data/lib/action_view/helpers/tags/color_field.rb +1 -2
  37. data/lib/action_view/helpers/tags/date_field.rb +1 -2
  38. data/lib/action_view/helpers/tags/date_select.rb +2 -3
  39. data/lib/action_view/helpers/tags/datetime_field.rb +0 -1
  40. data/lib/action_view/helpers/tags/datetime_local_field.rb +1 -2
  41. data/lib/action_view/helpers/tags/label.rb +4 -1
  42. data/lib/action_view/helpers/tags/month_field.rb +1 -2
  43. data/lib/action_view/helpers/tags/radio_button.rb +0 -1
  44. data/lib/action_view/helpers/tags/select.rb +1 -2
  45. data/lib/action_view/helpers/tags/text_field.rb +0 -1
  46. data/lib/action_view/helpers/tags/time_field.rb +1 -2
  47. data/lib/action_view/helpers/tags/translator.rb +1 -6
  48. data/lib/action_view/helpers/tags/week_field.rb +1 -2
  49. data/lib/action_view/helpers/text_helper.rb +4 -5
  50. data/lib/action_view/helpers/translation_helper.rb +94 -54
  51. data/lib/action_view/helpers/url_helper.rb +136 -28
  52. data/lib/action_view/helpers.rb +0 -2
  53. data/lib/action_view/layouts.rb +8 -10
  54. data/lib/action_view/log_subscriber.rb +30 -15
  55. data/lib/action_view/lookup_context.rb +63 -35
  56. data/lib/action_view/path_set.rb +3 -12
  57. data/lib/action_view/railtie.rb +42 -26
  58. data/lib/action_view/record_identifier.rb +2 -3
  59. data/lib/action_view/renderer/abstract_renderer.rb +142 -11
  60. data/lib/action_view/renderer/collection_renderer.rb +196 -0
  61. data/lib/action_view/renderer/object_renderer.rb +34 -0
  62. data/lib/action_view/renderer/partial_renderer/collection_caching.rb +61 -16
  63. data/lib/action_view/renderer/partial_renderer.rb +21 -273
  64. data/lib/action_view/renderer/renderer.rb +59 -4
  65. data/lib/action_view/renderer/streaming_template_renderer.rb +10 -8
  66. data/lib/action_view/renderer/template_renderer.rb +35 -27
  67. data/lib/action_view/rendering.rb +54 -33
  68. data/lib/action_view/routing_url_for.rb +13 -12
  69. data/lib/action_view/template/error.rb +30 -15
  70. data/lib/action_view/template/handlers/builder.rb +2 -2
  71. data/lib/action_view/template/handlers/erb/erubi.rb +15 -9
  72. data/lib/action_view/template/handlers/erb.rb +16 -11
  73. data/lib/action_view/template/handlers/html.rb +1 -1
  74. data/lib/action_view/template/handlers/raw.rb +2 -2
  75. data/lib/action_view/template/handlers.rb +1 -1
  76. data/lib/action_view/template/html.rb +5 -6
  77. data/lib/action_view/template/inline.rb +22 -0
  78. data/lib/action_view/template/raw_file.rb +25 -0
  79. data/lib/action_view/template/renderable.rb +24 -0
  80. data/lib/action_view/template/resolver.rb +191 -150
  81. data/lib/action_view/template/sources/file.rb +17 -0
  82. data/lib/action_view/template/sources.rb +13 -0
  83. data/lib/action_view/template/text.rb +2 -3
  84. data/lib/action_view/template.rb +66 -75
  85. data/lib/action_view/test_case.rb +21 -29
  86. data/lib/action_view/testing/resolvers.rb +18 -27
  87. data/lib/action_view/unbound_template.rb +31 -0
  88. data/lib/action_view/view_paths.rb +59 -38
  89. data/lib/action_view.rb +7 -2
  90. data/lib/assets/compiled/rails-ujs.js +32 -6
  91. metadata +29 -18
  92. data/lib/action_view/helpers/record_tag_helper.rb +0 -23
@@ -27,7 +27,7 @@ module ActionView #:nodoc:
27
27
  # Name: <%= person.name %><br/>
28
28
  # <% end %>
29
29
  #
30
- # The loop is setup in regular embedding tags <tt><% %></tt>, and the name is written using the output embedding tag <tt><%= %></tt>. Note that this
30
+ # The loop is set up in regular embedding tags <tt><% %></tt>, and the name is written using the output embedding tag <tt><%= %></tt>. Note that this
31
31
  # is not just a usage suggestion. Regular output functions like print or puts won't work with ERB templates. So this would be wrong:
32
32
  #
33
33
  # <%# WRONG %>
@@ -151,7 +151,7 @@ module ActionView #:nodoc:
151
151
  # Specify whether rendering within namespaced controllers should prefix
152
152
  # the partial paths for ActiveModel objects with the namespace.
153
153
  # (e.g., an Admin::PostsController would render @post using /admin/posts/_post.erb)
154
- cattr_accessor :prefix_partial_path_with_controller_namespace, default: true
154
+ class_attribute :prefix_partial_path_with_controller_namespace, default: true
155
155
 
156
156
  # Specify default_formats that can be rendered.
157
157
  cattr_accessor :default_formats
@@ -162,6 +162,9 @@ module ActionView #:nodoc:
162
162
  # Specify whether submit_tag should automatically disable on click
163
163
  cattr_accessor :automatically_disable_submit_tag, default: true
164
164
 
165
+ # Annotate rendered view with file names
166
+ cattr_accessor :annotate_rendered_view_with_filenames, default: false
167
+
165
168
  class_attribute :_routes
166
169
  class_attribute :logger
167
170
 
@@ -179,37 +182,100 @@ module ActionView #:nodoc:
179
182
  def xss_safe? #:nodoc:
180
183
  true
181
184
  end
185
+
186
+ def with_empty_template_cache # :nodoc:
187
+ subclass = Class.new(self) {
188
+ # We can't implement these as self.class because subclasses will
189
+ # share the same template cache as superclasses, so "changed?" won't work
190
+ # correctly.
191
+ define_method(:compiled_method_container) { subclass }
192
+ define_singleton_method(:compiled_method_container) { subclass }
193
+
194
+ def inspect
195
+ "#<ActionView::Base:#{'%#016x' % (object_id << 1)}>"
196
+ end
197
+ }
198
+ end
199
+
200
+ def changed?(other) # :nodoc:
201
+ compiled_method_container != other.compiled_method_container
202
+ end
182
203
  end
183
204
 
184
- attr_accessor :view_renderer
205
+ attr_reader :view_renderer, :lookup_context
185
206
  attr_internal :config, :assigns
186
207
 
187
- delegate :lookup_context, to: :view_renderer
188
208
  delegate :formats, :formats=, :locale, :locale=, :view_paths, :view_paths=, to: :lookup_context
189
209
 
190
210
  def assign(new_assigns) # :nodoc:
191
211
  @_assigns = new_assigns.each { |key, value| instance_variable_set("@#{key}", value) }
192
212
  end
193
213
 
194
- def initialize(context = nil, assigns = {}, controller = nil, formats = nil) #:nodoc:
214
+ # :stopdoc:
215
+
216
+ def self.empty
217
+ with_view_paths([])
218
+ end
219
+
220
+ def self.with_view_paths(view_paths, assigns = {}, controller = nil)
221
+ with_context ActionView::LookupContext.new(view_paths), assigns, controller
222
+ end
223
+
224
+ def self.with_context(context, assigns = {}, controller = nil)
225
+ new context, assigns, controller
226
+ end
227
+
228
+ # :startdoc:
229
+
230
+ def initialize(lookup_context, assigns, controller) #:nodoc:
195
231
  @_config = ActiveSupport::InheritableOptions.new
196
232
 
197
- if context.is_a?(ActionView::Renderer)
198
- @view_renderer = context
199
- else
200
- lookup_context = context.is_a?(ActionView::LookupContext) ?
201
- context : ActionView::LookupContext.new(context)
202
- lookup_context.formats = formats if formats
203
- lookup_context.prefixes = controller._prefixes if controller
204
- @view_renderer = ActionView::Renderer.new(lookup_context)
205
- end
233
+ @lookup_context = lookup_context
234
+
235
+ @view_renderer = ActionView::Renderer.new @lookup_context
236
+ @current_template = nil
206
237
 
207
- @cache_hit = {}
208
238
  assign(assigns)
209
239
  assign_controller(controller)
210
240
  _prepare_context
211
241
  end
212
242
 
243
+ def _run(method, template, locals, buffer, add_to_stack: true, &block)
244
+ _old_output_buffer, _old_virtual_path, _old_template = @output_buffer, @virtual_path, @current_template
245
+ @current_template = template if add_to_stack
246
+ @output_buffer = buffer
247
+ public_send(method, locals, buffer, &block)
248
+ ensure
249
+ @output_buffer, @virtual_path, @current_template = _old_output_buffer, _old_virtual_path, _old_template
250
+ end
251
+
252
+ def compiled_method_container
253
+ raise NotImplementedError, <<~msg.squish
254
+ Subclasses of ActionView::Base must implement `compiled_method_container`
255
+ or use the class method `with_empty_template_cache` for constructing
256
+ an ActionView::Base subclass that has an empty cache.
257
+ msg
258
+ end
259
+
260
+ def in_rendering_context(options)
261
+ old_view_renderer = @view_renderer
262
+ old_lookup_context = @lookup_context
263
+
264
+ if !lookup_context.html_fallback_for_js && options[:formats]
265
+ formats = Array(options[:formats])
266
+ if formats == [:js]
267
+ formats << :html
268
+ end
269
+ @lookup_context = lookup_context.with_prepended_formats(formats)
270
+ @view_renderer = ActionView::Renderer.new @lookup_context
271
+ end
272
+
273
+ yield @view_renderer
274
+ ensure
275
+ @view_renderer = old_view_renderer
276
+ @lookup_context = old_lookup_context
277
+ end
278
+
213
279
  ActiveSupport.run_load_hooks(:action_view, self)
214
280
  end
215
281
  end
@@ -3,6 +3,21 @@
3
3
  require "active_support/core_ext/string/output_safety"
4
4
 
5
5
  module ActionView
6
+ # Used as a buffer for views
7
+ #
8
+ # The main difference between this and ActiveSupport::SafeBuffer
9
+ # is for the methods `<<` and `safe_expr_append=` the inputs are
10
+ # checked for nil before they are assigned and `to_s` is called on
11
+ # the input. For example:
12
+ #
13
+ # obuf = ActionView::OutputBuffer.new "hello"
14
+ # obuf << 5
15
+ # puts obuf # => "hello5"
16
+ #
17
+ # sbuf = ActiveSupport::SafeBuffer.new "hello"
18
+ # sbuf << 5
19
+ # puts sbuf # => "hello\u0005"
20
+ #
6
21
  class OutputBuffer < ActiveSupport::SafeBuffer #:nodoc:
7
22
  def initialize(*)
8
23
  super
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionView
4
+ class CacheExpiry
5
+ class Executor
6
+ def initialize(watcher:)
7
+ @cache_expiry = CacheExpiry.new(watcher: watcher)
8
+ end
9
+
10
+ def before(target)
11
+ @cache_expiry.clear_cache_if_necessary
12
+ end
13
+ end
14
+
15
+ def initialize(watcher:)
16
+ @watched_dirs = nil
17
+ @watcher_class = watcher
18
+ @watcher = nil
19
+ @mutex = Mutex.new
20
+ end
21
+
22
+ def clear_cache_if_necessary
23
+ @mutex.synchronize do
24
+ watched_dirs = dirs_to_watch
25
+ return if watched_dirs.empty?
26
+
27
+ if watched_dirs != @watched_dirs
28
+ @watched_dirs = watched_dirs
29
+ @watcher = @watcher_class.new([], watched_dirs) do
30
+ clear_cache
31
+ end
32
+ @watcher.execute
33
+ else
34
+ @watcher.execute_if_updated
35
+ end
36
+ end
37
+ end
38
+
39
+ def clear_cache
40
+ ActionView::LookupContext::DetailsKey.clear
41
+ end
42
+
43
+ private
44
+ def dirs_to_watch
45
+ all_view_paths.grep(FileSystemResolver).map!(&:path).tap(&:uniq!).sort!
46
+ end
47
+
48
+ def all_view_paths
49
+ ActionView::ViewPaths.all_view_paths.flat_map(&:paths)
50
+ end
51
+ end
52
+ end
@@ -1,21 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActionView
4
- module CompiledTemplates #:nodoc:
5
- # holds compiled template code
6
- end
7
-
8
4
  # = Action View Context
9
5
  #
10
6
  # Action View contexts are supplied to Action Controller to render a template.
11
7
  # The default Action View context is ActionView::Base.
12
8
  #
13
- # In order to work with ActionController, a Context must just include this module.
14
- # The initialization of the variables used by the context (@output_buffer, @view_flow,
15
- # and @virtual_path) is responsibility of the object that includes this module
16
- # (although you can call _prepare_context defined below).
9
+ # In order to work with Action Controller, a Context must just include this
10
+ # module. The initialization of the variables used by the context
11
+ # (@output_buffer, @view_flow, and @virtual_path) is responsibility of the
12
+ # object that includes this module (although you can call _prepare_context
13
+ # defined below).
17
14
  module Context
18
- include CompiledTemplates
19
15
  attr_accessor :output_buffer, :view_flow
20
16
 
21
17
  # Prepares the context by setting the appropriate instance variables.
@@ -130,8 +130,9 @@ module ActionView
130
130
 
131
131
  def add_dependencies(render_dependencies, arguments, pattern)
132
132
  arguments.scan(pattern) do
133
- add_dynamic_dependency(render_dependencies, Regexp.last_match[:dynamic])
134
- add_static_dependency(render_dependencies, Regexp.last_match[:static])
133
+ match = Regexp.last_match
134
+ add_dynamic_dependency(render_dependencies, match[:dynamic])
135
+ add_static_dependency(render_dependencies, match[:static], match[:quote])
135
136
  end
136
137
  end
137
138
 
@@ -141,7 +142,12 @@ module ActionView
141
142
  end
142
143
  end
143
144
 
144
- def add_static_dependency(dependencies, dependency)
145
+ def add_static_dependency(dependencies, dependency, quote_type)
146
+ if quote_type == '"'
147
+ # Ignore if there is interpolation
148
+ return if dependency.include?('#{')
149
+ end
150
+
145
151
  if dependency
146
152
  if dependency.include?("/")
147
153
  dependencies << dependency
@@ -164,7 +170,7 @@ module ActionView
164
170
  def explicit_dependencies
165
171
  dependencies = source.scan(EXPLICIT_DEPENDENCY).flatten.uniq
166
172
 
167
- wildcards, explicits = dependencies.partition { |dependency| dependency[-1] == "*" }
173
+ wildcards, explicits = dependencies.partition { |dependency| dependency.end_with?("*") }
168
174
 
169
175
  (explicits + resolve_directories(wildcards)).uniq
170
176
  end
@@ -1,28 +1,24 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "concurrent/map"
4
3
  require "action_view/dependency_tracker"
5
- require "monitor"
6
4
 
7
5
  module ActionView
8
6
  class Digestor
9
7
  @@digest_mutex = Mutex.new
10
8
 
11
- module PerExecutionDigestCacheExpiry
12
- def self.before(target)
13
- ActionView::LookupContext::DetailsKey.clear
14
- end
15
- end
16
-
17
9
  class << self
18
10
  # Supported options:
19
11
  #
20
- # * <tt>name</tt> - Template name
21
- # * <tt>finder</tt> - An instance of <tt>ActionView::LookupContext</tt>
22
- # * <tt>dependencies</tt> - An array of dependent views
23
- def digest(name:, finder:, dependencies: [])
24
- dependencies ||= []
25
- cache_key = [ name, finder.rendered_format, dependencies ].flatten.compact.join(".")
12
+ # * <tt>name</tt> - Template name
13
+ # * <tt>format</tt> - Template format
14
+ # * <tt>finder</tt> - An instance of <tt>ActionView::LookupContext</tt>
15
+ # * <tt>dependencies</tt> - An array of dependent views
16
+ def digest(name:, format: nil, finder:, dependencies: nil)
17
+ if dependencies.nil? || dependencies.empty?
18
+ cache_key = "#{name}.#{format}"
19
+ else
20
+ cache_key = [ name, format, dependencies ].flatten.compact.join(".")
21
+ end
26
22
 
27
23
  # this is a correctly done double-checked locking idiom
28
24
  # (Concurrent::Map's lookups have volatile semantics)
@@ -32,7 +28,7 @@ module ActionView
32
28
  root = tree(name, finder, partial)
33
29
  dependencies.each do |injected_dep|
34
30
  root.children << Injected.new(injected_dep, nil, nil)
35
- end
31
+ end if dependencies
36
32
  finder.digest_cache[cache_key] = root.digest(finder)
37
33
  end
38
34
  end
@@ -45,10 +41,9 @@ module ActionView
45
41
  # Create a dependency tree for template named +name+.
46
42
  def tree(name, finder, partial = false, seen = {})
47
43
  logical_name = name.gsub(%r|/_|, "/")
44
+ interpolated = name.include?("#")
48
45
 
49
- if template = find_template(finder, logical_name, [], partial, [])
50
- finder.rendered_format ||= template.formats.first
51
-
46
+ if !interpolated && (template = find_template(finder, logical_name, [], partial, []))
52
47
  if node = seen[template.identifier] # handle cycles in the tree
53
48
  node
54
49
  else
@@ -61,7 +56,7 @@ module ActionView
61
56
  node
62
57
  end
63
58
  else
64
- unless name.include?("#") # Dynamic template partial names can never be tracked
59
+ unless interpolated # Dynamic template partial names can never be tracked
65
60
  logger.error " Couldn't find template for digesting: #{name}"
66
61
  end
67
62
 
@@ -72,9 +67,7 @@ module ActionView
72
67
  private
73
68
  def find_template(finder, name, prefixes, partial, keys)
74
69
  finder.disable_cache do
75
- format = finder.rendered_format
76
- result = finder.find_all(name, prefixes, partial, keys, formats: [format]).first if format
77
- result || finder.find_all(name, prefixes, partial, keys).first
70
+ finder.find_all(name, prefixes, partial, keys).first
78
71
  end
79
72
  end
80
73
  end
@@ -68,7 +68,6 @@ module ActionView
68
68
  end
69
69
 
70
70
  private
71
-
72
71
  def inside_fiber?
73
72
  Fiber.current.object_id != @root
74
73
  end
@@ -7,10 +7,10 @@ module ActionView
7
7
  end
8
8
 
9
9
  module VERSION
10
- MAJOR = 5
11
- MINOR = 2
12
- TINY = 7
13
- PRE = "1"
10
+ MAJOR = 6
11
+ MINOR = 1
12
+ TINY = 4
13
+ PRE = "6"
14
14
 
15
15
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
16
16
  end
@@ -38,7 +38,6 @@ module ActionView
38
38
  end
39
39
 
40
40
  private
41
-
42
41
  def object_has_errors?
43
42
  object.respond_to?(:errors) && object.errors.respond_to?(:[]) && error_message.present?
44
43
  end
@@ -3,7 +3,6 @@
3
3
  require "active_support/core_ext/array/extract_options"
4
4
  require "active_support/core_ext/hash/keys"
5
5
  require "active_support/core_ext/object/inclusion"
6
- require "active_support/core_ext/object/try"
7
6
  require "action_view/helpers/asset_url_helper"
8
7
  require "action_view/helpers/tag_helper"
9
8
 
@@ -24,13 +23,15 @@ module ActionView
24
23
  include AssetUrlHelper
25
24
  include TagHelper
26
25
 
26
+ mattr_accessor :preload_links_header
27
+
27
28
  # Returns an HTML script tag for each of the +sources+ provided.
28
29
  #
29
30
  # Sources may be paths to JavaScript files. Relative paths are assumed to be relative
30
31
  # to <tt>assets/javascripts</tt>, full paths are assumed to be relative to the document
31
32
  # root. Relative paths are idiomatic, use absolute paths only when needed.
32
33
  #
33
- # When passing paths, the ".js" extension is optional. If you do not want ".js"
34
+ # When passing paths, the ".js" extension is optional. If you do not want ".js"
34
35
  # appended to the path <tt>extname: false</tt> can be set on the options.
35
36
  #
36
37
  # You can modify the HTML attributes of the script tag by passing a hash as the
@@ -55,7 +56,7 @@ module ActionView
55
56
  # that path.
56
57
  # * <tt>:skip_pipeline</tt> - This option is used to bypass the asset pipeline
57
58
  # when it is set to true.
58
- # * <tt>:nonce<tt> - When set to true, adds an automatic nonce value if
59
+ # * <tt>:nonce</tt> - When set to true, adds an automatic nonce value if
59
60
  # you have Content Security Policy enabled.
60
61
  #
61
62
  # ==== Examples
@@ -87,21 +88,34 @@ module ActionView
87
88
  def javascript_include_tag(*sources)
88
89
  options = sources.extract_options!.stringify_keys
89
90
  path_options = options.extract!("protocol", "extname", "host", "skip_pipeline").symbolize_keys
90
- early_hints_links = []
91
+ preload_links = []
92
+ nopush = options["nopush"].nil? ? true : options.delete("nopush")
93
+ crossorigin = options.delete("crossorigin")
94
+ crossorigin = "anonymous" if crossorigin == true
95
+ integrity = options["integrity"]
91
96
 
92
97
  sources_tags = sources.uniq.map { |source|
93
98
  href = path_to_javascript(source, path_options)
94
- early_hints_links << "<#{href}>; rel=preload; as=script"
99
+ if preload_links_header && !options["defer"]
100
+ preload_link = "<#{href}>; rel=preload; as=script"
101
+ preload_link += "; crossorigin=#{crossorigin}" unless crossorigin.nil?
102
+ preload_link += "; integrity=#{integrity}" unless integrity.nil?
103
+ preload_link += "; nopush" if nopush
104
+ preload_links << preload_link
105
+ end
95
106
  tag_options = {
96
- "src" => href
107
+ "src" => href,
108
+ "crossorigin" => crossorigin
97
109
  }.merge!(options)
98
110
  if tag_options["nonce"] == true
99
111
  tag_options["nonce"] = content_security_policy_nonce
100
112
  end
101
- content_tag("script".freeze, "", tag_options)
113
+ content_tag("script", "", tag_options)
102
114
  }.join("\n").html_safe
103
115
 
104
- request.send_early_hints("Link" => early_hints_links.join("\n")) if respond_to?(:request) && request
116
+ if preload_links_header
117
+ send_preload_links_header(preload_links)
118
+ end
105
119
 
106
120
  sources_tags
107
121
  end
@@ -137,20 +151,33 @@ module ActionView
137
151
  def stylesheet_link_tag(*sources)
138
152
  options = sources.extract_options!.stringify_keys
139
153
  path_options = options.extract!("protocol", "host", "skip_pipeline").symbolize_keys
140
- early_hints_links = []
154
+ preload_links = []
155
+ crossorigin = options.delete("crossorigin")
156
+ crossorigin = "anonymous" if crossorigin == true
157
+ nopush = options["nopush"].nil? ? true : options.delete("nopush")
158
+ integrity = options["integrity"]
141
159
 
142
160
  sources_tags = sources.uniq.map { |source|
143
161
  href = path_to_stylesheet(source, path_options)
144
- early_hints_links << "<#{href}>; rel=preload; as=style"
162
+ if preload_links_header
163
+ preload_link = "<#{href}>; rel=preload; as=style"
164
+ preload_link += "; crossorigin=#{crossorigin}" unless crossorigin.nil?
165
+ preload_link += "; integrity=#{integrity}" unless integrity.nil?
166
+ preload_link += "; nopush" if nopush
167
+ preload_links << preload_link
168
+ end
145
169
  tag_options = {
146
170
  "rel" => "stylesheet",
147
171
  "media" => "screen",
172
+ "crossorigin" => crossorigin,
148
173
  "href" => href
149
174
  }.merge!(options)
150
175
  tag(:link, tag_options)
151
176
  }.join("\n").html_safe
152
177
 
153
- request.send_early_hints("Link" => early_hints_links.join("\n")) if respond_to?(:request) && request
178
+ if preload_links_header
179
+ send_preload_links_header(preload_links)
180
+ end
154
181
 
155
182
  sources_tags
156
183
  end
@@ -241,6 +268,7 @@ module ActionView
241
268
  # * <tt>:as</tt> - Override the auto-generated value for as attribute, calculated using +source+ extension and mime type.
242
269
  # * <tt>:crossorigin</tt> - Specify the crossorigin attribute, required to load cross-origin resources.
243
270
  # * <tt>:nopush</tt> - Specify if the use of server push is not desired for the resource. Defaults to +false+.
271
+ # * <tt>:integrity</tt> - Specify the integrity attribute.
244
272
  #
245
273
  # ==== Examples
246
274
  #
@@ -266,15 +294,16 @@ module ActionView
266
294
  # # => <link rel="preload" href="/media/audio.ogg" as="audio" type="audio/ogg" />
267
295
  #
268
296
  def preload_link_tag(source, options = {})
269
- href = asset_path(source, skip_pipeline: options.delete(:skip_pipeline))
297
+ href = path_to_asset(source, skip_pipeline: options.delete(:skip_pipeline))
270
298
  extname = File.extname(source).downcase.delete(".")
271
- mime_type = options.delete(:type) || Template::Types[extname].try(:to_s)
299
+ mime_type = options.delete(:type) || Template::Types[extname]&.to_s
272
300
  as_type = options.delete(:as) || resolve_link_as(extname, mime_type)
273
301
  crossorigin = options.delete(:crossorigin)
274
302
  crossorigin = "anonymous" if crossorigin == true || (crossorigin.blank? && as_type == "font")
303
+ integrity = options[:integrity]
275
304
  nopush = options.delete(:nopush) || false
276
305
 
277
- link_tag = tag.link({
306
+ link_tag = tag.link(**{
278
307
  rel: "preload",
279
308
  href: href,
280
309
  as: as_type,
@@ -282,12 +311,13 @@ module ActionView
282
311
  crossorigin: crossorigin
283
312
  }.merge!(options.symbolize_keys))
284
313
 
285
- early_hints_link = "<#{href}>; rel=preload; as=#{as_type}"
286
- early_hints_link += "; type=#{mime_type}" if mime_type
287
- early_hints_link += "; crossorigin=#{crossorigin}" if crossorigin
288
- early_hints_link += "; nopush" if nopush
314
+ preload_link = "<#{href}>; rel=preload; as=#{as_type}"
315
+ preload_link += "; type=#{mime_type}" if mime_type
316
+ preload_link += "; crossorigin=#{crossorigin}" if crossorigin
317
+ preload_link += "; integrity=#{integrity}" if integrity
318
+ preload_link += "; nopush" if nopush
289
319
 
290
- request.send_early_hints("Link" => early_hints_link) if respond_to?(:request) && request
320
+ send_preload_links_header([preload_link])
291
321
 
292
322
  link_tag
293
323
  end
@@ -329,14 +359,14 @@ module ActionView
329
359
  # image_tag("pic.jpg", srcset: [["pic_1024.jpg", "1024w"], ["pic_1980.jpg", "1980w"]], sizes: "100vw")
330
360
  # # => <img src="/assets/pic.jpg" srcset="/assets/pic_1024.jpg 1024w, /assets/pic_1980.jpg 1980w" sizes="100vw">
331
361
  #
332
- # Active Storage (images that are uploaded by the users of your app):
362
+ # Active Storage blobs (images that are uploaded by the users of your app):
333
363
  #
334
364
  # image_tag(user.avatar)
335
365
  # # => <img src="/rails/active_storage/blobs/.../tiger.jpg" />
336
- # image_tag(user.avatar.variant(resize: "100x100"))
337
- # # => <img src="/rails/active_storage/variants/.../tiger.jpg" />
338
- # image_tag(user.avatar.variant(resize: "100x100"), size: '100')
339
- # # => <img width="100" height="100" src="/rails/active_storage/variants/.../tiger.jpg" />
366
+ # image_tag(user.avatar.variant(resize_to_limit: [100, 100]))
367
+ # # => <img src="/rails/active_storage/representations/.../tiger.jpg" />
368
+ # image_tag(user.avatar.variant(resize_to_limit: [100, 100]), size: '100')
369
+ # # => <img width="100" height="100" src="/rails/active_storage/representations/.../tiger.jpg" />
340
370
  def image_tag(source, options = {})
341
371
  options = options.symbolize_keys
342
372
  check_for_image_tag_errors(options)
@@ -355,29 +385,6 @@ module ActionView
355
385
  tag("img", options)
356
386
  end
357
387
 
358
- # Returns a string suitable for an HTML image tag alt attribute.
359
- # The +src+ argument is meant to be an image file path.
360
- # The method removes the basename of the file path and the digest,
361
- # if any. It also removes hyphens and underscores from file names and
362
- # replaces them with spaces, returning a space-separated, titleized
363
- # string.
364
- #
365
- # ==== Examples
366
- #
367
- # image_alt('rails.png')
368
- # # => Rails
369
- #
370
- # image_alt('hyphenated-file-name.png')
371
- # # => Hyphenated file name
372
- #
373
- # image_alt('underscored_file_name.png')
374
- # # => Underscored file name
375
- def image_alt(src)
376
- ActiveSupport::Deprecation.warn("image_alt is deprecated and will be removed from Rails 6.0. You must explicitly set alt text on images.")
377
-
378
- File.basename(src, ".*".freeze).sub(/-[[:xdigit:]]{32,64}\z/, "".freeze).tr("-_".freeze, " ".freeze).capitalize
379
- end
380
-
381
388
  # Returns an HTML video tag for the +sources+. If +sources+ is a string,
382
389
  # a single video tag will be returned. If +sources+ is an array, a video
383
390
  # tag with nested source tags for each source will be returned. The
@@ -506,6 +513,16 @@ module ActionView
506
513
  type
507
514
  end
508
515
  end
516
+
517
+ def send_preload_links_header(preload_links)
518
+ if respond_to?(:request) && request
519
+ request.send_early_hints("Link" => preload_links.join("\n"))
520
+ end
521
+
522
+ if respond_to?(:response) && response
523
+ response.headers["Link"] = [response.headers["Link"].presence, *preload_links].compact.join(",")
524
+ end
525
+ end
509
526
  end
510
527
  end
511
528
  end
@@ -52,7 +52,7 @@ module ActionView
52
52
  # solution being slower. You should be sure to measure your actual
53
53
  # performance across targeted browsers both before and after this change.
54
54
  #
55
- # To implement the corresponding hosts you can either setup four actual
55
+ # To implement the corresponding hosts you can either set up four actual
56
56
  # hosts or use wildcard DNS to CNAME the wildcard to a single asset host.
57
57
  # You can read more about setting up your DNS CNAME records from your ISP.
58
58
  #
@@ -80,7 +80,7 @@ module ActionView
80
80
  # absolute path of the asset, for example "/assets/rails.png".
81
81
  #
82
82
  # ActionController::Base.asset_host = Proc.new { |source|
83
- # if source.ends_with?('.css')
83
+ # if source.end_with?('.css')
84
84
  # "http://stylesheets.example.com"
85
85
  # else
86
86
  # "http://assets.example.com"
@@ -98,8 +98,9 @@ module ActionView
98
98
  # have SSL certificates for each of the asset hosts this technique allows you
99
99
  # to avoid warnings in the client about mixed media.
100
100
  # Note that the +request+ parameter might not be supplied, e.g. when the assets
101
- # are precompiled via a Rake task. Make sure to use a +Proc+ instead of a lambda,
102
- # since a +Proc+ allows missing parameters and sets them to +nil+.
101
+ # are precompiled with the command <tt>bin/rails assets:precompile</tt>. Make sure to use a
102
+ # +Proc+ instead of a lambda, since a +Proc+ allows missing parameters and sets them
103
+ # to +nil+.
103
104
  #
104
105
  # config.action_controller.asset_host = Proc.new { |source, request|
105
106
  # if request && request.ssl?
@@ -132,6 +133,8 @@ module ActionView
132
133
  # which is implemented by sprockets-rails.
133
134
  #
134
135
  # asset_path("application.js") # => "/assets/application-60aa4fdc5cea14baf5400fba1abf4f2a46a5166bad4772b1effe341570f07de9.js"
136
+ # asset_path('application.js', host: 'example.com') # => "//example.com/assets/application.js"
137
+ # asset_path("application.js", host: 'example.com', protocol: 'https') # => "https://example.com/assets/application.js"
135
138
  #
136
139
  # === Without the asset pipeline (<tt>skip_pipeline: true</tt>)
137
140
  #
@@ -187,7 +190,7 @@ module ActionView
187
190
  return "" if source.blank?
188
191
  return source if URI_REGEXP.match?(source)
189
192
 
190
- tail, source = source[/([\?#].+)$/], source.sub(/([\?#].+)$/, "".freeze)
193
+ tail, source = source[/([\?#].+)$/], source.sub(/([\?#].+)$/, "")
191
194
 
192
195
  if extname = compute_asset_extname(source, options)
193
196
  source = "#{source}#{extname}"
@@ -203,7 +206,7 @@ module ActionView
203
206
 
204
207
  relative_url_root = defined?(config.relative_url_root) && config.relative_url_root
205
208
  if relative_url_root
206
- source = File.join(relative_url_root, source) unless source.starts_with?("#{relative_url_root}/")
209
+ source = File.join(relative_url_root, source) unless source.start_with?("#{relative_url_root}/")
207
210
  end
208
211
 
209
212
  if host = compute_asset_host(source, options)