浏览代码

added support of shadow dom elements

Gildas 7 年之前
父节点
当前提交
8c5faf123b

+ 1 - 0
extension/core/bg/processor.js

@@ -40,6 +40,7 @@ singlefile.processor = (() => {
 		options.imageData = message.imageData;
 		options.imageData = message.imageData;
 		options.postersData = message.postersData;
 		options.postersData = message.postersData;
 		options.usedFonts = message.usedFonts;
 		options.usedFonts = message.usedFonts;
+		options.shadowRootContents = message.shadowRootContents
 		options.insertSingleFileComment = true;
 		options.insertSingleFileComment = true;
 		options.insertFaviconLink = true;
 		options.insertFaviconLink = true;
 		options.backgroundTab = true;
 		options.backgroundTab = true;

+ 4 - 2
extension/core/content/content-autosave.js

@@ -18,7 +18,7 @@
  *   along with SingleFile.  If not, see <http://www.gnu.org/licenses/>.
  *   along with SingleFile.  If not, see <http://www.gnu.org/licenses/>.
  */
  */
 
 
-/* global singlefile, frameTree, browser, window, addEventListener, removeEventListener, document, location, docHelper */
+/* global singlefile, frameTree, browser, window, addEventListener, removeEventListener, document, location, docHelper, setTimeout */
 
 
 this.singlefile.autosave = this.singlefile.autosave || (async () => {
 this.singlefile.autosave = this.singlefile.autosave || (async () => {
 
 
@@ -57,6 +57,7 @@ this.singlefile.autosave = this.singlefile.autosave || (async () => {
 					imageData: docData.imageData,
 					imageData: docData.imageData,
 					postersData: docData.postersData,
 					postersData: docData.postersData,
 					usedFonts: docData.usedFonts,
 					usedFonts: docData.usedFonts,
+					shadowRootContents: docData.shadowRootContents,
 					framesData,
 					framesData,
 					url: location.href
 					url: location.href
 				});
 				});
@@ -95,8 +96,9 @@ this.singlefile.autosave = this.singlefile.autosave || (async () => {
 				fontsData: docData.fontsData,
 				fontsData: docData.fontsData,
 				stylesheetContents: docData.stylesheetContents,
 				stylesheetContents: docData.stylesheetContents,
 				imageData: docData.imageData,
 				imageData: docData.imageData,
-				postersData: docData.postersData,
+				postersData: docData.postersData,				
 				usedFonts: docData.usedFonts,
 				usedFonts: docData.usedFonts,
+				shadowRootContents: docData.shadowRootContents,
 				framesData,
 				framesData,
 				url: location.href
 				url: location.href
 			});
 			});

+ 35 - 14
lib/single-file/doc-helper.js

@@ -24,6 +24,7 @@ this.docHelper = this.docHelper || (() => {
 
 
 	const REMOVED_CONTENT_ATTRIBUTE_NAME = "data-single-file-removed-content";
 	const REMOVED_CONTENT_ATTRIBUTE_NAME = "data-single-file-removed-content";
 	const PRESERVED_SPACE_ELEMENT_ATTRIBUTE_NAME = "data-single-file-preserved-space-element";
 	const PRESERVED_SPACE_ELEMENT_ATTRIBUTE_NAME = "data-single-file-preserved-space-element";
+	const SHADOW_ROOT_ATTRIBUTE_NAME = "data-single-file-shadow-root-element";
 	const WIN_ID_ATTRIBUTE_NAME = "data-frame-tree-win-id";
 	const WIN_ID_ATTRIBUTE_NAME = "data-frame-tree-win-id";
 	const IMAGE_ATTRIBUTE_NAME = "data-single-file-image";
 	const IMAGE_ATTRIBUTE_NAME = "data-single-file-image";
 	const INPUT_VALUE_ATTRIBUTE_NAME = "data-single-file-value";
 	const INPUT_VALUE_ATTRIBUTE_NAME = "data-single-file-value";
@@ -44,6 +45,7 @@ this.docHelper = this.docHelper || (() => {
 		removedContentAttributeName,
 		removedContentAttributeName,
 		imagesAttributeName,
 		imagesAttributeName,
 		inputValueAttributeName,
 		inputValueAttributeName,
+		shadowRootAttributeName,
 		removeQuotes
 		removeQuotes
 	};
 	};
 
 
@@ -56,19 +58,19 @@ this.docHelper = this.docHelper || (() => {
 			element.parentElement.replaceChild(disabledNoscriptElement, element);
 			element.parentElement.replaceChild(disabledNoscriptElement, element);
 		});
 		});
 		doc.head.querySelectorAll("*:not(base):not(link):not(meta):not(noscript):not(script):not(style):not(template):not(title)").forEach(element => element.hidden = true);
 		doc.head.querySelectorAll("*:not(base):not(link):not(meta):not(noscript):not(script):not(style):not(template):not(title)").forEach(element => element.hidden = true);
-		let canvasData, imageData, usedFonts;
+		let canvasData, imageData, usedFonts, shadowRootContents;
 		if (win) {
 		if (win) {
 			canvasData = getCanvasData(doc, win);
 			canvasData = getCanvasData(doc, win);
 			imageData = getImageData(doc, win, options);
 			imageData = getImageData(doc, win, options);
 			if (options.removeHiddenElements || options.removeUnusedStyles || options.compressHTML) {
 			if (options.removeHiddenElements || options.removeUnusedStyles || options.compressHTML) {
-				let styles = getStyles(win, doc.body);
+				let elementsInfo = getElementsInfo(win, doc.body);
 				if (options.removeHiddenElements) {
 				if (options.removeHiddenElements) {
 					const markerRemovedContent = removedContentAttributeName(options.sessionId);
 					const markerRemovedContent = removedContentAttributeName(options.sessionId);
 					let ignoredTags = JSON.parse(JSON.stringify(IGNORED_REMOVED_TAG_NAMES));
 					let ignoredTags = JSON.parse(JSON.stringify(IGNORED_REMOVED_TAG_NAMES));
 					if (!options.removeScripts) {
 					if (!options.removeScripts) {
 						ignoredTags = ignoredTags.concat("SCRIPT");
 						ignoredTags = ignoredTags.concat("SCRIPT");
 					}
 					}
-					markHiddenCandidates(win, doc.body, styles, false, markerRemovedContent, new Set(), ignoredTags);
+					markHiddenCandidates(win, doc.body, elementsInfo, false, markerRemovedContent, new Set(), ignoredTags);
 					markHiddenElements(win, doc.body, markerRemovedContent);
 					markHiddenElements(win, doc.body, markerRemovedContent);
 					doc.querySelectorAll("iframe").forEach(element => {
 					doc.querySelectorAll("iframe").forEach(element => {
 						const boundingRect = element.getBoundingClientRect();
 						const boundingRect = element.getBoundingClientRect();
@@ -76,18 +78,30 @@ this.docHelper = this.docHelper || (() => {
 							element.setAttribute(markerRemovedContent, "");
 							element.setAttribute(markerRemovedContent, "");
 						}
 						}
 					});
 					});
-					styles = new Map(Array.from(styles).filter(([element]) => element.getAttribute(markerRemovedContent) != ""));
+					elementsInfo = new Map(Array.from(elementsInfo).filter(([element]) => element.getAttribute(markerRemovedContent) != ""));
 				}
 				}
 				if (options.removeUnusedStyles) {
 				if (options.removeUnusedStyles) {
-					usedFonts = getUsedFonts(styles);
+					usedFonts = getUsedFonts(elementsInfo);
 				}
 				}
 				if (options.compressHTML) {
 				if (options.compressHTML) {
-					styles.forEach((style, element) => {
-						if (style.whiteSpace.startsWith("pre")) {
+					elementsInfo.forEach((elementInfo, element) => {
+						if (elementInfo.whiteSpace.startsWith("pre")) {
 							element.setAttribute(preservedSpaceAttributeName(options.sessionId), "");
 							element.setAttribute(preservedSpaceAttributeName(options.sessionId), "");
 						}
 						}
 					});
 					});
 				}
 				}
+				elementsInfo.forEach((elementInfo, element) => {
+					let elementIndex = 0;
+					if (elementInfo.shadowRoot) {
+						const attributeName = shadowRootAttributeName(options.sessionId);
+						element.setAttribute(attributeName, elementIndex);
+						elementIndex++;
+						if (!shadowRootContents) {
+							shadowRootContents = [];
+						}
+						shadowRootContents.push({ content: element.shadowRoot.innerHTML, height: element.clientHeight });
+					}
+				});
 			}
 			}
 		}
 		}
 		retrieveInputValues(doc, options);
 		retrieveInputValues(doc, options);
@@ -97,7 +111,8 @@ this.docHelper = this.docHelper || (() => {
 			stylesheetContents: getStylesheetContents(doc),
 			stylesheetContents: getStylesheetContents(doc),
 			imageData,
 			imageData,
 			postersData: getPostersData(doc),
 			postersData: getPostersData(doc),
-			usedFonts
+			usedFonts,
+			shadowRootContents
 		};
 		};
 	}
 	}
 
 
@@ -116,12 +131,12 @@ this.docHelper = this.docHelper || (() => {
 		});
 		});
 	}
 	}
 
 
-	function getStyles(win, element, styles = new Map()) {
-		const elements = Array.from(element.childNodes).filter(node => node instanceof win.HTMLElement);
+	function getElementsInfo(win, element, elementsInfo = new Map()) {
+		const elements = Array.from(element.childNodes).filter(node => !win || node instanceof win.HTMLElement);
 		elements.forEach(element => {
 		elements.forEach(element => {
-			getStyles(win, element, styles);
+			getElementsInfo(win, element, elementsInfo);
 			const computedStyle = win.getComputedStyle(element);
 			const computedStyle = win.getComputedStyle(element);
-			styles.set(element, {
+			elementsInfo.set(element, {
 				display: computedStyle.getPropertyValue("display"),
 				display: computedStyle.getPropertyValue("display"),
 				opacity: computedStyle.getPropertyValue("opacity"),
 				opacity: computedStyle.getPropertyValue("opacity"),
 				visibility: computedStyle.getPropertyValue("visibility"),
 				visibility: computedStyle.getPropertyValue("visibility"),
@@ -129,10 +144,11 @@ this.docHelper = this.docHelper || (() => {
 				fontWeight: getFontWeight(computedStyle.getPropertyValue("font-weight")),
 				fontWeight: getFontWeight(computedStyle.getPropertyValue("font-weight")),
 				fontStyle: computedStyle.getPropertyValue("font-style") || "normal",
 				fontStyle: computedStyle.getPropertyValue("font-style") || "normal",
 				fontVariant: computedStyle.getPropertyValue("font-variant") || "normal",
 				fontVariant: computedStyle.getPropertyValue("font-variant") || "normal",
-				whiteSpace: computedStyle.getPropertyValue("white-space")
+				whiteSpace: computedStyle.getPropertyValue("white-space"),
+				shadowRoot: element.shadowRoot
 			});
 			});
 		});
 		});
-		return styles;
+		return elementsInfo;
 	}
 	}
 
 
 	function markHiddenCandidates(win, element, styles, elementHidden, markerRemovedContent, removedCandidates, ignoredTags) {
 	function markHiddenCandidates(win, element, styles, elementHidden, markerRemovedContent, removedCandidates, ignoredTags) {
@@ -186,6 +202,7 @@ this.docHelper = this.docHelper || (() => {
 		}
 		}
 		doc.querySelectorAll("[" + imagesAttributeName(options.sessionId) + "]").forEach(element => element.removeAttribute(imagesAttributeName(options.sessionId)));
 		doc.querySelectorAll("[" + imagesAttributeName(options.sessionId) + "]").forEach(element => element.removeAttribute(imagesAttributeName(options.sessionId)));
 		doc.querySelectorAll("[" + inputValueAttributeName(options.sessionId) + "]").forEach(element => element.removeAttribute(inputValueAttributeName(options.sessionId)));
 		doc.querySelectorAll("[" + inputValueAttributeName(options.sessionId) + "]").forEach(element => element.removeAttribute(inputValueAttributeName(options.sessionId)));
+		doc.querySelectorAll("[" + shadowRootAttributeName(options.sessionId) + "]").forEach(element => element.removeAttribute(shadowRootAttributeName(options.sessionId)));
 	}
 	}
 
 
 	function imagesAttributeName(sessionId) {
 	function imagesAttributeName(sessionId) {
@@ -196,6 +213,10 @@ this.docHelper = this.docHelper || (() => {
 		return PRESERVED_SPACE_ELEMENT_ATTRIBUTE_NAME + (sessionId || "");
 		return PRESERVED_SPACE_ELEMENT_ATTRIBUTE_NAME + (sessionId || "");
 	}
 	}
 
 
+	function shadowRootAttributeName(sessionId) {
+		return SHADOW_ROOT_ATTRIBUTE_NAME + (sessionId || "");
+	}
+
 	function removedContentAttributeName(sessionId) {
 	function removedContentAttributeName(sessionId) {
 		return REMOVED_CONTENT_ATTRIBUTE_NAME + (sessionId || "");
 		return REMOVED_CONTENT_ATTRIBUTE_NAME + (sessionId || "");
 	}
 	}

+ 2 - 0
lib/single-file/frame-tree.js

@@ -99,6 +99,7 @@ this.frameTree = this.frameTree || (() => {
 					frameData.canvasData = messageFrameData.canvasData;
 					frameData.canvasData = messageFrameData.canvasData;
 					frameData.fontsData = messageFrameData.fontsData;
 					frameData.fontsData = messageFrameData.fontsData;
 					frameData.usedFonts = messageFrameData.usedFonts;
 					frameData.usedFonts = messageFrameData.usedFonts;
+					frameData.shadowRootContents = messageFrameData.shadowRootContents;
 					frameData.processed = messageFrameData.processed;
 					frameData.processed = messageFrameData.processed;
 					frameData.timeout = messageFrameData.timeout;
 					frameData.timeout = messageFrameData.timeout;
 				}
 				}
@@ -260,6 +261,7 @@ this.frameTree = this.frameTree || (() => {
 			canvasData: docData.canvasData,
 			canvasData: docData.canvasData,
 			fontsData: docData.fontsData,
 			fontsData: docData.fontsData,
 			usedFonts: docData.usedFonts,
 			usedFonts: docData.usedFonts,
+			shadowRootContents: docData.shadowRootContents,
 			processed: true
 			processed: true
 		};
 		};
 	}
 	}

+ 4 - 0
lib/single-file/single-file-browser.js

@@ -283,6 +283,10 @@ this.SingleFile = this.SingleFile || (() => {
 		static inputValueAttributeName(sessionId) {
 		static inputValueAttributeName(sessionId) {
 			return docHelper.inputValueAttributeName(sessionId);
 			return docHelper.inputValueAttributeName(sessionId);
 		}
 		}
+
+		static shadowRootAttributeName(sessionId) {
+			return docHelper.shadowRootAttributeName(sessionId);
+		}
 	}
 	}
 
 
 	function log(...args) {
 	function log(...args) {

+ 17 - 1
lib/single-file/single-file-core.js

@@ -84,6 +84,7 @@ this.SingleFileCore = this.SingleFileCore || (() => {
 	const STAGES = [{
 	const STAGES = [{
 		sequential: [
 		sequential: [
 			{ action: "preProcessPage" },
 			{ action: "preProcessPage" },
+			{ action: "insertShadowRootContents" },
 			{ action: "replaceStyleContents" },
 			{ action: "replaceStyleContents" },
 			{ option: "selected", action: "removeUnselectedElements" },
 			{ option: "selected", action: "removeUnselectedElements" },
 			{ option: "removeVideoSrc", action: "insertVideoPosters" },
 			{ option: "removeVideoSrc", action: "insertVideoPosters" },
@@ -150,6 +151,7 @@ this.SingleFileCore = this.SingleFileCore || (() => {
 				this.options.imageData = docData.imageData;
 				this.options.imageData = docData.imageData;
 				this.options.postersData = docData.postersData;
 				this.options.postersData = docData.postersData;
 				this.options.usedFonts = docData.usedFonts;
 				this.options.usedFonts = docData.usedFonts;
+				this.options.shadowRootContents = docData.shadowRootContents;
 			}
 			}
 			this.options.content = this.options.content || (this.options.doc ? DOM.serialize(this.options.doc, false) : null);
 			this.options.content = this.options.content || (this.options.doc ? DOM.serialize(this.options.doc, false) : null);
 			this.onprogress = options.onprogress || (() => { });
 			this.onprogress = options.onprogress || (() => { });
@@ -654,6 +656,21 @@ this.SingleFileCore = this.SingleFileCore || (() => {
 			}
 			}
 		}
 		}
 
 
+		insertShadowRootContents() {
+			if (this.options.shadowRootContents) {
+				this.doc.querySelectorAll("[" + DOM.shadowRootAttributeName(this.options.sessionId) + "]").forEach((element, elementIndex) => {
+					const DOMParser = DOM.getParser();
+					const elementInfo = this.options.shadowRootContents[elementIndex];
+					if (DOMParser) {
+						const iframeElement = this.doc.createElement("iframe");
+						iframeElement.setAttribute("srcdoc", elementInfo.content);
+						iframeElement.setAttribute("style", "all:initial!important;border:0!important;width:100%!important;height:" + elementInfo.height + "px!important");
+						element.appendChild(iframeElement);
+					}
+				});
+			}
+		}
+
 		insertVideoPosters() {
 		insertVideoPosters() {
 			if (this.options.postersData) {
 			if (this.options.postersData) {
 				this.doc.querySelectorAll("video[src], video > source[src]").forEach((element, videoIndex) => {
 				this.doc.querySelectorAll("video[src], video > source[src]").forEach((element, videoIndex) => {
@@ -1031,7 +1048,6 @@ this.SingleFileCore = this.SingleFileCore || (() => {
 			if (frameElement.tagName == "OBJECT") {
 			if (frameElement.tagName == "OBJECT") {
 				frameElement.setAttribute("data", "data:text/html,");
 				frameElement.setAttribute("data", "data:text/html,");
 			} else {
 			} else {
-				frameElement.setAttribute("srcdoc", "");
 				frameElement.removeAttribute("src");
 				frameElement.removeAttribute("src");
 			}
 			}
 		}
 		}