Преглед изворни кода

use template tags to store the content of shadow elements

Gildas пре 6 година
родитељ
комит
7c84c0a66c
1 измењених фајлова са 95 додато и 27 уклоњено
  1. 95 27
      lib/single-file/single-file-core.js

+ 95 - 27
lib/single-file/single-file-core.js

@@ -85,7 +85,6 @@ this.SingleFileCore = this.SingleFileCore || (() => {
 	const STAGES = [{
 		sequential: [
 			{ action: "preProcessPage" },
-			{ action: "insertShadowRootContents" },
 			{ action: "replaceStyleContents" },
 			{ option: "selected", action: "removeUnselectedElements" },
 			{ option: "removeVideoSrc", action: "insertVideoPosters" },
@@ -106,6 +105,7 @@ this.SingleFileCore = this.SingleFileCore || (() => {
 		],
 		parallel: [
 			{ action: "resolveStylesheetURLs" },
+			{ action: "resolveShadowRootURLs" },
 			{ option: "!removeFrames", action: "resolveFrameURLs" },
 			{ option: "!removeImports", action: "resolveHtmlImportURLs" }
 		]
@@ -128,6 +128,7 @@ this.SingleFileCore = this.SingleFileCore || (() => {
 		],
 		parallel: [
 			{ option: "!removeFrames", action: "processFrames" },
+			{ action: "processShadowRoots" },
 			{ option: "!removeImports", action: "processHtmlImports" },
 		]
 	}, {
@@ -326,6 +327,7 @@ this.SingleFileCore = this.SingleFileCore || (() => {
 	const EMPTY_IMAGE = "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==";
 	const SCRIPT_TAG_FOUND = /<script/gi;
 	const NOSCRIPT_TAG_FOUND = /<noscript/gi;
+	const WC_ATTRIBUTE_NAME = "data-single-file-web-component";
 
 	class Processor {
 		constructor(options, batchRequest) {
@@ -348,6 +350,9 @@ this.SingleFileCore = this.SingleFileCore || (() => {
 			if (!this.options.saveRawPage && !this.options.removeFrames && this.options.framesData) {
 				this.options.framesData.forEach(frameData => this.maxResources += frameData.maxResources || 0);
 			}
+			if (this.options.shadowRootContents) {
+				this.options.shadowRootContents.forEach(shadowRootData => this.maxResources += shadowRootData.maxResources || 0);
+			}
 			this.stats.set("processed", "resources", this.maxResources);
 		}
 
@@ -502,22 +507,6 @@ this.SingleFileCore = this.SingleFileCore || (() => {
 			}
 		}
 
-		insertShadowRootContents() {
-			if (this.options.shadowRootContents) {
-				this.doc.querySelectorAll("[" + docUtil.SHADOW_ROOT_ATTRIBUTE_NAME + "]").forEach((element, elementIndex) => {
-					const elementInfo = this.options.shadowRootContents[elementIndex];
-					if (elementInfo) {
-						const frameElement = this.doc.createElement("iframe");
-						frameElement.setAttribute("style", "all:initial!important;border:0!important;width:100%!important;height:" + elementInfo.height + "px!important");
-						const windowId = "shadow-" + this.options.framesData.length;
-						frameElement.setAttribute(docUtil.WIN_ID_ATTRIBUTE_NAME, windowId);
-						this.options.framesData.push({ windowId, content: elementInfo.content, baseURI: this.baseURI, shadowRootContent: true });
-						element.appendChild(frameElement);
-					}
-				});
-			}
-		}
-
 		replaceStyleContents() {
 			if (this.options.stylesheetContents) {
 				this.doc.querySelectorAll("style").forEach((styleElement, styleIndex) => {
@@ -888,6 +877,47 @@ this.SingleFileCore = this.SingleFileCore || (() => {
 			}
 		}
 
+		async resolveShadowRootURLs() {
+			const rootOptions = this.options;
+			const batchRequest = this.batchRequest;
+			if (this.options.shadowRootContents) {
+				await resolveURLs(this.doc);
+			}
+
+			async function resolveURLs(rootElement) {
+				const shadowRootElements = Array.from((rootElement.querySelectorAll("[" + docUtil.SHADOW_ROOT_ATTRIBUTE_NAME + "]")));
+				await Promise.all(shadowRootElements.map(async element => {
+					const id = element.getAttribute(docUtil.SHADOW_ROOT_ATTRIBUTE_NAME);
+					const shadowRootData = rootOptions.shadowRootContents[Number(id)];
+					const options = Object.create(rootOptions);
+					options.insertSingleFileComment = false;
+					options.insertFaviconLink = false;
+					options.doc = null;
+					options.win = null;
+					options.url = options.baseURI = rootOptions.url;
+					if (shadowRootData.content) {
+						const doc = docUtil.parseDocContent(shadowRootData.content);
+						const docData = docUtil.preProcessDoc(doc, options.win, options);
+						options.content = docUtil.serialize(doc);
+						docUtil.postProcessDoc(doc, options);
+						options.postersData = docData.postersData;
+						options.canvasData = docData.canvasData;
+						options.stylesheetContents = docData.stylesheetContents;
+						options.fontsData = docData.fontsData;
+						options.imageData = docData.imageData;
+						options.usedFonts = docData.usedFonts;
+						options.shadowRootContents = docData.shadowRootContents;
+						shadowRootData.processor = new Runner(options);
+						shadowRootData.element = element;
+						await shadowRootData.processor.loadPage();
+						await shadowRootData.processor.initialize();
+						shadowRootData.maxResources = batchRequest.getMaxResources();
+						await resolveURLs(shadowRootData.processor.getDocument());
+					}
+				}));
+			}
+		}
+
 		async resolveHtmlImportURLs() {
 			const linkElements = Array.from(this.doc.querySelectorAll("link[rel=import][href]"));
 			if (!this.relImportProcessors) {
@@ -1008,14 +1038,6 @@ this.SingleFileCore = this.SingleFileCore || (() => {
 							if (frameData.processor) {
 								this.stats.add("processed", "frames", 1);
 								await frameData.processor.run();
-								if (frameData.shadowRootContent) {
-									const frameDoc = frameData.processor.getDocument();
-									frameDoc.documentElement.setAttribute("style", "overflow:hidden");
-									frameDoc.querySelectorAll("a[href]").forEach(element => {
-										element.target = "_blank";
-										element.rel = "noopener noreferrer";
-									});
-								}
 								const pageData = await frameData.processor.getPageData();
 								frameElement.removeAttribute(docUtil.WIN_ID_ATTRIBUTE_NAME);
 								let sandbox = "allow-popups allow-top-navigation allow-top-navigation-by-user-activation";
@@ -1043,6 +1065,52 @@ this.SingleFileCore = this.SingleFileCore || (() => {
 			}
 		}
 
+		async processShadowRoots() {
+			const rootDoc = this.doc;
+			const options = this.options;
+			const stats = this.stats;
+			if (this.options.shadowRootContents) {
+				await processRootElement(this.doc);
+				const scriptElement = rootDoc.createElement("script");
+				scriptElement.textContent = `processNode(document);function processNode(node){node.querySelectorAll("[${WC_ATTRIBUTE_NAME}]").forEach(element=>{const shadowRoot=element.previousElementSibling.attachShadow({mode:"open"});shadowRoot.innerHTML=element.innerHTML;element.remove();processNode(shadowRoot)})}`;
+				this.doc.body.appendChild(scriptElement);
+			}
+
+			async function processRootElement(rootElement) {
+				const shadowRootElements = Array.from(rootElement.querySelectorAll("[" + docUtil.SHADOW_ROOT_ATTRIBUTE_NAME + "]"));
+				await Promise.all(shadowRootElements.map(async element => {
+					const id = element.getAttribute(docUtil.SHADOW_ROOT_ATTRIBUTE_NAME);
+					if (id) {
+						const shadowRootData = options.shadowRootContents[Number(id)];
+						if (shadowRootData) {
+							options.shadowRootContents = options.shadowRootContents.filter(shadowRootInfo => shadowRootInfo.id != id);
+							if (shadowRootData.processor) {
+								stats.add("processed", "shadow root elements", 1);
+								await shadowRootData.processor.run();
+								const doc = shadowRootData.processor.getDocument();
+								const templateElement = rootDoc.createElement("template");
+								templateElement.setAttribute(WC_ATTRIBUTE_NAME, "");
+								if (doc.head) {
+									const metaCharset = doc.head.querySelector("meta[charset]");
+									if (metaCharset) {
+										metaCharset.remove();
+									}
+									doc.head.childNodes.forEach(node => templateElement.appendChild(doc.importNode(node, true)));
+								}
+								if (doc.body) {
+									doc.body.childNodes.forEach(node => templateElement.appendChild(doc.importNode(node, true)));
+								}
+								await processRootElement(templateElement);
+								element.insertAdjacentElement("afterend", templateElement);
+							} else {
+								stats.add("discarded", "shadow root elements", 1);
+							}
+						}
+					}
+				}));
+			}
+		}
+
 		async processHtmlImports() {
 			const linkElements = Array.from(this.doc.querySelectorAll("link[rel=import][href]"));
 			await Promise.all(linkElements.map(async linkElement => {
@@ -1069,7 +1137,7 @@ this.SingleFileCore = this.SingleFileCore || (() => {
 					if (stylesheetInfo.mediaText) {
 						styleElement.media = stylesheetInfo.mediaText;
 					}
-				} else {
+				} else if (!styleElement.closest("[" + WC_ATTRIBUTE_NAME + "]")) {
 					styleElement.remove();
 				}
 			});
@@ -1097,7 +1165,7 @@ this.SingleFileCore = this.SingleFileCore || (() => {
 					this.styles.delete(element);
 					let styleContent = cssTree.generate(declarations);
 					element.setAttribute("style", styleContent);
-				} else {
+				} else if (!element.closest("[" + WC_ATTRIBUTE_NAME + "]")) {
 					element.setAttribute("style", "");
 				}
 			});