Class: Rhales::EarliestInjectionDetector
- Inherits:
-
Object
- Object
- Rhales::EarliestInjectionDetector
- Defined in:
- lib/rhales/earliest_injection_detector.rb
Overview
Detects the earliest safe injection points in HTML head and body sections for optimal hydration script placement performance
Injection Priority Order
For <head></head>
section:
1. After the last <link>
tag
2. After the last <meta>
tag
3. After the first <script>
tag (assuming early scripts are intentional)
4. Before the </head>
tag
If no <head>
but there is <body>
:
- Before the <body>
tag
All injection points are validated for safety using SafeInjectionValidator to prevent injection inside unsafe contexts (scripts, styles, comments).
Instance Method Summary collapse
-
#detect(template_html) ⇒ Object
-
#detect_body_injection_point(scanner, validator, template_html) ⇒ Object
private
-
#detect_head_injection_point(scanner, validator, template_html) ⇒ Object
private
-
#find_after_first_script(template_html, head_start, head_end) ⇒ Object
private
-
#find_after_last_link(template_html, head_start, head_end) ⇒ Object
private
-
#find_after_last_meta(template_html, head_start, head_end) ⇒ Object
private
-
#find_head_section(template_html) ⇒ Object
private
-
#find_safe_injection_position(validator, preferred_position) ⇒ Object
private
Instance Method Details
#detect(template_html) ⇒ Object
22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
# File 'lib/rhales/earliest_injection_detector.rb', line 22 def detect(template_html) scanner = StringScanner.new(template_html) validator = SafeInjectionValidator.new(template_html) # Try head section injection points first head_injection_point = detect_head_injection_point(scanner, validator, template_html) return head_injection_point if head_injection_point # Fallback to body tag injection body_injection_point = detect_body_injection_point(scanner, validator, template_html) return body_injection_point if body_injection_point # No suitable injection point found nil end |
#detect_body_injection_point(scanner, validator, template_html) ⇒ Object (private)
62 63 64 65 66 67 68 69 70 71 72 73 |
# File 'lib/rhales/earliest_injection_detector.rb', line 62 def detect_body_injection_point(scanner, validator, template_html) scanner.pos = 0 # Find opening <body> tag if scanner.scan_until(/<body\b[^>]*>/i) body_start = scanner.pos - scanner.matched.length safe_position = find_safe_injection_position(validator, body_start) return safe_position if safe_position end nil end |
#detect_head_injection_point(scanner, validator, template_html) ⇒ Object (private)
40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
# File 'lib/rhales/earliest_injection_detector.rb', line 40 def detect_head_injection_point(scanner, validator, template_html) # Find head section bounds head_start, head_end = find_head_section(template_html) return nil unless head_start && head_end # Try injection points in priority order within head section injection_candidates = [ find_after_last_link(template_html, head_start, head_end), (template_html, head_start, head_end), find_after_first_script(template_html, head_start, head_end), head_end # Before </head> ].compact # Return first safe injection point injection_candidates.each do |position| safe_position = find_safe_injection_position(validator, position) return safe_position if safe_position end nil end |
#find_after_first_script(template_html, head_start, head_end) ⇒ Object (private)
113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 |
# File 'lib/rhales/earliest_injection_detector.rb', line 113 def find_after_first_script(template_html, head_start, head_end) head_content = template_html[head_start...head_end] scanner = StringScanner.new(head_content) # Find first script opening tag if scanner.scan_until(/<script\b[^>]*>/i) script_start = scanner.pos - scanner.matched.length # Find corresponding closing tag if scanner.scan_until(/<\/script>/i) first_script_end = scanner.pos return head_start + first_script_end end end nil end |
#find_after_last_link(template_html, head_start, head_end) ⇒ Object (private)
89 90 91 92 93 94 95 96 97 98 99 |
# File 'lib/rhales/earliest_injection_detector.rb', line 89 def find_after_last_link(template_html, head_start, head_end) head_content = template_html[head_start...head_end] scanner = StringScanner.new(head_content) last_link_end = nil while scanner.scan_until(/<link\b[^>]*\/?>/i) last_link_end = scanner.pos end last_link_end ? head_start + last_link_end : nil end |
#find_after_last_meta(template_html, head_start, head_end) ⇒ Object (private)
101 102 103 104 105 106 107 108 109 110 111 |
# File 'lib/rhales/earliest_injection_detector.rb', line 101 def (template_html, head_start, head_end) head_content = template_html[head_start...head_end] scanner = StringScanner.new(head_content) = nil while scanner.scan_until(/<meta\b[^>]*\/?>/i) = scanner.pos end ? head_start + : nil end |
#find_head_section(template_html) ⇒ Object (private)
75 76 77 78 79 80 81 82 83 84 85 86 87 |
# File 'lib/rhales/earliest_injection_detector.rb', line 75 def find_head_section(template_html) scanner = StringScanner.new(template_html) # Find opening <head> tag return nil unless scanner.scan_until(/<head\b[^>]*>/i) head_start = scanner.pos # Find closing </head> tag return nil unless scanner.scan_until(/<\/head>/i) head_end = scanner.pos - scanner.matched.length [head_start, head_end] end |
#find_safe_injection_position(validator, preferred_position) ⇒ Object (private)
131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 |
# File 'lib/rhales/earliest_injection_detector.rb', line 131 def find_safe_injection_position(validator, preferred_position) return nil unless preferred_position # First check if the preferred position is safe return preferred_position if validator.safe_injection_point?(preferred_position) # Try to find a safe position before the preferred position safe_before = validator.nearest_safe_point_before(preferred_position) return safe_before if safe_before # As a last resort, try after the preferred position safe_after = validator.nearest_safe_point_after(preferred_position) return safe_after if safe_after # No safe position found nil end |