single-file-core 1.0.52 → 1.0.53

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.
@@ -91,52 +91,67 @@ const RESOLVE_URLS_STAGE = 0;
91
91
  const REPLACE_DATA_STAGE = 1;
92
92
  const REPLACE_DOCS_STAGE = 2;
93
93
  const POST_PROCESS_STAGE = 3;
94
- const STAGES = [
95
- {
96
- sequential: [
97
- { action: "preProcessPage" },
98
- { option: "loadDeferredImagesKeepZoomLevel", action: "resetZoomLevel" },
99
- { action: "replaceStyleContents" },
100
- { action: "replaceInvalidElements" },
101
- { action: "resetCharsetMeta" },
102
- { option: "saveFavicon", action: "saveFavicon" },
103
- { action: "insertFonts" },
104
- { action: "insertShadowRootContents" },
105
- { action: "replaceCanvasElements" },
106
- { action: "setInputValues" },
107
- { option: "moveStylesInHead", action: "moveStylesInHead" },
108
- { option: "blockScripts", action: "removeEmbedScripts" },
109
- { option: "selected", action: "removeUnselectedElements" },
110
- { option: "blockVideos", action: "insertVideoPosters" },
111
- { option: "blockVideos", action: "insertVideoLinks" },
112
- { option: "removeFrames", action: "removeFrames" },
113
- { action: "removeDiscardedResources" },
114
- { option: "removeHiddenElements", action: "removeHiddenElements" },
115
- { action: "resolveHrefs" },
116
- { action: "resolveStyleAttributeURLs" }
117
- ],
118
- parallel: [{ option: "blockVideos", action: "insertMissingVideoPosters" }, { action: "resolveStylesheetURLs" }, { option: "!removeFrames", action: "resolveFrameURLs" }]
119
- },
120
- {
121
- sequential: [
122
- { option: "removeUnusedStyles", action: "removeUnusedStyles" },
123
- { option: "removeAlternativeMedias", action: "removeAlternativeMedias" },
124
- { option: "removeUnusedFonts", action: "removeUnusedFonts" }
125
- ],
126
- parallel: [{ action: "processStylesheets" }, { action: "processStyleAttributes" }, { action: "processPageResources" }, { action: "processScripts" }]
127
- },
128
- {
129
- sequential: [{ option: "removeAlternativeImages", action: "removeAlternativeImages" }],
130
- parallel: [
131
- { option: "removeAlternativeFonts", action: "removeAlternativeFonts" },
132
- { option: "!removeFrames", action: "processFrames" }
133
- ]
134
- },
135
- {
136
- sequential: [{ action: "replaceStylesheets" }, { action: "replaceStyleAttributes" }, { action: "insertVariables" }, { option: "compressHTML", action: "compressHTML" }, { action: "cleanupPage" }],
137
- parallel: [{ option: "enableMaff", action: "insertMAFFMetaData" }, { action: "setDocInfo" }]
138
- }
139
- ];
94
+ const STAGES = [{
95
+ sequential: [
96
+ { action: "preProcessPage" },
97
+ { option: "loadDeferredImagesKeepZoomLevel", action: "resetZoomLevel" },
98
+ { action: "replaceStyleContents" },
99
+ { action: "replaceInvalidElements" },
100
+ { action: "resetCharsetMeta" },
101
+ { option: "saveFavicon", action: "saveFavicon" },
102
+ { action: "insertFonts" },
103
+ { action: "insertShadowRootContents" },
104
+ { action: "replaceCanvasElements" },
105
+ { action: "setInputValues" },
106
+ { option: "moveStylesInHead", action: "moveStylesInHead" },
107
+ { option: "blockScripts", action: "removeEmbedScripts" },
108
+ { option: "selected", action: "removeUnselectedElements" },
109
+ { option: "blockVideos", action: "insertVideoPosters" },
110
+ { option: "blockVideos", action: "insertVideoLinks" },
111
+ { option: "removeFrames", action: "removeFrames" },
112
+ { action: "removeDiscardedResources" },
113
+ { option: "removeHiddenElements", action: "removeHiddenElements" },
114
+ { action: "resolveHrefs" },
115
+ { action: "resolveStyleAttributeURLs" }
116
+ ],
117
+ parallel: [
118
+ { option: "blockVideos", action: "insertMissingVideoPosters" },
119
+ { action: "resolveStylesheetURLs" },
120
+ { option: "!removeFrames", action: "resolveFrameURLs" }
121
+ ]
122
+ }, {
123
+ sequential: [
124
+ { option: "removeUnusedStyles", action: "removeUnusedStyles" },
125
+ { option: "removeAlternativeMedias", action: "removeAlternativeMedias" },
126
+ { option: "removeUnusedFonts", action: "removeUnusedFonts" }
127
+ ],
128
+ parallel: [
129
+ { action: "processStylesheets" },
130
+ { action: "processStyleAttributes" },
131
+ { action: "processPageResources" },
132
+ { action: "processScripts" }
133
+ ]
134
+ }, {
135
+ sequential: [
136
+ { option: "removeAlternativeImages", action: "removeAlternativeImages" }
137
+ ],
138
+ parallel: [
139
+ { option: "removeAlternativeFonts", action: "removeAlternativeFonts" },
140
+ { option: "!removeFrames", action: "processFrames" }
141
+ ]
142
+ }, {
143
+ sequential: [
144
+ { action: "replaceStylesheets" },
145
+ { action: "replaceStyleAttributes" },
146
+ { action: "insertVariables" },
147
+ { option: "compressHTML", action: "compressHTML" },
148
+ { action: "cleanupPage" }
149
+ ],
150
+ parallel: [
151
+ { option: "enableMaff", action: "insertMAFFMetaData" },
152
+ { action: "setDocInfo" }
153
+ ]
154
+ }];
140
155
 
141
156
  class Runner {
142
157
  constructor(options, root) {
@@ -170,7 +185,7 @@ class Runner {
170
185
  this.options.removeFrames = true;
171
186
  }
172
187
  this.options.content = this.options.content || (rootDocDefined ? util.serialize(this.options.doc) : null);
173
- this.onprogress = options.onprogress || (() => {});
188
+ this.onprogress = options.onprogress || (() => { });
174
189
  }
175
190
 
176
191
  async loadPage() {
@@ -259,23 +274,21 @@ class Runner {
259
274
  });
260
275
  let parallelTasksPromise;
261
276
  if (STAGES[step].parallel) {
262
- parallelTasksPromise = await Promise.all(
263
- STAGES[step].parallel.map(async task => {
264
- let startTime;
265
- if (DEBUG) {
266
- startTime = Date.now();
267
- log(" // STARTED task =", task.action);
268
- }
269
- this.onprogress(new ProgressEvent(STAGE_TASK_STARTED, { pageURL: this.options.url, step, task: task.action, frame }));
270
- if (!this.cancelled) {
271
- await this.executeTask(task);
272
- }
273
- this.onprogress(new ProgressEvent(STAGE_TASK_ENDED, { pageURL: this.options.url, step, task: task.action, frame }));
274
- if (DEBUG) {
275
- log(" // ENDED task =", task.action, "delay =", Date.now() - startTime);
276
- }
277
- })
278
- );
277
+ parallelTasksPromise = await Promise.all(STAGES[step].parallel.map(async task => {
278
+ let startTime;
279
+ if (DEBUG) {
280
+ startTime = Date.now();
281
+ log(" // STARTED task =", task.action);
282
+ }
283
+ this.onprogress(new ProgressEvent(STAGE_TASK_STARTED, { pageURL: this.options.url, step, task: task.action, frame }));
284
+ if (!this.cancelled) {
285
+ await this.executeTask(task);
286
+ }
287
+ this.onprogress(new ProgressEvent(STAGE_TASK_ENDED, { pageURL: this.options.url, step, task: task.action, frame }));
288
+ if (DEBUG) {
289
+ log(" // ENDED task =", task.action, "delay =", Date.now() - startTime);
290
+ }
291
+ }));
279
292
  } else {
280
293
  parallelTasksPromise = Promise.resolve();
281
294
  }
@@ -287,7 +300,7 @@ class Runner {
287
300
  }
288
301
 
289
302
  executeTask(task) {
290
- if (!task.option || (task.option.startsWith("!") && !this.options[task.option]) || this.options[task.option]) {
303
+ if (!task.option || ((task.option.startsWith("!") && !this.options[task.option]) || this.options[task.option])) {
291
304
  return this.processor[task.action]();
292
305
  }
293
306
  }
@@ -330,41 +343,39 @@ class BatchRequest {
330
343
  run(onloadListener, options) {
331
344
  const resourceURLs = [...this.requests.keys()];
332
345
  let indexResource = 0;
333
- return Promise.all(
334
- resourceURLs.map(async requestKey => {
335
- const [resourceURL, asBinary, expectedType, baseURI, blockMixedContent] = JSON.parse(requestKey);
336
- const resourceRequests = this.requests.get(requestKey);
337
- try {
338
- const currentIndexResource = indexResource;
339
- indexResource = indexResource + 1;
340
- const content = await util.getContent(resourceURL, {
341
- asBinary,
342
- expectedType,
343
- maxResourceSize: options.maxResourceSize,
344
- maxResourceSizeEnabled: options.maxResourceSizeEnabled,
345
- frameId: options.windowId,
346
- resourceReferrer: options.resourceReferrer,
347
- baseURI,
348
- blockMixedContent,
349
- acceptHeaders: options.acceptHeaders,
350
- networkTimeout: options.networkTimeout
346
+ return Promise.all(resourceURLs.map(async requestKey => {
347
+ const [resourceURL, asBinary, expectedType, baseURI, blockMixedContent] = JSON.parse(requestKey);
348
+ const resourceRequests = this.requests.get(requestKey);
349
+ try {
350
+ const currentIndexResource = indexResource;
351
+ indexResource = indexResource + 1;
352
+ const content = await util.getContent(resourceURL, {
353
+ asBinary,
354
+ expectedType,
355
+ maxResourceSize: options.maxResourceSize,
356
+ maxResourceSizeEnabled: options.maxResourceSizeEnabled,
357
+ frameId: options.windowId,
358
+ resourceReferrer: options.resourceReferrer,
359
+ baseURI,
360
+ blockMixedContent,
361
+ acceptHeaders: options.acceptHeaders,
362
+ networkTimeout: options.networkTimeout
363
+ });
364
+ onloadListener({ url: resourceURL });
365
+ if (!this.cancelled) {
366
+ resourceRequests.forEach(callbacks => {
367
+ const duplicateCallbacks = this.duplicates.get(requestKey);
368
+ const duplicate = duplicateCallbacks && duplicateCallbacks.length > 1 && duplicateCallbacks.includes(callbacks);
369
+ callbacks.resolve({ content: content.data, indexResource: currentIndexResource, duplicate });
351
370
  });
352
- onloadListener({ url: resourceURL });
353
- if (!this.cancelled) {
354
- resourceRequests.forEach(callbacks => {
355
- const duplicateCallbacks = this.duplicates.get(requestKey);
356
- const duplicate = duplicateCallbacks && duplicateCallbacks.length > 1 && duplicateCallbacks.includes(callbacks);
357
- callbacks.resolve({ content: content.data, indexResource: currentIndexResource, duplicate });
358
- });
359
- }
360
- } catch (error) {
361
- indexResource = indexResource + 1;
362
- onloadListener({ url: resourceURL });
363
- resourceRequests.forEach(resourceRequest => resourceRequest.reject(error));
364
371
  }
365
- this.requests.delete(requestKey);
366
- })
367
- );
372
+ } catch (error) {
373
+ indexResource = indexResource + 1;
374
+ onloadListener({ url: resourceURL });
375
+ resourceRequests.forEach(resourceRequest => resourceRequest.reject(error));
376
+ }
377
+ this.requests.delete(requestKey);
378
+ }));
368
379
  }
369
380
 
370
381
  cancel() {
@@ -459,8 +470,7 @@ class Processor {
459
470
  const url = util.parseURL(this.baseURI);
460
471
  if (this.options.insertSingleFileComment) {
461
472
  const firstComment = this.doc.documentElement.firstChild;
462
- let infobarURL = this.options.saveUrl,
463
- infobarSaveDate = this.options.saveDate;
473
+ let infobarURL = this.options.saveUrl, infobarSaveDate = this.options.saveDate;
464
474
  if (firstComment.nodeType == 8 && (firstComment.textContent.includes(util.COMMENT_HEADER_LEGACY) || firstComment.textContent.includes(util.COMMENT_HEADER))) {
465
475
  const info = this.doc.documentElement.firstChild.textContent.split("\n");
466
476
  try {
@@ -474,7 +484,10 @@ class Processor {
474
484
  }
475
485
  const infobarContent = (this.options.infobarContent || "").replace(/\\n/g, "\n").replace(/\\t/g, "\t");
476
486
  const commentNode = this.doc.createComment(
477
- "\n " + (this.options.useLegacyCommentHeader ? util.COMMENT_HEADER_LEGACY : util.COMMENT_HEADER) + " \n url: " + infobarURL + (this.options.removeSavedDate ? " " : " \n saved date: " + infobarSaveDate) + (infobarContent ? " \n info: " + infobarContent : "") + "\n"
487
+ "\n " + (this.options.useLegacyCommentHeader ? util.COMMENT_HEADER_LEGACY : util.COMMENT_HEADER) +
488
+ " \n url: " + infobarURL +
489
+ (this.options.removeSavedDate ? " " : " \n saved date: " + infobarSaveDate) +
490
+ (infobarContent ? " \n info: " + infobarContent : "") + "\n"
478
491
  );
479
492
  this.doc.documentElement.insertBefore(commentNode, this.doc.documentElement.firstChild);
480
493
  }
@@ -544,7 +557,10 @@ class Processor {
544
557
  if (attributeValue) {
545
558
  const imageData = this.options.images[Number(attributeValue)];
546
559
  if (imageData) {
547
- if (this.options.removeHiddenElements && ((imageData.size && !imageData.size.pxWidth && !imageData.size.pxHeight) || imgElement.getAttribute(util.HIDDEN_CONTENT_ATTRIBUTE_NAME) == "")) {
560
+ if (this.options.removeHiddenElements && (
561
+ (imageData.size && !imageData.size.pxWidth && !imageData.size.pxHeight) ||
562
+ imgElement.getAttribute(util.HIDDEN_CONTENT_ATTRIBUTE_NAME) == "")
563
+ ) {
548
564
  imgElement.setAttribute("src", util.EMPTY_RESOURCE);
549
565
  } else {
550
566
  if (imageData.currentSrc) {
@@ -656,8 +672,7 @@ class Processor {
656
672
  }
657
673
 
658
674
  insertVideoLinks() {
659
- const LINK_ICON =
660
- "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABAAgMAAADXB5lNAAABhmlDQ1BJQ0MgcHJvZmlsZQAAKJF9kj1Iw0AYht+mSkUrDnYQcchQnSyIijqWKhbBQmkrtOpgcukfNGlIUlwcBdeCgz+LVQcXZ10dXAVB8AfEydFJ0UVK/C4ptIjx4LiH9+59+e67A4RGhalm1wSgapaRisfEbG5VDLyiDwEAvZiVmKkn0osZeI6ve/j4ehfhWd7n/hz9St5kgE8kjjLdsIg3iGc2LZ3zPnGIlSSF+Jx43KACiR+5Lrv8xrnosMAzQ0YmNU8cIhaLHSx3MCsZKvE0cVhRNcoXsi4rnLc4q5Uaa9XJbxjMaytprtMcQRxLSCAJETJqKKMCCxFaNVJMpGg/5uEfdvxJcsnkKoORYwFVqJAcP/gb/O6tWZiadJOCMaD7xbY/RoHALtCs2/b3sW03TwD/M3Cltf3VBjD3SXq9rYWPgIFt4OK6rcl7wOUOMPSkS4bkSH6aQqEAvJ/RM+WAwVv6EGtu31r7OH0AMtSr5Rvg4BAYK1L2use9ezr79u+ZVv9+AFlNcp0UUpiqAAAACXBIWXMAAC4jAAAuIwF4pT92AAAAB3RJTUUH5AsHAB8H+DhhoQAAABl0RVh0Q29tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAAAJUExURQAAAICHi4qKioTuJAkAAAABdFJOUwBA5thmAAAAAWJLR0QCZgt8ZAAAAJJJREFUOI3t070NRCEMA2CnYAOyDyPwpHj/Va7hJ3FzV7zy3ET5JIwoAF6Jk4wzAJAkzxAYG9YRTgB+24wBgKmfrGAKTcEfAY4KRlRoIeBTgKOCERVaCPgU4Khge2GqKOBTgKOCERVaAEC/4PNcnyoSWHpjqkhwKxbcig0Q6AorXYF/+A6eIYD1lVbwG/jdA6/kA2THRAURVubcAAAAAElFTkSuQmCC";
675
+ const LINK_ICON = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABAAgMAAADXB5lNAAABhmlDQ1BJQ0MgcHJvZmlsZQAAKJF9kj1Iw0AYht+mSkUrDnYQcchQnSyIijqWKhbBQmkrtOpgcukfNGlIUlwcBdeCgz+LVQcXZ10dXAVB8AfEydFJ0UVK/C4ptIjx4LiH9+59+e67A4RGhalm1wSgapaRisfEbG5VDLyiDwEAvZiVmKkn0osZeI6ve/j4ehfhWd7n/hz9St5kgE8kjjLdsIg3iGc2LZ3zPnGIlSSF+Jx43KACiR+5Lrv8xrnosMAzQ0YmNU8cIhaLHSx3MCsZKvE0cVhRNcoXsi4rnLc4q5Uaa9XJbxjMaytprtMcQRxLSCAJETJqKKMCCxFaNVJMpGg/5uEfdvxJcsnkKoORYwFVqJAcP/gb/O6tWZiadJOCMaD7xbY/RoHALtCs2/b3sW03TwD/M3Cltf3VBjD3SXq9rYWPgIFt4OK6rcl7wOUOMPSkS4bkSH6aQqEAvJ/RM+WAwVv6EGtu31r7OH0AMtSr5Rvg4BAYK1L2use9ezr79u+ZVv9+AFlNcp0UUpiqAAAACXBIWXMAAC4jAAAuIwF4pT92AAAAB3RJTUUH5AsHAB8H+DhhoQAAABl0RVh0Q29tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAAAJUExURQAAAICHi4qKioTuJAkAAAABdFJOUwBA5thmAAAAAWJLR0QCZgt8ZAAAAJJJREFUOI3t070NRCEMA2CnYAOyDyPwpHj/Va7hJ3FzV7zy3ET5JIwoAF6Jk4wzAJAkzxAYG9YRTgB+24wBgKmfrGAKTcEfAY4KRlRoIeBTgKOCERVaCPgU4Khge2GqKOBTgKOCERVaAEC/4PNcnyoSWHpjqkhwKxbcig0Q6AorXYF/+A6eIYD1lVbwG/jdA6/kA2THRAURVubcAAAAAElFTkSuQmCC";
661
676
  const ICON_SIZE = "16px";
662
677
  this.doc.querySelectorAll("video").forEach(videoElement => {
663
678
  const attributeValue = videoElement.getAttribute(util.VIDEO_ATTRIBUTE_NAME);
@@ -866,8 +881,7 @@ class Processor {
866
881
 
867
882
  insertFonts() {
868
883
  if (this.options.fonts && this.options.fonts.length) {
869
- let firstStylesheet = this.doc.querySelector("style, link[rel=stylesheet]"),
870
- previousStyleElement;
884
+ let firstStylesheet = this.doc.querySelector("style, link[rel=stylesheet]"), previousStyleElement;
871
885
  this.options.fonts.forEach(fontData => {
872
886
  if (fontData["font-family"] && fontData.src) {
873
887
  let stylesheetContent = "@font-face{";
@@ -947,53 +961,51 @@ class Processor {
947
961
  }
948
962
 
949
963
  async insertMissingVideoPosters() {
950
- await Promise.all(
951
- Array.from(this.doc.querySelectorAll("video[src], video > source[src]")).map(async element => {
952
- let videoElement;
953
- if (element.tagName.toUpperCase() == "VIDEO") {
954
- videoElement = element;
955
- } else {
956
- videoElement = element.parentElement;
957
- }
958
- if (!videoElement.poster) {
959
- const attributeValue = videoElement.getAttribute(util.VIDEO_ATTRIBUTE_NAME);
960
- if (attributeValue) {
961
- const videoData = this.options.videos[Number(attributeValue)];
962
- const src = videoData.src || videoElement.src;
963
- if (src) {
964
- const temporaryVideoElement = this.doc.createElement("video");
965
- temporaryVideoElement.src = src;
966
- temporaryVideoElement.style.setProperty("width", videoData.size.pxWidth + "px", "important");
967
- temporaryVideoElement.style.setProperty("height", videoData.size.pxHeight + "px", "important");
968
- temporaryVideoElement.style.setProperty("display", "none", "important");
969
- temporaryVideoElement.crossOrigin = "anonymous";
970
- const canvasElement = this.doc.createElement("canvas");
971
- const context = canvasElement.getContext("2d");
972
- this.options.doc.body.appendChild(temporaryVideoElement);
973
- return new Promise(resolve => {
974
- temporaryVideoElement.currentTime = videoData.currentTime;
975
- temporaryVideoElement.oncanplay = () => {
976
- canvasElement.width = videoData.size.pxWidth;
977
- canvasElement.height = videoData.size.pxHeight;
978
- context.drawImage(temporaryVideoElement, 0, 0, canvasElement.width, canvasElement.height);
979
- try {
980
- videoElement.poster = canvasElement.toDataURL("image/png", "");
981
- } catch (error) {
982
- // ignored
983
- }
984
- temporaryVideoElement.remove();
985
- resolve();
986
- };
987
- temporaryVideoElement.onerror = () => {
988
- temporaryVideoElement.remove();
989
- resolve();
990
- };
991
- });
992
- }
964
+ await Promise.all(Array.from(this.doc.querySelectorAll("video[src], video > source[src]")).map(async element => {
965
+ let videoElement;
966
+ if (element.tagName.toUpperCase() == "VIDEO") {
967
+ videoElement = element;
968
+ } else {
969
+ videoElement = element.parentElement;
970
+ }
971
+ if (!videoElement.poster) {
972
+ const attributeValue = videoElement.getAttribute(util.VIDEO_ATTRIBUTE_NAME);
973
+ if (attributeValue) {
974
+ const videoData = this.options.videos[Number(attributeValue)];
975
+ const src = videoData.src || videoElement.src;
976
+ if (src) {
977
+ const temporaryVideoElement = this.doc.createElement("video");
978
+ temporaryVideoElement.src = src;
979
+ temporaryVideoElement.style.setProperty("width", videoData.size.pxWidth + "px", "important");
980
+ temporaryVideoElement.style.setProperty("height", videoData.size.pxHeight + "px", "important");
981
+ temporaryVideoElement.style.setProperty("display", "none", "important");
982
+ temporaryVideoElement.crossOrigin = "anonymous";
983
+ const canvasElement = this.doc.createElement("canvas");
984
+ const context = canvasElement.getContext("2d");
985
+ this.options.doc.body.appendChild(temporaryVideoElement);
986
+ return new Promise(resolve => {
987
+ temporaryVideoElement.currentTime = videoData.currentTime;
988
+ temporaryVideoElement.oncanplay = () => {
989
+ canvasElement.width = videoData.size.pxWidth;
990
+ canvasElement.height = videoData.size.pxHeight;
991
+ context.drawImage(temporaryVideoElement, 0, 0, canvasElement.width, canvasElement.height);
992
+ try {
993
+ videoElement.poster = canvasElement.toDataURL("image/png", "");
994
+ } catch (error) {
995
+ // ignored
996
+ }
997
+ temporaryVideoElement.remove();
998
+ resolve();
999
+ };
1000
+ temporaryVideoElement.onerror = () => {
1001
+ temporaryVideoElement.remove();
1002
+ resolve();
1003
+ };
1004
+ });
993
1005
  }
994
1006
  }
995
- })
996
- );
1007
+ }
1008
+ }));
997
1009
  }
998
1010
 
999
1011
  resolveStyleAttributeURLs() {
@@ -1010,40 +1022,36 @@ class Processor {
1010
1022
  }
1011
1023
 
1012
1024
  async resolveStylesheetURLs() {
1013
- await Promise.all(
1014
- Array.from(this.doc.querySelectorAll("style, link[rel*=stylesheet]")).map(async element => {
1015
- const options = Object.assign({}, this.options, { charset: this.charset });
1016
- let mediaText;
1017
- if (element.media) {
1018
- mediaText = element.media.toLowerCase();
1019
- }
1020
- const scoped = Boolean(element.closest("[" + SHADOWROOT_ATTRIBUTE_NAME + "]"));
1021
- const stylesheetInfo = {
1022
- mediaText,
1023
- scoped
1024
- };
1025
- if (element.tagName.toUpperCase() == "LINK" && element.charset) {
1026
- options.charset = element.charset;
1027
- }
1028
- await processElement(element, stylesheetInfo, this.stylesheets, this.baseURI, options, this.workStyleElement);
1029
- })
1030
- );
1025
+ await Promise.all(Array.from(this.doc.querySelectorAll("style, link[rel*=stylesheet]")).map(async element => {
1026
+ const options = Object.assign({}, this.options, { charset: this.charset });
1027
+ let mediaText;
1028
+ if (element.media) {
1029
+ mediaText = element.media.toLowerCase();
1030
+ }
1031
+ const scoped = Boolean(element.closest("[" + SHADOWROOT_ATTRIBUTE_NAME + "]"));
1032
+ const stylesheetInfo = {
1033
+ mediaText,
1034
+ scoped
1035
+ };
1036
+ if (element.tagName.toUpperCase() == "LINK" && element.charset) {
1037
+ options.charset = element.charset;
1038
+ }
1039
+ await processElement(element, stylesheetInfo, this.stylesheets, this.baseURI, options, this.workStyleElement);
1040
+ }));
1031
1041
  if (this.options.rootDocument) {
1032
1042
  const newResources = Object.keys(this.options.updatedResources)
1033
1043
  .filter(url => this.options.updatedResources[url].type == "stylesheet" && !this.options.updatedResources[url].retrieved)
1034
1044
  .map(url => this.options.updatedResources[url]);
1035
- await Promise.all(
1036
- newResources.map(async resource => {
1037
- resource.retrieved = true;
1038
- if (!this.options.blockStylesheets) {
1039
- const stylesheetInfo = {};
1040
- const element = this.doc.createElement("style");
1041
- this.doc.body.appendChild(element);
1042
- element.textContent = resource.content;
1043
- await processElement(element, stylesheetInfo, this.stylesheets, this.baseURI, this.options, this.workStyleElement);
1044
- }
1045
- })
1046
- );
1045
+ await Promise.all(newResources.map(async resource => {
1046
+ resource.retrieved = true;
1047
+ if (!this.options.blockStylesheets) {
1048
+ const stylesheetInfo = {};
1049
+ const element = this.doc.createElement("style");
1050
+ this.doc.body.appendChild(element);
1051
+ element.textContent = resource.content;
1052
+ await processElement(element, stylesheetInfo, this.stylesheets, this.baseURI, this.options, this.workStyleElement);
1053
+ }
1054
+ }));
1047
1055
  }
1048
1056
 
1049
1057
  async function processElement(element, stylesheetInfo, stylesheets, baseURI, options, workStyleElement) {
@@ -1074,28 +1082,26 @@ class Processor {
1074
1082
  async resolveFrameURLs() {
1075
1083
  if (!this.options.saveRawPage) {
1076
1084
  const frameElements = Array.from(this.doc.querySelectorAll("iframe, frame, object[type=\"text/html\"][data]"));
1077
- await Promise.all(
1078
- frameElements.map(async frameElement => {
1079
- if (frameElement.tagName.toUpperCase() == "OBJECT") {
1080
- frameElement.setAttribute("data", "data:text/html,");
1081
- } else {
1082
- const src = frameElement.getAttribute("src");
1083
- if (this.options.saveOriginalURLs && src && !isDataURL(src)) {
1084
- frameElement.setAttribute("data-sf-original-src", src);
1085
- }
1086
- frameElement.removeAttribute("src");
1087
- frameElement.removeAttribute("srcdoc");
1085
+ await Promise.all(frameElements.map(async frameElement => {
1086
+ if (frameElement.tagName.toUpperCase() == "OBJECT") {
1087
+ frameElement.setAttribute("data", "data:text/html,");
1088
+ } else {
1089
+ const src = frameElement.getAttribute("src");
1090
+ if (this.options.saveOriginalURLs && src && !isDataURL(src)) {
1091
+ frameElement.setAttribute("data-sf-original-src", src);
1088
1092
  }
1089
- Array.from(frameElement.childNodes).forEach(node => node.remove());
1090
- const frameWindowId = frameElement.getAttribute(util.WIN_ID_ATTRIBUTE_NAME);
1091
- if (this.options.frames && frameWindowId) {
1092
- const frameData = this.options.frames.find(frame => frame.windowId == frameWindowId);
1093
- if (frameData) {
1094
- await initializeProcessor(frameData, frameElement, frameWindowId, this.batchRequest, Object.create(this.options));
1095
- }
1093
+ frameElement.removeAttribute("src");
1094
+ frameElement.removeAttribute("srcdoc");
1095
+ }
1096
+ Array.from(frameElement.childNodes).forEach(node => node.remove());
1097
+ const frameWindowId = frameElement.getAttribute(util.WIN_ID_ATTRIBUTE_NAME);
1098
+ if (this.options.frames && frameWindowId) {
1099
+ const frameData = this.options.frames.find(frame => frame.windowId == frameWindowId);
1100
+ if (frameData) {
1101
+ await initializeProcessor(frameData, frameElement, frameWindowId, this.batchRequest, Object.create(this.options));
1096
1102
  }
1097
- })
1098
- );
1103
+ }
1104
+ }));
1099
1105
  }
1100
1106
 
1101
1107
  async function initializeProcessor(frameData, frameElement, frameWindowId, batchRequest, options) {
@@ -1198,11 +1204,15 @@ class Processor {
1198
1204
 
1199
1205
  async processStylesheets() {
1200
1206
  this.options.fontDeclarations = new Map();
1201
- await Promise.all([...this.stylesheets].map(([, stylesheetInfo]) => ProcessorHelper.processStylesheet(stylesheetInfo.stylesheet.children, this.baseURI, this.options, this.cssVariables, this.batchRequest)));
1207
+ await Promise.all([...this.stylesheets].map(([, stylesheetInfo]) =>
1208
+ ProcessorHelper.processStylesheet(stylesheetInfo.stylesheet.children, this.baseURI, this.options, this.cssVariables, this.batchRequest)
1209
+ ));
1202
1210
  }
1203
1211
 
1204
1212
  async processStyleAttributes() {
1205
- return Promise.all([...this.styles].map(([, stylesheet]) => ProcessorHelper.processStyle(stylesheet, this.baseURI, this.options, this.cssVariables, this.batchRequest)));
1213
+ return Promise.all([...this.styles].map(([, stylesheet]) =>
1214
+ ProcessorHelper.processStyle(stylesheet, this.baseURI, this.options, this.cssVariables, this.batchRequest)
1215
+ ));
1206
1216
  }
1207
1217
 
1208
1218
  async processPageResources() {
@@ -1220,11 +1230,11 @@ class Processor {
1220
1230
  this.doc.querySelectorAll("svg").forEach(element => element.remove());
1221
1231
  }
1222
1232
  let resourcePromises = processAttributeArgs.map(([selector, attributeName, processDuplicates, removeElementIfMissing]) =>
1223
- ProcessorHelper.processAttribute(this.doc.querySelectorAll(selector), attributeName, this.baseURI, this.options, "image", this.cssVariables, this.styles, this.batchRequest, processDuplicates, removeElementIfMissing)
1233
+ ProcessorHelper.processAttribute(this.doc, this.doc.querySelectorAll(selector), attributeName, this.baseURI, this.options, "image", this.cssVariables, this.styles, this.batchRequest, processDuplicates, removeElementIfMissing)
1224
1234
  );
1225
1235
  resourcePromises = resourcePromises.concat([ProcessorHelper.processXLinks(this.doc.querySelectorAll("use"), this.doc, this.baseURI, this.options, this.batchRequest), ProcessorHelper.processSrcset(this.doc.querySelectorAll("img[srcset], source[srcset]"), this.baseURI, this.options, this.batchRequest)]);
1226
- resourcePromises.push(ProcessorHelper.processAttribute(this.doc.querySelectorAll("audio[src], audio > source[src]"), "src", this.baseURI, this.options, "audio", this.cssVariables, this.styles, this.batchRequest));
1227
- resourcePromises.push(ProcessorHelper.processAttribute(this.doc.querySelectorAll("video[src], video > source[src]"), "src", this.baseURI, this.options, "video", this.cssVariables, this.styles, this.batchRequest));
1236
+ resourcePromises.push(ProcessorHelper.processAttribute(this.doc, this.doc.querySelectorAll("audio[src], audio > source[src]"), "src", this.baseURI, this.options, "audio", this.cssVariables, this.styles, this.batchRequest));
1237
+ resourcePromises.push(ProcessorHelper.processAttribute(this.doc, this.doc.querySelectorAll("video[src], video > source[src]"), "src", this.baseURI, this.options, "video", this.cssVariables, this.styles, this.batchRequest));
1228
1238
  await Promise.all(resourcePromises);
1229
1239
  if (this.options.saveFavicon) {
1230
1240
  ProcessorHelper.processShortcutIcons(this.doc);
@@ -1232,49 +1242,47 @@ class Processor {
1232
1242
  }
1233
1243
 
1234
1244
  async processScripts() {
1235
- await Promise.all(
1236
- Array.from(this.doc.querySelectorAll("script[src]")).map(async element => {
1237
- let resourceURL;
1238
- let scriptSrc;
1239
- scriptSrc = element.getAttribute("src");
1240
- if (this.options.saveOriginalURLs && !isDataURL(scriptSrc)) {
1241
- element.setAttribute("data-sf-original-src", scriptSrc);
1245
+ await Promise.all(Array.from(this.doc.querySelectorAll("script[src]")).map(async element => {
1246
+ let resourceURL;
1247
+ let scriptSrc;
1248
+ scriptSrc = element.getAttribute("src");
1249
+ if (this.options.saveOriginalURLs && !isDataURL(scriptSrc)) {
1250
+ element.setAttribute("data-sf-original-src", scriptSrc);
1251
+ }
1252
+ element.removeAttribute("integrity");
1253
+ if (!this.options.blockScripts) {
1254
+ element.textContent = "";
1255
+ try {
1256
+ resourceURL = util.resolveURL(scriptSrc, this.baseURI);
1257
+ } catch (error) {
1258
+ // ignored
1242
1259
  }
1243
- element.removeAttribute("integrity");
1244
- if (!this.options.blockScripts) {
1245
- element.textContent = "";
1246
- try {
1247
- resourceURL = util.resolveURL(scriptSrc, this.baseURI);
1248
- } catch (error) {
1249
- // ignored
1250
- }
1251
- if (testValidURL(resourceURL)) {
1252
- element.removeAttribute("src");
1253
- const content = await util.getContent(resourceURL, {
1254
- asBinary: true,
1255
- charset: this.charset != UTF8_CHARSET && this.charset,
1256
- maxResourceSize: this.options.maxResourceSize,
1257
- maxResourceSizeEnabled: this.options.maxResourceSizeEnabled,
1258
- frameId: this.options.windowId,
1259
- resourceReferrer: this.options.resourceReferrer,
1260
- baseURI: this.options.baseURI,
1261
- blockMixedContent: this.options.blockMixedContent,
1262
- expectedType: "script",
1263
- acceptHeaders: this.options.acceptHeaders,
1264
- networkTimeout: this.options.networkTimeout
1265
- });
1266
- content.data = getUpdatedResourceContent(resourceURL, content, this.options);
1267
- element.setAttribute("src", content.data);
1268
- if (element.getAttribute("async") == "async" || element.getAttribute(util.ASYNC_SCRIPT_ATTRIBUTE_NAME) == "") {
1269
- element.setAttribute("async", "");
1270
- }
1271
- }
1272
- } else {
1260
+ if (testValidURL(resourceURL)) {
1273
1261
  element.removeAttribute("src");
1262
+ const content = await util.getContent(resourceURL, {
1263
+ asBinary: true,
1264
+ charset: this.charset != UTF8_CHARSET && this.charset,
1265
+ maxResourceSize: this.options.maxResourceSize,
1266
+ maxResourceSizeEnabled: this.options.maxResourceSizeEnabled,
1267
+ frameId: this.options.windowId,
1268
+ resourceReferrer: this.options.resourceReferrer,
1269
+ baseURI: this.options.baseURI,
1270
+ blockMixedContent: this.options.blockMixedContent,
1271
+ expectedType: "script",
1272
+ acceptHeaders: this.options.acceptHeaders,
1273
+ networkTimeout: this.options.networkTimeout
1274
+ });
1275
+ content.data = getUpdatedResourceContent(resourceURL, content, this.options);
1276
+ element.setAttribute("src", content.data);
1277
+ if (element.getAttribute("async") == "async" || element.getAttribute(util.ASYNC_SCRIPT_ATTRIBUTE_NAME) == "") {
1278
+ element.setAttribute("async", "");
1279
+ }
1274
1280
  }
1275
- this.stats.add("processed", "scripts", 1);
1276
- })
1277
- );
1281
+ } else {
1282
+ element.removeAttribute("src");
1283
+ }
1284
+ this.stats.add("processed", "scripts", 1);
1285
+ }));
1278
1286
  }
1279
1287
 
1280
1288
  removeAlternativeImages() {
@@ -1288,42 +1296,40 @@ class Processor {
1288
1296
  async processFrames() {
1289
1297
  if (this.options.frames) {
1290
1298
  const frameElements = Array.from(this.doc.querySelectorAll("iframe, frame, object[type=\"text/html\"][data]"));
1291
- await Promise.all(
1292
- frameElements.map(async frameElement => {
1293
- const frameWindowId = frameElement.getAttribute(util.WIN_ID_ATTRIBUTE_NAME);
1294
- if (frameWindowId) {
1295
- const frameData = this.options.frames.find(frame => frame.windowId == frameWindowId);
1296
- if (frameData) {
1297
- this.options.frames = this.options.frames.filter(frame => frame.windowId != frameWindowId);
1298
- if (frameData.runner && frameElement.getAttribute(util.HIDDEN_FRAME_ATTRIBUTE_NAME) != "") {
1299
- this.stats.add("processed", "frames", 1);
1300
- await frameData.runner.run();
1301
- const pageData = await frameData.runner.getPageData();
1302
- frameElement.removeAttribute(util.WIN_ID_ATTRIBUTE_NAME);
1303
- let sandbox = "allow-popups allow-top-navigation allow-top-navigation-by-user-activation";
1304
- if (pageData.content.match(NOSCRIPT_TAG_FOUND) || pageData.content.match(CANVAS_TAG_FOUND) || pageData.content.match(SCRIPT_TAG_FOUND)) {
1305
- sandbox += " allow-scripts allow-same-origin";
1306
- }
1307
- frameElement.setAttribute("sandbox", sandbox);
1308
- if (frameElement.tagName.toUpperCase() == "OBJECT") {
1309
- frameElement.setAttribute("data", "data:text/html," + pageData.content);
1299
+ await Promise.all(frameElements.map(async frameElement => {
1300
+ const frameWindowId = frameElement.getAttribute(util.WIN_ID_ATTRIBUTE_NAME);
1301
+ if (frameWindowId) {
1302
+ const frameData = this.options.frames.find(frame => frame.windowId == frameWindowId);
1303
+ if (frameData) {
1304
+ this.options.frames = this.options.frames.filter(frame => frame.windowId != frameWindowId);
1305
+ if (frameData.runner && frameElement.getAttribute(util.HIDDEN_FRAME_ATTRIBUTE_NAME) != "") {
1306
+ this.stats.add("processed", "frames", 1);
1307
+ await frameData.runner.run();
1308
+ const pageData = await frameData.runner.getPageData();
1309
+ frameElement.removeAttribute(util.WIN_ID_ATTRIBUTE_NAME);
1310
+ let sandbox = "allow-popups allow-top-navigation allow-top-navigation-by-user-activation";
1311
+ if (pageData.content.match(NOSCRIPT_TAG_FOUND) || pageData.content.match(CANVAS_TAG_FOUND) || pageData.content.match(SCRIPT_TAG_FOUND)) {
1312
+ sandbox += " allow-scripts allow-same-origin";
1313
+ }
1314
+ frameElement.setAttribute("sandbox", sandbox);
1315
+ if (frameElement.tagName.toUpperCase() == "OBJECT") {
1316
+ frameElement.setAttribute("data", "data:text/html," + pageData.content);
1317
+ } else {
1318
+ if (frameElement.tagName.toUpperCase() == "FRAME") {
1319
+ frameElement.setAttribute("src", "data:text/html," + pageData.content.replace(/%/g, "%25").replace(/#/g, "%23"));
1310
1320
  } else {
1311
- if (frameElement.tagName.toUpperCase() == "FRAME") {
1312
- frameElement.setAttribute("src", "data:text/html," + pageData.content.replace(/%/g, "%25").replace(/#/g, "%23"));
1313
- } else {
1314
- frameElement.setAttribute("srcdoc", pageData.content);
1315
- frameElement.removeAttribute("src");
1316
- }
1321
+ frameElement.setAttribute("srcdoc", pageData.content);
1322
+ frameElement.removeAttribute("src");
1317
1323
  }
1318
- this.stats.addAll(pageData);
1319
- } else {
1320
- frameElement.removeAttribute(util.WIN_ID_ATTRIBUTE_NAME);
1321
- this.stats.add("discarded", "frames", 1);
1322
1324
  }
1325
+ this.stats.addAll(pageData);
1326
+ } else {
1327
+ frameElement.removeAttribute(util.WIN_ID_ATTRIBUTE_NAME);
1328
+ this.stats.add("discarded", "frames", 1);
1323
1329
  }
1324
1330
  }
1325
- })
1326
- );
1331
+ }
1332
+ }));
1327
1333
  }
1328
1334
  }
1329
1335
 
@@ -1511,56 +1517,56 @@ class ProcessorHelper {
1511
1517
  }
1512
1518
 
1513
1519
  static removeSingleLineCssComments(stylesheet) {
1514
- const removedRules = [];
1515
- for (let cssRule = stylesheet.children.head; cssRule; cssRule = cssRule.next) {
1516
- const ruleData = cssRule.data;
1517
- if (ruleData.type == "Raw" && ruleData.value && ruleData.value.trim().startsWith("//")) {
1518
- removedRules.push(cssRule);
1520
+ if (stylesheet.children) {
1521
+ const removedRules = [];
1522
+ for (let cssRule = stylesheet.children.head; cssRule; cssRule = cssRule.next) {
1523
+ const ruleData = cssRule.data;
1524
+ if (ruleData.type == "Raw" && ruleData.value && ruleData.value.trim().startsWith("//")) {
1525
+ removedRules.push(cssRule);
1526
+ }
1519
1527
  }
1528
+ removedRules.forEach(cssRule => stylesheet.children.remove(cssRule));
1520
1529
  }
1521
- removedRules.forEach(cssRule => stylesheet.children.remove(cssRule));
1522
1530
  }
1523
1531
 
1524
1532
  static async resolveImportURLs(stylesheet, baseURI, options, workStylesheet, importedStyleSheets = new Set()) {
1525
1533
  let importFound;
1526
1534
  ProcessorHelper.resolveStylesheetURLs(stylesheet, baseURI, workStylesheet);
1527
1535
  const imports = getImportFunctions(stylesheet);
1528
- await Promise.all(
1529
- imports.map(async node => {
1530
- const urlNode = cssTree.find(node, node => node.type == "Url") || cssTree.find(node, node => node.type == "String");
1531
- if (urlNode) {
1532
- let resourceURL = normalizeURL(urlNode.value);
1533
- if (!testIgnoredPath(resourceURL) && testValidPath(resourceURL)) {
1534
- urlNode.value = util.EMPTY_RESOURCE;
1535
- try {
1536
- resourceURL = util.resolveURL(resourceURL, baseURI);
1537
- } catch (error) {
1538
- // ignored
1536
+ await Promise.all(imports.map(async node => {
1537
+ const urlNode = cssTree.find(node, node => node.type == "Url") || cssTree.find(node, node => node.type == "String");
1538
+ if (urlNode) {
1539
+ let resourceURL = normalizeURL(urlNode.value);
1540
+ if (!testIgnoredPath(resourceURL) && testValidPath(resourceURL)) {
1541
+ urlNode.value = util.EMPTY_RESOURCE;
1542
+ try {
1543
+ resourceURL = util.resolveURL(resourceURL, baseURI);
1544
+ } catch (error) {
1545
+ // ignored
1546
+ }
1547
+ if (testValidURL(resourceURL) && !importedStyleSheets.has(resourceURL)) {
1548
+ const content = await getStylesheetContent(resourceURL);
1549
+ resourceURL = content.resourceURL;
1550
+ content.data = getUpdatedResourceContent(resourceURL, content, options);
1551
+ if (content.data && content.data.match(/^<!doctype /i)) {
1552
+ content.data = "";
1539
1553
  }
1540
- if (testValidURL(resourceURL) && !importedStyleSheets.has(resourceURL)) {
1541
- const content = await getStylesheetContent(resourceURL);
1542
- resourceURL = content.resourceURL;
1543
- content.data = getUpdatedResourceContent(resourceURL, content, options);
1544
- if (content.data && content.data.match(/^<!doctype /i)) {
1545
- content.data = "";
1546
- }
1547
- const mediaQueryListNode = cssTree.find(node, node => node.type == "MediaQueryList");
1548
- if (mediaQueryListNode) {
1549
- content.data = wrapMediaQuery(content.data, cssTree.generate(mediaQueryListNode));
1550
- }
1551
- const importedStylesheet = cssTree.parse(content.data, { context: "stylesheet", parseCustomProperty: true });
1552
- const ancestorStyleSheets = new Set(importedStyleSheets);
1553
- ancestorStyleSheets.add(resourceURL);
1554
- await ProcessorHelper.resolveImportURLs(importedStylesheet, resourceURL, options, workStylesheet, ancestorStyleSheets);
1555
- for (let keyName of Object.keys(importedStylesheet)) {
1556
- node[keyName] = importedStylesheet[keyName];
1557
- }
1558
- importFound = true;
1554
+ const mediaQueryListNode = cssTree.find(node, node => node.type == "MediaQueryList");
1555
+ if (mediaQueryListNode) {
1556
+ content.data = wrapMediaQuery(content.data, cssTree.generate(mediaQueryListNode));
1557
+ }
1558
+ const importedStylesheet = cssTree.parse(content.data, { context: "stylesheet", parseCustomProperty: true });
1559
+ const ancestorStyleSheets = new Set(importedStyleSheets);
1560
+ ancestorStyleSheets.add(resourceURL);
1561
+ await ProcessorHelper.resolveImportURLs(importedStylesheet, resourceURL, options, workStylesheet, ancestorStyleSheets);
1562
+ for (let keyName of Object.keys(importedStylesheet)) {
1563
+ node[keyName] = importedStylesheet[keyName];
1559
1564
  }
1565
+ importFound = true;
1560
1566
  }
1561
1567
  }
1562
- })
1563
- );
1568
+ }
1569
+ }));
1564
1570
  return importFound;
1565
1571
 
1566
1572
  async function getStylesheetContent(resourceURL) {
@@ -1685,153 +1691,147 @@ class ProcessorHelper {
1685
1691
 
1686
1692
  async function processFontFaceRule(ruleData) {
1687
1693
  const urls = getUrlFunctions(ruleData);
1688
- await Promise.all(
1689
- urls.map(async urlNode => {
1690
- const originalResourceURL = urlNode.value;
1691
- if (!options.blockFonts) {
1692
- const resourceURL = normalizeURL(originalResourceURL);
1693
- if (!testIgnoredPath(resourceURL) && testValidURL(resourceURL)) {
1694
- let { content } = await batchRequest.addURL(resourceURL, { asBinary: true, expectedType: "font", baseURI, blockMixedContent: options.blockMixedContent });
1695
- let resourceURLs = options.fontDeclarations.get(urlNode);
1696
- if (!resourceURLs) {
1697
- resourceURLs = [];
1698
- options.fontDeclarations.set(urlNode, resourceURLs);
1699
- }
1700
- resourceURLs.push(resourceURL);
1701
- if (!isDataURL(resourceURL) && options.saveOriginalURLs) {
1702
- urlNode.value = "-sf-url-original(" + JSON.stringify(originalResourceURL) + ") " + content;
1703
- } else {
1704
- urlNode.value = content;
1705
- }
1694
+ await Promise.all(urls.map(async urlNode => {
1695
+ const originalResourceURL = urlNode.value;
1696
+ if (!options.blockFonts) {
1697
+ const resourceURL = normalizeURL(originalResourceURL);
1698
+ if (!testIgnoredPath(resourceURL) && testValidURL(resourceURL)) {
1699
+ let { content } = await batchRequest.addURL(resourceURL, { asBinary: true, expectedType: "font", baseURI, blockMixedContent: options.blockMixedContent });
1700
+ let resourceURLs = options.fontDeclarations.get(urlNode);
1701
+ if (!resourceURLs) {
1702
+ resourceURLs = [];
1703
+ options.fontDeclarations.set(urlNode, resourceURLs);
1704
+ }
1705
+ resourceURLs.push(resourceURL);
1706
+ if (!isDataURL(resourceURL) && options.saveOriginalURLs) {
1707
+ urlNode.value = "-sf-url-original(" + JSON.stringify(originalResourceURL) + ") " + content;
1708
+ } else {
1709
+ urlNode.value = content;
1706
1710
  }
1707
- } else {
1708
- urlNode.value = util.EMPTY_RESOURCE;
1709
1711
  }
1710
- })
1711
- );
1712
+ } else {
1713
+ urlNode.value = util.EMPTY_RESOURCE;
1714
+ }
1715
+ }));
1712
1716
  }
1713
1717
  }
1714
1718
 
1715
1719
  static async processStyle(ruleData, baseURI, options, cssVariables, batchRequest) {
1716
1720
  const urls = getUrlFunctions(ruleData);
1717
- await Promise.all(
1718
- urls.map(async urlNode => {
1719
- const originalResourceURL = urlNode.value;
1720
- if (!options.blockImages) {
1721
- const resourceURL = normalizeURL(originalResourceURL);
1722
- if (!testIgnoredPath(resourceURL) && testValidURL(resourceURL)) {
1723
- let { content, indexResource, duplicate } = await batchRequest.addURL(resourceURL, { asBinary: true, expectedType: "image", groupDuplicates: options.groupDuplicateImages });
1724
- if (!originalResourceURL.startsWith("#")) {
1725
- const maxSizeDuplicateImages = options.maxSizeDuplicateImages || SINGLE_FILE_VARIABLE_MAX_SIZE;
1726
- if (duplicate && options.groupDuplicateImages && util.getContentSize(content) < maxSizeDuplicateImages) {
1727
- const varNode = cssTree.parse("var(" + SINGLE_FILE_VARIABLE_NAME_PREFIX + indexResource + ")", { context: "value" });
1728
- for (let keyName of Object.keys(varNode.children.head.data)) {
1729
- urlNode[keyName] = varNode.children.head.data[keyName];
1730
- }
1731
- cssVariables.set(indexResource, { content, url: originalResourceURL });
1721
+ await Promise.all(urls.map(async urlNode => {
1722
+ const originalResourceURL = urlNode.value;
1723
+ if (!options.blockImages) {
1724
+ const resourceURL = normalizeURL(originalResourceURL);
1725
+ if (!testIgnoredPath(resourceURL) && testValidURL(resourceURL)) {
1726
+ let { content, indexResource, duplicate } = await batchRequest.addURL(resourceURL, { asBinary: true, expectedType: "image", groupDuplicates: options.groupDuplicateImages });
1727
+ if (!originalResourceURL.startsWith("#")) {
1728
+ const maxSizeDuplicateImages = options.maxSizeDuplicateImages || SINGLE_FILE_VARIABLE_MAX_SIZE;
1729
+ if (duplicate && options.groupDuplicateImages && util.getContentSize(content) < maxSizeDuplicateImages) {
1730
+ const varNode = cssTree.parse("var(" + SINGLE_FILE_VARIABLE_NAME_PREFIX + indexResource + ")", { context: "value" });
1731
+ for (let keyName of Object.keys(varNode.children.head.data)) {
1732
+ urlNode[keyName] = varNode.children.head.data[keyName];
1733
+ }
1734
+ cssVariables.set(indexResource, { content, url: originalResourceURL });
1735
+ } else {
1736
+ if (!isDataURL(resourceURL) && options.saveOriginalURLs) {
1737
+ urlNode.value = "-sf-url-original(" + JSON.stringify(originalResourceURL) + ") " + content;
1732
1738
  } else {
1733
- if (!isDataURL(resourceURL) && options.saveOriginalURLs) {
1734
- urlNode.value = "-sf-url-original(" + JSON.stringify(originalResourceURL) + ") " + content;
1735
- } else {
1736
- urlNode.value = content;
1737
- }
1739
+ urlNode.value = content;
1738
1740
  }
1739
1741
  }
1740
1742
  }
1741
- } else {
1742
- urlNode.value = util.EMPTY_RESOURCE;
1743
1743
  }
1744
- })
1745
- );
1744
+ } else {
1745
+ urlNode.value = util.EMPTY_RESOURCE;
1746
+ }
1747
+ }));
1746
1748
  }
1747
1749
 
1748
- static async processAttribute(resourceElements, attributeName, baseURI, options, expectedType, cssVariables, styles, batchRequest, processDuplicates, removeElementIfMissing) {
1749
- await Promise.all(
1750
- Array.from(resourceElements).map(async resourceElement => {
1751
- let resourceURL = resourceElement.getAttribute(attributeName);
1752
- if (resourceURL != null) {
1753
- resourceURL = normalizeURL(resourceURL);
1754
- let originURL = resourceElement.dataset.singleFileOriginURL;
1755
- if (options.saveOriginalURLs && !isDataURL(resourceURL)) {
1756
- resourceElement.setAttribute("data-sf-original-" + attributeName, resourceURL);
1757
- }
1758
- delete resourceElement.dataset.singleFileOriginURL;
1759
- if (!options["block" + expectedType.charAt(0).toUpperCase() + expectedType.substring(1) + "s"]) {
1760
- if (!testIgnoredPath(resourceURL)) {
1761
- setAttributeEmpty(resourceElement, attributeName, expectedType);
1762
- if (testValidPath(resourceURL)) {
1763
- try {
1764
- resourceURL = util.resolveURL(resourceURL, baseURI);
1765
- } catch (error) {
1766
- // ignored
1767
- }
1768
- if (testValidURL(resourceURL)) {
1769
- let { content, indexResource, duplicate } = await batchRequest.addURL(
1770
- resourceURL,
1771
- { asBinary: true, expectedType, groupDuplicates: options.groupDuplicateImages && resourceElement.tagName.toUpperCase() == "IMG" && attributeName == "src" });
1772
- if (originURL) {
1773
- if (content == util.EMPTY_RESOURCE) {
1774
- try {
1775
- originURL = util.resolveURL(originURL, baseURI);
1776
- } catch (error) {
1777
- // ignored
1778
- }
1779
- try {
1780
- resourceURL = originURL;
1781
- content = (
1782
- await util.getContent(resourceURL, {
1783
- asBinary: true,
1784
- expectedType,
1785
- maxResourceSize: options.maxResourceSize,
1786
- maxResourceSizeEnabled: options.maxResourceSizeEnabled,
1787
- frameId: options.windowId,
1788
- resourceReferrer: options.resourceReferrer,
1789
- acceptHeaders: options.acceptHeaders,
1790
- networkTimeout: options.networkTimeout
1791
- })
1792
- ).data;
1793
- } catch (error) {
1794
- // ignored
1795
- }
1750
+ static async processAttribute(doc, resourceElements, attributeName, baseURI, options, expectedType, cssVariables, styles, batchRequest, processDuplicates, removeElementIfMissing) {
1751
+ await Promise.all(Array.from(resourceElements).map(async resourceElement => {
1752
+ let resourceURL = resourceElement.getAttribute(attributeName);
1753
+ if (resourceURL != null) {
1754
+ resourceURL = normalizeURL(resourceURL);
1755
+ let originURL = resourceElement.dataset.singleFileOriginURL;
1756
+ if (options.saveOriginalURLs && !isDataURL(resourceURL)) {
1757
+ resourceElement.setAttribute("data-sf-original-" + attributeName, resourceURL);
1758
+ }
1759
+ delete resourceElement.dataset.singleFileOriginURL;
1760
+ if (!options["block" + expectedType.charAt(0).toUpperCase() + expectedType.substring(1) + "s"]) {
1761
+ if (!testIgnoredPath(resourceURL)) {
1762
+ setAttributeEmpty(resourceElement, attributeName, expectedType);
1763
+ if (testValidPath(resourceURL)) {
1764
+ try {
1765
+ resourceURL = util.resolveURL(resourceURL, baseURI);
1766
+ } catch (error) {
1767
+ // ignored
1768
+ }
1769
+ if (testValidURL(resourceURL)) {
1770
+ let { content, indexResource, duplicate } = await batchRequest.addURL(
1771
+ resourceURL,
1772
+ { asBinary: true, expectedType, groupDuplicates: options.groupDuplicateImages && resourceElement.tagName.toUpperCase() == "IMG" && attributeName == "src" });
1773
+ if (originURL) {
1774
+ if (content == util.EMPTY_RESOURCE) {
1775
+ try {
1776
+ originURL = util.resolveURL(originURL, baseURI);
1777
+ } catch (error) {
1778
+ // ignored
1796
1779
  }
1797
- }
1798
- if (removeElementIfMissing && content == util.EMPTY_RESOURCE) {
1799
- resourceElement.remove();
1800
- } else if (content !== util.EMPTY_RESOURCE) {
1801
- let forbiddenPrefixFound = PREFIXES_FORBIDDEN_DATA_URI.filter(prefixDataURI => content.startsWith(prefixDataURI)).length;
1802
- if (forbiddenPrefixFound) {
1803
- forbiddenPrefixFound = await new Promise((resolve) => {
1804
- const element = options.doc.createElement(resourceElement.tagName);
1805
- element.setAttribute(attributeName, content);
1806
- element.onload = () => resolve();
1807
- element.onerror = () => resolve(true);
1808
- });
1780
+ try {
1781
+ resourceURL = originURL;
1782
+ content = (
1783
+ await util.getContent(resourceURL, {
1784
+ asBinary: true,
1785
+ expectedType,
1786
+ maxResourceSize: options.maxResourceSize,
1787
+ maxResourceSizeEnabled: options.maxResourceSizeEnabled,
1788
+ frameId: options.windowId,
1789
+ resourceReferrer: options.resourceReferrer,
1790
+ acceptHeaders: options.acceptHeaders,
1791
+ networkTimeout: options.networkTimeout
1792
+ })
1793
+ ).data;
1794
+ } catch (error) {
1795
+ // ignored
1809
1796
  }
1810
- if (!forbiddenPrefixFound) {
1811
- const isSVG = content.startsWith(PREFIX_DATA_URI_IMAGE_SVG);
1812
- const maxSizeDuplicateImages = options.maxSizeDuplicateImages || SINGLE_FILE_VARIABLE_MAX_SIZE;
1813
- if (expectedType == "image" && processDuplicates && duplicate && !isSVG && util.getContentSize(content) < maxSizeDuplicateImages) {
1814
- if (ProcessorHelper.replaceImageSource(resourceElement, SINGLE_FILE_VARIABLE_NAME_PREFIX + indexResource, options)) {
1815
- cssVariables.set(indexResource, { content, url: originURL });
1816
- const declarationList = cssTree.parse(resourceElement.getAttribute("style"), { context: "declarationList", parseCustomProperty: true });
1817
- styles.set(resourceElement, declarationList);
1818
- } else {
1819
- resourceElement.setAttribute(attributeName, content);
1820
- }
1797
+ }
1798
+ }
1799
+ if (removeElementIfMissing && content == util.EMPTY_RESOURCE) {
1800
+ resourceElement.remove();
1801
+ } else if (content !== util.EMPTY_RESOURCE) {
1802
+ let forbiddenPrefixFound = PREFIXES_FORBIDDEN_DATA_URI.filter(prefixDataURI => content.startsWith(prefixDataURI)).length;
1803
+ if (forbiddenPrefixFound) {
1804
+ forbiddenPrefixFound = await new Promise((resolve) => {
1805
+ const element = doc.createElement(resourceElement.tagName);
1806
+ element.setAttribute(attributeName, content);
1807
+ element.onload = () => resolve();
1808
+ element.onerror = () => resolve(true);
1809
+ });
1810
+ }
1811
+ if (!forbiddenPrefixFound) {
1812
+ const isSVG = content.startsWith(PREFIX_DATA_URI_IMAGE_SVG);
1813
+ const maxSizeDuplicateImages = options.maxSizeDuplicateImages || SINGLE_FILE_VARIABLE_MAX_SIZE;
1814
+ if (expectedType == "image" && processDuplicates && duplicate && !isSVG && util.getContentSize(content) < maxSizeDuplicateImages) {
1815
+ if (ProcessorHelper.replaceImageSource(resourceElement, SINGLE_FILE_VARIABLE_NAME_PREFIX + indexResource, options)) {
1816
+ cssVariables.set(indexResource, { content, url: originURL });
1817
+ const declarationList = cssTree.parse(resourceElement.getAttribute("style"), { context: "declarationList", parseCustomProperty: true });
1818
+ styles.set(resourceElement, declarationList);
1821
1819
  } else {
1822
1820
  resourceElement.setAttribute(attributeName, content);
1823
1821
  }
1822
+ } else {
1823
+ resourceElement.setAttribute(attributeName, content);
1824
1824
  }
1825
1825
  }
1826
1826
  }
1827
1827
  }
1828
1828
  }
1829
- } else {
1830
- setAttributeEmpty(resourceElement, attributeName, expectedType);
1831
1829
  }
1830
+ } else {
1831
+ setAttributeEmpty(resourceElement, attributeName, expectedType);
1832
1832
  }
1833
- })
1834
- );
1833
+ }
1834
+ }));
1835
1835
 
1836
1836
  function setAttributeEmpty(resourceElement, attributeName, expectedType) {
1837
1837
  if (expectedType == "video" || expectedType == "audio") {
@@ -1844,102 +1844,96 @@ class ProcessorHelper {
1844
1844
 
1845
1845
  static async processXLinks(resourceElements, doc, baseURI, options, batchRequest) {
1846
1846
  let attributeName = "xlink:href";
1847
- await Promise.all(
1848
- Array.from(resourceElements).map(async resourceElement => {
1849
- let originalResourceURL = resourceElement.getAttribute(attributeName);
1850
- if (originalResourceURL == null) {
1851
- attributeName = "href";
1852
- originalResourceURL = resourceElement.getAttribute(attributeName);
1853
- }
1854
- if (options.saveOriginalURLs && !isDataURL(originalResourceURL)) {
1855
- resourceElement.setAttribute("data-sf-original-href", originalResourceURL);
1856
- }
1857
- let resourceURL = normalizeURL(originalResourceURL);
1858
- if (!options.blockImages) {
1859
- if (testValidPath(resourceURL) && !testIgnoredPath(resourceURL)) {
1860
- resourceElement.setAttribute(attributeName, util.EMPTY_RESOURCE);
1861
- try {
1862
- resourceURL = util.resolveURL(resourceURL, baseURI);
1863
- } catch (error) {
1864
- // ignored
1865
- }
1866
- if (testValidURL(resourceURL)) {
1867
- const hashMatch = originalResourceURL.match(REGEXP_URL_HASH);
1868
- if (originalResourceURL.startsWith(baseURI + "#")) {
1869
- resourceElement.setAttribute(attributeName, hashMatch[0]);
1870
- } else {
1871
- const response = await batchRequest.addURL(resourceURL, { expectedType: "image" });
1872
- const svgDoc = util.parseSVGContent(response.content);
1873
- if (hashMatch && hashMatch[0]) {
1874
- let symbolElement;
1875
- try {
1876
- symbolElement = svgDoc.querySelector(hashMatch[0]);
1877
- } catch (error) {
1878
- // ignored
1879
- }
1880
- if (symbolElement) {
1881
- resourceElement.setAttribute(attributeName, hashMatch[0]);
1882
- resourceElement.parentElement.insertBefore(symbolElement, resourceElement.parentElement.firstChild);
1883
- }
1884
- } else {
1885
- const content = await batchRequest.addURL(resourceURL, { expectedType: "image" });
1886
- resourceElement.setAttribute(attributeName, PREFIX_DATA_URI_IMAGE_SVG + "," + content);
1847
+ await Promise.all(Array.from(resourceElements).map(async resourceElement => {
1848
+ let originalResourceURL = resourceElement.getAttribute(attributeName);
1849
+ if (originalResourceURL == null) {
1850
+ attributeName = "href";
1851
+ originalResourceURL = resourceElement.getAttribute(attributeName);
1852
+ }
1853
+ if (options.saveOriginalURLs && !isDataURL(originalResourceURL)) {
1854
+ resourceElement.setAttribute("data-sf-original-href", originalResourceURL);
1855
+ }
1856
+ let resourceURL = normalizeURL(originalResourceURL);
1857
+ if (!options.blockImages) {
1858
+ if (testValidPath(resourceURL) && !testIgnoredPath(resourceURL)) {
1859
+ resourceElement.setAttribute(attributeName, util.EMPTY_RESOURCE);
1860
+ try {
1861
+ resourceURL = util.resolveURL(resourceURL, baseURI);
1862
+ } catch (error) {
1863
+ // ignored
1864
+ }
1865
+ if (testValidURL(resourceURL)) {
1866
+ const hashMatch = originalResourceURL.match(REGEXP_URL_HASH);
1867
+ if (originalResourceURL.startsWith(baseURI + "#")) {
1868
+ resourceElement.setAttribute(attributeName, hashMatch[0]);
1869
+ } else {
1870
+ const response = await batchRequest.addURL(resourceURL, { expectedType: "image" });
1871
+ const svgDoc = util.parseSVGContent(response.content);
1872
+ if (hashMatch && hashMatch[0]) {
1873
+ let symbolElement;
1874
+ try {
1875
+ symbolElement = svgDoc.querySelector(hashMatch[0]);
1876
+ } catch (error) {
1877
+ // ignored
1878
+ }
1879
+ if (symbolElement) {
1880
+ resourceElement.setAttribute(attributeName, hashMatch[0]);
1881
+ resourceElement.parentElement.insertBefore(symbolElement, resourceElement.parentElement.firstChild);
1887
1882
  }
1883
+ } else {
1884
+ const content = await batchRequest.addURL(resourceURL, { expectedType: "image" });
1885
+ resourceElement.setAttribute(attributeName, PREFIX_DATA_URI_IMAGE_SVG + "," + content);
1888
1886
  }
1889
1887
  }
1890
- } else if (resourceURL == options.url) {
1891
- resourceElement.setAttribute(attributeName, originalResourceURL.substring(resourceURL.length));
1892
1888
  }
1893
- } else {
1894
- resourceElement.setAttribute(attributeName, util.EMPTY_RESOURCE);
1889
+ } else if (resourceURL == options.url) {
1890
+ resourceElement.setAttribute(attributeName, originalResourceURL.substring(resourceURL.length));
1895
1891
  }
1896
- })
1897
- );
1892
+ } else {
1893
+ resourceElement.setAttribute(attributeName, util.EMPTY_RESOURCE);
1894
+ }
1895
+ }));
1898
1896
  }
1899
1897
 
1900
1898
  static async processSrcset(resourceElements, baseURI, options, batchRequest) {
1901
- await Promise.all(
1902
- Array.from(resourceElements).map(async resourceElement => {
1903
- const originSrcset = resourceElement.getAttribute("srcset");
1904
- const srcset = util.parseSrcset(originSrcset);
1905
- if (options.saveOriginalURLs && !isDataURL(originSrcset)) {
1906
- resourceElement.setAttribute("data-sf-original-srcset", originSrcset);
1907
- }
1908
- if (!options.blockImages) {
1909
- const srcsetValues = await Promise.all(
1910
- srcset.map(async srcsetValue => {
1911
- let resourceURL = normalizeURL(srcsetValue.url);
1912
- if (!testIgnoredPath(resourceURL)) {
1913
- if (testValidPath(resourceURL)) {
1914
- try {
1915
- resourceURL = util.resolveURL(resourceURL, baseURI);
1916
- } catch (error) {
1917
- // ignored
1918
- }
1919
- if (testValidURL(resourceURL)) {
1920
- const { content } = await batchRequest.addURL(resourceURL, { asBinary: true, expectedType: "image" });
1921
- const forbiddenPrefixFound = PREFIXES_FORBIDDEN_DATA_URI.filter(prefixDataURI => content.startsWith(prefixDataURI)).length;
1922
- if (forbiddenPrefixFound) {
1923
- return "";
1924
- }
1925
- return content + (srcsetValue.w ? " " + srcsetValue.w + "w" : srcsetValue.d ? " " + srcsetValue.d + "x" : "");
1926
- } else {
1927
- return "";
1928
- }
1929
- } else {
1899
+ await Promise.all(Array.from(resourceElements).map(async resourceElement => {
1900
+ const originSrcset = resourceElement.getAttribute("srcset");
1901
+ const srcset = util.parseSrcset(originSrcset);
1902
+ if (options.saveOriginalURLs && !isDataURL(originSrcset)) {
1903
+ resourceElement.setAttribute("data-sf-original-srcset", originSrcset);
1904
+ }
1905
+ if (!options.blockImages) {
1906
+ const srcsetValues = await Promise.all(srcset.map(async srcsetValue => {
1907
+ let resourceURL = normalizeURL(srcsetValue.url);
1908
+ if (!testIgnoredPath(resourceURL)) {
1909
+ if (testValidPath(resourceURL)) {
1910
+ try {
1911
+ resourceURL = util.resolveURL(resourceURL, baseURI);
1912
+ } catch (error) {
1913
+ // ignored
1914
+ }
1915
+ if (testValidURL(resourceURL)) {
1916
+ const { content } = await batchRequest.addURL(resourceURL, { asBinary: true, expectedType: "image" });
1917
+ const forbiddenPrefixFound = PREFIXES_FORBIDDEN_DATA_URI.filter(prefixDataURI => content.startsWith(prefixDataURI)).length;
1918
+ if (forbiddenPrefixFound) {
1930
1919
  return "";
1931
1920
  }
1921
+ return content + (srcsetValue.w ? " " + srcsetValue.w + "w" : srcsetValue.d ? " " + srcsetValue.d + "x" : "");
1932
1922
  } else {
1933
- return resourceURL + (srcsetValue.w ? " " + srcsetValue.w + "w" : srcsetValue.d ? " " + srcsetValue.d + "x" : "");
1923
+ return "";
1934
1924
  }
1935
- })
1936
- );
1937
- resourceElement.setAttribute("srcset", srcsetValues.join(", "));
1938
- } else {
1939
- resourceElement.setAttribute("srcset", "");
1940
- }
1941
- })
1942
- );
1925
+ } else {
1926
+ return "";
1927
+ }
1928
+ } else {
1929
+ return resourceURL + (srcsetValue.w ? " " + srcsetValue.w + "w" : srcsetValue.d ? " " + srcsetValue.d + "x" : "");
1930
+ }
1931
+ }));
1932
+ resourceElement.setAttribute("srcset", srcsetValues.join(", "));
1933
+ } else {
1934
+ resourceElement.setAttribute("srcset", "");
1935
+ }
1936
+ }));
1943
1937
  }
1944
1938
 
1945
1939
  static replaceImageSource(imgElement, variableName, options) {
@@ -2137,4 +2131,6 @@ class Stats {
2137
2131
  }
2138
2132
  }
2139
2133
 
2140
- export { getClass };
2134
+ export {
2135
+ getClass
2136
+ };