|
@@ -887,6 +887,11 @@ class Processor {
|
|
|
resolveHrefs() {
|
|
resolveHrefs() {
|
|
|
this.doc.querySelectorAll("a[href], area[href], link[href]").forEach(element => {
|
|
this.doc.querySelectorAll("a[href], area[href], link[href]").forEach(element => {
|
|
|
const href = element.getAttribute("href").trim();
|
|
const href = element.getAttribute("href").trim();
|
|
|
|
|
+ if (element.tagName == "LINK" && element.rel.includes("stylesheet")) {
|
|
|
|
|
+ if (this.options.saveOriginalURLs && !isDataURL(href)) {
|
|
|
|
|
+ element.setAttribute("data-sf-original-href", href);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
if (!testIgnoredPath(href)) {
|
|
if (!testIgnoredPath(href)) {
|
|
|
let resolvedURL;
|
|
let resolvedURL;
|
|
|
try {
|
|
try {
|
|
@@ -915,7 +920,7 @@ class Processor {
|
|
|
if (this.options.compressCSS) {
|
|
if (this.options.compressCSS) {
|
|
|
styleContent = util.compressCSS(styleContent);
|
|
styleContent = util.compressCSS(styleContent);
|
|
|
}
|
|
}
|
|
|
- styleContent = ProcessorHelper.resolveStylesheetURLs(styleContent, this.baseURI, this.workStyleElement);
|
|
|
|
|
|
|
+ styleContent = ProcessorHelper.resolveStylesheetURLs(styleContent, this.baseURI, this.workStyleElement, this.options.saveOriginalURLs);
|
|
|
const declarationList = cssTree.parse(styleContent, { context: "declarationList" });
|
|
const declarationList = cssTree.parse(styleContent, { context: "declarationList" });
|
|
|
this.styles.set(element, declarationList);
|
|
this.styles.set(element, declarationList);
|
|
|
});
|
|
});
|
|
@@ -933,7 +938,8 @@ class Processor {
|
|
|
rootDocument: this.options.rootDocument,
|
|
rootDocument: this.options.rootDocument,
|
|
|
frameId: this.options.windowId,
|
|
frameId: this.options.windowId,
|
|
|
resourceReferrer: this.options.resourceReferrer,
|
|
resourceReferrer: this.options.resourceReferrer,
|
|
|
- blockMixedContent: this.options.blockMixedContent
|
|
|
|
|
|
|
+ blockMixedContent: this.options.blockMixedContent,
|
|
|
|
|
+ saveOriginalURLs: this.options.saveOriginalURLs
|
|
|
};
|
|
};
|
|
|
let mediaText;
|
|
let mediaText;
|
|
|
if (element.media) {
|
|
if (element.media) {
|
|
@@ -1001,6 +1007,10 @@ class Processor {
|
|
|
if (frameElement.tagName == "OBJECT") {
|
|
if (frameElement.tagName == "OBJECT") {
|
|
|
frameElement.setAttribute("data", "data:text/html,");
|
|
frameElement.setAttribute("data", "data:text/html,");
|
|
|
} else {
|
|
} else {
|
|
|
|
|
+ const src = frameElement.getAttribute("src");
|
|
|
|
|
+ if (this.options.saveOriginalURLs && !isDataURL(src)) {
|
|
|
|
|
+ frameElement.setAttribute("data-sf-original-src", src);
|
|
|
|
|
+ }
|
|
|
frameElement.removeAttribute("src");
|
|
frameElement.removeAttribute("src");
|
|
|
frameElement.removeAttribute("srcdoc");
|
|
frameElement.removeAttribute("srcdoc");
|
|
|
}
|
|
}
|
|
@@ -1101,6 +1111,9 @@ class Processor {
|
|
|
const linkElements = Array.from(this.doc.querySelectorAll("link[rel=import][href]"));
|
|
const linkElements = Array.from(this.doc.querySelectorAll("link[rel=import][href]"));
|
|
|
await Promise.all(linkElements.map(async linkElement => {
|
|
await Promise.all(linkElements.map(async linkElement => {
|
|
|
const resourceURL = linkElement.href;
|
|
const resourceURL = linkElement.href;
|
|
|
|
|
+ if (this.options.saveOriginalURLs && !isDataURL(resourceURL)) {
|
|
|
|
|
+ linkElement.setAttribute("data-sf-original-href", resourceURL);
|
|
|
|
|
+ }
|
|
|
linkElement.removeAttribute("href");
|
|
linkElement.removeAttribute("href");
|
|
|
const options = Object.create(this.options);
|
|
const options = Object.create(this.options);
|
|
|
options.insertSingleFileComment = false;
|
|
options.insertSingleFileComment = false;
|
|
@@ -1205,8 +1218,14 @@ class Processor {
|
|
|
let scriptSrc;
|
|
let scriptSrc;
|
|
|
if (element.tagName == "SCRIPT") {
|
|
if (element.tagName == "SCRIPT") {
|
|
|
scriptSrc = element.getAttribute("src");
|
|
scriptSrc = element.getAttribute("src");
|
|
|
|
|
+ if (this.options.saveOriginalURLs && !isDataURL(scriptSrc)) {
|
|
|
|
|
+ element.setAttribute("data-sf-original-src", scriptSrc);
|
|
|
|
|
+ }
|
|
|
} else {
|
|
} else {
|
|
|
scriptSrc = element.getAttribute("href");
|
|
scriptSrc = element.getAttribute("href");
|
|
|
|
|
+ if (this.options.saveOriginalURLs && !isDataURL(scriptSrc)) {
|
|
|
|
|
+ element.setAttribute("data-sf-original-href", scriptSrc);
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
element.removeAttribute("integrity");
|
|
element.removeAttribute("integrity");
|
|
|
element.textContent = "";
|
|
element.textContent = "";
|
|
@@ -1322,6 +1341,9 @@ class Processor {
|
|
|
if (stylesheetInfo) {
|
|
if (stylesheetInfo) {
|
|
|
this.stylesheets.delete(styleElement);
|
|
this.stylesheets.delete(styleElement);
|
|
|
let stylesheetContent = cssTree.generate(stylesheetInfo.stylesheet);
|
|
let stylesheetContent = cssTree.generate(stylesheetInfo.stylesheet);
|
|
|
|
|
+ if (this.options.saveOriginalURLs) {
|
|
|
|
|
+ stylesheetContent = replaceOriginalURLs(stylesheetContent);
|
|
|
|
|
+ }
|
|
|
styleElement.textContent = stylesheetContent;
|
|
styleElement.textContent = stylesheetContent;
|
|
|
if (stylesheetInfo.mediaText) {
|
|
if (stylesheetInfo.mediaText) {
|
|
|
styleElement.media = stylesheetInfo.mediaText;
|
|
styleElement.media = stylesheetInfo.mediaText;
|
|
@@ -1339,6 +1361,10 @@ class Processor {
|
|
|
styleElement.media = stylesheetInfo.mediaText;
|
|
styleElement.media = stylesheetInfo.mediaText;
|
|
|
}
|
|
}
|
|
|
let stylesheetContent = cssTree.generate(stylesheetInfo.stylesheet);
|
|
let stylesheetContent = cssTree.generate(stylesheetInfo.stylesheet);
|
|
|
|
|
+ if (this.options.saveOriginalURLs) {
|
|
|
|
|
+ stylesheetContent = replaceOriginalURLs(stylesheetContent);
|
|
|
|
|
+ styleElement.setAttribute("data-sf-original-href", linkElement.getAttribute("data-sf-original-href"));
|
|
|
|
|
+ }
|
|
|
styleElement.textContent = stylesheetContent;
|
|
styleElement.textContent = stylesheetContent;
|
|
|
linkElement.parentElement.replaceChild(styleElement, linkElement);
|
|
linkElement.parentElement.replaceChild(styleElement, linkElement);
|
|
|
} else {
|
|
} else {
|
|
@@ -1353,6 +1379,9 @@ class Processor {
|
|
|
if (declarations) {
|
|
if (declarations) {
|
|
|
this.styles.delete(element);
|
|
this.styles.delete(element);
|
|
|
let styleContent = cssTree.generate(declarations);
|
|
let styleContent = cssTree.generate(declarations);
|
|
|
|
|
+ if (this.options.saveOriginalURLs) {
|
|
|
|
|
+ styleContent = replaceOriginalURLs(styleContent);
|
|
|
|
|
+ }
|
|
|
element.setAttribute("style", styleContent);
|
|
element.setAttribute("style", styleContent);
|
|
|
} else {
|
|
} else {
|
|
|
element.setAttribute("style", "");
|
|
element.setAttribute("style", "");
|
|
@@ -1370,12 +1399,16 @@ class Processor {
|
|
|
this.doc.head.appendChild(styleElement);
|
|
this.doc.head.appendChild(styleElement);
|
|
|
}
|
|
}
|
|
|
let stylesheetContent = "";
|
|
let stylesheetContent = "";
|
|
|
- this.cssVariables.forEach((content, indexResource) => {
|
|
|
|
|
|
|
+ this.cssVariables.forEach(({ content, url }, indexResource) => {
|
|
|
this.cssVariables.delete(indexResource);
|
|
this.cssVariables.delete(indexResource);
|
|
|
if (stylesheetContent) {
|
|
if (stylesheetContent) {
|
|
|
stylesheetContent += ";";
|
|
stylesheetContent += ";";
|
|
|
}
|
|
}
|
|
|
- stylesheetContent += `${SINGLE_FILE_VARIABLE_NAME_PREFIX + indexResource}:url("${content}")`;
|
|
|
|
|
|
|
+ stylesheetContent += `${SINGLE_FILE_VARIABLE_NAME_PREFIX + indexResource}: `;
|
|
|
|
|
+ if (this.options.saveOriginalURLs) {
|
|
|
|
|
+ stylesheetContent += `/* original URL: ${url} */ `;
|
|
|
|
|
+ }
|
|
|
|
|
+ stylesheetContent += `url("${content}")`;
|
|
|
});
|
|
});
|
|
|
styleElement.textContent = ":root{" + stylesheetContent + "}";
|
|
styleElement.textContent = ":root{" + stylesheetContent + "}";
|
|
|
}
|
|
}
|
|
@@ -1591,7 +1624,7 @@ class ProcessorHelper {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
static async resolveImportURLs(stylesheetContent, baseURI, options, workStylesheet, importedStyleSheets = new Set()) {
|
|
static async resolveImportURLs(stylesheetContent, baseURI, options, workStylesheet, importedStyleSheets = new Set()) {
|
|
|
- stylesheetContent = ProcessorHelper.resolveStylesheetURLs(stylesheetContent, baseURI, workStylesheet);
|
|
|
|
|
|
|
+ stylesheetContent = ProcessorHelper.resolveStylesheetURLs(stylesheetContent, baseURI, workStylesheet, options.saveOriginalURLs);
|
|
|
const imports = getImportFunctions(stylesheetContent);
|
|
const imports = getImportFunctions(stylesheetContent);
|
|
|
await Promise.all(imports.map(async cssImport => {
|
|
await Promise.all(imports.map(async cssImport => {
|
|
|
const match = matchImport(cssImport);
|
|
const match = matchImport(cssImport);
|
|
@@ -1666,8 +1699,11 @@ class ProcessorHelper {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- static resolveStylesheetURLs(stylesheetContent, baseURI, workStylesheet) {
|
|
|
|
|
|
|
+ static resolveStylesheetURLs(stylesheetContent, baseURI, workStylesheet, saveOriginalURLs) {
|
|
|
const urlFunctions = getUrlFunctions(stylesheetContent, true);
|
|
const urlFunctions = getUrlFunctions(stylesheetContent, true);
|
|
|
|
|
+ if (saveOriginalURLs) {
|
|
|
|
|
+ stylesheetContent = addOriginalURLs(stylesheetContent);
|
|
|
|
|
+ }
|
|
|
urlFunctions.map(urlFunction => {
|
|
urlFunctions.map(urlFunction => {
|
|
|
const originalResourceURL = matchURL(urlFunction);
|
|
const originalResourceURL = matchURL(urlFunction);
|
|
|
let resourceURL = normalizeURL(originalResourceURL);
|
|
let resourceURL = normalizeURL(originalResourceURL);
|
|
@@ -1823,7 +1859,7 @@ class ProcessorHelper {
|
|
|
}
|
|
}
|
|
|
});
|
|
});
|
|
|
if (variableDefined) {
|
|
if (variableDefined) {
|
|
|
- cssVariables.set(indexResource, content);
|
|
|
|
|
|
|
+ cssVariables.set(indexResource, { content, url: originalResourceURL });
|
|
|
tokens.forEach(({ parent, token, value }) => parent.replace(token, value));
|
|
tokens.forEach(({ parent, token, value }) => parent.replace(token, value));
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
@@ -1850,6 +1886,9 @@ class ProcessorHelper {
|
|
|
if (resourceURL != null) {
|
|
if (resourceURL != null) {
|
|
|
resourceURL = normalizeURL(resourceURL);
|
|
resourceURL = normalizeURL(resourceURL);
|
|
|
let originURL = resourceElement.dataset.singleFileOriginURL;
|
|
let originURL = resourceElement.dataset.singleFileOriginURL;
|
|
|
|
|
+ if (options.saveOriginalURLs && !isDataURL(resourceURL)) {
|
|
|
|
|
+ resourceElement.setAttribute("data-sf-original-" + attributeName, resourceURL);
|
|
|
|
|
+ }
|
|
|
delete resourceElement.dataset.singleFileOriginURL;
|
|
delete resourceElement.dataset.singleFileOriginURL;
|
|
|
if (!testIgnoredPath(resourceURL)) {
|
|
if (!testIgnoredPath(resourceURL)) {
|
|
|
resourceElement.setAttribute(attributeName, EMPTY_IMAGE);
|
|
resourceElement.setAttribute(attributeName, EMPTY_IMAGE);
|
|
@@ -1892,7 +1931,7 @@ class ProcessorHelper {
|
|
|
const isSVG = content.startsWith(PREFIX_DATA_URI_IMAGE_SVG);
|
|
const isSVG = content.startsWith(PREFIX_DATA_URI_IMAGE_SVG);
|
|
|
if (processDuplicates && duplicate && options.groupDuplicateImages && !isSVG && util.getContentSize(content) < SINGLE_FILE_VARIABLE_MAX_SIZE) {
|
|
if (processDuplicates && duplicate && options.groupDuplicateImages && !isSVG && util.getContentSize(content) < SINGLE_FILE_VARIABLE_MAX_SIZE) {
|
|
|
if (ProcessorHelper.replaceImageSource(resourceElement, SINGLE_FILE_VARIABLE_NAME_PREFIX + indexResource, options)) {
|
|
if (ProcessorHelper.replaceImageSource(resourceElement, SINGLE_FILE_VARIABLE_NAME_PREFIX + indexResource, options)) {
|
|
|
- cssVariables.set(indexResource, content);
|
|
|
|
|
|
|
+ cssVariables.set(indexResource, { content, url: originURL });
|
|
|
const declarationList = cssTree.parse(resourceElement.getAttribute("style"), { context: "declarationList" });
|
|
const declarationList = cssTree.parse(resourceElement.getAttribute("style"), { context: "declarationList" });
|
|
|
styles.set(resourceElement, declarationList);
|
|
styles.set(resourceElement, declarationList);
|
|
|
} else {
|
|
} else {
|
|
@@ -1918,6 +1957,9 @@ class ProcessorHelper {
|
|
|
attributeName = "href";
|
|
attributeName = "href";
|
|
|
originalResourceURL = resourceElement.getAttribute(attributeName);
|
|
originalResourceURL = resourceElement.getAttribute(attributeName);
|
|
|
}
|
|
}
|
|
|
|
|
+ if (options.saveOriginalURLs && !isDataURL(originalResourceURL)) {
|
|
|
|
|
+ resourceElement.setAttribute("data-sf-original-href", originalResourceURL);
|
|
|
|
|
+ }
|
|
|
let resourceURL = normalizeURL(originalResourceURL);
|
|
let resourceURL = normalizeURL(originalResourceURL);
|
|
|
if (testValidPath(resourceURL) && !testIgnoredPath(resourceURL)) {
|
|
if (testValidPath(resourceURL) && !testIgnoredPath(resourceURL)) {
|
|
|
resourceElement.setAttribute(attributeName, EMPTY_IMAGE);
|
|
resourceElement.setAttribute(attributeName, EMPTY_IMAGE);
|
|
@@ -1958,7 +2000,9 @@ class ProcessorHelper {
|
|
|
|
|
|
|
|
static async processSrcset(resourceElements, attributeName, baseURI, batchRequest) {
|
|
static async processSrcset(resourceElements, attributeName, baseURI, batchRequest) {
|
|
|
await Promise.all(Array.from(resourceElements).map(async resourceElement => {
|
|
await Promise.all(Array.from(resourceElements).map(async resourceElement => {
|
|
|
- const srcset = util.parseSrcset(resourceElement.getAttribute(attributeName));
|
|
|
|
|
|
|
+ const originSrcset = resourceElement.getAttribute(attributeName);
|
|
|
|
|
+ const srcset = util.parseSrcset(originSrcset);
|
|
|
|
|
+ resourceElement.setAttribute("data-sf-original-srcset", originSrcset);
|
|
|
const srcsetValues = await Promise.all(srcset.map(async srcsetValue => {
|
|
const srcsetValues = await Promise.all(srcset.map(async srcsetValue => {
|
|
|
let resourceURL = normalizeURL(srcsetValue.url);
|
|
let resourceURL = normalizeURL(srcsetValue.url);
|
|
|
if (!testIgnoredPath(resourceURL)) {
|
|
if (!testIgnoredPath(resourceURL)) {
|
|
@@ -2169,6 +2213,25 @@ function matchURL(stylesheetContent) {
|
|
|
return match && match[1];
|
|
return match && match[1];
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+function addOriginalURLs(stylesheetContent) {
|
|
|
|
|
+ return stylesheetContent.replace(REGEXP_URL_FN, function (match, _0, url, _1, url2, _2, url3) {
|
|
|
|
|
+ url = url || url2 || url3;
|
|
|
|
|
+ if (isDataURL(url)) {
|
|
|
|
|
+ return match;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ return "-sf-url-original(" + JSON.stringify(url) + ") " + match;
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function isDataURL(url) {
|
|
|
|
|
+ return url && (url.startsWith(DATA_URI_PREFIX) || url.startsWith(BLOB_URI_PREFIX));
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function replaceOriginalURLs(stylesheetContent) {
|
|
|
|
|
+ return stylesheetContent.replace(/-sf-url-original\("(.*?)"\)/g, "/* original URL: $1 */");
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
function testIgnoredPath(resourceURL) {
|
|
function testIgnoredPath(resourceURL) {
|
|
|
return resourceURL && (resourceURL.startsWith(DATA_URI_PREFIX) || resourceURL == ABOUT_BLANK_URI);
|
|
return resourceURL && (resourceURL.startsWith(DATA_URI_PREFIX) || resourceURL == ABOUT_BLANK_URI);
|
|
|
}
|
|
}
|