athena129 commited on
Commit
b09f83a
·
verified ·
1 Parent(s): 8e95157

Fix renderMdToFragment infinite loop on unmatched ** / backtick (streaming-partial freeze)

Browse files
Files changed (1) hide show
  1. index.html +18 -1
index.html CHANGED
@@ -1218,11 +1218,18 @@ function el(tag, opts){
1218
  return e;
1219
  }
1220
 
1221
- /* Safe markdown — escape implicit, only **bold** and `code` are recognized. */
 
 
 
 
 
 
1222
  function renderMdToFragment(s){
1223
  const frag = document.createDocumentFragment();
1224
  let i = 0;
1225
  while (i < s.length){
 
1226
  // **bold**
1227
  if (s[i] === '*' && s[i+1] === '*'){
1228
  const end = s.indexOf('**', i + 2);
@@ -1231,6 +1238,10 @@ function renderMdToFragment(s){
1231
  i = end + 2;
1232
  continue;
1233
  }
 
 
 
 
1234
  }
1235
  // `code`
1236
  if (s[i] === '`'){
@@ -1240,6 +1251,10 @@ function renderMdToFragment(s){
1240
  i = end + 1;
1241
  continue;
1242
  }
 
 
 
 
1243
  }
1244
  // Plain run — find next special char
1245
  let next = i;
@@ -1250,6 +1265,8 @@ function renderMdToFragment(s){
1250
  }
1251
  frag.appendChild(document.createTextNode(s.slice(i, next)));
1252
  i = next;
 
 
1253
  }
1254
  return frag;
1255
  }
 
1218
  return e;
1219
  }
1220
 
1221
+ /* Safe markdown — escape implicit, only **bold** and `code` are recognized.
1222
+ IMPORTANT: streaming partial yields can land mid-`**` or mid-`` ` `` pair
1223
+ (e.g. cumulative text "... **The" before the closing `**` arrives). The
1224
+ unmatched-opener cases below are critical: without them the plain-run
1225
+ inner loop breaks immediately on the same `**`, the slice is empty, `i`
1226
+ doesn't advance, and the outer while loops forever — freezing the tab
1227
+ with "Page Unresponsive" once a streaming chunk happens to end mid-pair. */
1228
  function renderMdToFragment(s){
1229
  const frag = document.createDocumentFragment();
1230
  let i = 0;
1231
  while (i < s.length){
1232
+ const startI = i; // safety: guarantee forward progress every iteration
1233
  // **bold**
1234
  if (s[i] === '*' && s[i+1] === '*'){
1235
  const end = s.indexOf('**', i + 2);
 
1238
  i = end + 2;
1239
  continue;
1240
  }
1241
+ // Unmatched opening (mid-stream partial) — render as plain text and advance.
1242
+ frag.appendChild(document.createTextNode('**'));
1243
+ i += 2;
1244
+ continue;
1245
  }
1246
  // `code`
1247
  if (s[i] === '`'){
 
1251
  i = end + 1;
1252
  continue;
1253
  }
1254
+ // Unmatched opening — render as plain text and advance.
1255
+ frag.appendChild(document.createTextNode('`'));
1256
+ i += 1;
1257
+ continue;
1258
  }
1259
  // Plain run — find next special char
1260
  let next = i;
 
1265
  }
1266
  frag.appendChild(document.createTextNode(s.slice(i, next)));
1267
  i = next;
1268
+ // Defense in depth: never let the outer loop spin without progress.
1269
+ if (i === startI) i++;
1270
  }
1271
  return frag;
1272
  }