|
|
@@ -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", "");
|
|
|
}
|
|
|
});
|