actionview 6.1.4.4 → 7.0.0.alpha1
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 +93 -297
- data/MIT-LICENSE +1 -1
- data/lib/action_view/base.rb +3 -3
- data/lib/action_view/buffers.rb +2 -2
- data/lib/action_view/cache_expiry.rb +46 -32
- data/lib/action_view/dependency_tracker/erb_tracker.rb +154 -0
- data/lib/action_view/dependency_tracker/ripper_tracker.rb +59 -0
- data/lib/action_view/dependency_tracker.rb +6 -147
- data/lib/action_view/digestor.rb +7 -4
- data/lib/action_view/flows.rb +4 -4
- data/lib/action_view/gem_version.rb +4 -4
- data/lib/action_view/helpers/active_model_helper.rb +1 -1
- data/lib/action_view/helpers/asset_tag_helper.rb +84 -29
- data/lib/action_view/helpers/asset_url_helper.rb +7 -7
- data/lib/action_view/helpers/atom_feed_helper.rb +3 -4
- data/lib/action_view/helpers/cache_helper.rb +51 -3
- data/lib/action_view/helpers/capture_helper.rb +2 -2
- data/lib/action_view/helpers/controller_helper.rb +2 -2
- data/lib/action_view/helpers/csp_helper.rb +1 -1
- data/lib/action_view/helpers/csrf_helper.rb +1 -1
- data/lib/action_view/helpers/date_helper.rb +5 -5
- data/lib/action_view/helpers/debug_helper.rb +3 -1
- data/lib/action_view/helpers/form_helper.rb +72 -12
- data/lib/action_view/helpers/form_options_helper.rb +65 -33
- data/lib/action_view/helpers/form_tag_helper.rb +73 -30
- data/lib/action_view/helpers/javascript_helper.rb +3 -5
- data/lib/action_view/helpers/number_helper.rb +3 -4
- data/lib/action_view/helpers/output_safety_helper.rb +2 -2
- data/lib/action_view/helpers/rendering_helper.rb +1 -1
- data/lib/action_view/helpers/sanitize_helper.rb +2 -2
- data/lib/action_view/helpers/tag_helper.rb +17 -4
- data/lib/action_view/helpers/tags/base.rb +2 -14
- data/lib/action_view/helpers/tags/check_box.rb +1 -1
- data/lib/action_view/helpers/tags/collection_select.rb +1 -1
- data/lib/action_view/helpers/tags/time_field.rb +10 -1
- data/lib/action_view/helpers/tags/weekday_select.rb +27 -0
- data/lib/action_view/helpers/tags.rb +3 -2
- data/lib/action_view/helpers/text_helper.rb +24 -13
- data/lib/action_view/helpers/translation_helper.rb +1 -2
- data/lib/action_view/helpers/url_helper.rb +110 -81
- data/lib/action_view/helpers.rb +25 -25
- data/lib/action_view/lookup_context.rb +33 -52
- data/lib/action_view/model_naming.rb +1 -1
- data/lib/action_view/path_set.rb +16 -22
- data/lib/action_view/railtie.rb +15 -2
- data/lib/action_view/render_parser.rb +188 -0
- data/lib/action_view/renderer/abstract_renderer.rb +2 -2
- data/lib/action_view/renderer/partial_renderer.rb +0 -34
- data/lib/action_view/renderer/renderer.rb +4 -4
- data/lib/action_view/renderer/streaming_template_renderer.rb +3 -3
- data/lib/action_view/renderer/template_renderer.rb +6 -2
- data/lib/action_view/rendering.rb +2 -2
- data/lib/action_view/ripper_ast_parser.rb +198 -0
- data/lib/action_view/routing_url_for.rb +1 -1
- data/lib/action_view/template/error.rb +108 -13
- data/lib/action_view/template/handlers/erb.rb +6 -0
- data/lib/action_view/template/handlers.rb +3 -3
- data/lib/action_view/template/html.rb +3 -3
- data/lib/action_view/template/inline.rb +3 -3
- data/lib/action_view/template/raw_file.rb +3 -3
- data/lib/action_view/template/resolver.rb +84 -311
- data/lib/action_view/template/text.rb +3 -3
- data/lib/action_view/template/types.rb +14 -12
- data/lib/action_view/template.rb +10 -1
- data/lib/action_view/template_details.rb +66 -0
- data/lib/action_view/template_path.rb +64 -0
- data/lib/action_view/test_case.rb +6 -2
- data/lib/action_view/testing/resolvers.rb +11 -12
- data/lib/action_view/unbound_template.rb +33 -7
- data/lib/action_view.rb +3 -4
- metadata +22 -15
@@ -0,0 +1,154 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActionView
|
4
|
+
class DependencyTracker # :nodoc:
|
5
|
+
class ERBTracker # :nodoc:
|
6
|
+
EXPLICIT_DEPENDENCY = /# Template Dependency: (\S+)/
|
7
|
+
|
8
|
+
# A valid ruby identifier - suitable for class, method and specially variable names
|
9
|
+
IDENTIFIER = /
|
10
|
+
[[:alpha:]_] # at least one uppercase letter, lowercase letter or underscore
|
11
|
+
[[:word:]]* # followed by optional letters, numbers or underscores
|
12
|
+
/x
|
13
|
+
|
14
|
+
# Any kind of variable name. e.g. @instance, @@class, $global or local.
|
15
|
+
# Possibly following a method call chain
|
16
|
+
VARIABLE_OR_METHOD_CHAIN = /
|
17
|
+
(?:\$|@{1,2})? # optional global, instance or class variable indicator
|
18
|
+
(?:#{IDENTIFIER}\.)* # followed by an optional chain of zero-argument method calls
|
19
|
+
(?<dynamic>#{IDENTIFIER}) # and a final valid identifier, captured as DYNAMIC
|
20
|
+
/x
|
21
|
+
|
22
|
+
# A simple string literal. e.g. "School's out!"
|
23
|
+
STRING = /
|
24
|
+
(?<quote>['"]) # an opening quote
|
25
|
+
(?<static>.*?) # with anything inside, captured as STATIC
|
26
|
+
\k<quote> # and a matching closing quote
|
27
|
+
/x
|
28
|
+
|
29
|
+
# Part of any hash containing the :partial key
|
30
|
+
PARTIAL_HASH_KEY = /
|
31
|
+
(?:\bpartial:|:partial\s*=>) # partial key in either old or new style hash syntax
|
32
|
+
\s* # followed by optional spaces
|
33
|
+
/x
|
34
|
+
|
35
|
+
# Part of any hash containing the :layout key
|
36
|
+
LAYOUT_HASH_KEY = /
|
37
|
+
(?:\blayout:|:layout\s*=>) # layout key in either old or new style hash syntax
|
38
|
+
\s* # followed by optional spaces
|
39
|
+
/x
|
40
|
+
|
41
|
+
# Matches:
|
42
|
+
# partial: "comments/comment", collection: @all_comments => "comments/comment"
|
43
|
+
# (object: @single_comment, partial: "comments/comment") => "comments/comment"
|
44
|
+
#
|
45
|
+
# "comments/comments"
|
46
|
+
# 'comments/comments'
|
47
|
+
# ('comments/comments')
|
48
|
+
#
|
49
|
+
# (@topic) => "topics/topic"
|
50
|
+
# topics => "topics/topic"
|
51
|
+
# (message.topics) => "topics/topic"
|
52
|
+
RENDER_ARGUMENTS = /\A
|
53
|
+
(?:\s*\(?\s*) # optional opening paren surrounded by spaces
|
54
|
+
(?:.*?#{PARTIAL_HASH_KEY}|#{LAYOUT_HASH_KEY})? # optional hash, up to the partial or layout key declaration
|
55
|
+
(?:#{STRING}|#{VARIABLE_OR_METHOD_CHAIN}) # finally, the dependency name of interest
|
56
|
+
/xm
|
57
|
+
|
58
|
+
LAYOUT_DEPENDENCY = /\A
|
59
|
+
(?:\s*\(?\s*) # optional opening paren surrounded by spaces
|
60
|
+
(?:.*?#{LAYOUT_HASH_KEY}) # check if the line has layout key declaration
|
61
|
+
(?:#{STRING}|#{VARIABLE_OR_METHOD_CHAIN}) # finally, the dependency name of interest
|
62
|
+
/xm
|
63
|
+
|
64
|
+
def self.supports_view_paths? # :nodoc:
|
65
|
+
true
|
66
|
+
end
|
67
|
+
|
68
|
+
def self.call(name, template, view_paths = nil)
|
69
|
+
new(name, template, view_paths).dependencies
|
70
|
+
end
|
71
|
+
|
72
|
+
def initialize(name, template, view_paths = nil)
|
73
|
+
@name, @template, @view_paths = name, template, view_paths
|
74
|
+
end
|
75
|
+
|
76
|
+
def dependencies
|
77
|
+
render_dependencies + explicit_dependencies
|
78
|
+
end
|
79
|
+
|
80
|
+
attr_reader :name, :template
|
81
|
+
private :name, :template
|
82
|
+
|
83
|
+
private
|
84
|
+
def source
|
85
|
+
template.source
|
86
|
+
end
|
87
|
+
|
88
|
+
def directory
|
89
|
+
name.split("/")[0..-2].join("/")
|
90
|
+
end
|
91
|
+
|
92
|
+
def render_dependencies
|
93
|
+
render_dependencies = []
|
94
|
+
render_calls = source.split(/\brender\b/).drop(1)
|
95
|
+
|
96
|
+
render_calls.each do |arguments|
|
97
|
+
add_dependencies(render_dependencies, arguments, LAYOUT_DEPENDENCY)
|
98
|
+
add_dependencies(render_dependencies, arguments, RENDER_ARGUMENTS)
|
99
|
+
end
|
100
|
+
|
101
|
+
render_dependencies.uniq
|
102
|
+
end
|
103
|
+
|
104
|
+
def add_dependencies(render_dependencies, arguments, pattern)
|
105
|
+
arguments.scan(pattern) do
|
106
|
+
match = Regexp.last_match
|
107
|
+
add_dynamic_dependency(render_dependencies, match[:dynamic])
|
108
|
+
add_static_dependency(render_dependencies, match[:static], match[:quote])
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def add_dynamic_dependency(dependencies, dependency)
|
113
|
+
if dependency
|
114
|
+
dependencies << "#{dependency.pluralize}/#{dependency.singularize}"
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def add_static_dependency(dependencies, dependency, quote_type)
|
119
|
+
if quote_type == '"'
|
120
|
+
# Ignore if there is interpolation
|
121
|
+
return if dependency.include?('#{')
|
122
|
+
end
|
123
|
+
|
124
|
+
if dependency
|
125
|
+
if dependency.include?("/")
|
126
|
+
dependencies << dependency
|
127
|
+
else
|
128
|
+
dependencies << "#{directory}/#{dependency}"
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def resolve_directories(wildcard_dependencies)
|
134
|
+
return [] unless @view_paths
|
135
|
+
return [] if wildcard_dependencies.empty?
|
136
|
+
|
137
|
+
# Remove trailing "/*"
|
138
|
+
prefixes = wildcard_dependencies.map { |query| query[0..-3] }
|
139
|
+
|
140
|
+
@view_paths.flat_map(&:all_template_paths).uniq.filter_map { |path|
|
141
|
+
path.to_s if prefixes.include?(path.prefix)
|
142
|
+
}.sort
|
143
|
+
end
|
144
|
+
|
145
|
+
def explicit_dependencies
|
146
|
+
dependencies = source.scan(EXPLICIT_DEPENDENCY).flatten.uniq
|
147
|
+
|
148
|
+
wildcards, explicits = dependencies.partition { |dependency| dependency.end_with?("/*") }
|
149
|
+
|
150
|
+
(explicits + resolve_directories(wildcards)).uniq
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActionView
|
4
|
+
class DependencyTracker # :nodoc:
|
5
|
+
class RipperTracker # :nodoc:
|
6
|
+
EXPLICIT_DEPENDENCY = /# Template Dependency: (\S+)/
|
7
|
+
|
8
|
+
def self.call(name, template, view_paths = nil)
|
9
|
+
new(name, template, view_paths).dependencies
|
10
|
+
end
|
11
|
+
|
12
|
+
def dependencies
|
13
|
+
render_dependencies + explicit_dependencies
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.supports_view_paths? # :nodoc:
|
17
|
+
true
|
18
|
+
end
|
19
|
+
|
20
|
+
def initialize(name, template, view_paths = nil)
|
21
|
+
@name, @template, @view_paths = name, template, view_paths
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
attr_reader :template, :name, :view_paths
|
26
|
+
|
27
|
+
def render_dependencies
|
28
|
+
return [] unless template.source.include?("render")
|
29
|
+
|
30
|
+
compiled_source = template.handler.call(template, template.source)
|
31
|
+
|
32
|
+
RenderParser.new(@name, compiled_source).render_calls.filter_map do |render_call|
|
33
|
+
next if render_call.end_with?("/_")
|
34
|
+
render_call.gsub(%r|/_|, "/")
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def explicit_dependencies
|
39
|
+
dependencies = template.source.scan(EXPLICIT_DEPENDENCY).flatten.uniq
|
40
|
+
|
41
|
+
wildcards, explicits = dependencies.partition { |dependency| dependency.end_with?("/*") }
|
42
|
+
|
43
|
+
(explicits + resolve_directories(wildcards)).uniq
|
44
|
+
end
|
45
|
+
|
46
|
+
def resolve_directories(wildcard_dependencies)
|
47
|
+
return [] unless view_paths
|
48
|
+
return [] if wildcard_dependencies.empty?
|
49
|
+
|
50
|
+
# Remove trailing "/*"
|
51
|
+
prefixes = wildcard_dependencies.map { |query| query[0..-3] }
|
52
|
+
|
53
|
+
view_paths.flat_map(&:all_template_paths).uniq.filter_map { |path|
|
54
|
+
path.to_s if prefixes.include?(path.prefix)
|
55
|
+
}.sort
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -2,9 +2,15 @@
|
|
2
2
|
|
3
3
|
require "concurrent/map"
|
4
4
|
require "action_view/path_set"
|
5
|
+
require "action_view/render_parser"
|
5
6
|
|
6
7
|
module ActionView
|
7
8
|
class DependencyTracker # :nodoc:
|
9
|
+
extend ActiveSupport::Autoload
|
10
|
+
|
11
|
+
autoload :ERBTracker
|
12
|
+
autoload :RipperTracker
|
13
|
+
|
8
14
|
@trackers = Concurrent::Map.new
|
9
15
|
|
10
16
|
def self.find_dependencies(name, template, view_paths = nil)
|
@@ -29,153 +35,6 @@ module ActionView
|
|
29
35
|
@trackers.delete(handler)
|
30
36
|
end
|
31
37
|
|
32
|
-
class ERBTracker # :nodoc:
|
33
|
-
EXPLICIT_DEPENDENCY = /# Template Dependency: (\S+)/
|
34
|
-
|
35
|
-
# A valid ruby identifier - suitable for class, method and specially variable names
|
36
|
-
IDENTIFIER = /
|
37
|
-
[[:alpha:]_] # at least one uppercase letter, lowercase letter or underscore
|
38
|
-
[[:word:]]* # followed by optional letters, numbers or underscores
|
39
|
-
/x
|
40
|
-
|
41
|
-
# Any kind of variable name. e.g. @instance, @@class, $global or local.
|
42
|
-
# Possibly following a method call chain
|
43
|
-
VARIABLE_OR_METHOD_CHAIN = /
|
44
|
-
(?:\$|@{1,2})? # optional global, instance or class variable indicator
|
45
|
-
(?:#{IDENTIFIER}\.)* # followed by an optional chain of zero-argument method calls
|
46
|
-
(?<dynamic>#{IDENTIFIER}) # and a final valid identifier, captured as DYNAMIC
|
47
|
-
/x
|
48
|
-
|
49
|
-
# A simple string literal. e.g. "School's out!"
|
50
|
-
STRING = /
|
51
|
-
(?<quote>['"]) # an opening quote
|
52
|
-
(?<static>.*?) # with anything inside, captured as STATIC
|
53
|
-
\k<quote> # and a matching closing quote
|
54
|
-
/x
|
55
|
-
|
56
|
-
# Part of any hash containing the :partial key
|
57
|
-
PARTIAL_HASH_KEY = /
|
58
|
-
(?:\bpartial:|:partial\s*=>) # partial key in either old or new style hash syntax
|
59
|
-
\s* # followed by optional spaces
|
60
|
-
/x
|
61
|
-
|
62
|
-
# Part of any hash containing the :layout key
|
63
|
-
LAYOUT_HASH_KEY = /
|
64
|
-
(?:\blayout:|:layout\s*=>) # layout key in either old or new style hash syntax
|
65
|
-
\s* # followed by optional spaces
|
66
|
-
/x
|
67
|
-
|
68
|
-
# Matches:
|
69
|
-
# partial: "comments/comment", collection: @all_comments => "comments/comment"
|
70
|
-
# (object: @single_comment, partial: "comments/comment") => "comments/comment"
|
71
|
-
#
|
72
|
-
# "comments/comments"
|
73
|
-
# 'comments/comments'
|
74
|
-
# ('comments/comments')
|
75
|
-
#
|
76
|
-
# (@topic) => "topics/topic"
|
77
|
-
# topics => "topics/topic"
|
78
|
-
# (message.topics) => "topics/topic"
|
79
|
-
RENDER_ARGUMENTS = /\A
|
80
|
-
(?:\s*\(?\s*) # optional opening paren surrounded by spaces
|
81
|
-
(?:.*?#{PARTIAL_HASH_KEY}|#{LAYOUT_HASH_KEY})? # optional hash, up to the partial or layout key declaration
|
82
|
-
(?:#{STRING}|#{VARIABLE_OR_METHOD_CHAIN}) # finally, the dependency name of interest
|
83
|
-
/xm
|
84
|
-
|
85
|
-
LAYOUT_DEPENDENCY = /\A
|
86
|
-
(?:\s*\(?\s*) # optional opening paren surrounded by spaces
|
87
|
-
(?:.*?#{LAYOUT_HASH_KEY}) # check if the line has layout key declaration
|
88
|
-
(?:#{STRING}|#{VARIABLE_OR_METHOD_CHAIN}) # finally, the dependency name of interest
|
89
|
-
/xm
|
90
|
-
|
91
|
-
def self.supports_view_paths? # :nodoc:
|
92
|
-
true
|
93
|
-
end
|
94
|
-
|
95
|
-
def self.call(name, template, view_paths = nil)
|
96
|
-
new(name, template, view_paths).dependencies
|
97
|
-
end
|
98
|
-
|
99
|
-
def initialize(name, template, view_paths = nil)
|
100
|
-
@name, @template, @view_paths = name, template, view_paths
|
101
|
-
end
|
102
|
-
|
103
|
-
def dependencies
|
104
|
-
render_dependencies + explicit_dependencies
|
105
|
-
end
|
106
|
-
|
107
|
-
attr_reader :name, :template
|
108
|
-
private :name, :template
|
109
|
-
|
110
|
-
private
|
111
|
-
def source
|
112
|
-
template.source
|
113
|
-
end
|
114
|
-
|
115
|
-
def directory
|
116
|
-
name.split("/")[0..-2].join("/")
|
117
|
-
end
|
118
|
-
|
119
|
-
def render_dependencies
|
120
|
-
render_dependencies = []
|
121
|
-
render_calls = source.split(/\brender\b/).drop(1)
|
122
|
-
|
123
|
-
render_calls.each do |arguments|
|
124
|
-
add_dependencies(render_dependencies, arguments, LAYOUT_DEPENDENCY)
|
125
|
-
add_dependencies(render_dependencies, arguments, RENDER_ARGUMENTS)
|
126
|
-
end
|
127
|
-
|
128
|
-
render_dependencies.uniq
|
129
|
-
end
|
130
|
-
|
131
|
-
def add_dependencies(render_dependencies, arguments, pattern)
|
132
|
-
arguments.scan(pattern) do
|
133
|
-
match = Regexp.last_match
|
134
|
-
add_dynamic_dependency(render_dependencies, match[:dynamic])
|
135
|
-
add_static_dependency(render_dependencies, match[:static], match[:quote])
|
136
|
-
end
|
137
|
-
end
|
138
|
-
|
139
|
-
def add_dynamic_dependency(dependencies, dependency)
|
140
|
-
if dependency
|
141
|
-
dependencies << "#{dependency.pluralize}/#{dependency.singularize}"
|
142
|
-
end
|
143
|
-
end
|
144
|
-
|
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
|
-
|
151
|
-
if dependency
|
152
|
-
if dependency.include?("/")
|
153
|
-
dependencies << dependency
|
154
|
-
else
|
155
|
-
dependencies << "#{directory}/#{dependency}"
|
156
|
-
end
|
157
|
-
end
|
158
|
-
end
|
159
|
-
|
160
|
-
def resolve_directories(wildcard_dependencies)
|
161
|
-
return [] unless @view_paths
|
162
|
-
|
163
|
-
wildcard_dependencies.flat_map { |query, templates|
|
164
|
-
@view_paths.find_all_with_query(query).map do |template|
|
165
|
-
"#{File.dirname(query)}/#{File.basename(template).split('.').first}"
|
166
|
-
end
|
167
|
-
}.sort
|
168
|
-
end
|
169
|
-
|
170
|
-
def explicit_dependencies
|
171
|
-
dependencies = source.scan(EXPLICIT_DEPENDENCY).flatten.uniq
|
172
|
-
|
173
|
-
wildcards, explicits = dependencies.partition { |dependency| dependency.end_with?("*") }
|
174
|
-
|
175
|
-
(explicits + resolve_directories(wildcards)).uniq
|
176
|
-
end
|
177
|
-
end
|
178
|
-
|
179
38
|
register_tracker :erb, ERBTracker
|
180
39
|
end
|
181
40
|
end
|
data/lib/action_view/digestor.rb
CHANGED
@@ -17,15 +17,16 @@ module ActionView
|
|
17
17
|
if dependencies.nil? || dependencies.empty?
|
18
18
|
cache_key = "#{name}.#{format}"
|
19
19
|
else
|
20
|
-
|
20
|
+
dependencies_suffix = dependencies.flatten.tap(&:compact!).join(".")
|
21
|
+
cache_key = "#{name}.#{format}.#{dependencies_suffix}"
|
21
22
|
end
|
22
23
|
|
23
24
|
# this is a correctly done double-checked locking idiom
|
24
25
|
# (Concurrent::Map's lookups have volatile semantics)
|
25
26
|
finder.digest_cache[cache_key] || @@digest_mutex.synchronize do
|
26
27
|
finder.digest_cache.fetch(cache_key) do # re-check under lock
|
27
|
-
|
28
|
-
root = tree(
|
28
|
+
path = TemplatePath.parse(name)
|
29
|
+
root = tree(path.to_s, finder, path.partial?)
|
29
30
|
dependencies.each do |injected_dep|
|
30
31
|
root.children << Injected.new(injected_dep, nil, nil)
|
31
32
|
end if dependencies
|
@@ -43,7 +44,9 @@ module ActionView
|
|
43
44
|
logical_name = name.gsub(%r|/_|, "/")
|
44
45
|
interpolated = name.include?("#")
|
45
46
|
|
46
|
-
|
47
|
+
path = TemplatePath.parse(name)
|
48
|
+
|
49
|
+
if !interpolated && (template = find_template(finder, path.name, [path.prefix], partial, []))
|
47
50
|
if node = seen[template.identifier] # handle cycles in the tree
|
48
51
|
node
|
49
52
|
else
|
data/lib/action_view/flows.rb
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
require "active_support/core_ext/string/output_safety"
|
4
4
|
|
5
5
|
module ActionView
|
6
|
-
class OutputFlow
|
6
|
+
class OutputFlow # :nodoc:
|
7
7
|
attr_reader :content
|
8
8
|
|
9
9
|
def initialize
|
@@ -17,17 +17,17 @@ module ActionView
|
|
17
17
|
|
18
18
|
# Called by each renderer object to set the layout contents.
|
19
19
|
def set(key, value)
|
20
|
-
@content[key] = ActiveSupport::SafeBuffer.new(value)
|
20
|
+
@content[key] = ActiveSupport::SafeBuffer.new(value.to_s)
|
21
21
|
end
|
22
22
|
|
23
23
|
# Called by content_for
|
24
24
|
def append(key, value)
|
25
|
-
@content[key] << value
|
25
|
+
@content[key] << value.to_s
|
26
26
|
end
|
27
27
|
alias_method :append!, :append
|
28
28
|
end
|
29
29
|
|
30
|
-
class StreamingFlow < OutputFlow
|
30
|
+
class StreamingFlow < OutputFlow # :nodoc:
|
31
31
|
def initialize(view, fiber)
|
32
32
|
@view = view
|
33
33
|
@parent = nil
|
@@ -8,7 +8,7 @@ require "action_view/helpers/tag_helper"
|
|
8
8
|
|
9
9
|
module ActionView
|
10
10
|
# = Action View Asset Tag Helpers
|
11
|
-
module Helpers
|
11
|
+
module Helpers # :nodoc:
|
12
12
|
# This module provides methods for generating HTML that links views to assets such
|
13
13
|
# as images, JavaScripts, stylesheets, and feeds. These methods do not verify
|
14
14
|
# the assets exist before linking to them:
|
@@ -16,14 +16,15 @@ module ActionView
|
|
16
16
|
# image_tag("rails.png")
|
17
17
|
# # => <img src="/assets/rails.png" />
|
18
18
|
# stylesheet_link_tag("application")
|
19
|
-
# # => <link href="/assets/application.css?body=1"
|
19
|
+
# # => <link href="/assets/application.css?body=1" rel="stylesheet" />
|
20
20
|
module AssetTagHelper
|
21
|
-
extend ActiveSupport::Concern
|
22
|
-
|
23
21
|
include AssetUrlHelper
|
24
22
|
include TagHelper
|
25
23
|
|
24
|
+
mattr_accessor :image_loading
|
25
|
+
mattr_accessor :image_decoding
|
26
26
|
mattr_accessor :preload_links_header
|
27
|
+
mattr_accessor :apply_stylesheet_media_default
|
27
28
|
|
28
29
|
# Returns an HTML script tag for each of the +sources+ provided.
|
29
30
|
#
|
@@ -93,11 +94,12 @@ module ActionView
|
|
93
94
|
crossorigin = options.delete("crossorigin")
|
94
95
|
crossorigin = "anonymous" if crossorigin == true
|
95
96
|
integrity = options["integrity"]
|
97
|
+
rel = options["type"] == "module" ? "modulepreload" : "preload"
|
96
98
|
|
97
99
|
sources_tags = sources.uniq.map { |source|
|
98
100
|
href = path_to_javascript(source, path_options)
|
99
|
-
if preload_links_header && !options["defer"]
|
100
|
-
preload_link = "<#{href}>; rel
|
101
|
+
if preload_links_header && !options["defer"] && href.present? && !href.start_with?("data:")
|
102
|
+
preload_link = "<#{href}>; rel=#{rel}; as=script"
|
101
103
|
preload_link += "; crossorigin=#{crossorigin}" unless crossorigin.nil?
|
102
104
|
preload_link += "; integrity=#{integrity}" unless integrity.nil?
|
103
105
|
preload_link += "; nopush" if nopush
|
@@ -120,24 +122,41 @@ module ActionView
|
|
120
122
|
sources_tags
|
121
123
|
end
|
122
124
|
|
123
|
-
# Returns a stylesheet link tag for the sources specified as arguments.
|
124
|
-
#
|
125
|
+
# Returns a stylesheet link tag for the sources specified as arguments.
|
126
|
+
#
|
127
|
+
# When passing paths, the <tt>.css</tt> extension is optional.
|
128
|
+
# If you don't specify an extension, <tt>.css</tt> will be appended automatically.
|
129
|
+
# If you do not want <tt>.css</tt> appended to the path,
|
130
|
+
# set <tt>extname: false</tt> in the options.
|
125
131
|
# You can modify the link attributes by passing a hash as the last argument.
|
126
|
-
# For historical reasons, the 'media' attribute will always be present and defaults
|
127
|
-
# to "screen", so you must explicitly set it to "all" for the stylesheet(s) to
|
128
|
-
# apply to all media types.
|
129
132
|
#
|
130
133
|
# If the server supports Early Hints header links for these assets will be
|
131
134
|
# automatically pushed.
|
132
135
|
#
|
136
|
+
# ==== Options
|
137
|
+
#
|
138
|
+
# * <tt>:extname</tt> - Append an extension to the generated URL unless the extension
|
139
|
+
# already exists. This only applies for relative URLs.
|
140
|
+
# * <tt>:protocol</tt> - Sets the protocol of the generated URL. This option only
|
141
|
+
# applies when a relative URL and +host+ options are provided.
|
142
|
+
# * <tt>:host</tt> - When a relative URL is provided the host is added to the
|
143
|
+
# that path.
|
144
|
+
# * <tt>:skip_pipeline</tt> - This option is used to bypass the asset pipeline
|
145
|
+
# when it is set to true.
|
146
|
+
#
|
147
|
+
# ==== Examples
|
148
|
+
#
|
133
149
|
# stylesheet_link_tag "style"
|
134
|
-
# # => <link href="/assets/style.css"
|
150
|
+
# # => <link href="/assets/style.css" rel="stylesheet" />
|
135
151
|
#
|
136
152
|
# stylesheet_link_tag "style.css"
|
137
|
-
# # => <link href="/assets/style.css"
|
153
|
+
# # => <link href="/assets/style.css" rel="stylesheet" />
|
138
154
|
#
|
139
155
|
# stylesheet_link_tag "http://www.example.com/style.css"
|
140
|
-
# # => <link href="http://www.example.com/style.css"
|
156
|
+
# # => <link href="http://www.example.com/style.css" rel="stylesheet" />
|
157
|
+
#
|
158
|
+
# stylesheet_link_tag "style.less", extname: false, skip_pipeline: true, rel: "stylesheet/less"
|
159
|
+
# # => <link href="/stylesheets/style.less" rel="stylesheet/less">
|
141
160
|
#
|
142
161
|
# stylesheet_link_tag "style", media: "all"
|
143
162
|
# # => <link href="/assets/style.css" media="all" rel="stylesheet" />
|
@@ -146,11 +165,11 @@ module ActionView
|
|
146
165
|
# # => <link href="/assets/style.css" media="print" rel="stylesheet" />
|
147
166
|
#
|
148
167
|
# stylesheet_link_tag "random.styles", "/css/stylish"
|
149
|
-
# # => <link href="/assets/random.styles"
|
150
|
-
# # <link href="/css/stylish.css"
|
168
|
+
# # => <link href="/assets/random.styles" rel="stylesheet" />
|
169
|
+
# # <link href="/css/stylish.css" rel="stylesheet" />
|
151
170
|
def stylesheet_link_tag(*sources)
|
152
171
|
options = sources.extract_options!.stringify_keys
|
153
|
-
path_options = options.extract!("protocol", "host", "skip_pipeline").symbolize_keys
|
172
|
+
path_options = options.extract!("protocol", "extname", "host", "skip_pipeline").symbolize_keys
|
154
173
|
preload_links = []
|
155
174
|
crossorigin = options.delete("crossorigin")
|
156
175
|
crossorigin = "anonymous" if crossorigin == true
|
@@ -159,7 +178,7 @@ module ActionView
|
|
159
178
|
|
160
179
|
sources_tags = sources.uniq.map { |source|
|
161
180
|
href = path_to_stylesheet(source, path_options)
|
162
|
-
if preload_links_header
|
181
|
+
if preload_links_header && href.present? && !href.start_with?("data:")
|
163
182
|
preload_link = "<#{href}>; rel=preload; as=style"
|
164
183
|
preload_link += "; crossorigin=#{crossorigin}" unless crossorigin.nil?
|
165
184
|
preload_link += "; integrity=#{integrity}" unless integrity.nil?
|
@@ -168,10 +187,14 @@ module ActionView
|
|
168
187
|
end
|
169
188
|
tag_options = {
|
170
189
|
"rel" => "stylesheet",
|
171
|
-
"media" => "screen",
|
172
190
|
"crossorigin" => crossorigin,
|
173
191
|
"href" => href
|
174
192
|
}.merge!(options)
|
193
|
+
|
194
|
+
if apply_stylesheet_media_default && tag_options["media"].blank?
|
195
|
+
tag_options["media"] = "screen"
|
196
|
+
end
|
197
|
+
|
175
198
|
tag(:link, tag_options)
|
176
199
|
}.join("\n").html_safe
|
177
200
|
|
@@ -382,6 +405,10 @@ module ActionView
|
|
382
405
|
end
|
383
406
|
|
384
407
|
options[:width], options[:height] = extract_dimensions(options.delete(:size)) if options[:size]
|
408
|
+
|
409
|
+
options[:loading] ||= image_loading if image_loading
|
410
|
+
options[:decoding] ||= image_decoding if image_decoding
|
411
|
+
|
385
412
|
tag("img", options)
|
386
413
|
end
|
387
414
|
|
@@ -503,24 +530,52 @@ module ActionView
|
|
503
530
|
end
|
504
531
|
|
505
532
|
def resolve_link_as(extname, mime_type)
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
"
|
512
|
-
elsif (type = mime_type.to_s.split("/")[0]) && type.in?(%w(audio video font))
|
513
|
-
type
|
533
|
+
case extname
|
534
|
+
when "js" then "script"
|
535
|
+
when "css" then "style"
|
536
|
+
when "vtt" then "track"
|
537
|
+
else
|
538
|
+
mime_type.to_s.split("/").first.presence_in(%w(audio video font image))
|
514
539
|
end
|
515
540
|
end
|
516
541
|
|
517
|
-
|
542
|
+
MAX_HEADER_SIZE = 8_000 # Some HTTP client and proxies have a 8kiB header limit
|
543
|
+
def send_preload_links_header(preload_links, max_header_size: MAX_HEADER_SIZE)
|
544
|
+
return if preload_links.empty?
|
545
|
+
return if response.sending?
|
546
|
+
|
518
547
|
if respond_to?(:request) && request
|
519
548
|
request.send_early_hints("Link" => preload_links.join("\n"))
|
520
549
|
end
|
521
550
|
|
522
551
|
if respond_to?(:response) && response
|
523
|
-
|
552
|
+
header = response.headers["Link"]
|
553
|
+
header = header ? header.dup : +""
|
554
|
+
|
555
|
+
# rindex count characters not bytes, but we assume non-ascii characters
|
556
|
+
# are rare in urls, and we have a 192 bytes margin.
|
557
|
+
last_line_offset = header.rindex("\n")
|
558
|
+
last_line_size = if last_line_offset
|
559
|
+
header.bytesize - last_line_offset
|
560
|
+
else
|
561
|
+
header.bytesize
|
562
|
+
end
|
563
|
+
|
564
|
+
preload_links.each do |link|
|
565
|
+
if link.bytesize + last_line_size + 1 < max_header_size
|
566
|
+
unless header.empty?
|
567
|
+
header << ","
|
568
|
+
last_line_size += 1
|
569
|
+
end
|
570
|
+
else
|
571
|
+
header << "\n"
|
572
|
+
last_line_size = 0
|
573
|
+
end
|
574
|
+
header << link
|
575
|
+
last_line_size += link.bytesize
|
576
|
+
end
|
577
|
+
|
578
|
+
response.headers["Link"] = header
|
524
579
|
end
|
525
580
|
end
|
526
581
|
end
|