single-file-core 1.6.11 → 1.6.13

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.
package/core/helper.js CHANGED
@@ -94,6 +94,13 @@ const EMPTY_RESOURCE = "data:,";
94
94
  const POSTER_CONTENT_TYPES = ["image/webp", "image/jpeg"];
95
95
  const POSTER_QUALITY = 0.8;
96
96
  const NESTING_TRACK_ID_ATTRIBUTE_NAME = "data-sf-nesting-track-id";
97
+ const NESTING_START_MARKER = NESTING_TRACK_ID_ATTRIBUTE_NAME + "-start ";
98
+ const NESTING_END_MARKER = NESTING_TRACK_ID_ATTRIBUTE_NAME + "-end ";
99
+ const NESTING_RECREATED_ATTRIBUTE_NAME = NESTING_TRACK_ID_ATTRIBUTE_NAME + "-recreated";
100
+ const NESTING_SHADOW_ROOT_TRACK_ID_PREFIX = "s";
101
+ const HTML_NAMESPACE = "http://www.w3.org/1999/xhtml";
102
+ const RAW_TEXT_TAG_NAMES = ["SCRIPT", "STYLE", "TEXTAREA", "TITLE", "XMP", "IFRAME", "NOEMBED", "NOFRAMES", "PLAINTEXT", "NOSCRIPT"];
103
+ const COMMENT_NODE_FILTER = 128;
97
104
  const DEPRECATED_OPTION_NAMES = {
98
105
  loadDeferredImages: "loadDeferredContent",
99
106
  loadDeferredImagesMaxIdleTime: "loadDeferredContentMaxIdleTime",
@@ -141,6 +148,8 @@ export {
141
148
  parseDocContent,
142
149
  markInvalidNesting,
143
150
  fixInvalidNesting,
151
+ removeNestingMarkers,
152
+ getNestingMarkerData,
144
153
  normalizeOptions,
145
154
  ON_BEFORE_CAPTURE_EVENT_NAME,
146
155
  ON_AFTER_CAPTURE_EVENT_NAME,
@@ -171,6 +180,9 @@ export {
171
180
  MESSAGE_PREFIX,
172
181
  NO_SCRIPT_PROPERTY_NAME,
173
182
  NESTING_TRACK_ID_ATTRIBUTE_NAME,
183
+ NESTING_START_MARKER,
184
+ NESTING_END_MARKER,
185
+ NESTING_RECREATED_ATTRIBUTE_NAME,
174
186
  getPosterDataURI
175
187
  };
176
188
 
@@ -311,6 +323,7 @@ function preProcessDoc(doc, win, options) {
311
323
  markedElements: []
312
324
  };
313
325
  }
326
+ setNestingMarkersData(doc);
314
327
  let referrer = "";
315
328
  if (doc.referrer) {
316
329
  try {
@@ -343,18 +356,30 @@ function markInvalidNesting(doc) {
343
356
  if (!doc.body) {
344
357
  return;
345
358
  }
346
- addTrackIds(doc.body);
347
- const verificationDoc = parseDocContent(serialize(doc));
348
- const markedMap = buildTrackIdMap(doc.body);
349
- const normalizedMap = buildTrackIdMap(verificationDoc.body);
359
+ removeNestingMarkers(doc);
360
+ markInvalidNestingInRoot(doc, doc.body, "", () => serialize(doc));
361
+ getShadowRoots(doc.body).forEach((shadowRoot, indexShadowRoot) =>
362
+ markInvalidNestingInRoot(doc, shadowRoot, NESTING_SHADOW_ROOT_TRACK_ID_PREFIX + indexShadowRoot, () => shadowRoot.innerHTML));
363
+ }
364
+
365
+ function markInvalidNestingInRoot(doc, root, rootTrackId, getContent) {
366
+ if (rootTrackId) {
367
+ Array.from(root.children).forEach((child, indexChild) => addTrackIds(child, indexChild, rootTrackId));
368
+ } else {
369
+ addTrackIds(root);
370
+ }
371
+ const verificationDoc = parseDocContent(getContent());
372
+ const markedMap = buildTrackIdMap(root);
373
+ const normalizedMap = buildTrackIdMap(verificationDoc.documentElement);
350
374
  const trackIds = new Set();
375
+ const droppedElements = [];
351
376
  Object.keys(markedMap).forEach(id => {
352
377
  if (id in normalizedMap) {
353
378
  const markedParent = markedMap[id].parentElement?.getAttribute(NESTING_TRACK_ID_ATTRIBUTE_NAME) || null;
354
379
  const normalizedParent = normalizedMap[id]?.parentElement?.getAttribute(NESTING_TRACK_ID_ATTRIBUTE_NAME) || null;
355
380
  if (markedParent !== normalizedParent) {
356
381
  let current = markedMap[id];
357
- while (current && current !== doc.body) {
382
+ while (current && current !== root) {
358
383
  const currentId = current.getAttribute(NESTING_TRACK_ID_ATTRIBUTE_NAME);
359
384
  if (currentId) {
360
385
  trackIds.add(currentId);
@@ -362,9 +387,22 @@ function markInvalidNesting(doc) {
362
387
  current = current.parentElement;
363
388
  }
364
389
  }
390
+ } else if (testDroppedElement(markedMap[id])) {
391
+ trackIds.add(id);
392
+ droppedElements.push(markedMap[id]);
365
393
  }
366
394
  });
367
- cleanupTrackIds(doc.body, trackIds);
395
+ droppedElements.forEach(element => {
396
+ const id = element.getAttribute(NESTING_TRACK_ID_ATTRIBUTE_NAME);
397
+ element.prepend(doc.createComment(NESTING_START_MARKER + id));
398
+ element.append(doc.createComment(NESTING_END_MARKER + id));
399
+ });
400
+ setNestingMarkersData(root);
401
+ if (rootTrackId) {
402
+ Array.from(root.children).forEach(child => cleanupTrackIds(child, trackIds));
403
+ } else {
404
+ cleanupTrackIds(root, trackIds);
405
+ }
368
406
 
369
407
  function addTrackIds(element, index = 0, parentTrackId = "") {
370
408
  const trackId = parentTrackId ? `${parentTrackId}.${index + 1}` : `${index + 1}`;
@@ -375,9 +413,13 @@ function markInvalidNesting(doc) {
375
413
  Array.from(element.children).forEach((child, indexChild) => addTrackIds(child, indexChild, trackId));
376
414
  }
377
415
 
378
- function buildTrackIdMap(element) {
416
+ function buildTrackIdMap(root) {
379
417
  const trackIds = {};
380
- traverse(element);
418
+ if (root.getAttribute) {
419
+ traverse(root);
420
+ } else {
421
+ Array.from(root.children).forEach(traverse);
422
+ }
381
423
  return trackIds;
382
424
 
383
425
  function traverse(element) {
@@ -391,6 +433,21 @@ function markInvalidNesting(doc) {
391
433
  }
392
434
  }
393
435
 
436
+ function testDroppedElement(element) {
437
+ if (element.namespaceURI != HTML_NAMESPACE) {
438
+ return false;
439
+ }
440
+ let ancestor = element.parentElement;
441
+ while (ancestor) {
442
+ const tagName = ancestor.tagName.toUpperCase();
443
+ if (tagName == "TEMPLATE" || RAW_TEXT_TAG_NAMES.includes(tagName)) {
444
+ return false;
445
+ }
446
+ ancestor = ancestor.parentElement;
447
+ }
448
+ return true;
449
+ }
450
+
394
451
  function cleanupTrackIds(element, toKeep) {
395
452
  const id = element.getAttribute(NESTING_TRACK_ID_ATTRIBUTE_NAME);
396
453
  if (id && !toKeep.has(id)) {
@@ -400,33 +457,180 @@ function markInvalidNesting(doc) {
400
457
  }
401
458
  }
402
459
 
403
- function fixInvalidNesting(document, NESTING_TRACK_ID_ATTRIBUTE_NAME, preventCleanup = false) {
404
- const trackIds = {};
460
+ function getShadowRoots(element, shadowRoots = []) {
461
+ Array.from(element.children).forEach(child => {
462
+ if (child.namespaceURI == HTML_NAMESPACE && !child.classList.contains(SINGLE_FILE_UI_ELEMENT_CLASS) && child.tagName.toLowerCase() != INFOBAR_TAGNAME) {
463
+ const shadowRoot = getShadowRoot(child);
464
+ if (shadowRoot) {
465
+ shadowRoots.push(shadowRoot);
466
+ getShadowRoots(shadowRoot, shadowRoots);
467
+ }
468
+ }
469
+ getShadowRoots(child, shadowRoots);
470
+ });
471
+ return shadowRoots;
472
+ }
473
+
474
+ function getNestingMarkerData(element) {
475
+ return encodeURIComponent(JSON.stringify({
476
+ tag: element.localName,
477
+ attributes: Array.from(element.attributes)
478
+ .filter(attribute => attribute.name != NESTING_RECREATED_ATTRIBUTE_NAME)
479
+ .map(attribute => [attribute.name, attribute.value])
480
+ }));
481
+ }
482
+
483
+ function setNestingMarkersData(root) {
484
+ const walker = (root.ownerDocument || root).createTreeWalker(root, COMMENT_NODE_FILTER);
485
+ while (walker.nextNode()) {
486
+ const comment = walker.currentNode;
487
+ if (comment.data.startsWith(NESTING_START_MARKER)) {
488
+ const id = comment.data.substring(NESTING_START_MARKER.length).split(" ")[0];
489
+ comment.data = NESTING_START_MARKER + id + " " + getNestingMarkerData(comment.parentNode);
490
+ }
491
+ }
492
+ }
493
+
494
+ function removeNestingMarkers(doc) {
495
+ if (doc.body) {
496
+ [doc, ...getShadowRoots(doc.body)].forEach(root => {
497
+ const comments = [];
498
+ const walker = doc.createTreeWalker(root, COMMENT_NODE_FILTER);
499
+ while (walker.nextNode()) {
500
+ if (walker.currentNode.data.startsWith(NESTING_START_MARKER) || walker.currentNode.data.startsWith(NESTING_END_MARKER)) {
501
+ comments.push(walker.currentNode);
502
+ }
503
+ }
504
+ comments.forEach(comment => comment.remove());
505
+ });
506
+ }
507
+ }
508
+
509
+ function fixInvalidNesting(document, NESTING_TRACK_ID_ATTRIBUTE_NAME, preventCleanup = false, options = {}) {
510
+ const START_MARKER = NESTING_TRACK_ID_ATTRIBUTE_NAME + "-start ";
511
+ const END_MARKER = NESTING_TRACK_ID_ATTRIBUTE_NAME + "-end ";
512
+ const RECREATED_ATTRIBUTE_NAME = NESTING_TRACK_ID_ATTRIBUTE_NAME + "-recreated";
405
513
  if (document.currentScript) {
406
514
  document.currentScript.remove();
407
515
  }
408
- buildTrackIdMap(document.body);
409
- Object.keys(trackIds).forEach(id => {
410
- const element = trackIds[id];
411
- const idParts = id.split(".");
412
- if (idParts.length > 1) {
413
- const parentId = idParts.slice(0, -1).join(".");
414
- const expectedParent = trackIds[parentId];
415
- if (expectedParent && element.parentElement !== expectedParent && !element.contains(expectedParent)) {
416
- expectedParent.appendChild(element);
417
- }
516
+ const roots = [];
517
+ if (options.rootElement) {
518
+ roots.push(options.rootElement);
519
+ } else if (document.body) {
520
+ addRoots(document.body);
521
+ }
522
+ roots.forEach(root => {
523
+ recreateElements(root);
524
+ if (!options.recreateOnly) {
525
+ moveElements(root);
418
526
  }
419
527
  });
420
528
  if (!preventCleanup) {
421
- document.querySelectorAll("[" + NESTING_TRACK_ID_ATTRIBUTE_NAME + "]").forEach(element => element.removeAttribute(NESTING_TRACK_ID_ATTRIBUTE_NAME));
529
+ roots.forEach(root => {
530
+ const elements = Array.from(root.querySelectorAll("[" + NESTING_TRACK_ID_ATTRIBUTE_NAME + "]"));
531
+ if (root.getAttribute && root.getAttribute(NESTING_TRACK_ID_ATTRIBUTE_NAME)) {
532
+ elements.push(root);
533
+ }
534
+ elements.forEach(element => element.removeAttribute(NESTING_TRACK_ID_ATTRIBUTE_NAME));
535
+ });
422
536
  }
423
537
 
424
- function buildTrackIdMap(element) {
425
- const id = element.getAttribute(NESTING_TRACK_ID_ATTRIBUTE_NAME);
426
- if (id) {
427
- trackIds[id] = element;
538
+ function addRoots(root) {
539
+ roots.push(root);
540
+ root.querySelectorAll("*").forEach(element => {
541
+ if (element.shadowRoot) {
542
+ addRoots(element.shadowRoot);
543
+ }
544
+ });
545
+ }
546
+
547
+ function recreateElements(root) {
548
+ const startComments = [];
549
+ const walker = document.createTreeWalker(root, 128);
550
+ while (walker.nextNode()) {
551
+ if (walker.currentNode.data.startsWith(START_MARKER)) {
552
+ startComments.push(walker.currentNode);
553
+ }
554
+ }
555
+ startComments.forEach(startComment => {
556
+ const separatorIndex = startComment.data.indexOf(" ", START_MARKER.length);
557
+ let endComment, data;
558
+ if (separatorIndex != -1) {
559
+ const id = startComment.data.substring(START_MARKER.length, separatorIndex);
560
+ endComment = startComment.nextSibling;
561
+ while (endComment && !(endComment.nodeType == 8 && endComment.data == END_MARKER + id)) {
562
+ endComment = endComment.nextSibling;
563
+ }
564
+ try {
565
+ data = globalThis.JSON.parse(decodeURIComponent(startComment.data.substring(separatorIndex + 1)));
566
+ } catch {
567
+ /* ignored */
568
+ }
569
+ }
570
+ if (endComment && data) {
571
+ const element = document.createElement(data.tag);
572
+ data.attributes.forEach(([name, value]) => {
573
+ try {
574
+ element.setAttribute(name, value);
575
+ } catch {
576
+ /* ignored */
577
+ }
578
+ });
579
+ if (preventCleanup) {
580
+ element.setAttribute(RECREATED_ATTRIBUTE_NAME, "");
581
+ }
582
+ startComment.before(element);
583
+ while (startComment.nextSibling != endComment) {
584
+ element.appendChild(startComment.nextSibling);
585
+ }
586
+ startComment.remove();
587
+ endComment.remove();
588
+ }
589
+ });
590
+ }
591
+
592
+ function moveElements(root) {
593
+ const trackIds = {};
594
+ const elements = [];
595
+ if (root.getAttribute) {
596
+ buildTrackIdMap(root);
597
+ } else {
598
+ Array.from(root.children).forEach(buildTrackIdMap);
599
+ }
600
+ elements.forEach(element => {
601
+ const id = element.getAttribute(NESTING_TRACK_ID_ATTRIBUTE_NAME);
602
+ const originalElement = trackIds[id];
603
+ if (originalElement != element) {
604
+ if (!preventCleanup || options.mergeCopies) {
605
+ if (originalElement.contains(element)) {
606
+ element.replaceWith(...element.childNodes);
607
+ } else {
608
+ originalElement.append(...element.childNodes);
609
+ element.remove();
610
+ }
611
+ }
612
+ } else {
613
+ const idParts = id.split(".");
614
+ if (idParts.length > 1) {
615
+ const parentId = idParts.slice(0, -1).join(".");
616
+ const expectedParent = trackIds[parentId];
617
+ if (expectedParent && element.parentElement !== expectedParent && !element.contains(expectedParent)) {
618
+ expectedParent.appendChild(element);
619
+ }
620
+ }
621
+ }
622
+ });
623
+
624
+ function buildTrackIdMap(element) {
625
+ const id = element.getAttribute(NESTING_TRACK_ID_ATTRIBUTE_NAME);
626
+ if (id) {
627
+ if (!(id in trackIds)) {
628
+ trackIds[id] = element;
629
+ }
630
+ elements.push(element);
631
+ }
632
+ Array.from(element.children).forEach(buildTrackIdMap);
428
633
  }
429
- Array.from(element.children).forEach(buildTrackIdMap);
430
634
  }
431
635
  }
432
636
 
@@ -509,6 +713,7 @@ function getElementsInfo(win, doc, element, options, data = { usedFonts: new Map
509
713
  // ignored
510
714
  }
511
715
  getElementsInfo(win, doc, shadowRoot, options, data, adoptedStyleSheetsCache, elementHidden);
716
+ setNestingMarkersData(shadowRoot);
512
717
  shadowRootInfo.content = shadowRoot.innerHTML;
513
718
  shadowRootInfo.mode = shadowRoot.mode;
514
719
  shadowRootInfo.delegateFocus = shadowRoot.delegatesFocus;
@@ -852,6 +1057,7 @@ function testHiddenElement(element, computedStyle) {
852
1057
  }
853
1058
 
854
1059
  function postProcessDoc(doc, markedElements, invalidElements) {
1060
+ removeNestingMarkers(doc);
855
1061
  doc.querySelectorAll("[" + DISABLED_NOSCRIPT_ATTRIBUTE_NAME + "]").forEach(element => {
856
1062
  element.textContent = element.getAttribute(DISABLED_NOSCRIPT_ATTRIBUTE_NAME);
857
1063
  element.removeAttribute(DISABLED_NOSCRIPT_ATTRIBUTE_NAME);
package/core/index.js CHANGED
@@ -212,6 +212,9 @@ class Runner {
212
212
  this.options.frames = [];
213
213
  }
214
214
  this.options.content = this.options.content || (rootDocDefined ? util.serialize(this.options.doc) : null);
215
+ if (rootDocDefined) {
216
+ util.removeNestingMarkers(this.options.doc);
217
+ }
215
218
  this.onprogress = options.onprogress || (() => { });
216
219
  }
217
220
 
@@ -498,9 +501,9 @@ class Processor {
498
501
  pageContent = content.data || "";
499
502
  }
500
503
  this.doc = util.parseDocContent(pageContent, this.baseURI);
504
+ util.fixInvalidNesting(this.doc, true, { recreateOnly: true });
501
505
  removeInsertedParagraphs(this.doc);
502
- this.nestingPositions = getNestingPositions(this.doc);
503
- util.fixInvalidNesting(this.doc);
506
+ util.fixInvalidNesting(this.doc, true, { mergeCopies: true });
504
507
  if (this.options.saveRawPage) {
505
508
  let charset;
506
509
  this.doc.querySelectorAll("meta[charset]").forEach(element => {
@@ -608,13 +611,20 @@ class Processor {
608
611
  if (this.options.displayStats) {
609
612
  size = util.getContentSize(this.doc.documentElement.outerHTML);
610
613
  }
611
- restoreNestingPositions(this.nestingPositions);
612
- if (this.doc.querySelector(`[${util.NESTING_TRACK_ID_ATTRIBUTE_NAME}]`)) {
614
+ const collapsedElementCount = collapseRecreatedElements(this.doc);
615
+ const invalidNesting = collapsedElementCount || this.doc.querySelector(`[${util.NESTING_TRACK_ID_ATTRIBUTE_NAME}]`);
616
+ if (invalidNesting) {
613
617
  const scriptElement = this.doc.createElement("script");
614
618
  scriptElement.textContent = `(${util.getFixInvalidNestingSource()})(document, "${util.NESTING_TRACK_ID_ATTRIBUTE_NAME}");`;
615
619
  this.doc.body.appendChild(scriptElement);
616
620
  }
617
- const content = util.serialize(this.doc, this.options.compressHTML);
621
+ let content = util.serialize(this.doc, this.options.compressHTML);
622
+ if (invalidNesting) {
623
+ const closedParagraphs = getClosedParagraphs(this.doc, content);
624
+ if (closedParagraphs.size) {
625
+ content = util.serialize(this.doc, this.options.compressHTML, closedParagraphs);
626
+ }
627
+ }
618
628
  if (this.options.displayStats) {
619
629
  const contentSize = util.getContentSize(content);
620
630
  this.stats.set("processed", "HTML bytes", contentSize);
@@ -1005,11 +1015,17 @@ class Processor {
1005
1015
  }
1006
1016
 
1007
1017
  resetReferrerMeta() {
1008
- this.doc.querySelectorAll("meta[name=referrer]").forEach(element => element.remove());
1009
1018
  const metaElement = this.doc.createElement("meta");
1010
1019
  metaElement.setAttribute("name", "referrer");
1011
1020
  metaElement.setAttribute("content", "no-referrer");
1012
- this.doc.head.appendChild(metaElement);
1021
+ const referrerElements = Array.from(this.doc.querySelectorAll("meta[name=referrer]"));
1022
+ const headReferrerElement = referrerElements.find(element => element.parentElement == this.doc.head);
1023
+ if (headReferrerElement) {
1024
+ headReferrerElement.replaceWith(metaElement);
1025
+ } else {
1026
+ this.doc.head.appendChild(metaElement);
1027
+ }
1028
+ referrerElements.forEach(element => element.remove());
1013
1029
  }
1014
1030
 
1015
1031
  setInputValues() {
@@ -1482,6 +1498,12 @@ class Processor {
1482
1498
  if (shadowDoc.body) {
1483
1499
  shadowDoc.body.childNodes.forEach(node => templateElement.appendChild(shadowDoc.importNode(node, true)));
1484
1500
  }
1501
+ util.fixInvalidNesting(doc, true, { rootElement: templateElement, recreateOnly: true });
1502
+ removeInsertedParagraphs(templateElement);
1503
+ util.fixInvalidNesting(doc, true, { rootElement: templateElement, mergeCopies: true });
1504
+ if (templateElement.querySelector(`[${util.NESTING_TRACK_ID_ATTRIBUTE_NAME}]`)) {
1505
+ templateElement.setAttribute(SHADOWROOT_ATTRIBUTE_NAME, "open");
1506
+ }
1485
1507
  processElement(templateElement);
1486
1508
  if (element.firstChild) {
1487
1509
  element.insertBefore(templateElement, element.firstChild);
@@ -1795,34 +1817,40 @@ function testIgnoredPath(resourceURL) {
1795
1817
  return resourceURL && (resourceURL.startsWith(DATA_URI_PREFIX) || resourceURL == ABOUT_BLANK_URI);
1796
1818
  }
1797
1819
 
1798
- function getNestingPositions(doc) {
1799
- return Array.from(doc.querySelectorAll(`[${util.NESTING_TRACK_ID_ATTRIBUTE_NAME}]`)).map(element => ({
1800
- element,
1801
- parentNode: element.parentNode,
1802
- previousSibling: element.previousSibling,
1803
- nextSibling: element.nextSibling
1804
- }));
1820
+ function collapseRecreatedElements(doc) {
1821
+ const elements = Array.from(doc.querySelectorAll(`[${util.NESTING_RECREATED_ATTRIBUTE_NAME}]`)).reverse();
1822
+ elements.forEach(element => {
1823
+ const id = element.getAttribute(util.NESTING_TRACK_ID_ATTRIBUTE_NAME);
1824
+ element.before(doc.createComment(util.NESTING_START_MARKER + id + " " + util.getNestingMarkerData(element)));
1825
+ while (element.firstChild) {
1826
+ element.before(element.firstChild);
1827
+ }
1828
+ element.before(doc.createComment(util.NESTING_END_MARKER + id));
1829
+ element.remove();
1830
+ });
1831
+ return elements.length;
1832
+ }
1833
+
1834
+ function removeInsertedParagraphs(doc) {
1835
+ getInsertedParagraphs(doc).forEach((_, paragraph) => paragraph.remove());
1805
1836
  }
1806
1837
 
1807
- function restoreNestingPositions(positions) {
1808
- if (positions) {
1809
- positions.forEach(({ element, parentNode, previousSibling, nextSibling }) => {
1810
- if (element.isConnected && parentNode && parentNode.isConnected) {
1811
- if (previousSibling && previousSibling.parentNode == parentNode) {
1812
- parentNode.insertBefore(element, previousSibling.nextSibling);
1813
- } else if (!previousSibling) {
1814
- parentNode.insertBefore(element, parentNode.firstChild);
1815
- } else if (nextSibling && nextSibling.parentNode == parentNode) {
1816
- parentNode.insertBefore(element, nextSibling);
1817
- } else {
1818
- parentNode.appendChild(element);
1819
- }
1820
- }
1821
- });
1838
+ function getClosedParagraphs(doc, content) {
1839
+ const parsedDoc = util.parseDocContent(content);
1840
+ const roots = [];
1841
+ addRoots(parsedDoc);
1842
+ const trackIds = new Set();
1843
+ roots.forEach(root => getInsertedParagraphs(root).forEach(trackId => trackIds.add(trackId)));
1844
+ return new Set(Array.from(doc.querySelectorAll(`[${util.NESTING_TRACK_ID_ATTRIBUTE_NAME}]`)).filter(element =>
1845
+ element.tagName == PARAGRAPH_TAG_NAME && trackIds.has(element.getAttribute(util.NESTING_TRACK_ID_ATTRIBUTE_NAME))));
1846
+
1847
+ function addRoots(root) {
1848
+ roots.push(root);
1849
+ root.querySelectorAll("template").forEach(templateElement => addRoots(templateElement.content));
1822
1850
  }
1823
1851
  }
1824
1852
 
1825
- function removeInsertedParagraphs(doc) {
1853
+ function getInsertedParagraphs(doc) {
1826
1854
  const trackedElements = new Map();
1827
1855
  doc.querySelectorAll(`[${util.NESTING_TRACK_ID_ATTRIBUTE_NAME}]`).forEach(element =>
1828
1856
  trackedElements.set(element.getAttribute(util.NESTING_TRACK_ID_ATTRIBUTE_NAME), element));
@@ -1831,33 +1859,33 @@ function removeInsertedParagraphs(doc) {
1831
1859
  const parentTrackId = getParentTrackId(trackId);
1832
1860
  const expectedParent = trackedElements.get(parentTrackId);
1833
1861
  if (expectedParent && element.parentElement != expectedParent && !element.contains(expectedParent) &&
1834
- testParagraphExpectedAncestor(trackedElements, parentTrackId)) {
1862
+ getExpectedParagraphTrackId(trackedElements, parentTrackId)) {
1835
1863
  displacedElements.add(element);
1836
1864
  }
1837
1865
  });
1838
- const insertedParagraphs = new Set();
1866
+ const insertedParagraphs = new Map();
1839
1867
  displacedElements.forEach(element => {
1840
1868
  let sibling = element.nextSibling;
1841
1869
  while (sibling && (sibling.nodeType != 1 || displacedElements.has(sibling))) {
1842
1870
  sibling = sibling.nextSibling;
1843
1871
  }
1844
1872
  if (sibling && sibling.tagName == PARAGRAPH_TAG_NAME && !sibling.attributes.length && !sibling.childNodes.length) {
1845
- insertedParagraphs.add(sibling);
1873
+ const trackId = element.getAttribute(util.NESTING_TRACK_ID_ATTRIBUTE_NAME);
1874
+ insertedParagraphs.set(sibling, getExpectedParagraphTrackId(trackedElements, getParentTrackId(trackId)));
1846
1875
  }
1847
1876
  });
1848
- insertedParagraphs.forEach(paragraph => paragraph.remove());
1877
+ return insertedParagraphs;
1849
1878
  }
1850
1879
 
1851
- function testParagraphExpectedAncestor(trackedElements, trackId) {
1880
+ function getExpectedParagraphTrackId(trackedElements, trackId) {
1852
1881
  let element = trackedElements.get(trackId);
1853
1882
  while (element) {
1854
1883
  if (element.tagName == PARAGRAPH_TAG_NAME) {
1855
- return true;
1884
+ return trackId;
1856
1885
  }
1857
1886
  trackId = getParentTrackId(trackId);
1858
1887
  element = trackId ? trackedElements.get(trackId) : null;
1859
1888
  }
1860
- return false;
1861
1889
  }
1862
1890
 
1863
1891
  function getParentTrackId(trackId) {
package/core/util.js CHANGED
@@ -128,8 +128,14 @@ function getInstance(utilOptions) {
128
128
  return doc;
129
129
  }
130
130
  },
131
- fixInvalidNesting(doc, preventCleanup = true) {
132
- helper.fixInvalidNesting(doc, helper.NESTING_TRACK_ID_ATTRIBUTE_NAME, preventCleanup);
131
+ fixInvalidNesting(doc, preventCleanup = true, options) {
132
+ helper.fixInvalidNesting(doc, helper.NESTING_TRACK_ID_ATTRIBUTE_NAME, preventCleanup, options);
133
+ },
134
+ removeNestingMarkers(doc) {
135
+ helper.removeNestingMarkers(doc);
136
+ },
137
+ getNestingMarkerData(element) {
138
+ return helper.getNestingMarkerData(element);
133
139
  },
134
140
  markInvalidNesting(doc) {
135
141
  helper.markInvalidNesting(doc, helper.NESTING_TRACK_ID_ATTRIBUTE_NAME);
@@ -179,8 +185,8 @@ function getInstance(utilOptions) {
179
185
  postProcessDoc(doc, markedElements, invalidElements) {
180
186
  helper.postProcessDoc(doc, markedElements, invalidElements);
181
187
  },
182
- serialize(doc, compressHTML) {
183
- return modules.serializer.process(doc, compressHTML);
188
+ serialize(doc, compressHTML, omittedEndTagElements) {
189
+ return modules.serializer.process(doc, compressHTML, omittedEndTagElements);
184
190
  },
185
191
  removeQuotes(string) {
186
192
  return helper.removeQuotes(string);
@@ -226,6 +232,9 @@ function getInstance(utilOptions) {
226
232
  WAIT_FOR_USERSCRIPT_PROPERTY_NAME: helper.WAIT_FOR_USERSCRIPT_PROPERTY_NAME,
227
233
  NO_SCRIPT_PROPERTY_NAME: helper.NO_SCRIPT_PROPERTY_NAME,
228
234
  NESTING_TRACK_ID_ATTRIBUTE_NAME: helper.NESTING_TRACK_ID_ATTRIBUTE_NAME,
235
+ NESTING_START_MARKER: helper.NESTING_START_MARKER,
236
+ NESTING_END_MARKER: helper.NESTING_END_MARKER,
237
+ NESTING_RECREATED_ATTRIBUTE_NAME: helper.NESTING_RECREATED_ATTRIBUTE_NAME,
229
238
  getPosterDataURI: helper.getPosterDataURI
230
239
  };
231
240
 
@@ -59,7 +59,7 @@ export {
59
59
  process
60
60
  };
61
61
 
62
- function process(doc, compressHTML) {
62
+ function process(doc, compressHTML, omittedEndTagElements) {
63
63
  const docType = doc.doctype;
64
64
  let docTypeString = "";
65
65
  if (docType) {
@@ -74,16 +74,16 @@ function process(doc, compressHTML) {
74
74
  docTypeString += " [" + docType.internalSubset + "]";
75
75
  docTypeString += "> ";
76
76
  }
77
- return docTypeString + serialize(doc.documentElement, compressHTML);
77
+ return docTypeString + serialize(doc.documentElement, compressHTML, omittedEndTagElements);
78
78
  }
79
79
 
80
- function serialize(node, compressHTML, isSVG) {
80
+ function serialize(node, compressHTML, omittedEndTagElements) {
81
81
  if (node.nodeType == Node_TEXT_NODE) {
82
82
  return serializeTextNode(node);
83
83
  } else if (node.nodeType == Node_COMMENT_NODE) {
84
84
  return serializeCommentNode(node);
85
85
  } else if (node.nodeType == Node_ELEMENT_NODE) {
86
- return serializeElement(node, compressHTML, isSVG);
86
+ return serializeElement(node, compressHTML, omittedEndTagElements);
87
87
  }
88
88
  }
89
89
 
@@ -95,7 +95,7 @@ function serializeTextNode(textNode) {
95
95
  }
96
96
  if (!parentTagName || TEXT_NODE_TAGS.includes(parentTagName)) {
97
97
  if ((parentTagName == "SCRIPT" && (!parentNode.type || parentNode.type == "text/javascript")) || parentTagName == "STYLE") {
98
- return textNode.textContent.replace(/<\//gi, "<\\/").replace(/\/>/gi, "\\/>");
98
+ return textNode.textContent.replace(/<\//gi, "<\\/");
99
99
  }
100
100
  return textNode.textContent;
101
101
  } else {
@@ -107,7 +107,7 @@ function serializeCommentNode(commentNode) {
107
107
  return "<!--" + commentNode.textContent + "-->";
108
108
  }
109
109
 
110
- function serializeElement(element, compressHTML, isSVG) {
110
+ function serializeElement(element, compressHTML, omittedEndTagElements) {
111
111
  const tagName = getTagName(element);
112
112
  const omittedStartTag = compressHTML && OMITTED_START_TAGS.find(omittedStartTag => tagName == getTagName(omittedStartTag) && omittedStartTag.accept(element));
113
113
  let content = "";
@@ -119,10 +119,10 @@ function serializeElement(element, compressHTML, isSVG) {
119
119
  if (tagName == "TEMPLATE" && !element.childNodes.length) {
120
120
  content += element.innerHTML;
121
121
  } else {
122
- Array.from(element.childNodes).forEach(childNode => content += serialize(childNode, compressHTML, isSVG || tagName == "svg"));
122
+ Array.from(element.childNodes).forEach(childNode => content += serialize(childNode, compressHTML, omittedEndTagElements));
123
123
  }
124
- const omittedEndTag = compressHTML && OMITTED_END_TAGS.find(omittedEndTag => tagName == getTagName(omittedEndTag) && omittedEndTag.accept(element.nextSibling, element));
125
- if (isSVG || (!omittedEndTag && !VOID_TAG_NAMES.includes(tagName))) {
124
+ const omittedEndTag = (omittedEndTagElements && omittedEndTagElements.has(element)) || compressHTML && OMITTED_END_TAGS.find(omittedEndTag => tagName == getTagName(omittedEndTag) && omittedEndTag.accept(element.nextSibling, element));
125
+ if (!omittedEndTag && !VOID_TAG_NAMES.includes(tagName)) {
126
126
  content += "</" + tagName.toLowerCase() + ">";
127
127
  }
128
128
  return content;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "single-file-core",
3
- "version": "1.6.11",
3
+ "version": "1.6.13",
4
4
  "description": "SingleFile Core",
5
5
  "author": "Gildas Lormeau",
6
6
  "license": "AGPL-3.0-or-later",
@@ -31,6 +31,7 @@ import {
31
31
  WAIT_FOR_USERSCRIPT_PROPERTY_NAME,
32
32
  preProcessDoc,
33
33
  postProcessDoc,
34
+ markInvalidNesting,
34
35
  getShadowRoot
35
36
  } from "./core/helper.js";
36
37
 
@@ -46,6 +47,7 @@ const helper = {
46
47
  serialize(doc, compressHTML) {
47
48
  return serializer.process(doc, compressHTML);
48
49
  },
50
+ markInvalidNesting,
49
51
  getShadowRoot
50
52
  };
51
53