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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +250 -112
- data/MIT-LICENSE +1 -1
- data/README.rdoc +5 -3
- data/lib/action_view/base.rb +81 -15
- data/lib/action_view/buffers.rb +15 -0
- data/lib/action_view/cache_expiry.rb +52 -0
- data/lib/action_view/context.rb +5 -9
- data/lib/action_view/dependency_tracker.rb +10 -4
- data/lib/action_view/digestor.rb +15 -22
- data/lib/action_view/flows.rb +0 -1
- data/lib/action_view/gem_version.rb +4 -4
- data/lib/action_view/helpers/active_model_helper.rb +0 -1
- data/lib/action_view/helpers/asset_tag_helper.rb +64 -47
- data/lib/action_view/helpers/asset_url_helper.rb +9 -6
- data/lib/action_view/helpers/atom_feed_helper.rb +2 -1
- data/lib/action_view/helpers/cache_helper.rb +23 -22
- data/lib/action_view/helpers/capture_helper.rb +4 -0
- data/lib/action_view/helpers/csp_helper.rb +4 -2
- data/lib/action_view/helpers/csrf_helper.rb +1 -1
- data/lib/action_view/helpers/date_helper.rb +73 -30
- data/lib/action_view/helpers/form_helper.rb +305 -37
- data/lib/action_view/helpers/form_options_helper.rb +23 -23
- data/lib/action_view/helpers/form_tag_helper.rb +19 -16
- data/lib/action_view/helpers/javascript_helper.rb +12 -11
- data/lib/action_view/helpers/number_helper.rb +14 -8
- data/lib/action_view/helpers/output_safety_helper.rb +1 -1
- data/lib/action_view/helpers/rendering_helper.rb +17 -7
- data/lib/action_view/helpers/sanitize_helper.rb +12 -18
- data/lib/action_view/helpers/tag_helper.rb +100 -55
- data/lib/action_view/helpers/tags/base.rb +18 -11
- data/lib/action_view/helpers/tags/check_box.rb +0 -1
- data/lib/action_view/helpers/tags/collection_check_boxes.rb +0 -1
- data/lib/action_view/helpers/tags/collection_helpers.rb +0 -1
- data/lib/action_view/helpers/tags/collection_radio_buttons.rb +0 -1
- data/lib/action_view/helpers/tags/color_field.rb +1 -2
- data/lib/action_view/helpers/tags/date_field.rb +1 -2
- data/lib/action_view/helpers/tags/date_select.rb +2 -3
- data/lib/action_view/helpers/tags/datetime_field.rb +0 -1
- data/lib/action_view/helpers/tags/datetime_local_field.rb +1 -2
- data/lib/action_view/helpers/tags/label.rb +4 -1
- data/lib/action_view/helpers/tags/month_field.rb +1 -2
- data/lib/action_view/helpers/tags/radio_button.rb +0 -1
- data/lib/action_view/helpers/tags/select.rb +1 -2
- data/lib/action_view/helpers/tags/text_field.rb +0 -1
- data/lib/action_view/helpers/tags/time_field.rb +1 -2
- data/lib/action_view/helpers/tags/translator.rb +1 -6
- data/lib/action_view/helpers/tags/week_field.rb +1 -2
- data/lib/action_view/helpers/text_helper.rb +4 -5
- data/lib/action_view/helpers/translation_helper.rb +94 -54
- data/lib/action_view/helpers/url_helper.rb +136 -28
- data/lib/action_view/helpers.rb +0 -2
- data/lib/action_view/layouts.rb +8 -10
- data/lib/action_view/log_subscriber.rb +30 -15
- data/lib/action_view/lookup_context.rb +63 -35
- data/lib/action_view/path_set.rb +3 -12
- data/lib/action_view/railtie.rb +42 -26
- data/lib/action_view/record_identifier.rb +2 -3
- data/lib/action_view/renderer/abstract_renderer.rb +142 -11
- data/lib/action_view/renderer/collection_renderer.rb +196 -0
- data/lib/action_view/renderer/object_renderer.rb +34 -0
- data/lib/action_view/renderer/partial_renderer/collection_caching.rb +61 -16
- data/lib/action_view/renderer/partial_renderer.rb +21 -273
- data/lib/action_view/renderer/renderer.rb +59 -4
- data/lib/action_view/renderer/streaming_template_renderer.rb +10 -8
- data/lib/action_view/renderer/template_renderer.rb +35 -27
- data/lib/action_view/rendering.rb +54 -33
- data/lib/action_view/routing_url_for.rb +13 -12
- data/lib/action_view/template/error.rb +30 -15
- data/lib/action_view/template/handlers/builder.rb +2 -2
- data/lib/action_view/template/handlers/erb/erubi.rb +15 -9
- data/lib/action_view/template/handlers/erb.rb +16 -11
- data/lib/action_view/template/handlers/html.rb +1 -1
- data/lib/action_view/template/handlers/raw.rb +2 -2
- data/lib/action_view/template/handlers.rb +1 -1
- data/lib/action_view/template/html.rb +5 -6
- data/lib/action_view/template/inline.rb +22 -0
- data/lib/action_view/template/raw_file.rb +25 -0
- data/lib/action_view/template/renderable.rb +24 -0
- data/lib/action_view/template/resolver.rb +191 -150
- data/lib/action_view/template/sources/file.rb +17 -0
- data/lib/action_view/template/sources.rb +13 -0
- data/lib/action_view/template/text.rb +2 -3
- data/lib/action_view/template.rb +66 -75
- data/lib/action_view/test_case.rb +21 -29
- data/lib/action_view/testing/resolvers.rb +18 -27
- data/lib/action_view/unbound_template.rb +31 -0
- data/lib/action_view/view_paths.rb +59 -38
- data/lib/action_view.rb +7 -2
- data/lib/assets/compiled/rails-ujs.js +32 -6
- metadata +29 -18
- data/lib/action_view/helpers/record_tag_helper.rb +0 -23
data/lib/action_view/base.rb
CHANGED
@@ -27,7 +27,7 @@ module ActionView #:nodoc:
|
|
27
27
|
# Name: <%= person.name %><br/>
|
28
28
|
# <% end %>
|
29
29
|
#
|
30
|
-
# The loop is
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
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
|
data/lib/action_view/buffers.rb
CHANGED
@@ -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
|
data/lib/action_view/context.rb
CHANGED
@@ -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
|
14
|
-
# The initialization of the variables used by the context
|
15
|
-
# and @virtual_path) is responsibility of the
|
16
|
-
# (although you can call _prepare_context
|
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
|
-
|
134
|
-
|
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
|
173
|
+
wildcards, explicits = dependencies.partition { |dependency| dependency.end_with?("*") }
|
168
174
|
|
169
175
|
(explicits + resolve_directories(wildcards)).uniq
|
170
176
|
end
|
data/lib/action_view/digestor.rb
CHANGED
@@ -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>
|
21
|
-
# * <tt>
|
22
|
-
# * <tt>
|
23
|
-
|
24
|
-
|
25
|
-
|
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
|
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
|
-
|
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
|
data/lib/action_view/flows.rb
CHANGED
@@ -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.
|
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
|
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
|
-
|
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
|
-
|
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"
|
113
|
+
content_tag("script", "", tag_options)
|
102
114
|
}.join("\n").html_safe
|
103
115
|
|
104
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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 =
|
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]
|
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
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
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
|
-
|
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(
|
337
|
-
# # => <img src="/rails/active_storage/
|
338
|
-
# image_tag(user.avatar.variant(
|
339
|
-
# # => <img width="100" height="100" src="/rails/active_storage/
|
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
|
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.
|
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
|
102
|
-
# since a +Proc+ allows missing parameters and sets them
|
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(/([\?#].+)$/, ""
|
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.
|
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)
|