better_errors 2.4.0 → 2.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/.github/workflows/ci.yml +130 -0
- data/.github/workflows/release.yml +64 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +1 -1
- data/Gemfile +6 -0
- data/README.md +40 -8
- data/better_errors.gemspec +13 -2
- data/gemfiles/pry010.gemfile +2 -1
- data/gemfiles/pry011.gemfile +2 -1
- data/gemfiles/pry09.gemfile +2 -1
- data/gemfiles/rack.gemfile +2 -1
- data/gemfiles/rack_boc.gemfile +2 -1
- data/gemfiles/rails42.gemfile +3 -1
- data/gemfiles/rails42_boc.gemfile +3 -1
- data/gemfiles/rails42_haml.gemfile +3 -1
- data/gemfiles/rails50.gemfile +3 -1
- data/gemfiles/rails50_boc.gemfile +3 -1
- data/gemfiles/rails50_haml.gemfile +3 -1
- data/gemfiles/rails51.gemfile +3 -1
- data/gemfiles/rails51_boc.gemfile +3 -1
- data/gemfiles/rails51_haml.gemfile +3 -1
- data/gemfiles/rails52.gemfile +9 -0
- data/gemfiles/rails52_boc.gemfile +10 -0
- data/gemfiles/rails52_haml.gemfile +10 -0
- data/gemfiles/rails60.gemfile +8 -0
- data/gemfiles/rails60_boc.gemfile +9 -0
- data/gemfiles/rails60_haml.gemfile +9 -0
- data/lib/better_errors.rb +23 -32
- data/lib/better_errors/editor.rb +99 -0
- data/lib/better_errors/error_page.rb +37 -24
- data/lib/better_errors/exception_hint.rb +29 -0
- data/lib/better_errors/inspectable_value.rb +45 -0
- data/lib/better_errors/middleware.rb +59 -12
- data/lib/better_errors/raised_exception.rb +25 -4
- data/lib/better_errors/stack_frame.rb +25 -7
- data/lib/better_errors/templates/main.erb +78 -24
- data/lib/better_errors/templates/text.erb +5 -2
- data/lib/better_errors/templates/variable_info.erb +9 -2
- data/lib/better_errors/version.rb +1 -1
- metadata +37 -10
- data/.travis.yml +0 -68
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
module BetterErrors
|
|
2
|
+
class ExceptionHint
|
|
3
|
+
def initialize(exception)
|
|
4
|
+
@exception = exception
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
def hint
|
|
8
|
+
case exception
|
|
9
|
+
when NoMethodError
|
|
10
|
+
/\Aundefined method `(?<method>[^']+)' for (?<val>[^:]+):(?<klass>\w+)/.match(exception.message) do |match|
|
|
11
|
+
if match[:val] == "nil"
|
|
12
|
+
return "Something is `nil` when it probably shouldn't be."
|
|
13
|
+
elsif !match[:klass].start_with? '0x'
|
|
14
|
+
return "`#{match[:method]}` is being called on a `#{match[:klass]}` object, "\
|
|
15
|
+
"which might not be the type of object you were expecting."
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
when NameError
|
|
19
|
+
/\Aundefined local variable or method `(?<method>[^']+)' for/.match(exception.message) do |match|
|
|
20
|
+
return "`#{match[:method]}` is probably misspelled."
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
attr_reader :exception
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
require "cgi"
|
|
2
|
+
require "objspace" rescue nil
|
|
3
|
+
|
|
4
|
+
module BetterErrors
|
|
5
|
+
class ValueLargerThanConfiguredMaximum < StandardError; end
|
|
6
|
+
|
|
7
|
+
class InspectableValue
|
|
8
|
+
def initialize(value)
|
|
9
|
+
@original_value = value
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def to_html
|
|
13
|
+
raise ValueLargerThanConfiguredMaximum unless value_small_enough_to_inspect?
|
|
14
|
+
value_as_html
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
attr_reader :original_value
|
|
20
|
+
|
|
21
|
+
def value_as_html
|
|
22
|
+
@value_as_html ||= CGI.escapeHTML(value)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def value
|
|
26
|
+
@value ||= begin
|
|
27
|
+
if original_value.respond_to? :inspect
|
|
28
|
+
original_value.inspect
|
|
29
|
+
else
|
|
30
|
+
original_value
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def value_small_enough_to_inspect?
|
|
36
|
+
return true if BetterErrors.maximum_variable_inspect_size.nil?
|
|
37
|
+
|
|
38
|
+
if defined?(ObjectSpace) && defined?(ObjectSpace.memsize_of) && ObjectSpace.memsize_of(value)
|
|
39
|
+
ObjectSpace.memsize_of(value) <= BetterErrors.maximum_variable_inspect_size
|
|
40
|
+
else
|
|
41
|
+
value_as_html.length <= BetterErrors.maximum_variable_inspect_size
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
require "json"
|
|
2
2
|
require "ipaddr"
|
|
3
|
+
require "securerandom"
|
|
3
4
|
require "set"
|
|
4
5
|
require "rack"
|
|
5
6
|
|
|
@@ -33,12 +34,14 @@ module BetterErrors
|
|
|
33
34
|
# Adds an address to the set of IP addresses allowed to access Better
|
|
34
35
|
# Errors.
|
|
35
36
|
def self.allow_ip!(addr)
|
|
36
|
-
ALLOWED_IPS << IPAddr.new(addr)
|
|
37
|
+
ALLOWED_IPS << (addr.is_a?(IPAddr) ? addr : IPAddr.new(addr))
|
|
37
38
|
end
|
|
38
39
|
|
|
39
40
|
allow_ip! "127.0.0.0/8"
|
|
40
41
|
allow_ip! "::1/128" rescue nil # windows ruby doesn't have ipv6 support
|
|
41
42
|
|
|
43
|
+
CSRF_TOKEN_COOKIE_NAME = "BetterErrors-#{BetterErrors::VERSION}-CSRF-Token"
|
|
44
|
+
|
|
42
45
|
# A new instance of BetterErrors::Middleware
|
|
43
46
|
#
|
|
44
47
|
# @param app The Rack app/middleware to wrap with Better Errors
|
|
@@ -72,7 +75,7 @@ module BetterErrors
|
|
|
72
75
|
def better_errors_call(env)
|
|
73
76
|
case env["PATH_INFO"]
|
|
74
77
|
when %r{/__better_errors/(?<id>.+?)/(?<method>\w+)\z}
|
|
75
|
-
internal_call
|
|
78
|
+
internal_call(env, $~[:id], $~[:method])
|
|
76
79
|
when %r{/__better_errors/?\z}
|
|
77
80
|
show_error_page env
|
|
78
81
|
else
|
|
@@ -89,11 +92,14 @@ module BetterErrors
|
|
|
89
92
|
end
|
|
90
93
|
|
|
91
94
|
def show_error_page(env, exception=nil)
|
|
95
|
+
request = Rack::Request.new(env)
|
|
96
|
+
csrf_token = request.cookies[CSRF_TOKEN_COOKIE_NAME] || SecureRandom.uuid
|
|
97
|
+
|
|
92
98
|
type, content = if @error_page
|
|
93
99
|
if text?(env)
|
|
94
100
|
[ 'plain', @error_page.render('text') ]
|
|
95
101
|
else
|
|
96
|
-
[ 'html', @error_page.render ]
|
|
102
|
+
[ 'html', @error_page.render('main', csrf_token) ]
|
|
97
103
|
end
|
|
98
104
|
else
|
|
99
105
|
[ 'html', no_errors_page ]
|
|
@@ -104,12 +110,22 @@ module BetterErrors
|
|
|
104
110
|
status_code = ActionDispatch::ExceptionWrapper.new(env, exception).status_code
|
|
105
111
|
end
|
|
106
112
|
|
|
107
|
-
|
|
113
|
+
response = Rack::Response.new(content, status_code, { "Content-Type" => "text/#{type}; charset=utf-8" })
|
|
114
|
+
|
|
115
|
+
unless request.cookies[CSRF_TOKEN_COOKIE_NAME]
|
|
116
|
+
response.set_cookie(CSRF_TOKEN_COOKIE_NAME, value: csrf_token, path: "/", httponly: true, same_site: :strict)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# In older versions of Rack, the body returned here is actually a Rack::BodyProxy which seems to be a bug.
|
|
120
|
+
# (It contains status, headers and body and does not act like an array of strings.)
|
|
121
|
+
# Since we already have status code and body here, there's no need to use the ones in the Rack::Response.
|
|
122
|
+
(_status_code, headers, _body) = response.finish
|
|
123
|
+
[status_code, headers, [content]]
|
|
108
124
|
end
|
|
109
125
|
|
|
110
126
|
def text?(env)
|
|
111
127
|
env["HTTP_X_REQUESTED_WITH"] == "XMLHttpRequest" ||
|
|
112
|
-
|
|
128
|
+
!env["HTTP_ACCEPT"].to_s.include?('html')
|
|
113
129
|
end
|
|
114
130
|
|
|
115
131
|
def log_exception
|
|
@@ -129,13 +145,22 @@ module BetterErrors
|
|
|
129
145
|
end
|
|
130
146
|
end
|
|
131
147
|
|
|
132
|
-
def internal_call(env,
|
|
148
|
+
def internal_call(env, id, method)
|
|
149
|
+
return not_found_json_response unless %w[variables eval].include?(method)
|
|
133
150
|
return no_errors_json_response unless @error_page
|
|
134
|
-
return invalid_error_json_response if
|
|
151
|
+
return invalid_error_json_response if id != @error_page.id
|
|
152
|
+
|
|
153
|
+
request = Rack::Request.new(env)
|
|
154
|
+
return invalid_csrf_token_json_response unless request.cookies[CSRF_TOKEN_COOKIE_NAME]
|
|
155
|
+
|
|
156
|
+
request.body.rewind
|
|
157
|
+
body = JSON.parse(request.body.read)
|
|
158
|
+
return invalid_csrf_token_json_response unless request.cookies[CSRF_TOKEN_COOKIE_NAME] == body['csrfToken']
|
|
159
|
+
|
|
160
|
+
return not_acceptable_json_response unless request.content_type == 'application/json'
|
|
135
161
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
[200, { "Content-Type" => "text/plain; charset=utf-8" }, [JSON.dump(response)]]
|
|
162
|
+
response = @error_page.send("do_#{method}", body)
|
|
163
|
+
[200, { "Content-Type" => "application/json; charset=utf-8" }, [JSON.dump(response)]]
|
|
139
164
|
end
|
|
140
165
|
|
|
141
166
|
def no_errors_page
|
|
@@ -157,18 +182,40 @@ module BetterErrors
|
|
|
157
182
|
"The application has been restarted since this page loaded, " +
|
|
158
183
|
"or the framework is reloading all gems before each request "
|
|
159
184
|
end
|
|
160
|
-
[200, { "Content-Type" => "
|
|
185
|
+
[200, { "Content-Type" => "application/json; charset=utf-8" }, [JSON.dump(
|
|
161
186
|
error: 'No exception information available',
|
|
162
187
|
explanation: explanation,
|
|
163
188
|
)]]
|
|
164
189
|
end
|
|
165
190
|
|
|
166
191
|
def invalid_error_json_response
|
|
167
|
-
[200, { "Content-Type" => "
|
|
192
|
+
[200, { "Content-Type" => "application/json; charset=utf-8" }, [JSON.dump(
|
|
168
193
|
error: "Session expired",
|
|
169
194
|
explanation: "This page was likely opened from a previous exception, " +
|
|
170
195
|
"and the exception is no longer available in memory.",
|
|
171
196
|
)]]
|
|
172
197
|
end
|
|
198
|
+
|
|
199
|
+
def invalid_csrf_token_json_response
|
|
200
|
+
[200, { "Content-Type" => "application/json; charset=utf-8" }, [JSON.dump(
|
|
201
|
+
error: "Invalid CSRF Token",
|
|
202
|
+
explanation: "The browser session might have been cleared, " +
|
|
203
|
+
"or something went wrong.",
|
|
204
|
+
)]]
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
def not_found_json_response
|
|
208
|
+
[404, { "Content-Type" => "application/json; charset=utf-8" }, [JSON.dump(
|
|
209
|
+
error: "Not found",
|
|
210
|
+
explanation: "Not a recognized internal call.",
|
|
211
|
+
)]]
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
def not_acceptable_json_response
|
|
215
|
+
[406, { "Content-Type" => "application/json; charset=utf-8" }, [JSON.dump(
|
|
216
|
+
error: "Request not acceptable",
|
|
217
|
+
explanation: "The internal request did not match an acceptable content type.",
|
|
218
|
+
)]]
|
|
219
|
+
end
|
|
173
220
|
end
|
|
174
221
|
end
|
|
@@ -1,12 +1,23 @@
|
|
|
1
|
+
require 'better_errors/exception_hint'
|
|
2
|
+
|
|
1
3
|
# @private
|
|
2
4
|
module BetterErrors
|
|
3
5
|
class RaisedException
|
|
4
|
-
attr_reader :exception, :message, :backtrace
|
|
6
|
+
attr_reader :exception, :message, :backtrace, :hint
|
|
5
7
|
|
|
6
8
|
def initialize(exception)
|
|
7
|
-
if exception.respond_to?(:cause)
|
|
9
|
+
if exception.class.name == "ActionView::Template::Error" && exception.respond_to?(:cause)
|
|
10
|
+
# Rails 6+ exceptions of this type wrap the "real" exception, and the real exception
|
|
11
|
+
# is actually more useful than the ActionView-provided wrapper. Once Better Errors
|
|
12
|
+
# supports showing all exceptions in the cause stack, this should go away. Or perhaps
|
|
13
|
+
# this can be changed to provide guidance by showing the second error in the cause stack
|
|
14
|
+
# under this condition.
|
|
8
15
|
exception = exception.cause if exception.cause
|
|
9
16
|
elsif exception.respond_to?(:original_exception) && exception.original_exception
|
|
17
|
+
# This supports some specific Rails exceptions, and this is not intended to act the same as
|
|
18
|
+
# the Ruby's {Exception#cause}.
|
|
19
|
+
# It's possible this should only support ActionView::Template::Error, but by not changing
|
|
20
|
+
# this we're preserving longstanding behavior of Better Errors with Rails < 6.
|
|
10
21
|
exception = exception.original_exception
|
|
11
22
|
end
|
|
12
23
|
|
|
@@ -14,6 +25,7 @@ module BetterErrors
|
|
|
14
25
|
@message = exception.message
|
|
15
26
|
|
|
16
27
|
setup_backtrace
|
|
28
|
+
setup_hint
|
|
17
29
|
massage_syntax_error
|
|
18
30
|
end
|
|
19
31
|
|
|
@@ -36,8 +48,13 @@ module BetterErrors
|
|
|
36
48
|
|
|
37
49
|
def setup_backtrace_from_bindings
|
|
38
50
|
@backtrace = exception.__better_errors_bindings_stack.map { |binding|
|
|
39
|
-
|
|
40
|
-
|
|
51
|
+
if binding.respond_to?(:source_location) # Ruby >= 2.6
|
|
52
|
+
file = binding.source_location[0]
|
|
53
|
+
line = binding.source_location[1]
|
|
54
|
+
else
|
|
55
|
+
file = binding.eval "__FILE__"
|
|
56
|
+
line = binding.eval "__LINE__"
|
|
57
|
+
end
|
|
41
58
|
name = binding.frame_description
|
|
42
59
|
StackFrame.new(file, line, name, binding)
|
|
43
60
|
}
|
|
@@ -64,5 +81,9 @@ module BetterErrors
|
|
|
64
81
|
end
|
|
65
82
|
end
|
|
66
83
|
end
|
|
84
|
+
|
|
85
|
+
def setup_hint
|
|
86
|
+
@hint = ExceptionHint.new(exception).hint
|
|
87
|
+
end
|
|
67
88
|
end
|
|
68
89
|
end
|
|
@@ -69,21 +69,26 @@ module BetterErrors
|
|
|
69
69
|
def local_variables
|
|
70
70
|
return {} unless frame_binding
|
|
71
71
|
|
|
72
|
-
frame_binding.eval("local_variables")
|
|
72
|
+
lv = frame_binding.eval("local_variables")
|
|
73
|
+
return {} unless lv
|
|
74
|
+
|
|
75
|
+
lv.each_with_object({}) do |name, hash|
|
|
73
76
|
# Ruby 2.2's local_variables will include the hidden #$! variable if
|
|
74
77
|
# called from within a rescue context. This is not a valid variable name,
|
|
75
78
|
# so the local_variable_get method complains. This should probably be
|
|
76
79
|
# considered a bug in Ruby itself, but we need to work around it.
|
|
77
80
|
next if name == :"\#$!"
|
|
78
81
|
|
|
79
|
-
|
|
80
|
-
hash[name] = frame_binding.local_variable_get(name)
|
|
81
|
-
else
|
|
82
|
-
hash[name] = frame_binding.eval(name.to_s)
|
|
83
|
-
end
|
|
82
|
+
hash[name] = local_variable(name)
|
|
84
83
|
end
|
|
85
84
|
end
|
|
86
85
|
|
|
86
|
+
def local_variable(name)
|
|
87
|
+
get_local_variable(name) || eval_local_variable(name)
|
|
88
|
+
rescue NameError => ex
|
|
89
|
+
"#{ex.class}: #{ex.message}"
|
|
90
|
+
end
|
|
91
|
+
|
|
87
92
|
def instance_variables
|
|
88
93
|
return {} unless frame_binding
|
|
89
94
|
Hash[visible_instance_variables.map { |x|
|
|
@@ -92,7 +97,10 @@ module BetterErrors
|
|
|
92
97
|
end
|
|
93
98
|
|
|
94
99
|
def visible_instance_variables
|
|
95
|
-
frame_binding.eval("instance_variables")
|
|
100
|
+
iv = frame_binding.eval("instance_variables")
|
|
101
|
+
return {} unless iv
|
|
102
|
+
|
|
103
|
+
iv - BetterErrors.ignored_instance_variables
|
|
96
104
|
end
|
|
97
105
|
|
|
98
106
|
def to_s
|
|
@@ -114,5 +122,15 @@ module BetterErrors
|
|
|
114
122
|
@method_name = "##{method_name}"
|
|
115
123
|
end
|
|
116
124
|
end
|
|
125
|
+
|
|
126
|
+
def get_local_variable(name)
|
|
127
|
+
if defined?(frame_binding.local_variable_get)
|
|
128
|
+
frame_binding.local_variable_get(name)
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def eval_local_variable(name)
|
|
133
|
+
frame_binding.eval(name.to_s)
|
|
134
|
+
end
|
|
117
135
|
end
|
|
118
136
|
end
|
|
@@ -90,7 +90,7 @@
|
|
|
90
90
|
nav.sidebar,
|
|
91
91
|
.frame_info {
|
|
92
92
|
position: fixed;
|
|
93
|
-
top:
|
|
93
|
+
top: 102px;
|
|
94
94
|
bottom: 0;
|
|
95
95
|
|
|
96
96
|
box-sizing: border-box;
|
|
@@ -102,7 +102,7 @@
|
|
|
102
102
|
nav.sidebar {
|
|
103
103
|
width: 40%;
|
|
104
104
|
left: 20px;
|
|
105
|
-
top:
|
|
105
|
+
top: 122px;
|
|
106
106
|
bottom: 20px;
|
|
107
107
|
}
|
|
108
108
|
|
|
@@ -131,7 +131,7 @@
|
|
|
131
131
|
header.exception {
|
|
132
132
|
padding: 18px 20px;
|
|
133
133
|
|
|
134
|
-
height:
|
|
134
|
+
height: 66px;
|
|
135
135
|
min-height: 59px;
|
|
136
136
|
|
|
137
137
|
overflow: hidden;
|
|
@@ -146,6 +146,14 @@
|
|
|
146
146
|
}
|
|
147
147
|
|
|
148
148
|
/* Heading */
|
|
149
|
+
header.exception .fix-actions {
|
|
150
|
+
margin-top: .5em;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
header.exception .fix-actions input[type=submit] {
|
|
154
|
+
font-weight: bold;
|
|
155
|
+
}
|
|
156
|
+
|
|
149
157
|
header.exception h2 {
|
|
150
158
|
font-weight: 200;
|
|
151
159
|
font-size: 11pt;
|
|
@@ -153,7 +161,7 @@
|
|
|
153
161
|
|
|
154
162
|
header.exception h2,
|
|
155
163
|
header.exception p {
|
|
156
|
-
line-height: 1.
|
|
164
|
+
line-height: 1.5em;
|
|
157
165
|
overflow: hidden;
|
|
158
166
|
white-space: pre;
|
|
159
167
|
text-overflow: ellipsis;
|
|
@@ -166,7 +174,7 @@
|
|
|
166
174
|
|
|
167
175
|
header.exception p {
|
|
168
176
|
font-weight: 200;
|
|
169
|
-
font-size:
|
|
177
|
+
font-size: 17pt;
|
|
170
178
|
color: white;
|
|
171
179
|
}
|
|
172
180
|
|
|
@@ -587,6 +595,9 @@
|
|
|
587
595
|
color: #8080a0;
|
|
588
596
|
padding-left: 20px;
|
|
589
597
|
}
|
|
598
|
+
.console-has-been-used .live-console-hint {
|
|
599
|
+
display: none;
|
|
600
|
+
}
|
|
590
601
|
|
|
591
602
|
.hint:before {
|
|
592
603
|
content: '\25b2';
|
|
@@ -603,17 +614,6 @@
|
|
|
603
614
|
margin: 10px 0;
|
|
604
615
|
}
|
|
605
616
|
|
|
606
|
-
.sub:before {
|
|
607
|
-
content: '';
|
|
608
|
-
display: block;
|
|
609
|
-
width: 100%;
|
|
610
|
-
height: 4px;
|
|
611
|
-
|
|
612
|
-
border-radius: 2px;
|
|
613
|
-
background: rgba(0, 150, 200, 0.05);
|
|
614
|
-
box-shadow: 1px 1px 0 rgba(255, 255, 255, 0.7), inset 0 0 0 1px rgba(0, 0, 0, 0.04), inset 2px 2px 2px rgba(0, 0, 0, 0.07);
|
|
615
|
-
}
|
|
616
|
-
|
|
617
617
|
.sub h3 {
|
|
618
618
|
color: #39a;
|
|
619
619
|
font-size: 1.1em;
|
|
@@ -721,12 +721,22 @@
|
|
|
721
721
|
if(document.styleSheets[i].href)
|
|
722
722
|
document.styleSheets[i].disabled = true;
|
|
723
723
|
}
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
724
|
+
if (window.Turbolinks.controller) {
|
|
725
|
+
// Turbolinks > 5 (see https://github.com/turbolinks/turbolinks/issues/6)
|
|
726
|
+
document.addEventListener("turbolinks:load", function restoreCSS(e) {
|
|
727
|
+
for(var i=0; i < document.styleSheets.length; i++) {
|
|
728
|
+
document.styleSheets[i].disabled = false;
|
|
729
|
+
}
|
|
730
|
+
document.removeEventListener("turbolinks:load", restoreCSS, false);
|
|
731
|
+
});
|
|
732
|
+
} else {
|
|
733
|
+
document.addEventListener("page:restore", function restoreCSS(e) {
|
|
734
|
+
for(var i=0; i < document.styleSheets.length; i++) {
|
|
735
|
+
document.styleSheets[i].disabled = false;
|
|
736
|
+
}
|
|
737
|
+
document.removeEventListener("page:restore", restoreCSS, false);
|
|
738
|
+
});
|
|
739
|
+
}
|
|
730
740
|
}
|
|
731
741
|
</script>
|
|
732
742
|
|
|
@@ -734,6 +744,21 @@
|
|
|
734
744
|
<header class="exception">
|
|
735
745
|
<h2><strong><%= exception_type %></strong> <span>at <%= request_path %></span></h2>
|
|
736
746
|
<p><%= exception_message %></p>
|
|
747
|
+
<% unless active_support_actions.empty? %>
|
|
748
|
+
<div class='fix-actions'>
|
|
749
|
+
<% active_support_actions.each do |action, _| %>
|
|
750
|
+
<form class="button_to" method="post" action="<%= action_dispatch_action_endpoint %>">
|
|
751
|
+
<input type="submit" value="<%= action %>">
|
|
752
|
+
<input type="hidden" name="action" value="<%= action %>">
|
|
753
|
+
<input type="hidden" name="error" value="<%= exception_type %>">
|
|
754
|
+
<input type="hidden" name="location" value="<%= request_path %>">
|
|
755
|
+
</form>
|
|
756
|
+
<% end %>
|
|
757
|
+
</div>
|
|
758
|
+
<% end %>
|
|
759
|
+
<% if exception_hint %>
|
|
760
|
+
<h2>Hint: <%= exception_hint %></h2>
|
|
761
|
+
<% end %>
|
|
737
762
|
</header>
|
|
738
763
|
</div>
|
|
739
764
|
|
|
@@ -770,6 +795,7 @@
|
|
|
770
795
|
(function() {
|
|
771
796
|
|
|
772
797
|
var OID = "<%= id %>";
|
|
798
|
+
var csrfToken = "<%= csrf_token %>";
|
|
773
799
|
|
|
774
800
|
var previousFrame = null;
|
|
775
801
|
var previousFrameInfo = null;
|
|
@@ -780,6 +806,7 @@
|
|
|
780
806
|
var req = new XMLHttpRequest();
|
|
781
807
|
req.open("POST", "//" + window.location.host + <%== uri_prefix.gsub("<", "<").inspect %> + "/__better_errors/" + OID + "/" + method, true);
|
|
782
808
|
req.setRequestHeader("Content-Type", "application/json");
|
|
809
|
+
opts.csrfToken = csrfToken;
|
|
783
810
|
req.send(JSON.stringify(opts));
|
|
784
811
|
req.onreadystatechange = function() {
|
|
785
812
|
if(req.readyState == 4) {
|
|
@@ -793,6 +820,28 @@
|
|
|
793
820
|
return html.replace(/&/, "&").replace(/</g, "<");
|
|
794
821
|
}
|
|
795
822
|
|
|
823
|
+
function hasConsoleBeenUsedPreviously() {
|
|
824
|
+
return !!document.cookie.split('; ').find(function(cookie) {
|
|
825
|
+
return cookie.startsWith('BetterErrors-has-used-console=');
|
|
826
|
+
});
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
var consoleHasBeenUsed = hasConsoleBeenUsedPreviously();
|
|
830
|
+
|
|
831
|
+
function consoleWasJustUsed() {
|
|
832
|
+
if (consoleHasBeenUsed) {
|
|
833
|
+
return;
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
hideConsoleHint();
|
|
837
|
+
consoleHasBeenUsed = true;
|
|
838
|
+
document.cookie = "BetterErrors-has-used-console=true;path=/;max-age=31536000;samesite"
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
function hideConsoleHint() {
|
|
842
|
+
document.querySelector('body').className += " console-has-been-used";
|
|
843
|
+
}
|
|
844
|
+
|
|
796
845
|
function REPL(index) {
|
|
797
846
|
this.index = index;
|
|
798
847
|
|
|
@@ -814,15 +863,20 @@
|
|
|
814
863
|
this.inputElement = this.container.querySelector("input");
|
|
815
864
|
this.outputElement = this.container.querySelector("pre");
|
|
816
865
|
|
|
866
|
+
if (consoleHasBeenUsed) {
|
|
867
|
+
hideConsoleHint();
|
|
868
|
+
}
|
|
869
|
+
|
|
817
870
|
var self = this;
|
|
818
871
|
this.inputElement.onkeydown = function(ev) {
|
|
819
872
|
self.onKeyDown(ev);
|
|
873
|
+
consoleWasJustUsed();
|
|
820
874
|
};
|
|
821
875
|
|
|
822
876
|
this.setPrompt(">>");
|
|
823
877
|
|
|
824
878
|
REPL.all[this.index] = this;
|
|
825
|
-
}
|
|
879
|
+
};
|
|
826
880
|
|
|
827
881
|
REPL.prototype.focus = function() {
|
|
828
882
|
this.inputElement.focus();
|
|
@@ -943,7 +997,7 @@
|
|
|
943
997
|
if(response.explanation) {
|
|
944
998
|
el.innerHTML += "<p class='explanation'>" + escapeHTML(response.explanation) + "</p>";
|
|
945
999
|
}
|
|
946
|
-
el.innerHTML += "<p><a target='_new' href='https://github.com/
|
|
1000
|
+
el.innerHTML += "<p><a target='_new' href='https://github.com/BetterErrors/better_errors'>More about Better Errors</a></p>";
|
|
947
1001
|
} else {
|
|
948
1002
|
el.innerHTML = response.html;
|
|
949
1003
|
|