better_errors 2.8.2 → 2.10.0.beta2

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.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +142 -0
  3. data/.github/workflows/release.yml +68 -0
  4. data/.gitignore +4 -0
  5. data/.ruby-version +1 -0
  6. data/Gemfile +6 -1
  7. data/README.md +2 -2
  8. data/better_errors.gemspec +5 -4
  9. data/gemfiles/pry010.gemfile +2 -1
  10. data/gemfiles/pry011.gemfile +2 -1
  11. data/gemfiles/pry09.gemfile +2 -1
  12. data/gemfiles/rack.gemfile +2 -1
  13. data/gemfiles/rack_boc.gemfile +2 -1
  14. data/gemfiles/rails42.gemfile +2 -1
  15. data/gemfiles/rails42_boc.gemfile +2 -1
  16. data/gemfiles/rails42_haml.gemfile +2 -1
  17. data/gemfiles/rails50.gemfile +2 -1
  18. data/gemfiles/rails50_boc.gemfile +2 -1
  19. data/gemfiles/rails50_haml.gemfile +2 -1
  20. data/gemfiles/rails51.gemfile +2 -1
  21. data/gemfiles/rails51_boc.gemfile +2 -1
  22. data/gemfiles/rails51_haml.gemfile +2 -1
  23. data/gemfiles/rails52.gemfile +2 -1
  24. data/gemfiles/rails52_boc.gemfile +2 -1
  25. data/gemfiles/rails52_haml.gemfile +2 -1
  26. data/gemfiles/rails60.gemfile +2 -1
  27. data/gemfiles/rails60_boc.gemfile +2 -1
  28. data/gemfiles/rails60_haml.gemfile +2 -1
  29. data/gemfiles/rails61.gemfile +8 -0
  30. data/gemfiles/rails61_boc.gemfile +9 -0
  31. data/gemfiles/rails61_haml.gemfile +9 -0
  32. data/lib/better_errors.rb +15 -35
  33. data/lib/better_errors/code_formatter.rb +16 -27
  34. data/lib/better_errors/code_formatter/html.rb +15 -1
  35. data/lib/better_errors/editor.rb +103 -0
  36. data/lib/better_errors/error_page.rb +33 -15
  37. data/lib/better_errors/error_page_style.rb +30 -0
  38. data/lib/better_errors/exception_hint.rb +29 -0
  39. data/lib/better_errors/middleware.rb +20 -4
  40. data/lib/better_errors/raised_exception.rb +8 -1
  41. data/lib/better_errors/templates/main.erb +80 -717
  42. data/lib/better_errors/templates/text.erb +6 -3
  43. data/lib/better_errors/templates/variable_info.erb +18 -15
  44. data/lib/better_errors/version.rb +2 -1
  45. metadata +30 -7
  46. data/.travis.yml +0 -111
@@ -0,0 +1,30 @@
1
+ require "sassc"
2
+
3
+ module BetterErrors
4
+ # @private
5
+ module ErrorPageStyle
6
+ def self.compiled_css(for_deployment = false)
7
+ style_dir = File.expand_path("style", File.dirname(__FILE__))
8
+ style_file = "#{style_dir}/main.scss"
9
+
10
+ engine = SassC::Engine.new(
11
+ File.read(style_file),
12
+ filename: style_file,
13
+ style: for_deployment ? :compressed : :expanded,
14
+ line_comments: !for_deployment,
15
+ load_paths: [style_dir],
16
+ )
17
+ engine.render
18
+ end
19
+
20
+ def self.style_tag(csp_nonce)
21
+ style_file = File.expand_path("templates/main.css", File.dirname(__FILE__))
22
+ css = if File.exist?(style_file)
23
+ File.open(style_file).read
24
+ else
25
+ compiled_css(false)
26
+ end
27
+ "<style type='text/css' nonce='#{csp_nonce}'>\n#{css}\n</style>"
28
+ end
29
+ end
30
+ end
@@ -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
@@ -40,7 +40,7 @@ module BetterErrors
40
40
  allow_ip! "127.0.0.0/8"
41
41
  allow_ip! "::1/128" rescue nil # windows ruby doesn't have ipv6 support
42
42
 
43
- CSRF_TOKEN_COOKIE_NAME = "BetterErrors-#{VERSION}-CSRF-Token"
43
+ CSRF_TOKEN_COOKIE_NAME = "BetterErrors-#{BetterErrors::VERSION}-CSRF-Token"
44
44
 
45
45
  # A new instance of BetterErrors::Middleware
46
46
  #
@@ -94,12 +94,13 @@ module BetterErrors
94
94
  def show_error_page(env, exception=nil)
95
95
  request = Rack::Request.new(env)
96
96
  csrf_token = request.cookies[CSRF_TOKEN_COOKIE_NAME] || SecureRandom.uuid
97
+ csp_nonce = SecureRandom.base64(12)
97
98
 
98
99
  type, content = if @error_page
99
100
  if text?(env)
100
- [ 'plain', @error_page.render('text') ]
101
+ [ 'plain', @error_page.render_text ]
101
102
  else
102
- [ 'html', @error_page.render('main', csrf_token) ]
103
+ [ 'html', @error_page.render_main(csrf_token, csp_nonce) ]
103
104
  end
104
105
  else
105
106
  [ 'html', no_errors_page ]
@@ -110,7 +111,22 @@ module BetterErrors
110
111
  status_code = ActionDispatch::ExceptionWrapper.new(env, exception).status_code
111
112
  end
112
113
 
113
- response = Rack::Response.new(content, status_code, { "Content-Type" => "text/#{type}; charset=utf-8" })
114
+ headers = {
115
+ "Content-Type" => "text/#{type}; charset=utf-8",
116
+ "Content-Security-Policy" => [
117
+ "default-src 'none'",
118
+ # Specifying nonce makes a modern browser ignore 'unsafe-inline' which could still be set
119
+ # for older browsers without nonce support.
120
+ # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/script-src
121
+ "script-src 'self' 'nonce-#{csp_nonce}' 'unsafe-inline'",
122
+ "style-src 'self' 'nonce-#{csp_nonce}' 'unsafe-inline'",
123
+ "img-src data:",
124
+ "connect-src 'self'",
125
+ "navigate-to 'self' #{BetterErrors.editor.scheme}",
126
+ ].join('; '),
127
+ }
128
+
129
+ response = Rack::Response.new(content, status_code, headers)
114
130
 
115
131
  unless request.cookies[CSRF_TOKEN_COOKIE_NAME]
116
132
  response.set_cookie(CSRF_TOKEN_COOKIE_NAME, value: csrf_token, path: "/", httponly: true, same_site: :strict)
@@ -1,7 +1,9 @@
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
9
  if exception.class.name == "ActionView::Template::Error" && exception.respond_to?(:cause)
@@ -23,6 +25,7 @@ module BetterErrors
23
25
  @message = exception.message
24
26
 
25
27
  setup_backtrace
28
+ setup_hint
26
29
  massage_syntax_error
27
30
  end
28
31
 
@@ -78,5 +81,9 @@ module BetterErrors
78
81
  end
79
82
  end
80
83
  end
84
+
85
+ def setup_hint
86
+ @hint = ExceptionHint.new(exception).hint
87
+ end
81
88
  end
82
89
  end
@@ -2,714 +2,14 @@
2
2
  <html>
3
3
  <head>
4
4
  <title><%= exception_type %> at <%= request_path %></title>
5
+ <link rel="icon" href="data:;base64,=" />
5
6
  </head>
6
- <body>
7
+ <body class="better-errors-javascript-not-loaded">
7
8
  <%# Stylesheets are placed in the <body> for Turbolinks compatibility. %>
8
- <style>
9
- /* Basic reset */
10
- * {
11
- margin: 0;
12
- padding: 0;
13
- }
14
-
15
- table {
16
- width: 100%;
17
- border-collapse: collapse;
18
- }
19
-
20
- th, td {
21
- vertical-align: top;
22
- text-align: left;
23
- }
24
-
25
- textarea {
26
- resize: none;
27
- }
28
-
29
- body {
30
- font-size: 10pt;
31
- }
32
-
33
- body, td, input, textarea {
34
- font-family: helvetica neue, lucida grande, sans-serif;
35
- line-height: 1.5;
36
- color: #333;
37
- text-shadow: 0 1px 0 rgba(255, 255, 255, 0.6);
38
- }
39
-
40
- html {
41
- background: #f0f0f5;
42
- }
43
-
44
- .clearfix::after{
45
- clear: both;
46
- content: ".";
47
- display: block;
48
- height: 0;
49
- visibility: hidden;
50
- }
51
-
52
- /* ---------------------------------------------------------------------
53
- * Basic layout
54
- * --------------------------------------------------------------------- */
55
-
56
- /* Small */
57
- @media screen and (max-width: 1100px) {
58
- html {
59
- overflow-y: scroll;
60
- }
61
-
62
- body {
63
- margin: 0 20px;
64
- }
65
-
66
- header.exception {
67
- margin: 0 -20px;
68
- }
69
-
70
- nav.sidebar {
71
- padding: 0;
72
- margin: 20px 0;
73
- }
74
-
75
- ul.frames {
76
- max-height: 200px;
77
- overflow: auto;
78
- }
79
- }
80
-
81
- /* Wide */
82
- @media screen and (min-width: 1100px) {
83
- header.exception {
84
- position: fixed;
85
- top: 0;
86
- left: 0;
87
- right: 0;
88
- }
89
-
90
- nav.sidebar,
91
- .frame_info {
92
- position: fixed;
93
- top: 95px;
94
- bottom: 0;
95
-
96
- box-sizing: border-box;
97
-
98
- overflow-y: auto;
99
- overflow-x: hidden;
100
- }
101
-
102
- nav.sidebar {
103
- width: 40%;
104
- left: 20px;
105
- top: 115px;
106
- bottom: 20px;
107
- }
108
-
109
- .frame_info {
110
- right: 0;
111
- left: 40%;
112
-
113
- padding: 20px;
114
- padding-left: 10px;
115
- margin-left: 30px;
116
- }
117
- }
118
-
119
- nav.sidebar {
120
- background: #d3d3da;
121
- border-top: solid 3px #a33;
122
- border-bottom: solid 3px #a33;
123
- border-radius: 4px;
124
- box-shadow: 0 0 6px rgba(0, 0, 0, 0.2), inset 0 0 0 1px rgba(0, 0, 0, 0.1);
125
- }
126
-
127
- /* ---------------------------------------------------------------------
128
- * Header
129
- * --------------------------------------------------------------------- */
130
-
131
- header.exception {
132
- padding: 18px 20px;
133
-
134
- height: 59px;
135
- min-height: 59px;
136
-
137
- overflow: hidden;
138
-
139
- background-color: #20202a;
140
- color: #aaa;
141
- text-shadow: 0 1px 0 rgba(0, 0, 0, 0.3);
142
- font-weight: 200;
143
- box-shadow: inset 0 -5px 3px -3px rgba(0, 0, 0, 0.05), inset 0 -1px 0 rgba(0, 0, 0, 0.05);
144
-
145
- -webkit-text-smoothing: antialiased;
146
- }
147
-
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
-
157
- header.exception h2 {
158
- font-weight: 200;
159
- font-size: 11pt;
160
- }
161
-
162
- header.exception h2,
163
- header.exception p {
164
- line-height: 1.5em;
165
- overflow: hidden;
166
- white-space: pre;
167
- text-overflow: ellipsis;
168
- }
169
-
170
- header.exception h2 strong {
171
- font-weight: 700;
172
- color: #d55;
173
- }
174
-
175
- header.exception p {
176
- font-weight: 200;
177
- font-size: 17pt;
178
- color: white;
179
- }
180
-
181
- header.exception:hover {
182
- height: auto;
183
- z-index: 2;
184
- }
185
-
186
- header.exception:hover h2,
187
- header.exception:hover p {
188
- padding-right: 20px;
189
- overflow-y: auto;
190
- word-wrap: break-word;
191
- white-space: pre-wrap;
192
- height: auto;
193
- max-height: 7.5em;
194
- }
195
-
196
- @media screen and (max-width: 1100px) {
197
- header.exception {
198
- height: auto;
199
- }
200
-
201
- header.exception h2,
202
- header.exception p {
203
- padding-right: 20px;
204
- overflow-y: auto;
205
- word-wrap: break-word;
206
- height: auto;
207
- max-height: 7em;
208
- }
209
- }
210
-
211
- <%#
212
- /* Light theme */
213
- header.exception {
214
- text-shadow: 0 1px 0 rgba(250, 250, 250, 0.6);
215
- background: rgba(200,100,50,0.10);
216
- color: #977;
217
- }
218
- header.exception h2 strong {
219
- color: #533;
220
- }
221
- header.exception p {
222
- color: #744;
223
- }
224
- %>
225
-
226
- /* ---------------------------------------------------------------------
227
- * Navigation
228
- * --------------------------------------------------------------------- */
229
-
230
- nav.tabs {
231
- border-bottom: solid 1px #ddd;
232
-
233
- background-color: #eee;
234
- text-align: center;
235
-
236
- padding: 6px;
237
-
238
- box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1);
239
- }
240
-
241
- nav.tabs a {
242
- display: inline-block;
243
-
244
- height: 22px;
245
- line-height: 22px;
246
- padding: 0 10px;
247
-
248
- text-decoration: none;
249
- font-size: 8pt;
250
- font-weight: bold;
251
-
252
- color: #999;
253
- text-shadow: 0 1px 0 rgba(255, 255, 255, 0.6);
254
- }
255
-
256
- nav.tabs a.selected {
257
- color: white;
258
- background: rgba(0, 0, 0, 0.5);
259
- border-radius: 16px;
260
- box-shadow: 1px 1px 0 rgba(255, 255, 255, 0.1);
261
- text-shadow: 0 0 4px rgba(0, 0, 0, 0.4), 0 1px 0 rgba(0, 0, 0, 0.4);
262
- }
263
-
264
- nav.tabs a.disabled {
265
- text-decoration: line-through;
266
- text-shadow: none;
267
- cursor: default;
268
- }
269
-
270
- /* ---------------------------------------------------------------------
271
- * Sidebar
272
- * --------------------------------------------------------------------- */
273
-
274
- ul.frames {
275
- box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
276
- }
277
-
278
- /* Each item */
279
- ul.frames li {
280
- background-color: #f8f8f8;
281
- background: -webkit-linear-gradient(top, #f8f8f8 80%, #f0f0f0);
282
- background: -moz-linear-gradient(top, #f8f8f8 80%, #f0f0f0);
283
- background: linear-gradient(top, #f8f8f8 80%, #f0f0f0);
284
- box-shadow: inset 0 -1px 0 #e2e2e2;
285
- padding: 7px 20px;
286
-
287
- cursor: pointer;
288
- overflow: hidden;
289
- }
290
-
291
- ul.frames .name,
292
- ul.frames .location {
293
- overflow: hidden;
294
- height: 1.5em;
295
-
296
- white-space: nowrap;
297
- word-wrap: none;
298
- text-overflow: ellipsis;
299
- }
300
-
301
- ul.frames .method {
302
- color: #966;
303
- }
304
-
305
- ul.frames .location {
306
- font-size: 0.85em;
307
- font-weight: 400;
308
- color: #999;
309
- }
310
-
311
- ul.frames .line {
312
- font-weight: bold;
313
- }
314
-
315
- /* Selected frame */
316
- ul.frames li.selected {
317
- background: #38a;
318
- box-shadow: inset 0 1px 0 rgba(0, 0, 0, 0.1), inset 0 2px 0 rgba(255, 255, 255, 0.01), inset 0 -1px 0 rgba(0, 0, 0, 0.1);
319
- }
320
-
321
- ul.frames li.selected .name,
322
- ul.frames li.selected .method,
323
- ul.frames li.selected .location {
324
- color: white;
325
- text-shadow: 0 1px 0 rgba(0, 0, 0, 0.2);
326
- }
327
-
328
- ul.frames li.selected .location {
329
- opacity: 0.6;
330
- }
331
-
332
- /* Iconography */
333
- ul.frames li {
334
- padding-left: 60px;
335
- position: relative;
336
- }
337
-
338
- ul.frames li .icon {
339
- display: block;
340
- width: 20px;
341
- height: 20px;
342
- line-height: 20px;
343
- border-radius: 15px;
344
-
345
- text-align: center;
346
-
347
- background: white;
348
- border: solid 2px #ccc;
349
-
350
- font-size: 9pt;
351
- font-weight: 200;
352
- font-style: normal;
353
-
354
- position: absolute;
355
- top: 14px;
356
- left: 20px;
357
- }
358
-
359
- ul.frames .icon.application {
360
- background: #808090;
361
- border-color: #555;
362
- }
363
-
364
- ul.frames .icon.application:before {
365
- content: 'A';
366
- color: white;
367
- text-shadow: 0 0 3px rgba(0, 0, 0, 0.2);
368
- }
369
-
370
- /* Responsiveness -- flow to single-line mode */
371
- @media screen and (max-width: 1100px) {
372
- ul.frames li {
373
- padding-top: 6px;
374
- padding-bottom: 6px;
375
- padding-left: 36px;
376
- line-height: 1.3;
377
- }
378
-
379
- ul.frames li .icon {
380
- width: 11px;
381
- height: 11px;
382
- line-height: 11px;
383
-
384
- top: 7px;
385
- left: 10px;
386
- font-size: 5pt;
387
- }
388
-
389
- ul.frames .name,
390
- ul.frames .location {
391
- display: inline-block;
392
- line-height: 1.3;
393
- height: 1.3em;
394
- }
395
-
396
- ul.frames .name {
397
- margin-right: 10px;
398
- }
399
- }
400
-
401
- /* ---------------------------------------------------------------------
402
- * Monospace
403
- * --------------------------------------------------------------------- */
404
-
405
- pre, code, .be-repl input, .be-repl .command-line span, textarea, .code_linenums {
406
- font-family: menlo, lucida console, monospace;
407
- font-size: 8pt;
408
- }
409
-
410
- /* ---------------------------------------------------------------------
411
- * Display area
412
- * --------------------------------------------------------------------- */
413
-
414
- .trace_info {
415
- background: #fff;
416
- padding: 6px;
417
- border-radius: 3px;
418
- margin-bottom: 2px;
419
- box-shadow: 0 0 10px rgba(0, 0, 0, 0.03), 1px 1px 0 rgba(0, 0, 0, 0.05), -1px 1px 0 rgba(0, 0, 0, 0.05), 0 0 0 4px rgba(0, 0, 0, 0.04);
420
- }
421
-
422
- .code_block{
423
- background: #f1f1f1;
424
- border-left: 1px solid #ccc;
425
- }
426
-
427
- /* Titlebar */
428
- .trace_info .title {
429
- background: #f1f1f1;
430
-
431
- box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.3);
432
- overflow: hidden;
433
- padding: 6px 10px;
434
-
435
- border: solid 1px #ccc;
436
- border-bottom: 0;
437
-
438
- border-top-left-radius: 2px;
439
- border-top-right-radius: 2px;
440
- }
441
-
442
- .trace_info .title .name,
443
- .trace_info .title .location {
444
- font-size: 9pt;
445
- line-height: 26px;
446
- height: 26px;
447
- overflow: hidden;
448
- }
449
-
450
- .trace_info .title .location {
451
- float: left;
452
- font-weight: bold;
453
- font-size: 10pt;
454
- }
455
-
456
- .trace_info .title .location a {
457
- color:inherit;
458
- text-decoration:none;
459
- border-bottom:1px solid #aaaaaa;
460
- }
461
-
462
- .trace_info .title .location a:hover {
463
- border-color:#666666;
464
- }
465
-
466
- .trace_info .title .name {
467
- float: right;
468
- font-weight: 200;
469
- }
470
-
471
- .code, .be-console, .unavailable {
472
- background: #fff;
473
- padding: 5px;
474
-
475
- box-shadow: inset 3px 3px 3px rgba(0, 0, 0, 0.1), inset 0 0 0 1px rgba(0, 0, 0, 0.1);
476
- }
477
-
478
- .code_linenums{
479
- background:#f1f1f1;
480
- padding-top:10px;
481
- padding-bottom:9px;
482
- float:left;
483
- }
484
-
485
- .code_linenums span{
486
- display:block;
487
- padding:0 12px;
488
- }
489
-
490
- .code {
491
- margin-bottom: -1px;
492
- border-top-left-radius:2px;
493
- padding: 10px 0;
494
- overflow: auto;
495
- }
496
-
497
- .code pre{
498
- padding-left:12px;
499
- min-height:16px;
500
- }
501
-
502
- /* Source unavailable */
503
- p.unavailable {
504
- padding: 20px 0 40px 0;
505
- text-align: center;
506
- color: #b99;
507
- font-weight: bold;
508
- }
509
-
510
- p.unavailable:before {
511
- content: '\00d7';
512
- display: block;
513
-
514
- color: #daa;
515
-
516
- text-align: center;
517
- font-size: 40pt;
518
- font-weight: normal;
519
- margin-bottom: -10px;
520
- }
521
-
522
- @-webkit-keyframes highlight {
523
- 0% { background: rgba(220, 30, 30, 0.3); }
524
- 100% { background: rgba(220, 30, 30, 0.1); }
525
- }
526
- @-moz-keyframes highlight {
527
- 0% { background: rgba(220, 30, 30, 0.3); }
528
- 100% { background: rgba(220, 30, 30, 0.1); }
529
- }
530
- @keyframes highlight {
531
- 0% { background: rgba(220, 30, 30, 0.3); }
532
- 100% { background: rgba(220, 30, 30, 0.1); }
533
- }
534
-
535
- .code .highlight, .code_linenums .highlight {
536
- background: rgba(220, 30, 30, 0.1);
537
- -webkit-animation: highlight 400ms linear 1;
538
- -moz-animation: highlight 400ms linear 1;
539
- animation: highlight 400ms linear 1;
540
- }
541
-
542
- /* REPL shell */
543
- .be-console {
544
- padding: 0 1px 10px 1px;
545
- border-bottom-left-radius: 2px;
546
- border-bottom-right-radius: 2px;
547
- }
548
-
549
- .be-console pre {
550
- padding: 10px 10px 0 10px;
551
- max-height: 400px;
552
- overflow-x: none;
553
- overflow-y: auto;
554
- margin-bottom: -3px;
555
- word-wrap: break-word;
556
- white-space: pre-wrap;
557
- }
558
-
559
- /* .command-line > span + input */
560
- .be-console .command-line {
561
- display: table;
562
- width: 100%;
563
- }
564
-
565
- .be-console .command-line span,
566
- .be-console .command-line input {
567
- display: table-cell;
568
- }
569
-
570
- .be-console .command-line span {
571
- width: 1%;
572
- padding-right: 5px;
573
- padding-left: 10px;
574
- white-space: pre;
575
- }
576
-
577
- .be-console .command-line input {
578
- width: 99%;
579
- }
580
-
581
- /* Input box */
582
- .be-console input,
583
- .be-console input:focus {
584
- outline: 0;
585
- border: 0;
586
- padding: 0;
587
- background: transparent;
588
- margin: 0;
589
- }
590
-
591
- /* Hint text */
592
- .hint {
593
- margin: 15px 0 20px 0;
594
- font-size: 8pt;
595
- color: #8080a0;
596
- padding-left: 20px;
597
- }
598
-
599
- .hint:before {
600
- content: '\25b2';
601
- margin-right: 5px;
602
- opacity: 0.5;
603
- }
604
-
605
- /* ---------------------------------------------------------------------
606
- * Variable infos
607
- * --------------------------------------------------------------------- */
608
-
609
- .sub {
610
- padding: 10px 0;
611
- margin: 10px 0;
612
- }
613
-
614
- .sub:before {
615
- content: '';
616
- display: block;
617
- width: 100%;
618
- height: 4px;
619
-
620
- border-radius: 2px;
621
- background: rgba(0, 150, 200, 0.05);
622
- 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);
623
- }
624
-
625
- .sub h3 {
626
- color: #39a;
627
- font-size: 1.1em;
628
- margin: 10px 0;
629
- text-shadow: 0 1px 0 rgba(255, 255, 255, 0.6);
630
-
631
- -webkit-font-smoothing: antialiased;
632
- }
633
-
634
- .sub .inset {
635
- overflow-y: auto;
636
- }
637
-
638
- .sub table {
639
- table-layout: fixed;
640
- }
641
-
642
- .sub table td {
643
- border-top: dotted 1px #ddd;
644
- padding: 7px 1px;
645
- }
646
-
647
- .sub table td.name {
648
- width: 150px;
649
-
650
- font-weight: bold;
651
- font-size: 0.8em;
652
- padding-right: 20px;
653
-
654
- word-wrap: break-word;
655
- }
656
-
657
- .sub table td pre {
658
- max-height: 15em;
659
- overflow-y: auto;
660
- }
661
-
662
- .sub table td pre {
663
- width: 100%;
664
-
665
- word-wrap: break-word;
666
- white-space: normal;
667
- }
668
-
669
- /* "(object doesn't support inspect)" */
670
- .sub .unsupported {
671
- font-family: sans-serif;
672
- color: #777;
673
- }
674
-
675
- /* ---------------------------------------------------------------------
676
- * Scrollbar
677
- * --------------------------------------------------------------------- */
678
-
679
- nav.sidebar::-webkit-scrollbar,
680
- .inset pre::-webkit-scrollbar,
681
- .be-console pre::-webkit-scrollbar,
682
- .code::-webkit-scrollbar {
683
- width: 10px;
684
- height: 10px;
685
- }
686
-
687
- .inset pre::-webkit-scrollbar-thumb,
688
- .be-console pre::-webkit-scrollbar-thumb,
689
- .code::-webkit-scrollbar-thumb {
690
- background: #ccc;
691
- border-radius: 5px;
692
- }
693
-
694
- nav.sidebar::-webkit-scrollbar-thumb {
695
- background: rgba(0, 0, 0, 0.0);
696
- border-radius: 5px;
697
- }
698
-
699
- nav.sidebar:hover::-webkit-scrollbar-thumb {
700
- background-color: #999;
701
- background: -webkit-linear-gradient(left, #aaa, #999);
702
- }
703
-
704
- .be-console pre:hover::-webkit-scrollbar-thumb,
705
- .inset pre:hover::-webkit-scrollbar-thumb,
706
- .code:hover::-webkit-scrollbar-thumb {
707
- background: #888;
708
- }
709
- </style>
9
+ <%== ErrorPageStyle.style_tag(csp_nonce) %>
710
10
 
711
11
  <%# IE8 compatibility crap %>
712
- <script>
12
+ <script nonce="<%= csp_nonce %>">
713
13
  (function() {
714
14
  var elements = ["section", "nav", "header", "footer", "audio"];
715
15
  for (var i = 0; i < elements.length; i++) {
@@ -723,7 +23,7 @@
723
23
  rendered in the host app's layout. Let's empty out the styles of the
724
24
  host app.
725
25
  %>
726
- <script>
26
+ <script nonce="<%= csp_nonce %>">
727
27
  if (window.Turbolinks) {
728
28
  for(var i=0; i < document.styleSheets.length; i++) {
729
29
  if(document.styleSheets[i].href)
@@ -748,6 +48,15 @@
748
48
  }
749
49
  </script>
750
50
 
51
+ <p class='no-inline-style-notice'>
52
+ <strong>
53
+ Better Errors can't apply inline style<span class='no-javascript-notice'> (or run Javascript)</span>,
54
+ possibly because you have a Content Security Policy along with Turbolinks.
55
+ But you can
56
+ <a href='/__better_errors' target="_blank">open the interactive console in a new tab/window</a>.
57
+ </strong>
58
+ </p>
59
+
751
60
  <div class='top'>
752
61
  <header class="exception">
753
62
  <h2><strong><%= exception_type %></strong> <span>at <%= request_path %></span></h2>
@@ -764,6 +73,9 @@
764
73
  <% end %>
765
74
  </div>
766
75
  <% end %>
76
+ <% if exception_hint %>
77
+ <h2>Hint: <%= exception_hint %></h2>
78
+ <% end %>
767
79
  </header>
768
80
  </div>
769
81
 
@@ -791,21 +103,37 @@
791
103
  </ul>
792
104
  </nav>
793
105
 
794
- <% backtrace_frames.each_with_index do |frame, index| %>
795
- <div class="frame_info" id="frame_info_<%= index %>" style="display:none;"></div>
796
- <% end %>
106
+ <div class="frameInfos">
107
+ <div class="frame_info current" data-frame-idx="0">
108
+ <p class='no-javascript-notice'>
109
+ Better Errors can't run Javascript here<span class='no-inline-style-notice'> (or apply inline style)</span>,
110
+ possibly because you have a Content Security Policy along with Turbolinks.
111
+ But you can
112
+ <a href='/__better_errors' target="_blank">open the interactive console in a new tab/window</a>.
113
+ </p>
114
+ <!-- this is enough information to show something in case JS doesn't get to load -->
115
+ <%== ErrorPage.render_template('variable_info', first_frame_variable_info) %>
116
+ </div>
117
+ </div>
797
118
  </section>
798
119
  </body>
799
- <script>
120
+ <script nonce="<%= csp_nonce %>">
800
121
  (function() {
801
122
 
802
123
  var OID = "<%= id %>";
803
124
  var csrfToken = "<%= csrf_token %>";
804
125
 
805
126
  var previousFrame = null;
806
- var previousFrameInfo = null;
807
127
  var allFrames = document.querySelectorAll("ul.frames li");
808
- var allFrameInfos = document.querySelectorAll(".frame_info");
128
+ var frameInfos = document.querySelector(".frameInfos");
129
+
130
+ document.querySelector('body').classList.remove("better-errors-javascript-not-loaded");
131
+ document.querySelector('body').classList.add("better-errors-javascript-loaded");
132
+
133
+ var noJSNotices = document.querySelectorAll('.no-javascript-notice');
134
+ for(var i = 0; i < noJSNotices.length; i++) {
135
+ noJSNotices[i].remove();
136
+ }
809
137
 
810
138
  function apiCall(method, opts, cb) {
811
139
  var req = new XMLHttpRequest();
@@ -825,6 +153,28 @@
825
153
  return html.replace(/&/, "&amp;").replace(/</g, "&lt;");
826
154
  }
827
155
 
156
+ function hasConsoleBeenUsedPreviously() {
157
+ return !!document.cookie.split('; ').find(function(cookie) {
158
+ return cookie.startsWith('BetterErrors-has-used-console=');
159
+ });
160
+ }
161
+
162
+ var consoleHasBeenUsed = hasConsoleBeenUsedPreviously();
163
+
164
+ function consoleWasJustUsed() {
165
+ if (consoleHasBeenUsed) {
166
+ return;
167
+ }
168
+
169
+ hideConsoleHint();
170
+ consoleHasBeenUsed = true;
171
+ document.cookie = "BetterErrors-has-used-console=true;path=/;max-age=31536000;samesite"
172
+ }
173
+
174
+ function hideConsoleHint() {
175
+ document.querySelector('body').className += " console-has-been-used";
176
+ }
177
+
828
178
  function REPL(index) {
829
179
  this.index = index;
830
180
 
@@ -846,15 +196,20 @@
846
196
  this.inputElement = this.container.querySelector("input");
847
197
  this.outputElement = this.container.querySelector("pre");
848
198
 
199
+ if (consoleHasBeenUsed) {
200
+ hideConsoleHint();
201
+ }
202
+
849
203
  var self = this;
850
204
  this.inputElement.onkeydown = function(ev) {
851
205
  self.onKeyDown(ev);
206
+ consoleWasJustUsed();
852
207
  };
853
208
 
854
209
  this.setPrompt(">>");
855
210
 
856
211
  REPL.all[this.index] = this;
857
- }
212
+ };
858
213
 
859
214
  REPL.prototype.focus = function() {
860
215
  this.inputElement.focus();
@@ -894,7 +249,7 @@
894
249
  self.writeOutput(response.error + "\n");
895
250
  }
896
251
  self.writeOutput(self._prompt + " ");
897
- self.writeRawOutput(response.highlighted_input + "\n");
252
+ self.writeRawOutput("<span class='syntax-highlighted'>" + response.highlighted_input + "</span>\n");
898
253
  self.writeOutput(response.result);
899
254
  self.setPrompt(response.prompt);
900
255
  self.setInput(response.prefilled_input);
@@ -952,17 +307,25 @@
952
307
  };
953
308
 
954
309
  function switchTo(el) {
955
- if(previousFrameInfo) previousFrameInfo.style.display = "none";
956
- previousFrameInfo = el;
310
+ var currentFrameInfo = document.querySelectorAll('.frame_info.current');
311
+ for(var i = 0; i < currentFrameInfo.length; i++) {
312
+ currentFrameInfo[i].className = "frame_info";
313
+ }
957
314
 
958
- el.style.display = "block";
315
+ el.className = "frame_info current";
959
316
 
960
317
  var replInput = el.querySelector('.be-console input');
961
318
  if (replInput) replInput.focus();
962
319
  }
963
320
 
964
321
  function selectFrameInfo(index) {
965
- var el = allFrameInfos[index];
322
+ var el = document.querySelector(".frame_info[data-frame-idx='" + index + "']")
323
+ if (!el) {
324
+ el = document.createElement("div");
325
+ el.className = "frame_info";
326
+ el.setAttribute('data-frame-idx', index);
327
+ frameInfos.appendChild(el);
328
+ }
966
329
  if(el) {
967
330
  if (el.loaded) {
968
331
  return switchTo(el);