single-file-core 1.6.12 → 1.6.14

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
@@ -601,7 +601,7 @@ function fixInvalidNesting(document, NESTING_TRACK_ID_ATTRIBUTE_NAME, preventCle
601
601
  const id = element.getAttribute(NESTING_TRACK_ID_ATTRIBUTE_NAME);
602
602
  const originalElement = trackIds[id];
603
603
  if (originalElement != element) {
604
- if (!preventCleanup) {
604
+ if (!preventCleanup || options.mergeCopies) {
605
605
  if (originalElement.contains(element)) {
606
606
  element.replaceWith(...element.childNodes);
607
607
  } else {
package/core/index.js CHANGED
@@ -503,8 +503,7 @@ class Processor {
503
503
  this.doc = util.parseDocContent(pageContent, this.baseURI);
504
504
  util.fixInvalidNesting(this.doc, true, { recreateOnly: true });
505
505
  removeInsertedParagraphs(this.doc);
506
- this.nestingPositions = getNestingPositions(this.doc);
507
- util.fixInvalidNesting(this.doc);
506
+ util.fixInvalidNesting(this.doc, true, { mergeCopies: true });
508
507
  if (this.options.saveRawPage) {
509
508
  let charset;
510
509
  this.doc.querySelectorAll("meta[charset]").forEach(element => {
@@ -612,14 +611,20 @@ class Processor {
612
611
  if (this.options.displayStats) {
613
612
  size = util.getContentSize(this.doc.documentElement.outerHTML);
614
613
  }
615
- restoreNestingPositions(this.nestingPositions);
616
614
  const collapsedElementCount = collapseRecreatedElements(this.doc);
617
- if (collapsedElementCount || this.doc.querySelector(`[${util.NESTING_TRACK_ID_ATTRIBUTE_NAME}]`)) {
615
+ const invalidNesting = collapsedElementCount || this.doc.querySelector(`[${util.NESTING_TRACK_ID_ATTRIBUTE_NAME}]`);
616
+ if (invalidNesting) {
618
617
  const scriptElement = this.doc.createElement("script");
619
618
  scriptElement.textContent = `(${util.getFixInvalidNestingSource()})(document, "${util.NESTING_TRACK_ID_ATTRIBUTE_NAME}");`;
620
619
  this.doc.body.appendChild(scriptElement);
621
620
  }
622
- 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
+ }
623
628
  if (this.options.displayStats) {
624
629
  const contentSize = util.getContentSize(content);
625
630
  this.stats.set("processed", "HTML bytes", contentSize);
@@ -1010,11 +1015,17 @@ class Processor {
1010
1015
  }
1011
1016
 
1012
1017
  resetReferrerMeta() {
1013
- this.doc.querySelectorAll("meta[name=referrer]").forEach(element => element.remove());
1014
1018
  const metaElement = this.doc.createElement("meta");
1015
1019
  metaElement.setAttribute("name", "referrer");
1016
1020
  metaElement.setAttribute("content", "no-referrer");
1017
- 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());
1018
1029
  }
1019
1030
 
1020
1031
  setInputValues() {
@@ -1447,7 +1458,6 @@ class Processor {
1447
1458
  insertShadowRootContents() {
1448
1459
  const doc = this.doc;
1449
1460
  const options = this.options;
1450
- const nestingPositions = this.nestingPositions || [];
1451
1461
  if (options.shadowRoots && options.shadowRoots.length) {
1452
1462
  processElement(this.doc);
1453
1463
  }
@@ -1490,8 +1500,7 @@ class Processor {
1490
1500
  }
1491
1501
  util.fixInvalidNesting(doc, true, { rootElement: templateElement, recreateOnly: true });
1492
1502
  removeInsertedParagraphs(templateElement);
1493
- nestingPositions.push(...getNestingPositions(templateElement));
1494
- util.fixInvalidNesting(doc, true, { rootElement: templateElement });
1503
+ util.fixInvalidNesting(doc, true, { rootElement: templateElement, mergeCopies: true });
1495
1504
  if (templateElement.querySelector(`[${util.NESTING_TRACK_ID_ATTRIBUTE_NAME}]`)) {
1496
1505
  templateElement.setAttribute(SHADOWROOT_ATTRIBUTE_NAME, "open");
1497
1506
  }
@@ -1808,33 +1817,6 @@ function testIgnoredPath(resourceURL) {
1808
1817
  return resourceURL && (resourceURL.startsWith(DATA_URI_PREFIX) || resourceURL == ABOUT_BLANK_URI);
1809
1818
  }
1810
1819
 
1811
- function getNestingPositions(doc) {
1812
- return Array.from(doc.querySelectorAll(`[${util.NESTING_TRACK_ID_ATTRIBUTE_NAME}]`)).map(element => ({
1813
- element,
1814
- parentNode: element.parentNode,
1815
- previousSibling: element.previousSibling,
1816
- nextSibling: element.nextSibling
1817
- }));
1818
- }
1819
-
1820
- function restoreNestingPositions(positions) {
1821
- if (positions) {
1822
- positions.forEach(({ element, parentNode, previousSibling, nextSibling }) => {
1823
- if (element.isConnected && parentNode && parentNode.isConnected) {
1824
- if (previousSibling && previousSibling.parentNode == parentNode) {
1825
- parentNode.insertBefore(element, previousSibling.nextSibling);
1826
- } else if (!previousSibling) {
1827
- parentNode.insertBefore(element, parentNode.firstChild);
1828
- } else if (nextSibling && nextSibling.parentNode == parentNode) {
1829
- parentNode.insertBefore(element, nextSibling);
1830
- } else {
1831
- parentNode.appendChild(element);
1832
- }
1833
- }
1834
- });
1835
- }
1836
- }
1837
-
1838
1820
  function collapseRecreatedElements(doc) {
1839
1821
  const elements = Array.from(doc.querySelectorAll(`[${util.NESTING_RECREATED_ATTRIBUTE_NAME}]`)).reverse();
1840
1822
  elements.forEach(element => {
@@ -1850,6 +1832,25 @@ function collapseRecreatedElements(doc) {
1850
1832
  }
1851
1833
 
1852
1834
  function removeInsertedParagraphs(doc) {
1835
+ getInsertedParagraphs(doc).forEach((_, paragraph) => paragraph.remove());
1836
+ }
1837
+
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));
1850
+ }
1851
+ }
1852
+
1853
+ function getInsertedParagraphs(doc) {
1853
1854
  const trackedElements = new Map();
1854
1855
  doc.querySelectorAll(`[${util.NESTING_TRACK_ID_ATTRIBUTE_NAME}]`).forEach(element =>
1855
1856
  trackedElements.set(element.getAttribute(util.NESTING_TRACK_ID_ATTRIBUTE_NAME), element));
@@ -1858,33 +1859,33 @@ function removeInsertedParagraphs(doc) {
1858
1859
  const parentTrackId = getParentTrackId(trackId);
1859
1860
  const expectedParent = trackedElements.get(parentTrackId);
1860
1861
  if (expectedParent && element.parentElement != expectedParent && !element.contains(expectedParent) &&
1861
- testParagraphExpectedAncestor(trackedElements, parentTrackId)) {
1862
+ getExpectedParagraphTrackId(trackedElements, parentTrackId)) {
1862
1863
  displacedElements.add(element);
1863
1864
  }
1864
1865
  });
1865
- const insertedParagraphs = new Set();
1866
+ const insertedParagraphs = new Map();
1866
1867
  displacedElements.forEach(element => {
1867
1868
  let sibling = element.nextSibling;
1868
1869
  while (sibling && (sibling.nodeType != 1 || displacedElements.has(sibling))) {
1869
1870
  sibling = sibling.nextSibling;
1870
1871
  }
1871
1872
  if (sibling && sibling.tagName == PARAGRAPH_TAG_NAME && !sibling.attributes.length && !sibling.childNodes.length) {
1872
- insertedParagraphs.add(sibling);
1873
+ const trackId = element.getAttribute(util.NESTING_TRACK_ID_ATTRIBUTE_NAME);
1874
+ insertedParagraphs.set(sibling, getExpectedParagraphTrackId(trackedElements, getParentTrackId(trackId)));
1873
1875
  }
1874
1876
  });
1875
- insertedParagraphs.forEach(paragraph => paragraph.remove());
1877
+ return insertedParagraphs;
1876
1878
  }
1877
1879
 
1878
- function testParagraphExpectedAncestor(trackedElements, trackId) {
1880
+ function getExpectedParagraphTrackId(trackedElements, trackId) {
1879
1881
  let element = trackedElements.get(trackId);
1880
1882
  while (element) {
1881
1883
  if (element.tagName == PARAGRAPH_TAG_NAME) {
1882
- return true;
1884
+ return trackId;
1883
1885
  }
1884
1886
  trackId = getParentTrackId(trackId);
1885
1887
  element = trackId ? trackedElements.get(trackId) : null;
1886
1888
  }
1887
- return false;
1888
1889
  }
1889
1890
 
1890
1891
  function getParentTrackId(trackId) {
package/core/infobar.js CHANGED
@@ -149,12 +149,26 @@ const INFOBAR_STYLES = `
149
149
  }
150
150
  `;
151
151
  const INFOBAR_ANIMATIONS_STYLES = `
152
- .infobar {
152
+ .infobar::before {
153
+ content: "";
154
+ position: absolute;
155
+ inset: 0;
156
+ z-index: -1;
157
+ background-color: #dd6a00;
158
+ border-radius: inherit;
159
+ opacity: 0;
160
+ pointer-events: none;
153
161
  animation-name: flash;
154
162
  animation-duration: .5s;
155
163
  animation-timing-function: cubic-bezier(0.39, 0.58, 0.57, 1);
156
164
  animation-delay: 1s;
157
165
  animation-iteration-count: 2;
166
+ transition: visibility 0s 999999s;
167
+ }
168
+
169
+ .infobar:focus-within::before, .infobar.infobar-focus::before {
170
+ visibility: hidden;
171
+ transition-delay: 0s;
158
172
  }
159
173
 
160
174
  .infobar::after {
@@ -180,10 +194,10 @@ const INFOBAR_ANIMATIONS_STYLES = `
180
194
 
181
195
  @keyframes flash {
182
196
  0%, 100% {
183
- background-color: #737373;
197
+ opacity: 0;
184
198
  }
185
199
  50% {
186
- background-color: #dd6a00;
200
+ opacity: 1;
187
201
  }
188
202
  }
189
203
 
@@ -199,7 +213,7 @@ const INFOBAR_ANIMATIONS_STYLES = `
199
213
  }
200
214
 
201
215
  @media (prefers-reduced-motion: reduce) {
202
- .infobar,
216
+ .infobar::before,
203
217
  .infobar::after {
204
218
  animation-name: none;
205
219
  }
package/core/util.js CHANGED
@@ -185,8 +185,8 @@ function getInstance(utilOptions) {
185
185
  postProcessDoc(doc, markedElements, invalidElements) {
186
186
  helper.postProcessDoc(doc, markedElements, invalidElements);
187
187
  },
188
- serialize(doc, compressHTML) {
189
- return modules.serializer.process(doc, compressHTML);
188
+ serialize(doc, compressHTML, omittedEndTagElements) {
189
+ return modules.serializer.process(doc, compressHTML, omittedEndTagElements);
190
190
  },
191
191
  removeQuotes(string) {
192
192
  return helper.removeQuotes(string);
@@ -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) {
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);
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) {
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,9 +119,9 @@ function serializeElement(element, compressHTML) {
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));
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));
124
+ const omittedEndTag = (omittedEndTagElements && omittedEndTagElements.has(element)) || compressHTML && OMITTED_END_TAGS.find(omittedEndTag => tagName == getTagName(omittedEndTag) && omittedEndTag.accept(element.nextSibling, element));
125
125
  if (!omittedEndTag && !VOID_TAG_NAMES.includes(tagName)) {
126
126
  content += "</" + tagName.toLowerCase() + ">";
127
127
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "single-file-core",
3
- "version": "1.6.12",
3
+ "version": "1.6.14",
4
4
  "description": "SingleFile Core",
5
5
  "author": "Gildas Lormeau",
6
6
  "license": "AGPL-3.0-or-later",