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 +22 -3
- package/core/index.js +21 -1
- package/core/lib/doctype.js +50 -0
- package/core/util.js +3 -19
- package/package.json +1 -1
- package/processors/compression/compression-display.js +2 -17
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
|
-
|
|
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
|
|
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 =
|
|
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
|
@@ -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
|
}
|