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