|
|
@@ -317,7 +317,10 @@ this.SingleFileCore = this.SingleFileCore || (() => {
|
|
|
// ---------
|
|
|
// Processor
|
|
|
// ---------
|
|
|
- const EMPTY_DATA_URI = "data:base64,";
|
|
|
+ const PREFIX_DATA_URI_IMAGE = "data:image/";
|
|
|
+ const PREFIX_DATA_URI_AUDIO = "data:audio/";
|
|
|
+ const PREFIX_DATA_URI_VIDEO = "data:video/";
|
|
|
+ const PREFIX_DATA_URI_IMAGE_SVG = "data:image/svg+xml";
|
|
|
const EMPTY_IMAGE = "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==";
|
|
|
const SCRIPT_TAG_FOUND = /<script/gi;
|
|
|
const NOSCRIPT_TAG_FOUND = /<noscript/gi;
|
|
|
@@ -522,7 +525,34 @@ this.SingleFileCore = this.SingleFileCore || (() => {
|
|
|
}
|
|
|
|
|
|
removeUnselectedElements() {
|
|
|
- ProcessorHelper.removeUnselectedElements(this.doc);
|
|
|
+ removeUnmarkedElements(this.doc.body);
|
|
|
+ this.doc.body.removeAttribute(SELECTED_CONTENT_ATTRIBUTE_NAME);
|
|
|
+
|
|
|
+ function removeUnmarkedElements(element) {
|
|
|
+ let selectedElementFound = false;
|
|
|
+ Array.from(element.childNodes).forEach(node => {
|
|
|
+ if (node.nodeType == 1) {
|
|
|
+ const isSelectedElement = node.getAttribute(SELECTED_CONTENT_ATTRIBUTE_NAME) == "";
|
|
|
+ selectedElementFound = selectedElementFound || isSelectedElement;
|
|
|
+ if (isSelectedElement) {
|
|
|
+ node.removeAttribute(SELECTED_CONTENT_ATTRIBUTE_NAME);
|
|
|
+ removeUnmarkedElements(node);
|
|
|
+ } else if (selectedElementFound) {
|
|
|
+ removeNode(node);
|
|
|
+ } else {
|
|
|
+ node.style.setProperty("display", "none", "important");
|
|
|
+ Array.from(node.childNodes).forEach(removeNode);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ function removeNode(node) {
|
|
|
+ const tagName = node.tagName && node.tagName.toLowerCase();
|
|
|
+ if (tagName != "svg" && tagName != "style" && tagName != "link") {
|
|
|
+ node.remove();
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
insertVideoPosters() {
|
|
|
@@ -794,7 +824,12 @@ this.SingleFileCore = this.SingleFileCore || (() => {
|
|
|
if (!this.options.saveRawPage && this.options.framesData) {
|
|
|
const frameElements = Array.from(this.doc.querySelectorAll("iframe, frame, object[type=\"text/html\"][data]"));
|
|
|
await Promise.all(frameElements.map(async frameElement => {
|
|
|
- ProcessorHelper.setFrameEmptySrc(frameElement);
|
|
|
+ if (frameElement.tagName == "OBJECT") {
|
|
|
+ frameElement.setAttribute("data", "data:text/html,");
|
|
|
+ } else {
|
|
|
+ frameElement.removeAttribute("src");
|
|
|
+ frameElement.removeAttribute("srcdoc");
|
|
|
+ }
|
|
|
const frameWindowId = frameElement.getAttribute(docUtil.WIN_ID_ATTRIBUTE_NAME);
|
|
|
if (frameWindowId) {
|
|
|
const frameData = this.options.framesData.find(frame => frame.windowId == frameWindowId);
|
|
|
@@ -954,7 +989,16 @@ this.SingleFileCore = this.SingleFileCore || (() => {
|
|
|
} else {
|
|
|
frameElement.setAttribute("sandbox", "");
|
|
|
}
|
|
|
- ProcessorHelper.setFrameContent(frameElement, pageData.content);
|
|
|
+ if (frameElement.tagName == "OBJECT") {
|
|
|
+ frameElement.setAttribute("data", "data:text/html," + pageData.content);
|
|
|
+ } else {
|
|
|
+ if (frameElement.tagName == "FRAME") {
|
|
|
+ frameElement.setAttribute("src", "data:text/html," + pageData.content.replace(/#/g, "%23"));
|
|
|
+ } else {
|
|
|
+ frameElement.setAttribute("srcdoc", pageData.content);
|
|
|
+ frameElement.removeAttribute("src");
|
|
|
+ }
|
|
|
+ }
|
|
|
this.stats.addAll(pageData);
|
|
|
} else {
|
|
|
this.stats.add("discarded", "frames", 1);
|
|
|
@@ -1057,15 +1101,14 @@ this.SingleFileCore = this.SingleFileCore || (() => {
|
|
|
// ---------------
|
|
|
// ProcessorHelper
|
|
|
// ---------------
|
|
|
+ const DATA_URI_PREFIX = "data:";
|
|
|
+ const ABOUT_BLANK_URI = "about:blank";
|
|
|
+ const EMPTY_DATA_URI = "data:base64,";
|
|
|
const REGEXP_AMP = /&/g;
|
|
|
const REGEXP_NBSP = /\u00a0/g;
|
|
|
const REGEXP_START_TAG = /</g;
|
|
|
const REGEXP_END_TAG = />/g;
|
|
|
const REGEXP_URL_HASH = /(#.+?)$/;
|
|
|
- const PREFIX_DATA_URI_IMAGE = "data:image/";
|
|
|
- const PREFIX_DATA_URI_AUDIO = "data:audio/";
|
|
|
- const PREFIX_DATA_URI_VIDEO = "data:video/";
|
|
|
- const PREFIX_DATA_URI_IMAGE_SVG = "data:image/svg+xml";
|
|
|
const PREFIX_DATA_URI_NO_MIMETYPE = "data:;";
|
|
|
const PREFIX_DATA_URI_OCTET_STREAM = /^data:(application|binary)\/octet-stream/;
|
|
|
const PREFIX_DATA_URI_NULL_STREAM = /^data:null;/;
|
|
|
@@ -1155,59 +1198,6 @@ this.SingleFileCore = this.SingleFileCore || (() => {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- static setFrameEmptySrc(frameElement) {
|
|
|
- if (frameElement.tagName == "OBJECT") {
|
|
|
- frameElement.setAttribute("data", "data:text/html,");
|
|
|
- } else {
|
|
|
- frameElement.removeAttribute("src");
|
|
|
- frameElement.removeAttribute("srcdoc");
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- static setFrameContent(frameElement, content) {
|
|
|
- if (frameElement.tagName == "OBJECT") {
|
|
|
- frameElement.setAttribute("data", "data:text/html," + content);
|
|
|
- } else {
|
|
|
- if (frameElement.tagName == "FRAME") {
|
|
|
- frameElement.setAttribute("src", "data:text/html," + content.replace(/#/g, "%23"));
|
|
|
- } else {
|
|
|
- frameElement.setAttribute("srcdoc", content);
|
|
|
- frameElement.removeAttribute("src");
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- static removeUnselectedElements(doc) {
|
|
|
- removeUnmarkedElements(doc.body);
|
|
|
- doc.body.removeAttribute(SELECTED_CONTENT_ATTRIBUTE_NAME);
|
|
|
-
|
|
|
- function removeUnmarkedElements(element) {
|
|
|
- let selectedElementFound = false;
|
|
|
- Array.from(element.childNodes).forEach(node => {
|
|
|
- if (node.nodeType == 1) {
|
|
|
- const isSelectedElement = node.getAttribute(SELECTED_CONTENT_ATTRIBUTE_NAME) == "";
|
|
|
- selectedElementFound = selectedElementFound || isSelectedElement;
|
|
|
- if (isSelectedElement) {
|
|
|
- node.removeAttribute(SELECTED_CONTENT_ATTRIBUTE_NAME);
|
|
|
- removeUnmarkedElements(node);
|
|
|
- } else if (selectedElementFound) {
|
|
|
- removeNode(node);
|
|
|
- } else {
|
|
|
- node.style.setProperty("display", "none", "important");
|
|
|
- Array.from(node.childNodes).forEach(removeNode);
|
|
|
- }
|
|
|
- }
|
|
|
- });
|
|
|
- }
|
|
|
-
|
|
|
- function removeNode(node) {
|
|
|
- const tagName = node.tagName && node.tagName.toLowerCase();
|
|
|
- if (tagName != "svg" && tagName != "style" && tagName != "link") {
|
|
|
- node.remove();
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
static removeSingleLineCssComments(stylesheet) {
|
|
|
const removedRules = [];
|
|
|
for (let cssRule = stylesheet.children.head; cssRule; cssRule = cssRule.next) {
|
|
|
@@ -1430,7 +1420,7 @@ this.SingleFileCore = this.SingleFileCore || (() => {
|
|
|
if (content.startsWith(prefixDataURI) || content.startsWith(PREFIX_DATA_URI_NO_MIMETYPE) || content.match(PREFIX_DATA_URI_OCTET_STREAM) || content.match(PREFIX_DATA_URI_NULL_STREAM)) {
|
|
|
const isSVG = content.startsWith(PREFIX_DATA_URI_IMAGE_SVG);
|
|
|
if (processDuplicates && duplicate && options.groupDuplicateImages && !isSVG) {
|
|
|
- if (Util.replaceImageSource(resourceElement, SINGLE_FILE_VARIABLE_NAME_PREFIX + indexResource, options)) {
|
|
|
+ if (ProcessorHelper.replaceImageSource(resourceElement, SINGLE_FILE_VARIABLE_NAME_PREFIX + indexResource, options)) {
|
|
|
cssVariables.set(indexResource, content);
|
|
|
const declarationList = cssTree.parse(resourceElement.getAttribute("style"), { context: "declarationList" });
|
|
|
styles.set(resourceElement, declarationList);
|
|
|
@@ -1516,17 +1506,38 @@ this.SingleFileCore = this.SingleFileCore || (() => {
|
|
|
}));
|
|
|
}
|
|
|
|
|
|
+ static replaceImageSource(imgElement, variableName, options) {
|
|
|
+ const dataAttributeName = docUtil.IMAGE_ATTRIBUTE_NAME;
|
|
|
+ if (imgElement.getAttribute(dataAttributeName) != null) {
|
|
|
+ const imgData = options.imageData[Number(imgElement.getAttribute(dataAttributeName))];
|
|
|
+ if (imgData.replaceable) {
|
|
|
+ imgElement.setAttribute("src", `${PREFIX_DATA_URI_IMAGE_SVG},<svg xmlns="http://www.w3.org/2000/svg" width="${imgData.size.pxWidth}" height="${imgData.size.pxHeight}"><rect fill-opacity="0"/></svg>`);
|
|
|
+ const backgroundStyle = {};
|
|
|
+ const backgroundSize = (imgData.objectFit == "content" || imgData.objectFit == "cover") && imgData.objectFit;
|
|
|
+ if (backgroundSize) {
|
|
|
+ backgroundStyle["background-size"] = imgData.objectFit;
|
|
|
+ }
|
|
|
+ if (imgData.objectPosition) {
|
|
|
+ backgroundStyle["background-position"] = imgData.objectPosition;
|
|
|
+ }
|
|
|
+ if (imgData.backgroundColor) {
|
|
|
+ backgroundStyle["background-color"] = imgData.backgroundColor;
|
|
|
+ }
|
|
|
+ ProcessorHelper.setBackgroundImage(imgElement, "var(" + variableName + ")", backgroundStyle);
|
|
|
+ imgElement.removeAttribute(dataAttributeName);
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
// ----
|
|
|
// Util
|
|
|
// ----
|
|
|
- const DATA_URI_PREFIX = "data:";
|
|
|
const BLOB_URI_PREFIX = "blob:";
|
|
|
const HTTP_URI_PREFIX = /^https?:\/\//;
|
|
|
const FILE_URI_PREFIX = /^file:\/\//;
|
|
|
const EMPTY_URL = /^https?:\/\/+\s*$/;
|
|
|
- const ABOUT_BLANK_URI = "about:blank";
|
|
|
const NOT_EMPTY_URL = /^(https?:\/\/|file:\/\/|blob:).+/;
|
|
|
const REGEXP_URL_FN = /(url\s*\(\s*'(.*?)'\s*\))|(url\s*\(\s*"(.*?)"\s*\))|(url\s*\(\s*(.*?)\s*\))/gi;
|
|
|
const REGEXP_URL_SIMPLE_QUOTES_FN = /^url\s*\(\s*'(.*?)'\s*\)$/i;
|
|
|
@@ -1673,30 +1684,6 @@ this.SingleFileCore = this.SingleFileCore || (() => {
|
|
|
return stylesheetContent;
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
- static replaceImageSource(imgElement, variableName, options) {
|
|
|
- const dataAttributeName = docUtil.IMAGE_ATTRIBUTE_NAME;
|
|
|
- if (imgElement.getAttribute(dataAttributeName) != null) {
|
|
|
- const imgData = options.imageData[Number(imgElement.getAttribute(dataAttributeName))];
|
|
|
- if (imgData.replaceable) {
|
|
|
- imgElement.setAttribute("src", `${PREFIX_DATA_URI_IMAGE_SVG},<svg xmlns="http://www.w3.org/2000/svg" width="${imgData.size.pxWidth}" height="${imgData.size.pxHeight}"><rect fill-opacity="0"/></svg>`);
|
|
|
- const backgroundStyle = {};
|
|
|
- const backgroundSize = (imgData.objectFit == "content" || imgData.objectFit == "cover") && imgData.objectFit;
|
|
|
- if (backgroundSize) {
|
|
|
- backgroundStyle["background-size"] = imgData.objectFit;
|
|
|
- }
|
|
|
- if (imgData.objectPosition) {
|
|
|
- backgroundStyle["background-position"] = imgData.objectPosition;
|
|
|
- }
|
|
|
- if (imgData.backgroundColor) {
|
|
|
- backgroundStyle["background-color"] = imgData.backgroundColor;
|
|
|
- }
|
|
|
- ProcessorHelper.setBackgroundImage(imgElement, "var(" + variableName + ")", backgroundStyle);
|
|
|
- imgElement.removeAttribute(dataAttributeName);
|
|
|
- return true;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
}
|
|
|
|
|
|
function log(...args) {
|