single-file-core 1.5.111 → 1.5.113

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
@@ -75,6 +75,8 @@ const COMMENT_HEADER_LEGACY = "Archive processed by SingleFile";
75
75
  const SINGLE_FILE_UI_ELEMENT_CLASS = "single-file-ui-element";
76
76
  const INFOBAR_TAGNAME = infobar.INFOBAR_TAGNAME;
77
77
  const EMPTY_RESOURCE = "data:,";
78
+ const POSTER_CONTENT_TYPES = ["image/webp", "image/jpeg"];
79
+ const POSTER_QUALITY = 0.8;
78
80
  const DEFAULT_REPLACED_CHARACTERS = ["~", "+", "?", "%", "*", ":", "|", "\"", "<", ">", "\\\\", "\x00-\x1f", "\x7F"];
79
81
  const DEFAULT_REPLACEMENT_CHARACTER = "_";
80
82
  const DEFAULT_REPLACEMENT_CHARACTERS = ["~", "+", "?", "%", "*", ":", "|", """, "<", ">", "\"];
@@ -143,9 +145,24 @@ export {
143
145
  WAIT_FOR_USERSCRIPT_PROPERTY_NAME,
144
146
  MESSAGE_PREFIX,
145
147
  NO_SCRIPT_PROPERTY_NAME,
146
- NESTING_TRACK_ID_ATTRIBUTE_NAME
148
+ NESTING_TRACK_ID_ATTRIBUTE_NAME,
149
+ getPosterDataURI
147
150
  };
148
151
 
152
+ // a poster is a still of a video the reader cannot play, so it is stored in a
153
+ // lossy format: the same frame costs an order of magnitude less than as a PNG.
154
+ // toDataURL falls back to PNG without reporting it when it does not support the
155
+ // format, so the type it returned is tested instead of assumed
156
+ function getPosterDataURI(canvasElement) {
157
+ for (const contentType of POSTER_CONTENT_TYPES) {
158
+ const dataURI = canvasElement.toDataURL(contentType, POSTER_QUALITY);
159
+ if (dataURI.startsWith("data:" + contentType)) {
160
+ return dataURI;
161
+ }
162
+ }
163
+ return canvasElement.toDataURL("image/png");
164
+ }
165
+
149
166
  let userScriptHandlerObserver;
150
167
 
151
168
  function initUserScriptHandler() {
@@ -534,14 +551,16 @@ function getResourcesInfo(win, doc, element, options, data, elementHidden, compu
534
551
  });
535
552
  element.setAttribute(VIDEO_ATTRIBUTE_NAME, data.videos.length - 1);
536
553
  }
537
- if (!element.getAttribute("poster")) {
554
+ // the posters are only inserted when the videos are blocked, capturing them
555
+ // otherwise encodes a frame per video for nothing
556
+ if (options.blockVideos && !element.getAttribute("poster")) {
538
557
  const canvasElement = doc.createElement("canvas");
539
558
  const context = canvasElement.getContext("2d");
540
559
  canvasElement.width = element.videoWidth;
541
560
  canvasElement.height = element.videoHeight;
542
561
  try {
543
562
  context.drawImage(element, 0, 0, canvasElement.width, canvasElement.height);
544
- data.posters.push(canvasElement.toDataURL("image/png"));
563
+ data.posters.push(getPosterDataURI(canvasElement));
545
564
  element.setAttribute(POSTER_ATTRIBUTE_NAME, data.posters.length - 1);
546
565
  data.markedElements.push(element);
547
566
  // eslint-disable-next-line no-unused-vars
package/core/index.js CHANGED
@@ -155,6 +155,7 @@ const STAGES = [{
155
155
  { action: "replaceStylesheets" },
156
156
  { action: "replaceStyleAttributes" },
157
157
  { action: "insertVariables" },
158
+ { action: "removeEmptyMediaAutoplay" },
158
159
  { option: "compressHTML", action: "compressHTML" },
159
160
  { action: "cleanupPage" }
160
161
  ],
@@ -824,6 +825,25 @@ class Processor {
824
825
  });
825
826
  }
826
827
 
828
+ // a media element left without any source can never start, and the attribute
829
+ // would keep it announcing a playback that never happens. The sources are
830
+ // dropped whenever they could not be stored, so this is not specific to
831
+ // blocked videos, and it runs on the final state to leave alone an element
832
+ // that kept one of several sources
833
+ removeEmptyMediaAutoplay() {
834
+ this.doc.querySelectorAll("video[autoplay], audio[autoplay]").forEach(element => {
835
+ const sourceElements = Array.from(element.querySelectorAll("source"));
836
+ if (!hasSource(element) && !sourceElements.some(hasSource)) {
837
+ element.removeAttribute("autoplay");
838
+ }
839
+ });
840
+
841
+ function hasSource(element) {
842
+ const src = element.getAttribute("src");
843
+ return Boolean(src) && src != util.EMPTY_RESOURCE;
844
+ }
845
+ }
846
+
827
847
  removeFrames() {
828
848
  const frameElements = this.doc.querySelectorAll("iframe, frame, object[type=\"text/html\"][data]");
829
849
  this.stats.set("discarded", "frames", frameElements.length);
@@ -1195,7 +1215,7 @@ class Processor {
1195
1215
  canvasElement.height = videoData.size.videoHeight;
1196
1216
  context.drawImage(temporaryVideoElement, 0, 0, canvasElement.width, canvasElement.height);
1197
1217
  try {
1198
- videoElement.poster = canvasElement.toDataURL("image/png");
1218
+ videoElement.poster = util.getPosterDataURI(canvasElement);
1199
1219
  // eslint-disable-next-line no-unused-vars
1200
1220
  } catch (error) {
1201
1221
  videoElement.poster = "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='" + videoData.size.videoWidth + "' height='" + videoData.size.videoHeight + "'%3E%3C/svg%3E";
@@ -0,0 +1,50 @@
1
+ /*
2
+ * Copyright 2010-2026 Gildas Lormeau
3
+ * contact : gildas.lormeau <at> gmail.com
4
+ *
5
+ * This file is part of SingleFile.
6
+ *
7
+ * The code in this file is free software: you can redistribute it and/or
8
+ * modify it under the terms of the GNU Affero General Public License
9
+ * (GNU AGPL) as published by the Free Software Foundation, either version 3
10
+ * of the License, or (at your option) any later version.
11
+ *
12
+ * The code in this file is distributed in the hope that it will be useful,
13
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
15
+ * General Public License for more details.
16
+ *
17
+ * As additional permission under GNU AGPL version 3 section 7, you may
18
+ * distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU
19
+ * AGPL normally required by section 4, provided you include this license
20
+ * notice and a URL through which recipients can access the Corresponding
21
+ * Source.
22
+ */
23
+
24
+ // this module is imported by the script embedded in self-extracting archives, it
25
+ // must stay free of dependencies
26
+
27
+ export {
28
+ getDoctypeString
29
+ };
30
+
31
+ function getDoctypeString(doc) {
32
+ const docType = doc.doctype;
33
+ let docTypeString = "";
34
+ if (docType) {
35
+ docTypeString = "<!DOCTYPE " + docType.nodeName;
36
+ if (docType.publicId) {
37
+ docTypeString += " PUBLIC \"" + docType.publicId + "\"";
38
+ if (docType.systemId) {
39
+ docTypeString += " \"" + docType.systemId + "\"";
40
+ }
41
+ } else if (docType.systemId) {
42
+ docTypeString += " SYSTEM \"" + docType.systemId + "\"";
43
+ }
44
+ if (docType.internalSubset) {
45
+ docTypeString += " [" + docType.internalSubset + "]";
46
+ }
47
+ docTypeString += ">";
48
+ }
49
+ return docTypeString;
50
+ }
package/core/util.js CHANGED
@@ -24,6 +24,7 @@
24
24
  import * as vendor from "./../vendor/index.js";
25
25
  import * as modules from "./../modules/index.js";
26
26
  import * as helper from "./helper.js";
27
+ import { getDoctypeString } from "./lib/doctype.js";
27
28
 
28
29
  const DEBUG = false;
29
30
  const ONE_MB = 1024 * 1024;
@@ -225,7 +226,8 @@ function getInstance(utilOptions) {
225
226
  INFOBAR_TAGNAME: helper.INFOBAR_TAGNAME,
226
227
  WAIT_FOR_USERSCRIPT_PROPERTY_NAME: helper.WAIT_FOR_USERSCRIPT_PROPERTY_NAME,
227
228
  NO_SCRIPT_PROPERTY_NAME: helper.NO_SCRIPT_PROPERTY_NAME,
228
- NESTING_TRACK_ID_ATTRIBUTE_NAME: helper.NESTING_TRACK_ID_ATTRIBUTE_NAME
229
+ NESTING_TRACK_ID_ATTRIBUTE_NAME: helper.NESTING_TRACK_ID_ATTRIBUTE_NAME,
230
+ getPosterDataURI: helper.getPosterDataURI
229
231
  };
230
232
 
231
233
  async function getContent(resourceURL, options) {
@@ -488,24 +490,6 @@ function guessMIMEType(expectedType, buffer) {
488
490
  }
489
491
  }
490
492
 
491
- function getDoctypeString(doc) {
492
- const docType = doc.doctype;
493
- let docTypeString = "";
494
- if (docType) {
495
- docTypeString = "<!DOCTYPE " + docType.nodeName;
496
- if (docType.publicId) {
497
- docTypeString += " PUBLIC \"" + docType.publicId + "\"";
498
- if (docType.systemId)
499
- docTypeString += " \"" + docType.systemId + "\"";
500
- } else if (docType.systemId)
501
- docTypeString += " SYSTEM \"" + docType.systemId + "\"";
502
- if (docType.internalSubset)
503
- docTypeString += " [" + docType.internalSubset + "]";
504
- docTypeString += "> ";
505
- }
506
- return docTypeString;
507
- }
508
-
509
493
  function log(...args) {
510
494
  console.log("S-File <browser>", ...args); // eslint-disable-line no-console
511
495
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "single-file-core",
3
- "version": "1.5.111",
3
+ "version": "1.5.113",
4
4
  "description": "SingleFile Core",
5
5
  "author": "Gildas Lormeau",
6
6
  "license": "AGPL-3.0-or-later",
@@ -23,6 +23,8 @@
23
23
 
24
24
  /* global DOMParser, setTimeout */
25
25
 
26
+ import { getDoctypeString } from "./../../core/lib/doctype.js";
27
+
26
28
  export {
27
29
  display
28
30
  };
@@ -89,21 +91,4 @@ async function display(document, docContent, { disableFramePointerEvents, inPlac
89
91
  });
90
92
  document.documentElement.setAttribute("data-sfz", "");
91
93
  document.querySelectorAll("link[rel*=icon]").forEach(element => element.replaceWith(element.cloneNode(true)));
92
- function getDoctypeString(doc) {
93
- const docType = doc.doctype;
94
- let docTypeString = "";
95
- if (docType) {
96
- docTypeString = "<!DOCTYPE " + docType.nodeName;
97
- if (docType.publicId) {
98
- docTypeString += " PUBLIC \"" + docType.publicId + "\"";
99
- if (docType.systemId)
100
- docTypeString += " \"" + docType.systemId + "\"";
101
- } else if (docType.systemId)
102
- docTypeString += " SYSTEM \"" + docType.systemId + "\"";
103
- if (docType.internalSubset)
104
- docTypeString += " [" + docType.internalSubset + "]";
105
- docTypeString += "> ";
106
- }
107
- return docTypeString;
108
- }
109
94
  }