|
|
@@ -25,11 +25,20 @@ this.imagesMinifier = this.imagesMinifier || (() => {
|
|
|
const DEBUG = false;
|
|
|
const SVG_NS = "http://www.w3.org/2000/svg";
|
|
|
const PREFIX_DATA_URI_IMAGE_SVG = "data:image/svg+xml";
|
|
|
- const TRANSFORMED_IMAGE_ATTRIBUTE = "data-single-file-image-transform";
|
|
|
- const IGNORED_ATTRIBUTES = ["src", "viewBox", "preserveAspectRatio", "xlink:href"];
|
|
|
+ const SINGLE_FILE_IMAGE_ATTRIBUTE = "single-file-ref";
|
|
|
+ const IGNORED_ATTRIBUTES = ["src", "viewBox", "preserveAspectRatio", "xlink:href", "title", "class"];
|
|
|
+ const SINGLE_FILE_SELECTOR = {
|
|
|
+ type: "attribute",
|
|
|
+ action: "exists",
|
|
|
+ ignoreCase: false,
|
|
|
+ name: SINGLE_FILE_IMAGE_ATTRIBUTE,
|
|
|
+ value: ""
|
|
|
+ };
|
|
|
|
|
|
return {
|
|
|
process: (doc, mediaAllInfo, options) => {
|
|
|
+ const matchedImageSelectors = new Map();
|
|
|
+ getImageRuleInfos(mediaAllInfo, matchedImageSelectors);
|
|
|
const imageGroups = getImageGroups(doc);
|
|
|
let duplicates = new Set();
|
|
|
const duplicateURLs = [];
|
|
|
@@ -40,12 +49,12 @@ this.imagesMinifier = this.imagesMinifier || (() => {
|
|
|
}
|
|
|
});
|
|
|
if (duplicateURLs.length) {
|
|
|
- processStyleSheets(doc, duplicates, mediaAllInfo);
|
|
|
+ processStyleSheets(doc, duplicates, mediaAllInfo, matchedImageSelectors);
|
|
|
processImages(doc, duplicates, duplicateURLs, options);
|
|
|
}
|
|
|
},
|
|
|
postProcess(doc) {
|
|
|
- doc.querySelectorAll("svg[" + TRANSFORMED_IMAGE_ATTRIBUTE + "]").forEach(svgElement => {
|
|
|
+ doc.querySelectorAll("svg[" + SINGLE_FILE_IMAGE_ATTRIBUTE + "]").forEach(svgElement => {
|
|
|
const useElement = svgElement.childNodes[0];
|
|
|
if (useElement) {
|
|
|
const refImageId = useElement.getAttribute("xlink:href").substring(1);
|
|
|
@@ -54,13 +63,37 @@ this.imagesMinifier = this.imagesMinifier || (() => {
|
|
|
if (refImageElement && refImageElement.getAttribute("xlink:href").startsWith(PREFIX_DATA_URI_IMAGE_SVG)) {
|
|
|
svgElement.removeAttributeNS(SVG_NS, "preserveAspectRatio");
|
|
|
}
|
|
|
- svgElement.removeAttribute(TRANSFORMED_IMAGE_ATTRIBUTE);
|
|
|
}
|
|
|
}
|
|
|
});
|
|
|
}
|
|
|
};
|
|
|
|
|
|
+ function getImageRuleInfos(parentMediaInfo, matchedImageSelectors) {
|
|
|
+ parentMediaInfo.elements.forEach((elementInfo, element) => {
|
|
|
+ const tagName = element.tagName.toLowerCase();
|
|
|
+ if (tagName == "img" || tagName == "svg") {
|
|
|
+ elementInfo.forEach(selectorInfo => {
|
|
|
+ if (selectorInfo.selector) {
|
|
|
+ let selector = JSON.parse(JSON.stringify(selectorInfo.selector));
|
|
|
+ selector.push({
|
|
|
+ type: "pseudo",
|
|
|
+ name: "not",
|
|
|
+ data: [[SINGLE_FILE_SELECTOR]]
|
|
|
+ });
|
|
|
+ const selectors = matchedImageSelectors.get(selectorInfo.ruleInfo.cssRule.selectorText) || JSON.parse(JSON.stringify(selectorInfo.ruleInfo.selectors));
|
|
|
+ selectors[selectorInfo.selectorIndex] = selector;
|
|
|
+ selector = JSON.parse(JSON.stringify(selectorInfo.selector));
|
|
|
+ selector.push(SINGLE_FILE_SELECTOR);
|
|
|
+ selectors.push(selector);
|
|
|
+ matchedImageSelectors.set(selectorInfo.ruleInfo.cssRule.selectorText, selectors);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+ });
|
|
|
+ parentMediaInfo.medias.forEach(mediaInfo => getImageRuleInfos(mediaInfo, matchedImageSelectors));
|
|
|
+ }
|
|
|
+
|
|
|
function getImageGroups(doc) {
|
|
|
const imageGroups = new Map();
|
|
|
doc.querySelectorAll("img[src]:not([srcset])").forEach(imageElement => {
|
|
|
@@ -76,7 +109,7 @@ this.imagesMinifier = this.imagesMinifier || (() => {
|
|
|
return imageGroups;
|
|
|
}
|
|
|
|
|
|
- function processStyleSheets(doc, duplicates, mediaAllInfo) {
|
|
|
+ function processStyleSheets(doc, duplicates, mediaAllInfo, matchedImageSelectors) {
|
|
|
const matchedSelectors = getMatchedSelectors(duplicates, mediaAllInfo);
|
|
|
doc.querySelectorAll("style").forEach((styleElement, sheetIndex) => {
|
|
|
if (styleElement.sheet) {
|
|
|
@@ -87,7 +120,7 @@ this.imagesMinifier = this.imagesMinifier || (() => {
|
|
|
} else {
|
|
|
mediaInfo = mediaAllInfo;
|
|
|
}
|
|
|
- styleElement.textContent = processRules(doc, cssRules, sheetIndex, mediaInfo, matchedSelectors);
|
|
|
+ styleElement.textContent = processRules(doc, cssRules, sheetIndex, mediaInfo, matchedSelectors, matchedImageSelectors);
|
|
|
}
|
|
|
});
|
|
|
}
|
|
|
@@ -123,21 +156,22 @@ this.imagesMinifier = this.imagesMinifier || (() => {
|
|
|
const useElement = doc.createElementNS(SVG_NS, "use");
|
|
|
svgElement.appendChild(useElement);
|
|
|
imgElement.getAttributeNames().forEach(attributeName => {
|
|
|
- try {
|
|
|
- if (!IGNORED_ATTRIBUTES.includes(attributeName)) {
|
|
|
- svgElement.setAttribute(attributeName, imgElement.getAttribute(attributeName));
|
|
|
- }
|
|
|
- } catch (error) {
|
|
|
- if (!IGNORED_ATTRIBUTES.includes(attributeName)) {
|
|
|
- try {
|
|
|
- svgElement.setAttributeNS(SVG_NS, attributeName, imgElement.getAttribute(attributeName));
|
|
|
- } catch (error) {
|
|
|
- /* ignored */
|
|
|
- }
|
|
|
+ if (!IGNORED_ATTRIBUTES.concat([docHelper.imagesAttributeName(options.sessionId)]).includes(attributeName)) {
|
|
|
+ try {
|
|
|
+ svgElement.setAttributeNS(SVG_NS, attributeName, imgElement.getAttribute(attributeName));
|
|
|
+ } catch (error) {
|
|
|
+ /* ignored */
|
|
|
}
|
|
|
}
|
|
|
});
|
|
|
- svgElement.setAttribute(TRANSFORMED_IMAGE_ATTRIBUTE, "");
|
|
|
+ svgElement.setAttribute("class", imgElement.getAttribute("class"));
|
|
|
+ const title = imgElement.getAttribute("title");
|
|
|
+ if (title) {
|
|
|
+ const titleElement = doc.createElementNS(SVG_NS, "title");
|
|
|
+ titleElement.textContent = title;
|
|
|
+ svgElement.appendChild(titleElement);
|
|
|
+ }
|
|
|
+ svgElement.setAttribute(SINGLE_FILE_IMAGE_ATTRIBUTE, "");
|
|
|
svgElement.setAttributeNS(SVG_NS, "viewBox", "0 0 " + width + " " + height);
|
|
|
svgElement.setAttributeNS(SVG_NS, "width", imageData.clientWidth);
|
|
|
svgElement.setAttributeNS(SVG_NS, "height", imageData.clientHeight);
|
|
|
@@ -165,16 +199,16 @@ this.imagesMinifier = this.imagesMinifier || (() => {
|
|
|
|
|
|
function getMatchedSelectors(duplicates, parentMediaInfo, matchedRules = new Map()) {
|
|
|
duplicates.forEach(imageElement => {
|
|
|
- let elementInfos = parentMediaInfo.elements.get(imageElement);
|
|
|
- if (!elementInfos) {
|
|
|
- elementInfos = parentMediaInfo.pseudos.get(imageElement);
|
|
|
+ let elementInfo = parentMediaInfo.elements.get(imageElement);
|
|
|
+ if (!elementInfo) {
|
|
|
+ elementInfo = parentMediaInfo.pseudos.get(imageElement);
|
|
|
}
|
|
|
- if (elementInfos) {
|
|
|
- elementInfos.forEach(elementInfo => {
|
|
|
- if (elementInfo.cssRule) {
|
|
|
- let selectorInfo = matchedRules.get(elementInfo.cssRule.selectorText);
|
|
|
- if (!selectorInfo) {
|
|
|
- matchedRules.set(elementInfo.cssRule.selectorText, elementInfo.selectors);
|
|
|
+ if (elementInfo) {
|
|
|
+ elementInfo.forEach(elementInfo => {
|
|
|
+ if (elementInfo.ruleInfo) {
|
|
|
+ let selectors = matchedRules.get(elementInfo.selectorText);
|
|
|
+ if (!selectors) {
|
|
|
+ matchedRules.set(elementInfo.ruleInfo.cssRule.selectorText, elementInfo.ruleInfo.selectors);
|
|
|
}
|
|
|
}
|
|
|
});
|
|
|
@@ -184,7 +218,7 @@ this.imagesMinifier = this.imagesMinifier || (() => {
|
|
|
return matchedRules;
|
|
|
}
|
|
|
|
|
|
- function processRules(doc, cssRules, sheetIndex, mediaInfo, matchedSelectors) {
|
|
|
+ function processRules(doc, cssRules, sheetIndex, mediaInfo, matchedSelectors, matchedImageSelectors) {
|
|
|
let sheetContent = "", mediaRuleIndex = 0;
|
|
|
let startTime;
|
|
|
if (DEBUG && cssRules.length > 1) {
|
|
|
@@ -194,14 +228,16 @@ this.imagesMinifier = this.imagesMinifier || (() => {
|
|
|
Array.from(cssRules).forEach(cssRule => {
|
|
|
if (cssRule.type == CSSRule.MEDIA_RULE) {
|
|
|
sheetContent += "@media " + Array.from(cssRule.media).join(",") + "{";
|
|
|
- sheetContent += processRules(doc, cssRule.cssRules, sheetIndex, mediaInfo.medias.get("rule-" + sheetIndex + "-" + mediaRuleIndex + "-" + cssRule.media.mediaText), matchedSelectors);
|
|
|
+ sheetContent += processRules(doc, cssRule.cssRules, sheetIndex, mediaInfo.medias.get("rule-" + sheetIndex + "-" + mediaRuleIndex + "-" + cssRule.media.mediaText), matchedSelectors, matchedImageSelectors);
|
|
|
mediaRuleIndex++;
|
|
|
sheetContent += "}";
|
|
|
} else if (cssRule.type == CSSRule.STYLE_RULE) {
|
|
|
- const selectors = matchedSelectors.get(cssRule.selectorText);
|
|
|
- if (selectors) {
|
|
|
+ const imageSelectors = matchedImageSelectors.get(cssRule.selectorText);
|
|
|
+ let selectors = matchedSelectors.get(cssRule.selectorText);
|
|
|
+ if (imageSelectors || selectors) {
|
|
|
+ selectors = imageSelectors || selectors;
|
|
|
selectors.forEach(selector => {
|
|
|
- const newSelector = transformSelector(selector);
|
|
|
+ const newSelector = getSVGSelector(selector);
|
|
|
if (newSelector) {
|
|
|
selectors.push(newSelector);
|
|
|
}
|
|
|
@@ -220,18 +256,27 @@ this.imagesMinifier = this.imagesMinifier || (() => {
|
|
|
return sheetContent;
|
|
|
}
|
|
|
|
|
|
- function transformSelector(selector) {
|
|
|
+ function getSVGSelector(selector) {
|
|
|
selector = JSON.parse(JSON.stringify(selector));
|
|
|
let simpleSelector, selectorIndex = selector.length - 1, imageTagFound;
|
|
|
while (selectorIndex >= 0 && !imageTagFound) {
|
|
|
simpleSelector = selector[selectorIndex];
|
|
|
- imageTagFound = simpleSelector.type == "tag" && simpleSelector.name == "img";
|
|
|
- if (!imageTagFound) {
|
|
|
+ if (simpleSelector.type == "pseudo" && simpleSelector.name == "not") {
|
|
|
+ const negatedSelector = simpleSelector.data[0][0];
|
|
|
+ if (negatedSelector.type == SINGLE_FILE_SELECTOR.type && negatedSelector.action == SINGLE_FILE_SELECTOR.action && negatedSelector.name == SINGLE_FILE_SELECTOR.name) {
|
|
|
+ selector.splice(selectorIndex, 1);
|
|
|
+ }
|
|
|
selectorIndex--;
|
|
|
+ } else {
|
|
|
+ imageTagFound = simpleSelector.type == "tag" && simpleSelector.name == "img";
|
|
|
+ if (!imageTagFound) {
|
|
|
+ selectorIndex--;
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
if (imageTagFound) {
|
|
|
simpleSelector.name = "svg";
|
|
|
+ selector.splice(selectorIndex + 1, 0, SINGLE_FILE_SELECTOR);
|
|
|
return selector;
|
|
|
}
|
|
|
}
|