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.
- package/package.json +1 -1
- package/single-file-core.js +549 -540
- package/single-file-util.js +1 -0
package/single-file-core.js
CHANGED
|
@@ -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
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
{ option: "blockVideos", action: "insertMissingVideoPosters" },
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
},
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
{
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
{ action: "
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
},
|
|
135
|
-
|
|
136
|
-
{ option: "
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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(
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
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 || (
|
|
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(
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
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
|
-
|
|
373
|
-
|
|
374
|
-
|
|
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,
|
|
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(
|
|
487
|
-
" \n url: " + infobarURL +
|
|
488
|
-
|
|
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] :
|
|
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
|
|
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 =
|
|
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
|
|
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]"),
|
|
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(
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
temporaryVideoElement.
|
|
1011
|
-
|
|
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(
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
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(
|
|
1093
|
-
|
|
1094
|
-
frameElement.
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
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.
|
|
1101
|
-
frameElement.
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
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(
|
|
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(
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
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
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
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
|
-
|
|
1292
|
-
|
|
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(
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
frameElement.
|
|
1327
|
-
|
|
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.
|
|
1332
|
-
|
|
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(
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
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
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
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
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
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(
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
{ asBinary: true, expectedType: "font", baseURI, blockMixedContent: options.blockMixedContent });
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
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
|
-
}
|
|
1800
|
-
|
|
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(
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
{ asBinary: true, expectedType: "image", groupDuplicates: options.groupDuplicateImages });
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
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
|
-
|
|
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
|
-
}
|
|
1833
|
-
|
|
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(
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
resourceURL
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
if (!
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
{ asBinary: true, expectedType, groupDuplicates: options.groupDuplicateImages && resourceElement.tagName == "IMG" && attributeName == "src" });
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
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
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
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(
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
if (
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
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
|
|
1962
|
-
|
|
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
|
|
1967
|
-
resourceElement.setAttribute(attributeName,
|
|
1974
|
+
} else {
|
|
1975
|
+
resourceElement.setAttribute(attributeName, util.EMPTY_RESOURCE);
|
|
1968
1976
|
}
|
|
1969
|
-
}
|
|
1970
|
-
|
|
1971
|
-
}
|
|
1972
|
-
}));
|
|
1977
|
+
})
|
|
1978
|
+
);
|
|
1973
1979
|
}
|
|
1974
1980
|
|
|
1975
1981
|
static async processSrcset(resourceElements, baseURI, options, batchRequest) {
|
|
1976
|
-
await Promise.all(
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
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
|
-
}
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
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(/\/([^/]+)$/),
|
|
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 };
|