Class: Rhales::EarliestInjectionDetector

Inherits:
Object
  • Object
show all
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

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),
    find_after_last_meta(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


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 find_after_last_meta(template_html, head_start, head_end)
  head_content = template_html[head_start...head_end]
  scanner = StringScanner.new(head_content)
  last_meta_end = nil

  while scanner.scan_until(/<meta\b[^>]*\/?>/i)
    last_meta_end = scanner.pos
  end

  last_meta_end ? head_start + last_meta_end : 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