single-file-core 1.5.71 → 1.5.72

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/core/index.js CHANGED
@@ -1223,17 +1223,20 @@ class Processor {
1223
1223
  }
1224
1224
 
1225
1225
  async resolveStylesheetsURLs() {
1226
- const scriptContents = [];
1226
+ const stylesContents = [];
1227
1227
  this.options.inlineStylesheets = new Map();
1228
1228
  this.options.inlineStylesheetsRefs = new Map();
1229
- this.doc.querySelectorAll("style").forEach(element => {
1230
- if (element.textContent) {
1231
- const indexContent = scriptContents.indexOf(element.textContent);
1229
+ this.doc.querySelectorAll("style").forEach(styleElement => {
1230
+ if (styleElement.textContent) {
1231
+ const indexContent = stylesContents.indexOf(styleElement.textContent);
1232
1232
  if (indexContent == -1) {
1233
- this.options.inlineStylesheets.set(scriptContents.length, element.textContent);
1234
- scriptContents.push(element.textContent);
1233
+ this.options.inlineStylesheets.set(stylesContents.length, {
1234
+ styleElement,
1235
+ content: styleElement.textContent
1236
+ });
1237
+ stylesContents.push(styleElement.textContent);
1235
1238
  } else {
1236
- this.options.inlineStylesheetsRefs.set(element, indexContent);
1239
+ this.options.inlineStylesheetsRefs.set(styleElement, indexContent);
1237
1240
  }
1238
1241
  }
1239
1242
  });
@@ -61,6 +61,8 @@ const FONT_STRETCHES = {
61
61
  };
62
62
  const Blob = globalThis.Blob;
63
63
  const FileReader = globalThis.FileReader;
64
+ const Image = globalThis.Image;
65
+ const OffscreenCanvas = globalThis.OffscreenCanvas;
64
66
 
65
67
  let util, cssTree;
66
68
 
@@ -77,6 +79,7 @@ export {
77
79
  testIgnoredPath,
78
80
  testValidPath,
79
81
  testValidURL,
82
+ resizeImage,
80
83
  toDataURI
81
84
  };
82
85
 
@@ -105,7 +108,8 @@ class ProcessorHelperCommon {
105
108
  this.processAttribute(doc.querySelectorAll(selector), attributeName, baseURI, options, "image", resources, removeElementIfMissing, batchRequest, styles, processDuplicates)
106
109
  );
107
110
  resourcePromises = resourcePromises.concat([
108
- this.processXLinks(doc.querySelectorAll("use"), doc, baseURI, options, batchRequest),
111
+ this.processXLinks(doc.querySelectorAll("use"), doc, baseURI, options, batchRequest, "xlink:href"),
112
+ this.processXLinks(doc.querySelectorAll("use"), doc, baseURI, options, batchRequest, "href"),
109
113
  this.processSrcset(doc.querySelectorAll("img[srcset], source[srcset]"), baseURI, options, resources, batchRequest)
110
114
  ]);
111
115
  resourcePromises.push(this.processAttribute(doc.querySelectorAll("object[data*=\".pdf\"]"), "data", baseURI, options, null, resources, false, batchRequest, styles));
@@ -120,8 +124,7 @@ class ProcessorHelperCommon {
120
124
  }
121
125
  }
122
126
 
123
- async processXLinks(resourceElements, doc, baseURI, options, batchRequest) {
124
- let attributeName = "xlink:href";
127
+ async processXLinks(resourceElements, doc, baseURI, options, batchRequest, attributeName) {
125
128
  await Promise.all(Array.from(resourceElements).map(async resourceElement => {
126
129
  let originalResourceURL = resourceElement.getAttribute(attributeName);
127
130
  if (originalResourceURL == null) {
@@ -330,7 +333,7 @@ class ProcessorHelperCommon {
330
333
 
331
334
  replacePseudoClassDefined(stylesheet) {
332
335
  cssTree.walk(stylesheet, {
333
- enter: function(node, item, list) {
336
+ enter: function (node, item, list) {
334
337
  if (node.type == "PseudoClassSelector" && node.name == "defined") {
335
338
  if (item.prev == null || item.prev.data.type == "Combinator" || item.prev.data.type == "WhiteSpace") {
336
339
  list.replace(item, cssTree.parse("*", { context: "selector" }).children.head);
@@ -659,11 +662,42 @@ function getFontStretch(stretch) {
659
662
  return FONT_STRETCHES[stretch] || stretch;
660
663
  }
661
664
 
665
+ async function resizeImage(dataURI, { imageReductionFactor }) {
666
+ if (dataURI) {
667
+ const contentType = dataURI.substring(5, dataURI.indexOf(";"));
668
+ if (contentType == "image/jpeg" ||
669
+ contentType == "image/png" ||
670
+ contentType == "image/webp") {
671
+ try {
672
+ const image = new Image();
673
+ image.src = dataURI;
674
+ await new Promise((resolve, reject) => {
675
+ image.onload = resolve;
676
+ image.onerror = reject;
677
+ });
678
+ const width = image.naturalWidth / imageReductionFactor;
679
+ const height = image.naturalHeight / imageReductionFactor;
680
+ const canvas = new OffscreenCanvas(width, height);
681
+ const context = canvas.getContext("2d");
682
+ context.drawImage(image, 0, 0, width, height);
683
+ const blob = await canvas.convertToBlob({ type: contentType });
684
+ if (blob.type == contentType) {
685
+ dataURI = await toDataURI(blob, contentType);
686
+ }
687
+ } catch {
688
+ // ignored
689
+ }
690
+ }
691
+ }
692
+ return dataURI;
693
+ }
694
+
662
695
  function toDataURI(content, contentType, charset) {
696
+ const blob = content instanceof Blob ? content : new Blob([content], { type: (contentType || "") + (charset ? ";charset=" + charset : "") });
663
697
  return new Promise((resolve, reject) => {
664
698
  const reader = new FileReader();
665
699
  reader.onload = () => resolve(reader.result);
666
700
  reader.onerror = () => reject(new Error(reader.error));
667
- reader.readAsDataURL(new Blob([content], { type: (contentType || "") + (charset ? ";charset=" + charset : "") }));
701
+ reader.readAsDataURL(blob);
668
702
  });
669
703
  }
@@ -59,6 +59,7 @@ import {
59
59
  testIgnoredPath,
60
60
  testValidPath,
61
61
  testValidURL,
62
+ resizeImage,
62
63
  toDataURI
63
64
  } from "./processor-helper-common.js";
64
65
 
@@ -99,9 +100,12 @@ function getProcessorHelperClass(utilInstance) {
99
100
  this.removeSingleLineCssComments(stylesheet);
100
101
  }
101
102
  this.replacePseudoClassDefined(stylesheet);
102
- options.inlineStylesheets.forEach((content, index) => {
103
+ options.inlineStylesheets.forEach(({ content, styleElement }, index) => {
103
104
  if (content === element.textContent) {
104
- options.inlineStylesheets.set(index, this.generateStylesheetContent(stylesheet, options));
105
+ options.inlineStylesheets.set(index, {
106
+ styleElement,
107
+ content: this.generateStylesheetContent(stylesheet, options)
108
+ });
105
109
  }
106
110
  });
107
111
  stylesheetInfo.stylesheet = stylesheet;
@@ -112,31 +116,39 @@ function getProcessorHelperClass(utilInstance) {
112
116
  }
113
117
 
114
118
  replaceStylesheets(doc, stylesheets, options) {
115
- doc.querySelectorAll("style").forEach(styleElement => {
116
- const stylesheetInfo = stylesheets.get(styleElement);
119
+ doc.querySelectorAll("style").forEach(element => {
120
+ const stylesheetInfo = stylesheets.get(element);
117
121
  if (stylesheetInfo) {
118
- stylesheets.delete(styleElement);
119
- const stylesheetRefIndex = options.inlineStylesheetsRefs.get(styleElement);
122
+ stylesheets.delete(element);
123
+ const stylesheetRefIndex = options.inlineStylesheetsRefs.get(element);
120
124
  if (stylesheetRefIndex === undefined) {
121
- styleElement.textContent = this.generateStylesheetContent(stylesheetInfo.stylesheet, options);
125
+ element.textContent = this.generateStylesheetContent(stylesheetInfo.stylesheet, options);
126
+ options.inlineStylesheets.forEach(({ styleElement }, index) => {
127
+ if (styleElement === element) {
128
+ options.inlineStylesheets.set(index, {
129
+ styleElement,
130
+ content: element.textContent
131
+ });
132
+ }
133
+ });
122
134
  } else if (options.groupDuplicateStylesheets) {
123
135
  if (!doc.querySelector("style[" + DUPLICATE_STYLESHEET_ATTRIBUTE_NAME + "=\"" + stylesheetRefIndex + "\"]")) {
124
136
  const styleElement = doc.createElement("style");
125
- styleElement.textContent = options.inlineStylesheets.get(stylesheetRefIndex);
137
+ styleElement.textContent = options.inlineStylesheets.get(stylesheetRefIndex).content;
126
138
  styleElement.setAttribute("media", "not all");
127
139
  styleElement.setAttribute(DUPLICATE_STYLESHEET_ATTRIBUTE_NAME, stylesheetRefIndex);
128
140
  doc.head.appendChild(styleElement);
129
141
  }
130
- styleElement.textContent = "/* */";
131
- styleElement.setAttribute("onload", "this.textContent=document.querySelector('style[" + DUPLICATE_STYLESHEET_ATTRIBUTE_NAME + "=\"" + stylesheetRefIndex + "\"]').textContent;this.removeAttribute(\"onload\")");
142
+ element.textContent = "/* */";
143
+ element.setAttribute("onload", "this.textContent=document.querySelector('style[" + DUPLICATE_STYLESHEET_ATTRIBUTE_NAME + "=\"" + stylesheetRefIndex + "\"]').textContent;this.removeAttribute(\"onload\")");
132
144
  } else {
133
- styleElement.textContent = options.inlineStylesheets.get(stylesheetRefIndex);
145
+ element.textContent = options.inlineStylesheets.get(stylesheetRefIndex).content;
134
146
  }
135
147
  if (stylesheetInfo.mediaText) {
136
- styleElement.media = stylesheetInfo.mediaText;
148
+ element.media = stylesheetInfo.mediaText;
137
149
  }
138
150
  } else {
139
- styleElement.remove();
151
+ element.remove();
140
152
  }
141
153
  });
142
154
  if (options.groupDuplicateStylesheets && doc.querySelector("style[" + DUPLICATE_STYLESHEET_ATTRIBUTE_NAME + "]")) {
@@ -377,6 +389,9 @@ function getProcessorHelperClass(utilInstance) {
377
389
  }
378
390
  }
379
391
  }
392
+ if (options.imageReductionFactor > 1) {
393
+ content = await resizeImage(content, options);
394
+ }
380
395
  if (removeElementIfMissing && this.testEmptyResource(content)) {
381
396
  resourceElement.remove();
382
397
  } else if (!this.testEmptyResource(content)) {
@@ -25,6 +25,7 @@ import * as cssTree from "./../../vendor/css-tree.js";
25
25
 
26
26
  const JSON = globalThis.JSON;
27
27
  const FontFace = globalThis.FontFace;
28
+ const Blob = globalThis.Blob;
28
29
 
29
30
  const ABOUT_BLANK_URI = "about:blank";
30
31
  const UTF8_CHARSET = "utf-8";
@@ -49,7 +50,9 @@ import {
49
50
  replaceOriginalURLs,
50
51
  testIgnoredPath,
51
52
  testValidPath,
52
- testValidURL
53
+ testValidURL,
54
+ resizeImage,
55
+ toDataURI
53
56
  } from "./processor-helper-common.js";
54
57
 
55
58
  export {
@@ -102,7 +105,7 @@ function getProcessorHelperClass(utilInstance) {
102
105
  linkElement.setAttribute("type", "text/css");
103
106
  const name = "stylesheet_" + resources.stylesheets.size + ".css";
104
107
  linkElement.setAttribute("href", name);
105
- let content = options.inlineStylesheets.get(stylesheetRefIndex);
108
+ let { content } = options.inlineStylesheets.get(stylesheetRefIndex);
106
109
  const stylesheet = cssTree.parse(content, { context: "stylesheet", parseCustomProperty: true });
107
110
  this.replacePseudoClassDefined(stylesheet);
108
111
  content = this.generateStylesheetContent(stylesheet, options);
@@ -324,7 +327,7 @@ function getProcessorHelperClass(utilInstance) {
324
327
  }
325
328
  if (testValidURL(resourceURL)) {
326
329
  const declaredContentType = ["OBJECT", "EMBED"].includes(resourceElement.tagName.toUpperCase()) ? resourceElement.getAttribute("type") : "";
327
- let { content, indexResource, extension, contentType } = await batchRequest.addURL(resourceURL,
330
+ let { content, indexResource, extension, contentType, charset } = await batchRequest.addURL(resourceURL,
328
331
  { asBinary: true, expectedType, contentType: declaredContentType });
329
332
  if (originURL) {
330
333
  if (this.testEmptyResource(content)) {
@@ -353,6 +356,10 @@ function getProcessorHelperClass(utilInstance) {
353
356
  }
354
357
  }
355
358
  }
359
+ if (options.imageReductionFactor > 1) {
360
+ const dataURI = await resizeImage(await toDataURI(new Blob([content], { type: contentType }), charset), options);
361
+ content = (await util.getContent(dataURI, { asBinary: true })).data;
362
+ }
356
363
  if (removeElementIfMissing && this.testEmptyResource(content)) {
357
364
  resourceElement.remove();
358
365
  } else if (!this.testEmptyResource(content)) {
@@ -294,11 +294,12 @@ function processSelectors(ruleData, processingContext, docContext) {
294
294
  scopeRelative
295
295
  } = analyzeSelector(selector.data);
296
296
  registerSelector(selector, ruleData, scopeRelative, processingContext, docContext);
297
- if (!hasPseudoElement && !hasDynamicStatePseudoClass &&
298
- (!startsWithCombinator || !ancestorsSelectors || !ancestorsSelectors.length)) {
297
+ if (!startsWithCombinator || !ancestorsSelectors || !ancestorsSelectors.length) {
299
298
  const matchedElements = matchElements(selector, ancestorsSelectors, docContext);
300
299
  if (matchedElements.length) {
301
- updateMatchingSelectors(matchedElements, selector, docContext);
300
+ if (!hasPseudoElement && !hasDynamicStatePseudoClass) {
301
+ updateMatchingSelectors(matchedElements, selector, docContext);
302
+ }
302
303
  } else {
303
304
  removedSelectors.push(selector);
304
305
  }
@@ -46,6 +46,8 @@ const UNMATCHABLE_PSEUDO_CLASSES = [
46
46
  "seeking",
47
47
  "stalled",
48
48
  "volume-locked",
49
+ "after",
50
+ "before"
49
51
  ];
50
52
 
51
53
  export {
@@ -57,7 +59,9 @@ export {
57
59
  * Optional `ancestors` array may be provided to expand nesting selectors (`&`).
58
60
  */
59
61
  function sanitizeSelector(selector, ancestors, docContext) {
60
- if (!docContext.normalizedSelectorText) docContext.normalizedSelectorText = new WeakMap();
62
+ if (!docContext.normalizedSelectorText) {
63
+ docContext.normalizedSelectorText = new WeakMap();
64
+ }
61
65
  if (docContext.normalizedSelectorText.has(selector)) {
62
66
  return docContext.normalizedSelectorText.get(selector);
63
67
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "single-file-core",
3
- "version": "1.5.71",
3
+ "version": "1.5.72",
4
4
  "description": "SingleFile Core",
5
5
  "author": "Gildas Lormeau",
6
6
  "license": "AGPL-3.0-or-later",