single-file-core 1.0.44 → 1.0.46

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.
@@ -1,23 +1,23 @@
1
1
  /*
2
2
  * Copyright 2010-2022 Gildas Lormeau
3
3
  * contact : gildas.lormeau <at> gmail.com
4
- *
4
+ *
5
5
  * This file is part of SingleFile.
6
6
  *
7
- * The code in this file is free software: you can redistribute it and/or
8
- * modify it under the terms of the GNU Affero General Public License
7
+ * The code in this file is free software: you can redistribute it and/or
8
+ * modify it under the terms of the GNU Affero General Public License
9
9
  * (GNU AGPL) as published by the Free Software Foundation, either version 3
10
10
  * of the License, or (at your option) any later version.
11
- *
12
- * The code in this file is distributed in the hope that it will be useful,
13
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
11
+ *
12
+ * The code in this file is distributed in the hope that it will be useful,
13
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
15
15
  * General Public License for more details.
16
16
  *
17
- * As additional permission under GNU AGPL version 3 section 7, you may
18
- * distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU
19
- * AGPL normally required by section 4, provided you include this license
20
- * notice and a URL through which recipients can access the Corresponding
17
+ * As additional permission under GNU AGPL version 3 section 7, you may
18
+ * distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU
19
+ * AGPL normally required by section 4, provided you include this license
20
+ * notice and a URL through which recipients can access the Corresponding
21
21
  * Source.
22
22
  */
23
23
 
@@ -91,67 +91,52 @@ 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
- 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: "replaceCanvasElements" },
103
- { action: "insertFonts" },
104
- { action: "insertShadowRootContents" },
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
- }];
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: "replaceCanvasElements" },
104
+ { action: "insertFonts" },
105
+ { action: "insertShadowRootContents" },
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
+ ];
155
140
 
156
141
  class Runner {
157
142
  constructor(options, root) {
@@ -185,7 +170,7 @@ class Runner {
185
170
  this.options.removeFrames = true;
186
171
  }
187
172
  this.options.content = this.options.content || (rootDocDefined ? util.serialize(this.options.doc) : null);
188
- this.onprogress = options.onprogress || (() => { });
173
+ this.onprogress = options.onprogress || (() => {});
189
174
  }
190
175
 
191
176
  async loadPage() {
@@ -274,21 +259,23 @@ class Runner {
274
259
  });
275
260
  let parallelTasksPromise;
276
261
  if (STAGES[step].parallel) {
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
- }));
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
+ );
292
279
  } else {
293
280
  parallelTasksPromise = Promise.resolve();
294
281
  }
@@ -300,7 +287,7 @@ class Runner {
300
287
  }
301
288
 
302
289
  executeTask(task) {
303
- if (!task.option || ((task.option.startsWith("!") && !this.options[task.option]) || this.options[task.option])) {
290
+ if (!task.option || (task.option.startsWith("!") && !this.options[task.option]) || this.options[task.option]) {
304
291
  return this.processor[task.action]();
305
292
  }
306
293
  }
@@ -343,39 +330,41 @@ class BatchRequest {
343
330
  run(onloadListener, options) {
344
331
  const resourceURLs = [...this.requests.keys()];
345
332
  let indexResource = 0;
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 });
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
370
351
  });
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));
371
364
  }
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
- }));
365
+ this.requests.delete(requestKey);
366
+ })
367
+ );
379
368
  }
380
369
 
381
370
  cancel() {
@@ -421,7 +410,7 @@ class Processor {
421
410
  }
422
411
  this.maxResources = this.batchRequest.getMaxResources();
423
412
  if (!this.options.saveRawPage && !this.options.removeFrames && this.options.frames) {
424
- this.options.frames.forEach(frameData => this.maxResources += frameData.maxResources || 0);
413
+ this.options.frames.forEach(frameData => (this.maxResources += frameData.maxResources || 0));
425
414
  }
426
415
  this.stats.set("processed", "resources", this.maxResources);
427
416
  }
@@ -470,7 +459,8 @@ class Processor {
470
459
  const url = util.parseURL(this.baseURI);
471
460
  if (this.options.insertSingleFileComment) {
472
461
  const firstComment = this.doc.documentElement.firstChild;
473
- let infobarURL = this.options.saveUrl, infobarSaveDate = this.options.saveDate;
462
+ let infobarURL = this.options.saveUrl,
463
+ infobarSaveDate = this.options.saveDate;
474
464
  if (firstComment.nodeType == 8 && (firstComment.textContent.includes(util.COMMENT_HEADER_LEGACY) || firstComment.textContent.includes(util.COMMENT_HEADER))) {
475
465
  const info = this.doc.documentElement.firstChild.textContent.split("\n");
476
466
  try {
@@ -483,10 +473,9 @@ class Processor {
483
473
  }
484
474
  }
485
475
  const infobarContent = (this.options.infobarContent || "").replace(/\\n/g, "\n").replace(/\\t/g, "\t");
486
- const commentNode = this.doc.createComment("\n " + (this.options.useLegacyCommentHeader ? util.COMMENT_HEADER_LEGACY : util.COMMENT_HEADER) +
487
- " \n url: " + infobarURL +
488
- (this.options.removeSavedDate ? " " : " \n saved date: " + infobarSaveDate) +
489
- (infobarContent ? " \n info: " + infobarContent : "") + "\n");
476
+ 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"
478
+ );
490
479
  this.doc.documentElement.insertBefore(commentNode, this.doc.documentElement.firstChild);
491
480
  }
492
481
  if (this.options.insertCanonicalLink && this.options.saveUrl.match(HTTP_URI_PREFIX)) {
@@ -528,19 +517,16 @@ class Processor {
528
517
  this.stats.set("processed", "HTML bytes", contentSize);
529
518
  this.stats.add("discarded", "HTML bytes", size - contentSize);
530
519
  }
531
- let filename = await ProcessorHelper.evalTemplate(this.options.filenameTemplate, this.options, content) || "";
520
+ let filename = (await ProcessorHelper.evalTemplate(this.options.filenameTemplate, this.options, content)) || "";
532
521
  const replacementCharacter = this.options.filenameReplacementCharacter;
533
522
  filename = util.getValidFilename(filename, this.options.filenameReplacedCharacters, replacementCharacter);
534
523
  if (!this.options.backgroundSave) {
535
524
  filename = filename.replace(/\//g, replacementCharacter);
536
525
  }
537
- if (!this.options.keepFilename &&
538
- ((this.options.filenameMaxLengthUnit == "bytes" && util.getContentSize(filename) > this.options.filenameMaxLength) || (filename.length > this.options.filenameMaxLength))) {
526
+ if (!this.options.keepFilename && ((this.options.filenameMaxLengthUnit == "bytes" && util.getContentSize(filename) > this.options.filenameMaxLength) || filename.length > this.options.filenameMaxLength)) {
539
527
  const extensionMatch = filename.match(/(\.[^.]{3,4})$/);
540
528
  const extension = extensionMatch && extensionMatch[0] && extensionMatch[0].length > 1 ? extensionMatch[0] : "";
541
- filename = this.options.filenameMaxLengthUnit == "bytes" ?
542
- await util.truncateText(filename, this.options.filenameMaxLength - extension.length) :
543
- filename.substring(0, this.options.filenameMaxLength - extension.length);
529
+ filename = this.options.filenameMaxLengthUnit == "bytes" ? await util.truncateText(filename, this.options.filenameMaxLength - extension.length) : filename.substring(0, this.options.filenameMaxLength - extension.length);
544
530
  filename = filename + "…" + extension;
545
531
  }
546
532
  if (!filename) {
@@ -549,7 +535,7 @@ class Processor {
549
535
  const matchTitle = this.baseURI.match(/([^/]*)\/?(\.html?.*)$/) || this.baseURI.match(/\/\/([^/]*)\/?$/);
550
536
  const pageData = {
551
537
  stats: this.stats.data,
552
- title: this.options.title || (this.baseURI && matchTitle ? matchTitle[1] : (url.hostname ? url.hostname : "")),
538
+ title: this.options.title || (this.baseURI && matchTitle ? matchTitle[1] : url.hostname ? url.hostname : ""),
553
539
  filename,
554
540
  content
555
541
  };
@@ -572,10 +558,7 @@ class Processor {
572
558
  if (attributeValue) {
573
559
  const imageData = this.options.images[Number(attributeValue)];
574
560
  if (imageData) {
575
- if (this.options.removeHiddenElements && (
576
- (imageData.size && !imageData.size.pxWidth && !imageData.size.pxHeight) ||
577
- (imgElement.getAttribute(util.HIDDEN_CONTENT_ATTRIBUTE_NAME) == "")
578
- )) {
561
+ if (this.options.removeHiddenElements && ((imageData.size && !imageData.size.pxWidth && !imageData.size.pxHeight) || imgElement.getAttribute(util.HIDDEN_CONTENT_ATTRIBUTE_NAME) == "")) {
579
562
  imgElement.setAttribute("src", util.EMPTY_RESOURCE);
580
563
  } else {
581
564
  if (imageData.currentSrc) {
@@ -658,7 +641,7 @@ class Processor {
658
641
  function canHideNode(node) {
659
642
  if (node.nodeType == 1) {
660
643
  const tagName = node.tagName && node.tagName.toLowerCase();
661
- return (tagName != "svg" && tagName != "style" && tagName != "link");
644
+ return tagName != "svg" && tagName != "style" && tagName != "link";
662
645
  }
663
646
  }
664
647
  }
@@ -684,7 +667,8 @@ class Processor {
684
667
  }
685
668
 
686
669
  insertVideoLinks() {
687
- 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";
670
+ const LINK_ICON =
671
+ "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";
688
672
  const ICON_SIZE = "16px";
689
673
  this.doc.querySelectorAll("video").forEach(videoElement => {
690
674
  const attributeValue = videoElement.getAttribute(util.VIDEO_ATTRIBUTE_NAME);
@@ -771,7 +755,10 @@ class Processor {
771
755
  objectElements.forEach(element => element.remove());
772
756
  const replacedAttributeValue = this.doc.querySelectorAll("link[rel~=preconnect], link[rel~=prerender], link[rel~=dns-prefetch], link[rel~=preload], link[rel~=manifest], link[rel~=prefetch]");
773
757
  replacedAttributeValue.forEach(element => {
774
- const relValue = element.getAttribute("rel").replace(/(preconnect|prerender|dns-prefetch|preload|prefetch|manifest)/g, "").trim();
758
+ const relValue = element
759
+ .getAttribute("rel")
760
+ .replace(/(preconnect|prerender|dns-prefetch|preload|prefetch|manifest)/g, "")
761
+ .trim();
775
762
  if (relValue.length) {
776
763
  element.setAttribute("rel", relValue);
777
764
  } else {
@@ -890,7 +877,8 @@ class Processor {
890
877
 
891
878
  insertFonts() {
892
879
  if (this.options.fonts && this.options.fonts.length) {
893
- let firstStylesheet = this.doc.querySelector("style, link[rel=stylesheet]"), previousStyleElement;
880
+ let firstStylesheet = this.doc.querySelector("style, link[rel=stylesheet]"),
881
+ previousStyleElement;
894
882
  this.options.fonts.forEach(fontData => {
895
883
  if (fontData["font-family"] && fontData.src) {
896
884
  let stylesheetContent = "@font-face{";
@@ -970,51 +958,53 @@ class Processor {
970
958
  }
971
959
 
972
960
  async insertMissingVideoPosters() {
973
- await Promise.all(Array.from(this.doc.querySelectorAll("video[src], video > source[src]")).map(async element => {
974
- let videoElement;
975
- if (element.tagName == "VIDEO") {
976
- videoElement = element;
977
- } else {
978
- videoElement = element.parentElement;
979
- }
980
- if (!videoElement.poster) {
981
- const attributeValue = videoElement.getAttribute(util.VIDEO_ATTRIBUTE_NAME);
982
- if (attributeValue) {
983
- const videoData = this.options.videos[Number(attributeValue)];
984
- const src = videoData.src || videoElement.src;
985
- if (src) {
986
- const temporaryVideoElement = this.doc.createElement("video");
987
- temporaryVideoElement.src = src;
988
- temporaryVideoElement.style.setProperty("width", videoData.size.pxWidth + "px", "important");
989
- temporaryVideoElement.style.setProperty("height", videoData.size.pxHeight + "px", "important");
990
- temporaryVideoElement.style.setProperty("display", "none", "important");
991
- temporaryVideoElement.crossOrigin = "anonymous";
992
- const canvasElement = this.doc.createElement("canvas");
993
- const context = canvasElement.getContext("2d");
994
- this.options.doc.body.appendChild(temporaryVideoElement);
995
- return new Promise(resolve => {
996
- temporaryVideoElement.currentTime = videoData.currentTime;
997
- temporaryVideoElement.oncanplay = () => {
998
- canvasElement.width = videoData.size.pxWidth;
999
- canvasElement.height = videoData.size.pxHeight;
1000
- context.drawImage(temporaryVideoElement, 0, 0, canvasElement.width, canvasElement.height);
1001
- try {
1002
- videoElement.poster = canvasElement.toDataURL("image/png", "");
1003
- } catch (error) {
1004
- // ignored
1005
- }
1006
- temporaryVideoElement.remove();
1007
- resolve();
1008
- };
1009
- temporaryVideoElement.onerror = () => {
1010
- temporaryVideoElement.remove();
1011
- resolve();
1012
- };
1013
- });
961
+ await Promise.all(
962
+ Array.from(this.doc.querySelectorAll("video[src], video > source[src]")).map(async element => {
963
+ let videoElement;
964
+ if (element.tagName == "VIDEO") {
965
+ videoElement = element;
966
+ } else {
967
+ videoElement = element.parentElement;
968
+ }
969
+ if (!videoElement.poster) {
970
+ const attributeValue = videoElement.getAttribute(util.VIDEO_ATTRIBUTE_NAME);
971
+ if (attributeValue) {
972
+ const videoData = this.options.videos[Number(attributeValue)];
973
+ const src = videoData.src || videoElement.src;
974
+ if (src) {
975
+ const temporaryVideoElement = this.doc.createElement("video");
976
+ temporaryVideoElement.src = src;
977
+ temporaryVideoElement.style.setProperty("width", videoData.size.pxWidth + "px", "important");
978
+ temporaryVideoElement.style.setProperty("height", videoData.size.pxHeight + "px", "important");
979
+ temporaryVideoElement.style.setProperty("display", "none", "important");
980
+ temporaryVideoElement.crossOrigin = "anonymous";
981
+ const canvasElement = this.doc.createElement("canvas");
982
+ const context = canvasElement.getContext("2d");
983
+ this.options.doc.body.appendChild(temporaryVideoElement);
984
+ return new Promise(resolve => {
985
+ temporaryVideoElement.currentTime = videoData.currentTime;
986
+ temporaryVideoElement.oncanplay = () => {
987
+ canvasElement.width = videoData.size.pxWidth;
988
+ canvasElement.height = videoData.size.pxHeight;
989
+ context.drawImage(temporaryVideoElement, 0, 0, canvasElement.width, canvasElement.height);
990
+ try {
991
+ videoElement.poster = canvasElement.toDataURL("image/png", "");
992
+ } catch (error) {
993
+ // ignored
994
+ }
995
+ temporaryVideoElement.remove();
996
+ resolve();
997
+ };
998
+ temporaryVideoElement.onerror = () => {
999
+ temporaryVideoElement.remove();
1000
+ resolve();
1001
+ };
1002
+ });
1003
+ }
1014
1004
  }
1015
1005
  }
1016
- }
1017
- }));
1006
+ })
1007
+ );
1018
1008
  }
1019
1009
 
1020
1010
  resolveStyleAttributeURLs() {
@@ -1031,34 +1021,40 @@ class Processor {
1031
1021
  }
1032
1022
 
1033
1023
  async resolveStylesheetURLs() {
1034
- await Promise.all(Array.from(this.doc.querySelectorAll("style, link[rel*=stylesheet]")).map(async element => {
1035
- const options = Object.assign({}, this.options, { charset: this.charset });
1036
- let mediaText;
1037
- if (element.media) {
1038
- mediaText = element.media.toLowerCase();
1039
- }
1040
- const scoped = Boolean(element.closest("[" + SHADOWROOT_ATTRIBUTE_NAME + "]"));
1041
- const stylesheetInfo = {
1042
- mediaText,
1043
- scoped
1044
- };
1045
- if (element.tagName == "LINK" && element.charset) {
1046
- options.charset = element.charset;
1047
- }
1048
- await processElement(element, stylesheetInfo, this.stylesheets, this.baseURI, options, this.workStyleElement);
1049
- }));
1050
- if (this.options.rootDocument) {
1051
- const newResources = Object.keys(this.options.updatedResources).filter(url => this.options.updatedResources[url].type == "stylesheet" && !this.options.updatedResources[url].retrieved).map(url => this.options.updatedResources[url]);
1052
- await Promise.all(newResources.map(async resource => {
1053
- resource.retrieved = true;
1054
- if (!this.options.blockStylesheets) {
1055
- const stylesheetInfo = {};
1056
- const element = this.doc.createElement("style");
1057
- this.doc.body.appendChild(element);
1058
- element.textContent = resource.content;
1059
- await processElement(element, stylesheetInfo, this.stylesheets, this.baseURI, this.options, this.workStyleElement);
1024
+ await Promise.all(
1025
+ 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 == "LINK" && element.charset) {
1037
+ options.charset = element.charset;
1060
1038
  }
1061
- }));
1039
+ await processElement(element, stylesheetInfo, this.stylesheets, this.baseURI, options, this.workStyleElement);
1040
+ })
1041
+ );
1042
+ if (this.options.rootDocument) {
1043
+ const newResources = Object.keys(this.options.updatedResources)
1044
+ .filter(url => this.options.updatedResources[url].type == "stylesheet" && !this.options.updatedResources[url].retrieved)
1045
+ .map(url => this.options.updatedResources[url]);
1046
+ await Promise.all(
1047
+ newResources.map(async resource => {
1048
+ resource.retrieved = true;
1049
+ if (!this.options.blockStylesheets) {
1050
+ const stylesheetInfo = {};
1051
+ const element = this.doc.createElement("style");
1052
+ this.doc.body.appendChild(element);
1053
+ element.textContent = resource.content;
1054
+ await processElement(element, stylesheetInfo, this.stylesheets, this.baseURI, this.options, this.workStyleElement);
1055
+ }
1056
+ })
1057
+ );
1062
1058
  }
1063
1059
 
1064
1060
  async function processElement(element, stylesheetInfo, stylesheets, baseURI, options, workStyleElement) {
@@ -1089,26 +1085,28 @@ class Processor {
1089
1085
  async resolveFrameURLs() {
1090
1086
  if (!this.options.saveRawPage) {
1091
1087
  const frameElements = Array.from(this.doc.querySelectorAll("iframe, frame, object[type=\"text/html\"][data]"));
1092
- await Promise.all(frameElements.map(async frameElement => {
1093
- if (frameElement.tagName == "OBJECT") {
1094
- frameElement.setAttribute("data", "data:text/html,");
1095
- } else {
1096
- const src = frameElement.getAttribute("src");
1097
- if (this.options.saveOriginalURLs && src && !isDataURL(src)) {
1098
- frameElement.setAttribute("data-sf-original-src", src);
1088
+ await Promise.all(
1089
+ frameElements.map(async frameElement => {
1090
+ if (frameElement.tagName == "OBJECT") {
1091
+ frameElement.setAttribute("data", "data:text/html,");
1092
+ } else {
1093
+ const src = frameElement.getAttribute("src");
1094
+ if (this.options.saveOriginalURLs && src && !isDataURL(src)) {
1095
+ frameElement.setAttribute("data-sf-original-src", src);
1096
+ }
1097
+ frameElement.removeAttribute("src");
1098
+ frameElement.removeAttribute("srcdoc");
1099
1099
  }
1100
- frameElement.removeAttribute("src");
1101
- frameElement.removeAttribute("srcdoc");
1102
- }
1103
- Array.from(frameElement.childNodes).forEach(node => node.remove());
1104
- const frameWindowId = frameElement.getAttribute(util.WIN_ID_ATTRIBUTE_NAME);
1105
- if (this.options.frames && frameWindowId) {
1106
- const frameData = this.options.frames.find(frame => frame.windowId == frameWindowId);
1107
- if (frameData) {
1108
- await initializeProcessor(frameData, frameElement, frameWindowId, this.batchRequest, Object.create(this.options));
1100
+ Array.from(frameElement.childNodes).forEach(node => node.remove());
1101
+ const frameWindowId = frameElement.getAttribute(util.WIN_ID_ATTRIBUTE_NAME);
1102
+ if (this.options.frames && frameWindowId) {
1103
+ const frameData = this.options.frames.find(frame => frame.windowId == frameWindowId);
1104
+ if (frameData) {
1105
+ await initializeProcessor(frameData, frameElement, frameWindowId, this.batchRequest, Object.create(this.options));
1106
+ }
1109
1107
  }
1110
- }
1111
- }));
1108
+ })
1109
+ );
1112
1110
  }
1113
1111
 
1114
1112
  async function initializeProcessor(frameData, frameElement, frameWindowId, batchRequest, options) {
@@ -1152,7 +1150,7 @@ class Processor {
1152
1150
  }
1153
1151
 
1154
1152
  function processElement(element) {
1155
- const shadowRootElements = Array.from((element.querySelectorAll("[" + util.SHADOW_ROOT_ATTRIBUTE_NAME + "]")));
1153
+ const shadowRootElements = Array.from(element.querySelectorAll("[" + util.SHADOW_ROOT_ATTRIBUTE_NAME + "]"));
1156
1154
  shadowRootElements.forEach(element => {
1157
1155
  const attributeValue = element.getAttribute(util.SHADOW_ROOT_ATTRIBUTE_NAME);
1158
1156
  if (attributeValue) {
@@ -1211,15 +1209,11 @@ class Processor {
1211
1209
 
1212
1210
  async processStylesheets() {
1213
1211
  this.options.fontDeclarations = new Map();
1214
- await Promise.all([...this.stylesheets].map(([, stylesheetInfo]) =>
1215
- ProcessorHelper.processStylesheet(stylesheetInfo.stylesheet.children, this.baseURI, this.options, this.cssVariables, this.batchRequest)
1216
- ));
1212
+ await Promise.all([...this.stylesheets].map(([, stylesheetInfo]) => ProcessorHelper.processStylesheet(stylesheetInfo.stylesheet.children, this.baseURI, this.options, this.cssVariables, this.batchRequest)));
1217
1213
  }
1218
1214
 
1219
1215
  async processStyleAttributes() {
1220
- return Promise.all([...this.styles].map(([, stylesheet]) =>
1221
- ProcessorHelper.processStyle(stylesheet, this.baseURI, this.options, this.cssVariables, this.batchRequest)
1222
- ));
1216
+ return Promise.all([...this.styles].map(([, stylesheet]) => ProcessorHelper.processStyle(stylesheet, this.baseURI, this.options, this.cssVariables, this.batchRequest)));
1223
1217
  }
1224
1218
 
1225
1219
  async processPageResources() {
@@ -1239,10 +1233,7 @@ class Processor {
1239
1233
  let resourcePromises = processAttributeArgs.map(([selector, attributeName, processDuplicates, removeElementIfMissing]) =>
1240
1234
  ProcessorHelper.processAttribute(this.doc.querySelectorAll(selector), attributeName, this.baseURI, this.options, "image", this.cssVariables, this.styles, this.batchRequest, processDuplicates, removeElementIfMissing)
1241
1235
  );
1242
- resourcePromises = resourcePromises.concat([
1243
- ProcessorHelper.processXLinks(this.doc.querySelectorAll("use"), this.doc, this.baseURI, this.options, this.batchRequest),
1244
- ProcessorHelper.processSrcset(this.doc.querySelectorAll("img[srcset], source[srcset]"), this.baseURI, this.options, this.batchRequest)
1245
- ]);
1236
+ 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)]);
1246
1237
  resourcePromises.push(ProcessorHelper.processAttribute(this.doc.querySelectorAll("audio[src], audio > source[src]"), "src", this.baseURI, this.options, "audio", this.cssVariables, this.styles, this.batchRequest));
1247
1238
  resourcePromises.push(ProcessorHelper.processAttribute(this.doc.querySelectorAll("video[src], video > source[src]"), "src", this.baseURI, this.options, "video", this.cssVariables, this.styles, this.batchRequest));
1248
1239
  await Promise.all(resourcePromises);
@@ -1252,47 +1243,49 @@ class Processor {
1252
1243
  }
1253
1244
 
1254
1245
  async processScripts() {
1255
- await Promise.all(Array.from(this.doc.querySelectorAll("script[src]")).map(async element => {
1256
- let resourceURL;
1257
- let scriptSrc;
1258
- scriptSrc = element.getAttribute("src");
1259
- if (this.options.saveOriginalURLs && !isDataURL(scriptSrc)) {
1260
- element.setAttribute("data-sf-original-src", scriptSrc);
1261
- }
1262
- element.removeAttribute("integrity");
1263
- if (!this.options.blockScripts) {
1264
- element.textContent = "";
1265
- try {
1266
- resourceURL = util.resolveURL(scriptSrc, this.baseURI);
1267
- } catch (error) {
1268
- // ignored
1246
+ await Promise.all(
1247
+ Array.from(this.doc.querySelectorAll("script[src]")).map(async element => {
1248
+ let resourceURL;
1249
+ let scriptSrc;
1250
+ scriptSrc = element.getAttribute("src");
1251
+ if (this.options.saveOriginalURLs && !isDataURL(scriptSrc)) {
1252
+ element.setAttribute("data-sf-original-src", scriptSrc);
1269
1253
  }
1270
- if (testValidURL(resourceURL)) {
1271
- element.removeAttribute("src");
1272
- const content = await util.getContent(resourceURL, {
1273
- asBinary: true,
1274
- charset: this.charset != UTF8_CHARSET && this.charset,
1275
- maxResourceSize: this.options.maxResourceSize,
1276
- maxResourceSizeEnabled: this.options.maxResourceSizeEnabled,
1277
- frameId: this.options.windowId,
1278
- resourceReferrer: this.options.resourceReferrer,
1279
- baseURI: this.options.baseURI,
1280
- blockMixedContent: this.options.blockMixedContent,
1281
- expectedType: "script",
1282
- acceptHeaders: this.options.acceptHeaders,
1283
- networkTimeout: this.options.networkTimeout
1284
- });
1285
- content.data = getUpdatedResourceContent(resourceURL, content, this.options);
1286
- element.setAttribute("src", content.data);
1287
- if (element.getAttribute("async") == "async" || element.getAttribute(util.ASYNC_SCRIPT_ATTRIBUTE_NAME) == "") {
1288
- element.setAttribute("async", "");
1254
+ element.removeAttribute("integrity");
1255
+ if (!this.options.blockScripts) {
1256
+ element.textContent = "";
1257
+ try {
1258
+ resourceURL = util.resolveURL(scriptSrc, this.baseURI);
1259
+ } catch (error) {
1260
+ // ignored
1289
1261
  }
1262
+ if (testValidURL(resourceURL)) {
1263
+ element.removeAttribute("src");
1264
+ const content = await util.getContent(resourceURL, {
1265
+ asBinary: true,
1266
+ charset: this.charset != UTF8_CHARSET && this.charset,
1267
+ maxResourceSize: this.options.maxResourceSize,
1268
+ maxResourceSizeEnabled: this.options.maxResourceSizeEnabled,
1269
+ frameId: this.options.windowId,
1270
+ resourceReferrer: this.options.resourceReferrer,
1271
+ baseURI: this.options.baseURI,
1272
+ blockMixedContent: this.options.blockMixedContent,
1273
+ expectedType: "script",
1274
+ acceptHeaders: this.options.acceptHeaders,
1275
+ networkTimeout: this.options.networkTimeout
1276
+ });
1277
+ content.data = getUpdatedResourceContent(resourceURL, content, this.options);
1278
+ element.setAttribute("src", content.data);
1279
+ if (element.getAttribute("async") == "async" || element.getAttribute(util.ASYNC_SCRIPT_ATTRIBUTE_NAME) == "") {
1280
+ element.setAttribute("async", "");
1281
+ }
1282
+ }
1283
+ } else {
1284
+ element.removeAttribute("src");
1290
1285
  }
1291
- } else {
1292
- element.removeAttribute("src");
1293
- }
1294
- this.stats.add("processed", "scripts", 1);
1295
- }));
1286
+ this.stats.add("processed", "scripts", 1);
1287
+ })
1288
+ );
1296
1289
  }
1297
1290
 
1298
1291
  removeAlternativeImages() {
@@ -1306,40 +1299,42 @@ class Processor {
1306
1299
  async processFrames() {
1307
1300
  if (this.options.frames) {
1308
1301
  const frameElements = Array.from(this.doc.querySelectorAll("iframe, frame, object[type=\"text/html\"][data]"));
1309
- await Promise.all(frameElements.map(async frameElement => {
1310
- const frameWindowId = frameElement.getAttribute(util.WIN_ID_ATTRIBUTE_NAME);
1311
- if (frameWindowId) {
1312
- const frameData = this.options.frames.find(frame => frame.windowId == frameWindowId);
1313
- if (frameData) {
1314
- this.options.frames = this.options.frames.filter(frame => frame.windowId != frameWindowId);
1315
- if (frameData.runner && frameElement.getAttribute(util.HIDDEN_FRAME_ATTRIBUTE_NAME) != "") {
1316
- this.stats.add("processed", "frames", 1);
1317
- await frameData.runner.run();
1318
- const pageData = await frameData.runner.getPageData();
1319
- frameElement.removeAttribute(util.WIN_ID_ATTRIBUTE_NAME);
1320
- let sandbox = "allow-popups allow-top-navigation allow-top-navigation-by-user-activation";
1321
- if (pageData.content.match(NOSCRIPT_TAG_FOUND) || pageData.content.match(CANVAS_TAG_FOUND) || pageData.content.match(SCRIPT_TAG_FOUND)) {
1322
- sandbox += " allow-scripts allow-same-origin";
1323
- }
1324
- frameElement.setAttribute("sandbox", sandbox);
1325
- if (frameElement.tagName == "OBJECT") {
1326
- frameElement.setAttribute("data", "data:text/html," + pageData.content);
1327
- } else {
1328
- if (frameElement.tagName == "FRAME") {
1329
- frameElement.setAttribute("src", "data:text/html," + pageData.content.replace(/%/g, "%25").replace(/#/g, "%23"));
1302
+ await Promise.all(
1303
+ frameElements.map(async frameElement => {
1304
+ const frameWindowId = frameElement.getAttribute(util.WIN_ID_ATTRIBUTE_NAME);
1305
+ if (frameWindowId) {
1306
+ const frameData = this.options.frames.find(frame => frame.windowId == frameWindowId);
1307
+ if (frameData) {
1308
+ this.options.frames = this.options.frames.filter(frame => frame.windowId != frameWindowId);
1309
+ if (frameData.runner && frameElement.getAttribute(util.HIDDEN_FRAME_ATTRIBUTE_NAME) != "") {
1310
+ this.stats.add("processed", "frames", 1);
1311
+ await frameData.runner.run();
1312
+ const pageData = await frameData.runner.getPageData();
1313
+ frameElement.removeAttribute(util.WIN_ID_ATTRIBUTE_NAME);
1314
+ let sandbox = "allow-popups allow-top-navigation allow-top-navigation-by-user-activation";
1315
+ if (pageData.content.match(NOSCRIPT_TAG_FOUND) || pageData.content.match(CANVAS_TAG_FOUND) || pageData.content.match(SCRIPT_TAG_FOUND)) {
1316
+ sandbox += " allow-scripts allow-same-origin";
1317
+ }
1318
+ frameElement.setAttribute("sandbox", sandbox);
1319
+ if (frameElement.tagName == "OBJECT") {
1320
+ frameElement.setAttribute("data", "data:text/html," + pageData.content);
1330
1321
  } else {
1331
- frameElement.setAttribute("srcdoc", pageData.content);
1332
- frameElement.removeAttribute("src");
1322
+ if (frameElement.tagName == "FRAME") {
1323
+ frameElement.setAttribute("src", "data:text/html," + pageData.content.replace(/%/g, "%25").replace(/#/g, "%23"));
1324
+ } else {
1325
+ frameElement.setAttribute("srcdoc", pageData.content);
1326
+ frameElement.removeAttribute("src");
1327
+ }
1333
1328
  }
1329
+ this.stats.addAll(pageData);
1330
+ } else {
1331
+ frameElement.removeAttribute(util.WIN_ID_ATTRIBUTE_NAME);
1332
+ this.stats.add("discarded", "frames", 1);
1334
1333
  }
1335
- this.stats.addAll(pageData);
1336
- } else {
1337
- frameElement.removeAttribute(util.WIN_ID_ATTRIBUTE_NAME);
1338
- this.stats.add("discarded", "frames", 1);
1339
1334
  }
1340
1335
  }
1341
- }
1342
- }));
1336
+ })
1337
+ );
1343
1338
  }
1344
1339
  }
1345
1340
 
@@ -1621,40 +1616,42 @@ class ProcessorHelper {
1621
1616
  let importFound;
1622
1617
  ProcessorHelper.resolveStylesheetURLs(stylesheet, baseURI, workStylesheet);
1623
1618
  const imports = getImportFunctions(stylesheet);
1624
- await Promise.all(imports.map(async node => {
1625
- const urlNode = cssTree.find(node, node => node.type == "Url") || cssTree.find(node, node => node.type == "String");
1626
- if (urlNode) {
1627
- let resourceURL = normalizeURL(urlNode.value);
1628
- if (!testIgnoredPath(resourceURL) && testValidPath(resourceURL)) {
1629
- urlNode.value = util.EMPTY_RESOURCE;
1630
- try {
1631
- resourceURL = util.resolveURL(resourceURL, baseURI);
1632
- } catch (error) {
1633
- // ignored
1634
- }
1635
- if (testValidURL(resourceURL) && !importedStyleSheets.has(resourceURL)) {
1636
- const content = await getStylesheetContent(resourceURL);
1637
- resourceURL = content.resourceURL;
1638
- content.data = getUpdatedResourceContent(resourceURL, content, options);
1639
- if (content.data && content.data.match(/^<!doctype /i)) {
1640
- content.data = "";
1641
- }
1642
- const mediaQueryListNode = cssTree.find(node, node => node.type == "MediaQueryList");
1643
- if (mediaQueryListNode) {
1644
- content.data = wrapMediaQuery(content.data, cssTree.generate(mediaQueryListNode));
1619
+ await Promise.all(
1620
+ imports.map(async node => {
1621
+ const urlNode = cssTree.find(node, node => node.type == "Url") || cssTree.find(node, node => node.type == "String");
1622
+ if (urlNode) {
1623
+ let resourceURL = normalizeURL(urlNode.value);
1624
+ if (!testIgnoredPath(resourceURL) && testValidPath(resourceURL)) {
1625
+ urlNode.value = util.EMPTY_RESOURCE;
1626
+ try {
1627
+ resourceURL = util.resolveURL(resourceURL, baseURI);
1628
+ } catch (error) {
1629
+ // ignored
1645
1630
  }
1646
- const importedStylesheet = cssTree.parse(content.data, { context: "stylesheet", parseCustomProperty: true });
1647
- const ancestorStyleSheets = new Set(importedStyleSheets);
1648
- ancestorStyleSheets.add(resourceURL);
1649
- await ProcessorHelper.resolveImportURLs(importedStylesheet, resourceURL, options, workStylesheet, ancestorStyleSheets);
1650
- for (let keyName of Object.keys(importedStylesheet)) {
1651
- node[keyName] = importedStylesheet[keyName];
1631
+ if (testValidURL(resourceURL) && !importedStyleSheets.has(resourceURL)) {
1632
+ const content = await getStylesheetContent(resourceURL);
1633
+ resourceURL = content.resourceURL;
1634
+ content.data = getUpdatedResourceContent(resourceURL, content, options);
1635
+ if (content.data && content.data.match(/^<!doctype /i)) {
1636
+ content.data = "";
1637
+ }
1638
+ const mediaQueryListNode = cssTree.find(node, node => node.type == "MediaQueryList");
1639
+ if (mediaQueryListNode) {
1640
+ content.data = wrapMediaQuery(content.data, cssTree.generate(mediaQueryListNode));
1641
+ }
1642
+ const importedStylesheet = cssTree.parse(content.data, { context: "stylesheet", parseCustomProperty: true });
1643
+ const ancestorStyleSheets = new Set(importedStyleSheets);
1644
+ ancestorStyleSheets.add(resourceURL);
1645
+ await ProcessorHelper.resolveImportURLs(importedStylesheet, resourceURL, options, workStylesheet, ancestorStyleSheets);
1646
+ for (let keyName of Object.keys(importedStylesheet)) {
1647
+ node[keyName] = importedStylesheet[keyName];
1648
+ }
1649
+ importFound = true;
1652
1650
  }
1653
- importFound = true;
1654
1651
  }
1655
1652
  }
1656
- }
1657
- }));
1653
+ })
1654
+ );
1658
1655
  return importFound;
1659
1656
 
1660
1657
  async function getStylesheetContent(resourceURL) {
@@ -1697,25 +1694,27 @@ class ProcessorHelper {
1697
1694
  urls.map(urlNode => {
1698
1695
  const originalResourceURL = urlNode.value;
1699
1696
  let resourceURL = normalizeURL(originalResourceURL);
1700
- workStylesheet.textContent = "tmp { content:\"" + resourceURL + "\"}";
1701
- if (workStylesheet.sheet && workStylesheet.sheet.cssRules) {
1702
- resourceURL = util.removeQuotes(workStylesheet.sheet.cssRules[0].style.getPropertyValue("content"));
1703
- }
1704
1697
  if (!testIgnoredPath(resourceURL)) {
1705
- if (!resourceURL || testValidPath(resourceURL)) {
1706
- let resolvedURL;
1707
- if (!originalResourceURL.startsWith("#")) {
1708
- try {
1709
- resolvedURL = util.resolveURL(resourceURL, baseURI);
1710
- } catch (error) {
1711
- // ignored
1698
+ workStylesheet.textContent = "tmp { content:\"" + resourceURL + "\"}";
1699
+ if (workStylesheet.sheet && workStylesheet.sheet.cssRules) {
1700
+ resourceURL = util.removeQuotes(workStylesheet.sheet.cssRules[0].style.getPropertyValue("content"));
1701
+ }
1702
+ if (!testIgnoredPath(resourceURL)) {
1703
+ if (!resourceURL || testValidPath(resourceURL)) {
1704
+ let resolvedURL;
1705
+ if (!originalResourceURL.startsWith("#")) {
1706
+ try {
1707
+ resolvedURL = util.resolveURL(resourceURL, baseURI);
1708
+ } catch (error) {
1709
+ // ignored
1710
+ }
1712
1711
  }
1712
+ if (testValidURL(resolvedURL)) {
1713
+ urlNode.value = resolvedURL;
1714
+ }
1715
+ } else {
1716
+ urlNode.value = util.EMPTY_RESOURCE;
1713
1717
  }
1714
- if (testValidURL(resolvedURL)) {
1715
- urlNode.value = resolvedURL;
1716
- }
1717
- } else {
1718
- urlNode.value = util.EMPTY_RESOURCE;
1719
1718
  }
1720
1719
  }
1721
1720
  });
@@ -1777,138 +1776,143 @@ class ProcessorHelper {
1777
1776
 
1778
1777
  async function processFontFaceRule(ruleData) {
1779
1778
  const urls = getUrlFunctions(ruleData);
1780
- await Promise.all(urls.map(async urlNode => {
1781
- const originalResourceURL = urlNode.value;
1782
- if (!options.blockFonts) {
1783
- const resourceURL = normalizeURL(originalResourceURL);
1784
- if (!testIgnoredPath(resourceURL) && testValidURL(resourceURL)) {
1785
- let { content } = await batchRequest.addURL(resourceURL,
1786
- { asBinary: true, expectedType: "font", baseURI, blockMixedContent: options.blockMixedContent });
1787
- let resourceURLs = options.fontDeclarations.get(urlNode);
1788
- if (!resourceURLs) {
1789
- resourceURLs = [];
1790
- options.fontDeclarations.set(urlNode, resourceURLs);
1791
- }
1792
- resourceURLs.push(resourceURL);
1793
- if (!isDataURL(resourceURL) && options.saveOriginalURLs) {
1794
- urlNode.value = "-sf-url-original(" + JSON.stringify(originalResourceURL) + ") " + content;
1795
- } else {
1796
- urlNode.value = content;
1779
+ await Promise.all(
1780
+ urls.map(async urlNode => {
1781
+ const originalResourceURL = urlNode.value;
1782
+ if (!options.blockFonts) {
1783
+ const resourceURL = normalizeURL(originalResourceURL);
1784
+ if (!testIgnoredPath(resourceURL) && testValidURL(resourceURL)) {
1785
+ let { content } = await batchRequest.addURL(resourceURL, { asBinary: true, expectedType: "font", baseURI, blockMixedContent: options.blockMixedContent });
1786
+ let resourceURLs = options.fontDeclarations.get(urlNode);
1787
+ if (!resourceURLs) {
1788
+ resourceURLs = [];
1789
+ options.fontDeclarations.set(urlNode, resourceURLs);
1790
+ }
1791
+ resourceURLs.push(resourceURL);
1792
+ if (!isDataURL(resourceURL) && options.saveOriginalURLs) {
1793
+ urlNode.value = "-sf-url-original(" + JSON.stringify(originalResourceURL) + ") " + content;
1794
+ } else {
1795
+ urlNode.value = content;
1796
+ }
1797
1797
  }
1798
+ } else {
1799
+ urlNode.value = util.EMPTY_RESOURCE;
1798
1800
  }
1799
- } else {
1800
- urlNode.value = util.EMPTY_RESOURCE;
1801
- }
1802
- }));
1801
+ })
1802
+ );
1803
1803
  }
1804
1804
  }
1805
1805
 
1806
1806
  static async processStyle(ruleData, baseURI, options, cssVariables, batchRequest) {
1807
1807
  const urls = getUrlFunctions(ruleData);
1808
- await Promise.all(urls.map(async urlNode => {
1809
- const originalResourceURL = urlNode.value;
1810
- if (!options.blockImages) {
1811
- const resourceURL = normalizeURL(originalResourceURL);
1812
- if (!testIgnoredPath(resourceURL) && testValidURL(resourceURL)) {
1813
- let { content, indexResource, duplicate } = await batchRequest.addURL(resourceURL,
1814
- { asBinary: true, expectedType: "image", groupDuplicates: options.groupDuplicateImages });
1815
- if (!originalResourceURL.startsWith("#")) {
1816
- const maxSizeDuplicateImages = options.maxSizeDuplicateImages || SINGLE_FILE_VARIABLE_MAX_SIZE;
1817
- if (duplicate && options.groupDuplicateImages && util.getContentSize(content) < maxSizeDuplicateImages) {
1818
- const varNode = cssTree.parse("var(" + SINGLE_FILE_VARIABLE_NAME_PREFIX + indexResource + ")", { context: "value" });
1819
- for (let keyName of Object.keys(varNode.children.head.data)) {
1820
- urlNode[keyName] = varNode.children.head.data[keyName];
1821
- }
1822
- cssVariables.set(indexResource, { content, url: originalResourceURL });
1823
- } else {
1824
- if (!isDataURL(resourceURL) && options.saveOriginalURLs) {
1825
- urlNode.value = "-sf-url-original(" + JSON.stringify(originalResourceURL) + ") " + content;
1808
+ await Promise.all(
1809
+ urls.map(async urlNode => {
1810
+ const originalResourceURL = urlNode.value;
1811
+ if (!options.blockImages) {
1812
+ const resourceURL = normalizeURL(originalResourceURL);
1813
+ if (!testIgnoredPath(resourceURL) && testValidURL(resourceURL)) {
1814
+ let { content, indexResource, duplicate } = await batchRequest.addURL(resourceURL, { asBinary: true, expectedType: "image", groupDuplicates: options.groupDuplicateImages });
1815
+ if (!originalResourceURL.startsWith("#")) {
1816
+ const maxSizeDuplicateImages = options.maxSizeDuplicateImages || SINGLE_FILE_VARIABLE_MAX_SIZE;
1817
+ if (duplicate && options.groupDuplicateImages && util.getContentSize(content) < maxSizeDuplicateImages) {
1818
+ const varNode = cssTree.parse("var(" + SINGLE_FILE_VARIABLE_NAME_PREFIX + indexResource + ")", { context: "value" });
1819
+ for (let keyName of Object.keys(varNode.children.head.data)) {
1820
+ urlNode[keyName] = varNode.children.head.data[keyName];
1821
+ }
1822
+ cssVariables.set(indexResource, { content, url: originalResourceURL });
1826
1823
  } else {
1827
- urlNode.value = content;
1824
+ if (!isDataURL(resourceURL) && options.saveOriginalURLs) {
1825
+ urlNode.value = "-sf-url-original(" + JSON.stringify(originalResourceURL) + ") " + content;
1826
+ } else {
1827
+ urlNode.value = content;
1828
+ }
1828
1829
  }
1829
1830
  }
1830
1831
  }
1832
+ } else {
1833
+ urlNode.value = util.EMPTY_RESOURCE;
1831
1834
  }
1832
- } else {
1833
- urlNode.value = util.EMPTY_RESOURCE;
1834
- }
1835
- }));
1835
+ })
1836
+ );
1836
1837
  }
1837
1838
 
1838
1839
  static async processAttribute(resourceElements, attributeName, baseURI, options, expectedType, cssVariables, styles, batchRequest, processDuplicates, removeElementIfMissing) {
1839
- await Promise.all(Array.from(resourceElements).map(async resourceElement => {
1840
- let resourceURL = resourceElement.getAttribute(attributeName);
1841
- if (resourceURL != null) {
1842
- resourceURL = normalizeURL(resourceURL);
1843
- let originURL = resourceElement.dataset.singleFileOriginURL;
1844
- if (options.saveOriginalURLs && !isDataURL(resourceURL)) {
1845
- resourceElement.setAttribute("data-sf-original-" + attributeName, resourceURL);
1846
- }
1847
- delete resourceElement.dataset.singleFileOriginURL;
1848
- if (!options["block" + expectedType.charAt(0).toUpperCase() + expectedType.substring(1) + "s"]) {
1849
- if (!testIgnoredPath(resourceURL)) {
1850
- setAttributeEmpty(resourceElement, attributeName, expectedType);
1851
- if (testValidPath(resourceURL)) {
1852
- try {
1853
- resourceURL = util.resolveURL(resourceURL, baseURI);
1854
- } catch (error) {
1855
- // ignored
1856
- }
1857
- if (testValidURL(resourceURL)) {
1858
- let { content, indexResource, duplicate } = await batchRequest.addURL(resourceURL,
1859
- { asBinary: true, expectedType, groupDuplicates: options.groupDuplicateImages && resourceElement.tagName == "IMG" && attributeName == "src" });
1860
- if (originURL) {
1861
- if (content == util.EMPTY_RESOURCE) {
1862
- try {
1863
- originURL = util.resolveURL(originURL, baseURI);
1864
- } catch (error) {
1865
- // ignored
1866
- }
1867
- try {
1868
- resourceURL = originURL;
1869
- content = (await util.getContent(resourceURL, {
1870
- asBinary: true,
1871
- expectedType,
1872
- maxResourceSize: options.maxResourceSize,
1873
- maxResourceSizeEnabled: options.maxResourceSizeEnabled,
1874
- frameId: options.windowId,
1875
- resourceReferrer: options.resourceReferrer,
1876
- acceptHeaders: options.acceptHeaders,
1877
- networkTimeout: options.networkTimeout
1878
- })).data;
1879
- } catch (error) {
1880
- // ignored
1840
+ await Promise.all(
1841
+ Array.from(resourceElements).map(async resourceElement => {
1842
+ let resourceURL = resourceElement.getAttribute(attributeName);
1843
+ if (resourceURL != null) {
1844
+ resourceURL = normalizeURL(resourceURL);
1845
+ let originURL = resourceElement.dataset.singleFileOriginURL;
1846
+ if (options.saveOriginalURLs && !isDataURL(resourceURL)) {
1847
+ resourceElement.setAttribute("data-sf-original-" + attributeName, resourceURL);
1848
+ }
1849
+ delete resourceElement.dataset.singleFileOriginURL;
1850
+ if (!options["block" + expectedType.charAt(0).toUpperCase() + expectedType.substring(1) + "s"]) {
1851
+ if (!testIgnoredPath(resourceURL)) {
1852
+ setAttributeEmpty(resourceElement, attributeName, expectedType);
1853
+ if (testValidPath(resourceURL)) {
1854
+ try {
1855
+ resourceURL = util.resolveURL(resourceURL, baseURI);
1856
+ } catch (error) {
1857
+ // ignored
1858
+ }
1859
+ if (testValidURL(resourceURL)) {
1860
+ let { content, indexResource, duplicate } = await batchRequest.addURL(resourceURL, { asBinary: true, expectedType, groupDuplicates: options.groupDuplicateImages && resourceElement.tagName == "IMG" && attributeName == "src" });
1861
+ if (originURL) {
1862
+ if (content == util.EMPTY_RESOURCE) {
1863
+ try {
1864
+ originURL = util.resolveURL(originURL, baseURI);
1865
+ } catch (error) {
1866
+ // ignored
1867
+ }
1868
+ try {
1869
+ resourceURL = originURL;
1870
+ content = (
1871
+ await util.getContent(resourceURL, {
1872
+ asBinary: true,
1873
+ expectedType,
1874
+ maxResourceSize: options.maxResourceSize,
1875
+ maxResourceSizeEnabled: options.maxResourceSizeEnabled,
1876
+ frameId: options.windowId,
1877
+ resourceReferrer: options.resourceReferrer,
1878
+ acceptHeaders: options.acceptHeaders,
1879
+ networkTimeout: options.networkTimeout
1880
+ })
1881
+ ).data;
1882
+ } catch (error) {
1883
+ // ignored
1884
+ }
1881
1885
  }
1882
1886
  }
1883
- }
1884
- if (removeElementIfMissing && content == util.EMPTY_RESOURCE) {
1885
- resourceElement.remove();
1886
- } else if (content !== util.EMPTY_RESOURCE) {
1887
- const forbiddenPrefixFound = PREFIXES_FORBIDDEN_DATA_URI.filter(prefixDataURI => content.startsWith(prefixDataURI)).length;
1888
- if (!forbiddenPrefixFound) {
1889
- const isSVG = content.startsWith(PREFIX_DATA_URI_IMAGE_SVG);
1890
- const maxSizeDuplicateImages = options.maxSizeDuplicateImages || SINGLE_FILE_VARIABLE_MAX_SIZE;
1891
- if (expectedType == "image" && processDuplicates && duplicate && !isSVG && util.getContentSize(content) < maxSizeDuplicateImages) {
1892
- if (ProcessorHelper.replaceImageSource(resourceElement, SINGLE_FILE_VARIABLE_NAME_PREFIX + indexResource, options)) {
1893
- cssVariables.set(indexResource, { content, url: originURL });
1894
- const declarationList = cssTree.parse(resourceElement.getAttribute("style"), { context: "declarationList", parseCustomProperty: true });
1895
- styles.set(resourceElement, declarationList);
1887
+ if (removeElementIfMissing && content == util.EMPTY_RESOURCE) {
1888
+ resourceElement.remove();
1889
+ } else if (content !== util.EMPTY_RESOURCE) {
1890
+ const forbiddenPrefixFound = PREFIXES_FORBIDDEN_DATA_URI.filter(prefixDataURI => content.startsWith(prefixDataURI)).length;
1891
+ if (!forbiddenPrefixFound) {
1892
+ const isSVG = content.startsWith(PREFIX_DATA_URI_IMAGE_SVG);
1893
+ const maxSizeDuplicateImages = options.maxSizeDuplicateImages || SINGLE_FILE_VARIABLE_MAX_SIZE;
1894
+ if (expectedType == "image" && processDuplicates && duplicate && !isSVG && util.getContentSize(content) < maxSizeDuplicateImages) {
1895
+ if (ProcessorHelper.replaceImageSource(resourceElement, SINGLE_FILE_VARIABLE_NAME_PREFIX + indexResource, options)) {
1896
+ cssVariables.set(indexResource, { content, url: originURL });
1897
+ const declarationList = cssTree.parse(resourceElement.getAttribute("style"), { context: "declarationList", parseCustomProperty: true });
1898
+ styles.set(resourceElement, declarationList);
1899
+ } else {
1900
+ resourceElement.setAttribute(attributeName, content);
1901
+ }
1896
1902
  } else {
1897
1903
  resourceElement.setAttribute(attributeName, content);
1898
1904
  }
1899
- } else {
1900
- resourceElement.setAttribute(attributeName, content);
1901
1905
  }
1902
1906
  }
1903
1907
  }
1904
1908
  }
1905
1909
  }
1910
+ } else {
1911
+ setAttributeEmpty(resourceElement, attributeName, expectedType);
1906
1912
  }
1907
- } else {
1908
- setAttributeEmpty(resourceElement, attributeName, expectedType);
1909
1913
  }
1910
- }
1911
- }));
1914
+ })
1915
+ );
1912
1916
 
1913
1917
  function setAttributeEmpty(resourceElement, attributeName, expectedType) {
1914
1918
  if (expectedType == "video" || expectedType == "audio") {
@@ -1921,96 +1925,102 @@ class ProcessorHelper {
1921
1925
 
1922
1926
  static async processXLinks(resourceElements, doc, baseURI, options, batchRequest) {
1923
1927
  let attributeName = "xlink:href";
1924
- await Promise.all(Array.from(resourceElements).map(async resourceElement => {
1925
- let originalResourceURL = resourceElement.getAttribute(attributeName);
1926
- if (originalResourceURL == null) {
1927
- attributeName = "href";
1928
- originalResourceURL = resourceElement.getAttribute(attributeName);
1929
- }
1930
- if (options.saveOriginalURLs && !isDataURL(originalResourceURL)) {
1931
- resourceElement.setAttribute("data-sf-original-href", originalResourceURL);
1932
- }
1933
- let resourceURL = normalizeURL(originalResourceURL);
1934
- if (!options.blockImages) {
1935
- if (testValidPath(resourceURL) && !testIgnoredPath(resourceURL)) {
1936
- resourceElement.setAttribute(attributeName, util.EMPTY_RESOURCE);
1937
- try {
1938
- resourceURL = util.resolveURL(resourceURL, baseURI);
1939
- } catch (error) {
1940
- // ignored
1941
- }
1942
- if (testValidURL(resourceURL)) {
1943
- const hashMatch = originalResourceURL.match(REGEXP_URL_HASH);
1944
- if (originalResourceURL.startsWith(baseURI + "#")) {
1945
- resourceElement.setAttribute(attributeName, hashMatch[0]);
1946
- } else {
1947
- const response = await batchRequest.addURL(resourceURL, { expectedType: "image" });
1948
- const svgDoc = util.parseSVGContent(response.content);
1949
- if (hashMatch && hashMatch[0]) {
1950
- let symbolElement;
1951
- try {
1952
- symbolElement = svgDoc.querySelector(hashMatch[0]);
1953
- } catch (error) {
1954
- // ignored
1955
- }
1956
- if (symbolElement) {
1957
- resourceElement.setAttribute(attributeName, hashMatch[0]);
1958
- resourceElement.parentElement.insertBefore(symbolElement, resourceElement.parentElement.firstChild);
1959
- }
1928
+ await Promise.all(
1929
+ Array.from(resourceElements).map(async resourceElement => {
1930
+ let originalResourceURL = resourceElement.getAttribute(attributeName);
1931
+ if (originalResourceURL == null) {
1932
+ attributeName = "href";
1933
+ originalResourceURL = resourceElement.getAttribute(attributeName);
1934
+ }
1935
+ if (options.saveOriginalURLs && !isDataURL(originalResourceURL)) {
1936
+ resourceElement.setAttribute("data-sf-original-href", originalResourceURL);
1937
+ }
1938
+ let resourceURL = normalizeURL(originalResourceURL);
1939
+ if (!options.blockImages) {
1940
+ if (testValidPath(resourceURL) && !testIgnoredPath(resourceURL)) {
1941
+ resourceElement.setAttribute(attributeName, util.EMPTY_RESOURCE);
1942
+ try {
1943
+ resourceURL = util.resolveURL(resourceURL, baseURI);
1944
+ } catch (error) {
1945
+ // ignored
1946
+ }
1947
+ if (testValidURL(resourceURL)) {
1948
+ const hashMatch = originalResourceURL.match(REGEXP_URL_HASH);
1949
+ if (originalResourceURL.startsWith(baseURI + "#")) {
1950
+ resourceElement.setAttribute(attributeName, hashMatch[0]);
1960
1951
  } else {
1961
- const content = await batchRequest.addURL(resourceURL, { expectedType: "image" });
1962
- resourceElement.setAttribute(attributeName, PREFIX_DATA_URI_IMAGE_SVG + "," + content);
1952
+ const response = await batchRequest.addURL(resourceURL, { expectedType: "image" });
1953
+ const svgDoc = util.parseSVGContent(response.content);
1954
+ if (hashMatch && hashMatch[0]) {
1955
+ let symbolElement;
1956
+ try {
1957
+ symbolElement = svgDoc.querySelector(hashMatch[0]);
1958
+ } catch (error) {
1959
+ // ignored
1960
+ }
1961
+ if (symbolElement) {
1962
+ resourceElement.setAttribute(attributeName, hashMatch[0]);
1963
+ resourceElement.parentElement.insertBefore(symbolElement, resourceElement.parentElement.firstChild);
1964
+ }
1965
+ } else {
1966
+ const content = await batchRequest.addURL(resourceURL, { expectedType: "image" });
1967
+ resourceElement.setAttribute(attributeName, PREFIX_DATA_URI_IMAGE_SVG + "," + content);
1968
+ }
1963
1969
  }
1964
1970
  }
1971
+ } else if (resourceURL == options.url) {
1972
+ resourceElement.setAttribute(attributeName, originalResourceURL.substring(resourceURL.length));
1965
1973
  }
1966
- } else if (resourceURL == options.url) {
1967
- resourceElement.setAttribute(attributeName, originalResourceURL.substring(resourceURL.length));
1974
+ } else {
1975
+ resourceElement.setAttribute(attributeName, util.EMPTY_RESOURCE);
1968
1976
  }
1969
- } else {
1970
- resourceElement.setAttribute(attributeName, util.EMPTY_RESOURCE);
1971
- }
1972
- }));
1977
+ })
1978
+ );
1973
1979
  }
1974
1980
 
1975
1981
  static async processSrcset(resourceElements, baseURI, options, batchRequest) {
1976
- await Promise.all(Array.from(resourceElements).map(async resourceElement => {
1977
- const originSrcset = resourceElement.getAttribute("srcset");
1978
- const srcset = util.parseSrcset(originSrcset);
1979
- if (options.saveOriginalURLs && !isDataURL(originSrcset)) {
1980
- resourceElement.setAttribute("data-sf-original-srcset", originSrcset);
1981
- }
1982
- if (!options.blockImages) {
1983
- const srcsetValues = await Promise.all(srcset.map(async srcsetValue => {
1984
- let resourceURL = normalizeURL(srcsetValue.url);
1985
- if (!testIgnoredPath(resourceURL)) {
1986
- if (testValidPath(resourceURL)) {
1987
- try {
1988
- resourceURL = util.resolveURL(resourceURL, baseURI);
1989
- } catch (error) {
1990
- // ignored
1991
- }
1992
- if (testValidURL(resourceURL)) {
1993
- const { content } = await batchRequest.addURL(resourceURL, { asBinary: true, expectedType: "image" });
1994
- const forbiddenPrefixFound = PREFIXES_FORBIDDEN_DATA_URI.filter(prefixDataURI => content.startsWith(prefixDataURI)).length;
1995
- if (forbiddenPrefixFound) {
1982
+ await Promise.all(
1983
+ Array.from(resourceElements).map(async resourceElement => {
1984
+ const originSrcset = resourceElement.getAttribute("srcset");
1985
+ const srcset = util.parseSrcset(originSrcset);
1986
+ if (options.saveOriginalURLs && !isDataURL(originSrcset)) {
1987
+ resourceElement.setAttribute("data-sf-original-srcset", originSrcset);
1988
+ }
1989
+ if (!options.blockImages) {
1990
+ const srcsetValues = await Promise.all(
1991
+ srcset.map(async srcsetValue => {
1992
+ let resourceURL = normalizeURL(srcsetValue.url);
1993
+ if (!testIgnoredPath(resourceURL)) {
1994
+ if (testValidPath(resourceURL)) {
1995
+ try {
1996
+ resourceURL = util.resolveURL(resourceURL, baseURI);
1997
+ } catch (error) {
1998
+ // ignored
1999
+ }
2000
+ if (testValidURL(resourceURL)) {
2001
+ const { content } = await batchRequest.addURL(resourceURL, { asBinary: true, expectedType: "image" });
2002
+ const forbiddenPrefixFound = PREFIXES_FORBIDDEN_DATA_URI.filter(prefixDataURI => content.startsWith(prefixDataURI)).length;
2003
+ if (forbiddenPrefixFound) {
2004
+ return "";
2005
+ }
2006
+ return content + (srcsetValue.w ? " " + srcsetValue.w + "w" : srcsetValue.d ? " " + srcsetValue.d + "x" : "");
2007
+ } else {
2008
+ return "";
2009
+ }
2010
+ } else {
1996
2011
  return "";
1997
2012
  }
1998
- return content + (srcsetValue.w ? " " + srcsetValue.w + "w" : srcsetValue.d ? " " + srcsetValue.d + "x" : "");
1999
2013
  } else {
2000
- return "";
2014
+ return resourceURL + (srcsetValue.w ? " " + srcsetValue.w + "w" : srcsetValue.d ? " " + srcsetValue.d + "x" : "");
2001
2015
  }
2002
- } else {
2003
- return "";
2004
- }
2005
- } else {
2006
- return resourceURL + (srcsetValue.w ? " " + srcsetValue.w + "w" : srcsetValue.d ? " " + srcsetValue.d + "x" : "");
2007
- }
2008
- }));
2009
- resourceElement.setAttribute("srcset", srcsetValues.join(", "));
2010
- } else {
2011
- resourceElement.setAttribute("srcset", "");
2012
- }
2013
- }));
2016
+ })
2017
+ );
2018
+ resourceElement.setAttribute("srcset", srcsetValues.join(", "));
2019
+ } else {
2020
+ resourceElement.setAttribute("srcset", "");
2021
+ }
2022
+ })
2023
+ );
2014
2024
  }
2015
2025
 
2016
2026
  static replaceImageSource(imgElement, variableName, options) {
@@ -2130,7 +2140,8 @@ async function evalTemplateVariable(template, variableName, valueGetter, dontRep
2130
2140
  }
2131
2141
 
2132
2142
  function getLastSegment(url, replacementCharacter) {
2133
- let lastSegmentMatch = url.pathname.match(/\/([^/]+)$/), lastSegment = lastSegmentMatch && lastSegmentMatch[0];
2143
+ let lastSegmentMatch = url.pathname.match(/\/([^/]+)$/),
2144
+ lastSegment = lastSegmentMatch && lastSegmentMatch[0];
2134
2145
  if (!lastSegment) {
2135
2146
  lastSegmentMatch = url.href.match(/([^/]+)\/?$/);
2136
2147
  lastSegment = lastSegmentMatch && lastSegmentMatch[0];
@@ -2266,6 +2277,4 @@ class Stats {
2266
2277
  }
2267
2278
  }
2268
2279
 
2269
- export {
2270
- getClass
2271
- };
2280
+ export { getClass };