Przeglądaj źródła

use single-file-core module from NPM

Gildas 3 lat temu
rodzic
commit
aafbb93ddc
39 zmienionych plików z 194 dodań i 9383 usunięć
  1. 0 0
      lib/single-file.js
  2. 131 0
      package-lock.json
  3. 2 0
      package.json
  4. 28 9
      rollup.config.dev.js
  5. 27 9
      rollup.config.js
  6. 1 1
      src/extension/ui/content/content-ui-editor-helper-web.js
  7. 0 86
      src/single-file/index.js
  8. 0 340
      src/single-file/modules/css-fonts-alt-minifier.js
  9. 0 371
      src/single-file/modules/css-fonts-minifier.js
  10. 0 350
      src/single-file/modules/css-matched-rules.js
  11. 0 91
      src/single-file/modules/css-medias-alt-minifier.js
  12. 0 146
      src/single-file/modules/css-rules-minifier.js
  13. 0 109
      src/single-file/modules/html-images-alt-minifier.js
  14. 0 262
      src/single-file/modules/html-minifier.js
  15. 0 180
      src/single-file/modules/html-serializer.js
  16. 0 42
      src/single-file/modules/index.js
  17. 0 418
      src/single-file/processors/frame-tree/content/content-frame-tree.js
  18. 0 404
      src/single-file/processors/hooks/content/content-hooks-frames-web.js
  19. 0 214
      src/single-file/processors/hooks/content/content-hooks-frames.js
  20. 0 48
      src/single-file/processors/hooks/content/content-hooks-web.js
  21. 0 39
      src/single-file/processors/hooks/content/content-hooks.js
  22. 0 34
      src/single-file/processors/index.js
  23. 0 229
      src/single-file/processors/lazy/content/content-lazy-loader.js
  24. 1 33
      src/single-file/single-file-bootstrap.js
  25. 0 2471
      src/single-file/single-file-core.js
  26. 1 1
      src/single-file/single-file-frames.js
  27. 0 557
      src/single-file/single-file-helper.js
  28. 1 0
      src/single-file/single-file-hooks-frames-web.js
  29. 1 0
      src/single-file/single-file-hooks-web.js
  30. 0 412
      src/single-file/single-file-util.js
  31. 1 0
      src/single-file/single-file.js
  32. 0 343
      src/single-file/vendor/css-font-property-parser.js
  33. 0 474
      src/single-file/vendor/css-media-query-parser.js
  34. 0 789
      src/single-file/vendor/css-minifier.js
  35. 0 24
      src/single-file/vendor/css-tree.js
  36. 0 72
      src/single-file/vendor/css-unescape.js
  37. 0 339
      src/single-file/vendor/html-srcset-parser.js
  38. 0 40
      src/single-file/vendor/index.js
  39. 0 446
      src/single-file/vendor/mime-type-parser.js

Plik diff jest za duży
+ 0 - 0
lib/single-file.js


+ 131 - 0
package-lock.json

@@ -30,16 +30,56 @@
 				"js-tokens": "^4.0.0"
 			}
 		},
+		"@rollup/plugin-node-resolve": {
+			"version": "13.3.0",
+			"resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-13.3.0.tgz",
+			"integrity": "sha512-Lus8rbUo1eEcnS4yTFKLZrVumLPY+YayBdWXgFSHYhTT2iJbMhoaaBL3xl5NCdeRytErGr8tZ0L71BMRmnlwSw==",
+			"dev": true,
+			"requires": {
+				"@rollup/pluginutils": "^3.1.0",
+				"@types/resolve": "1.17.1",
+				"deepmerge": "^4.2.2",
+				"is-builtin-module": "^3.1.0",
+				"is-module": "^1.0.0",
+				"resolve": "^1.19.0"
+			}
+		},
+		"@rollup/pluginutils": {
+			"version": "3.1.0",
+			"resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz",
+			"integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==",
+			"dev": true,
+			"requires": {
+				"@types/estree": "0.0.39",
+				"estree-walker": "^1.0.1",
+				"picomatch": "^2.2.2"
+			}
+		},
 		"@tootallnate/once": {
 			"version": "2.0.0",
 			"resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz",
 			"integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A=="
 		},
+		"@types/estree": {
+			"version": "0.0.39",
+			"resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz",
+			"integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==",
+			"dev": true
+		},
 		"@types/node": {
 			"version": "17.0.23",
 			"resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.23.tgz",
 			"integrity": "sha512-UxDxWn7dl97rKVeVS61vErvw086aCYhDLyvRQZ5Rk65rZKepaFdm53GeqXaKBuOhED4e9uWq34IC3TdSdJJ2Gw=="
 		},
+		"@types/resolve": {
+			"version": "1.17.1",
+			"resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz",
+			"integrity": "sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==",
+			"dev": true,
+			"requires": {
+				"@types/node": "*"
+			}
+		},
 		"@types/yauzl": {
 			"version": "2.9.2",
 			"resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.9.2.tgz",
@@ -160,6 +200,12 @@
 			"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
 			"dev": true
 		},
+		"builtin-modules": {
+			"version": "3.3.0",
+			"resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz",
+			"integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==",
+			"dev": true
+		},
 		"chalk": {
 			"version": "2.4.2",
 			"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
@@ -305,6 +351,12 @@
 			"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
 			"integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="
 		},
+		"deepmerge": {
+			"version": "4.2.2",
+			"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz",
+			"integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==",
+			"dev": true
+		},
 		"delayed-stream": {
 			"version": "1.0.0",
 			"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
@@ -369,6 +421,12 @@
 			"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
 			"integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="
 		},
+		"estree-walker": {
+			"version": "1.0.1",
+			"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz",
+			"integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==",
+			"dev": true
+		},
 		"esutils": {
 			"version": "2.0.3",
 			"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
@@ -439,6 +497,12 @@
 			"dev": true,
 			"optional": true
 		},
+		"function-bind": {
+			"version": "1.1.1",
+			"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
+			"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
+			"dev": true
+		},
 		"get-caller-file": {
 			"version": "2.0.5",
 			"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
@@ -465,6 +529,15 @@
 				"path-is-absolute": "^1.0.0"
 			}
 		},
+		"has": {
+			"version": "1.0.3",
+			"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
+			"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
+			"dev": true,
+			"requires": {
+				"function-bind": "^1.1.1"
+			}
+		},
 		"has-flag": {
 			"version": "3.0.0",
 			"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
@@ -530,11 +603,35 @@
 			"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
 			"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
 		},
+		"is-builtin-module": {
+			"version": "3.1.0",
+			"resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.1.0.tgz",
+			"integrity": "sha512-OV7JjAgOTfAFJmHZLvpSTb4qi0nIILDV1gWPYDnDJUTNFM5aGlRAhk4QcT8i7TuAleeEV5Fdkqn3t4mS+Q11fg==",
+			"dev": true,
+			"requires": {
+				"builtin-modules": "^3.0.0"
+			}
+		},
+		"is-core-module": {
+			"version": "2.9.0",
+			"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.9.0.tgz",
+			"integrity": "sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A==",
+			"dev": true,
+			"requires": {
+				"has": "^1.0.3"
+			}
+		},
 		"is-fullwidth-code-point": {
 			"version": "3.0.0",
 			"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
 			"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="
 		},
+		"is-module": {
+			"version": "1.0.0",
+			"resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz",
+			"integrity": "sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE=",
+			"dev": true
+		},
 		"is-potential-custom-element-name": {
 			"version": "1.0.1",
 			"resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz",
@@ -811,11 +908,23 @@
 			"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
 			"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
 		},
+		"path-parse": {
+			"version": "1.0.7",
+			"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
+			"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
+			"dev": true
+		},
 		"pend": {
 			"version": "1.2.0",
 			"resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
 			"integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA="
 		},
+		"picomatch": {
+			"version": "2.3.1",
+			"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+			"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+			"dev": true
+		},
 		"pkg-dir": {
 			"version": "4.2.0",
 			"resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz",
@@ -906,6 +1015,17 @@
 			"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
 			"integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I="
 		},
+		"resolve": {
+			"version": "1.22.0",
+			"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz",
+			"integrity": "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==",
+			"dev": true,
+			"requires": {
+				"is-core-module": "^2.8.1",
+				"path-parse": "^1.0.7",
+				"supports-preserve-symlinks-flag": "^1.0.0"
+			}
+		},
 		"rimraf": {
 			"version": "3.0.2",
 			"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
@@ -977,6 +1097,11 @@
 			"resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz",
 			"integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E="
 		},
+		"single-file-core": {
+			"version": "1.0.0",
+			"resolved": "https://registry.npmjs.org/single-file-core/-/single-file-core-1.0.0.tgz",
+			"integrity": "sha512-dCdnrQSX1M72PQ2B5kH8ZsXa8GjZCJpSiKXNJYzOjNz0B3i2uLqfhZBChJVBC3Eesgf31bYAavW8CERJqtupDA=="
+		},
 		"source-map": {
 			"version": "0.6.1",
 			"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
@@ -1035,6 +1160,12 @@
 				"has-flag": "^3.0.0"
 			}
 		},
+		"supports-preserve-symlinks-flag": {
+			"version": "1.0.0",
+			"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
+			"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
+			"dev": true
+		},
 		"symbol-tree": {
 			"version": "3.2.4",
 			"resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",

+ 2 - 0
package.json

@@ -14,10 +14,12 @@
 		"jsdom": "19.0.0",
 		"puppeteer-core": "13.5.2",
 		"selenium-webdriver": "4.1.1",
+		"single-file-core": "1.0.0",
 		"strong-data-uri": "1.0.6",
 		"yargs": "17.4.0"
 	},
 	"devDependencies": {
+		"@rollup/plugin-node-resolve": "^13.3.0",
 		"rollup": "2.60.0",
 		"rollup-plugin-terser": "7.0.2"
 	}

+ 28 - 9
rollup.config.dev.js

@@ -1,11 +1,20 @@
+import resolve from "@rollup/plugin-node-resolve";
+
+const PLUGINS = [
+	resolve({ moduleDirectories: ["node_modules"] })
+];
+const EXTERNAL = ["single-file-core"];
+
 export default [{
-	input: ["src/single-file/index.js"],
+	input: ["src/single-file/single-file.js"],
 	output: [{
 		file: "lib/single-file.js",
 		format: "umd",
 		name: "singlefile",
 		plugins: []
-	}]
+	}],
+	plugins: PLUGINS,
+	external: EXTERNAL
 }, {
 	input: ["src/single-file/single-file-frames.js"],
 	output: [{
@@ -13,7 +22,9 @@ export default [{
 		format: "umd",
 		name: "singlefile",
 		plugins: []
-	}]
+	}],
+	plugins: PLUGINS,
+	external: EXTERNAL
 }, {
 	input: ["src/single-file/single-file-bootstrap.js"],
 	output: [{
@@ -21,7 +32,9 @@ export default [{
 		format: "umd",
 		name: "singlefileBootstrap",
 		plugins: []
-	}]
+	}],
+	plugins: PLUGINS,
+	external: EXTERNAL
 }, {
 	input: ["src/common/ui/content/content-infobar.js"],
 	output: [{
@@ -60,19 +73,23 @@ export default [{
 		plugins: []
 	}]
 }, {
-	input: ["src/single-file/processors/hooks/content/content-hooks-web.js"],
+	input: ["src/single-file/single-file-hooks-web.js"],
 	output: [{
 		file: "lib/web/hooks/hooks-web.js",
 		format: "iife",
 		plugins: []
-	}]
+	}],
+	plugins: PLUGINS,
+	external: EXTERNAL
 }, {
-	input: ["src/single-file/processors/hooks/content/content-hooks-frames-web.js"],
+	input: ["src/single-file/single-file-hooks-frames-web.js"],
 	output: [{
 		file: "lib/web/hooks/hooks-frames-web.js",
 		format: "iife",
 		plugins: []
-	}]
+	}],
+	plugins: PLUGINS,
+	external: EXTERNAL
 }, {
 	input: ["src/common/ui/content/content-infobar-web.js"],
 	output: [{
@@ -101,7 +118,9 @@ export default [{
 		format: "umd",
 		name: "singlefile",
 		plugins: []
-	}]
+	}],
+	plugins: PLUGINS,
+	external: EXTERNAL
 }, {
 	input: ["src/extension/lib/single-file/browser-polyfill/chrome-browser-polyfill.js"],
 	output: [{

+ 27 - 9
rollup.config.js

@@ -1,13 +1,21 @@
 import { terser } from "rollup-plugin-terser";
+import resolve from "@rollup/plugin-node-resolve";
+
+const PLUGINS = [
+	resolve({ moduleDirectories: ["node_modules"] })
+];
+const EXTERNAL = ["single-file-core"];
 
 export default [{
-	input: ["src/single-file/index.js"],
+	input: ["src/single-file/single-file.js"],
 	output: [{
 		file: "lib/single-file.js",
 		format: "umd",
 		name: "singlefile",
 		plugins: [terser()]
-	}]
+	}],
+	plugins: PLUGINS,
+	external: EXTERNAL
 }, {
 	input: ["src/single-file/single-file-frames.js"],
 	output: [{
@@ -15,7 +23,9 @@ export default [{
 		format: "umd",
 		name: "singlefile",
 		plugins: [terser()]
-	}]
+	}],
+	plugins: PLUGINS,
+	external: EXTERNAL
 }, {
 	input: ["src/single-file/single-file-bootstrap.js"],
 	output: [{
@@ -23,7 +33,9 @@ export default [{
 		format: "umd",
 		name: "singlefileBootstrap",
 		plugins: [terser()]
-	}]
+	}],
+	plugins: PLUGINS,
+	external: EXTERNAL
 }, {
 	input: ["src/common/ui/content/content-infobar.js"],
 	output: [{
@@ -62,19 +74,23 @@ export default [{
 		plugins: [terser()]
 	}]
 }, {
-	input: ["src/single-file/processors/hooks/content/content-hooks-web.js"],
+	input: ["src/single-file/single-file-hooks-web.js"],
 	output: [{
 		file: "lib/web/hooks/hooks-web.js",
 		format: "iife",
 		plugins: [terser()]
-	}]
+	}],
+	plugins: PLUGINS,
+	external: EXTERNAL
 }, {
-	input: ["src/single-file/processors/hooks/content/content-hooks-frames-web.js"],
+	input: ["src/single-file/single-file-hooks-frames-web.js"],
 	output: [{
 		file: "lib/web/hooks/hooks-frames-web.js",
 		format: "iife",
 		plugins: [terser()]
-	}]
+	}],
+	plugins: PLUGINS,
+	external: EXTERNAL
 }, {
 	input: ["src/common/ui/content/content-infobar-web.js"],
 	output: [{
@@ -103,7 +119,9 @@ export default [{
 		format: "umd",
 		name: "singlefile",
 		plugins: [terser()]
-	}]
+	}],
+	plugins: PLUGINS,
+	external: EXTERNAL
 }, {
 	input: ["src/extension/lib/single-file/browser-polyfill/chrome-browser-polyfill.js"],
 	output: [{

+ 1 - 1
src/extension/ui/content/content-ui-editor-helper-web.js

@@ -21,7 +21,7 @@
  *   Source.
  */
 
-import * as serializer from "../../../../src/single-file/modules/html-serializer.js";
+import * as serializer from "single-file-core/modules/html-serializer.js";
 
 const helper = {
 	serialize(doc, compressHTML) {

+ 0 - 86
src/single-file/index.js

@@ -1,86 +0,0 @@
-/*
- * Copyright 2010-2020 Gildas Lormeau
- * contact : gildas.lormeau <at> gmail.com
- * 
- * This file is part of SingleFile.
- *
- *   The code in this file is free software: you can redistribute it and/or 
- *   modify it under the terms of the GNU Affero General Public License 
- *   (GNU AGPL) as published by the Free Software Foundation, either version 3
- *   of the License, or (at your option) any later version.
- * 
- *   The code in this file is distributed in the hope that it will be useful, 
- *   but WITHOUT ANY WARRANTY; without even the implied warranty of 
- *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero 
- *   General Public License for more details.
- *
- *   As additional permission under GNU AGPL version 3 section 7, you may 
- *   distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU 
- *   AGPL normally required by section 4, provided you include this license 
- *   notice and a URL through which recipients can access the Corresponding 
- *   Source.
- */
-
-/* global globalThis */
-
-import * as processors from "./processors/index.js";
-import * as vendor from "./vendor/index.js";
-import * as modules from "./modules/index.js";
-import * as core from "./single-file-core.js";
-import * as helper from "./single-file-helper.js";
-import * as util from "./single-file-util.js";
-
-let SingleFile;
-export {
-	init,
-	getPageData,
-	processors,
-	vendor,
-	modules,
-	helper,
-	SingleFile
-};
-
-function init(initOptions) {
-	SingleFile = core.getClass(util.getInstance(initOptions), vendor.cssTree);
-}
-
-async function getPageData(options = {}, initOptions, doc = globalThis.document, win = globalThis) {
-	const frames = processors.frameTree;
-	let framesSessionId;
-	init(initOptions);
-	if (doc && win) {
-		helper.initDoc(doc);
-		const preInitializationPromises = [];
-		if (!options.saveRawPage) {
-			if (!options.removeFrames && frames && globalThis.frames && globalThis.frames.length) {
-				let frameTreePromise;
-				if (options.loadDeferredImages) {
-					frameTreePromise = new Promise(resolve => globalThis.setTimeout(() => resolve(frames.getAsync(options)), options.loadDeferredImagesMaxIdleTime - frames.TIMEOUT_INIT_REQUEST_MESSAGE));
-				} else {
-					frameTreePromise = frames.getAsync(options);
-				}
-				preInitializationPromises.push(frameTreePromise);
-			}
-			if (options.loadDeferredImages) {
-				preInitializationPromises.push(processors.lazy.process(options));
-			}
-		}
-		[options.frames] = await Promise.all(preInitializationPromises);
-		framesSessionId = options.frames && options.frames.sessionId;
-	}
-	options.doc = doc;
-	options.win = win;
-	options.insertCanonicalLink = true;
-	options.onprogress = event => {
-		if (event.type === event.RESOURCES_INITIALIZED && doc && win && options.loadDeferredImages) {
-			processors.lazy.resetZoomLevel(options);
-		}
-	};
-	const processor = new SingleFile(options);
-	await processor.run();
-	if (framesSessionId) {
-		frames.cleanup(framesSessionId);
-	}
-	return await processor.getPageData();
-}

+ 0 - 340
src/single-file/modules/css-fonts-alt-minifier.js

@@ -1,340 +0,0 @@
-/*
- * Copyright 2010-2020 Gildas Lormeau
- * contact : gildas.lormeau <at> gmail.com
- * 
- * This file is part of SingleFile.
- *
- *   The code in this file is free software: you can redistribute it and/or 
- *   modify it under the terms of the GNU Affero General Public License 
- *   (GNU AGPL) as published by the Free Software Foundation, either version 3
- *   of the License, or (at your option) any later version.
- * 
- *   The code in this file is distributed in the hope that it will be useful, 
- *   but WITHOUT ANY WARRANTY; without even the implied warranty of 
- *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero 
- *   General Public License for more details.
- *
- *   As additional permission under GNU AGPL version 3 section 7, you may 
- *   distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU 
- *   AGPL normally required by section 4, provided you include this license 
- *   notice and a URL through which recipients can access the Corresponding 
- *   Source.
- */
-
-/* global globalThis */
-
-import * as cssTree from "./../vendor/css-tree.js";
-import {
-	normalizeFontFamily,
-	getFontWeight
-} from "./../single-file-helper.js";
-
-const helper = {
-	normalizeFontFamily,
-	getFontWeight
-};
-
-const FontFace = globalThis.FontFace;
-
-const REGEXP_URL_SIMPLE_QUOTES_FN = /url\s*\(\s*'(.*?)'\s*\)/i;
-const REGEXP_URL_DOUBLE_QUOTES_FN = /url\s*\(\s*"(.*?)"\s*\)/i;
-const REGEXP_URL_NO_QUOTES_FN = /url\s*\(\s*(.*?)\s*\)/i;
-const REGEXP_URL_FUNCTION = /(url|local|-sf-url-original)\(.*?\)\s*(,|$)/g;
-const REGEXP_SIMPLE_QUOTES_STRING = /^'(.*?)'$/;
-const REGEXP_DOUBLE_QUOTES_STRING = /^"(.*?)"$/;
-const REGEXP_URL_FUNCTION_WOFF = /^url\(\s*["']?data:font\/(woff2?)/;
-const REGEXP_URL_FUNCTION_WOFF_ALT = /^url\(\s*["']?data:application\/x-font-(woff)/;
-const REGEXP_FONT_FORMAT = /\.([^.?#]+)((\?|#).*?)?$/;
-const REGEXP_FONT_FORMAT_VALUE = /format\((.*?)\)\s*,?$/;
-const REGEXP_FONT_SRC = /(.*?)\s*,?$/;
-const EMPTY_URL_SOURCE = /^url\(["']?data:[^,]*,?["']?\)/;
-const LOCAL_SOURCE = "local(";
-const MEDIA_ALL = "all";
-const FONT_STRETCHES = {
-	"ultra-condensed": "50%",
-	"extra-condensed": "62.5%",
-	"condensed": "75%",
-	"semi-condensed": "87.5%",
-	"normal": "100%",
-	"semi-expanded": "112.5%",
-	"expanded": "125%",
-	"extra-expanded": "150%",
-	"ultra-expanded": "200%"
-};
-const FONT_MAX_LOAD_DELAY = 5000;
-
-export {
-	process
-};
-
-async function process(doc, stylesheets, fontDeclarations, fontTests) {
-	const fontsDetails = {
-		fonts: new Map(),
-		medias: new Map(),
-		supports: new Map()
-	};
-	const stats = { rules: { processed: 0, discarded: 0 }, fonts: { processed: 0, discarded: 0 } };
-	let sheetIndex = 0;
-	stylesheets.forEach(stylesheetInfo => {
-		const cssRules = stylesheetInfo.stylesheet.children;
-		if (cssRules) {
-			stats.rules.processed += cssRules.size;
-			stats.rules.discarded += cssRules.size;
-			if (stylesheetInfo.mediaText && stylesheetInfo.mediaText != MEDIA_ALL) {
-				const mediaFontsDetails = createFontsDetailsInfo();
-				fontsDetails.medias.set("media-" + sheetIndex + "-" + stylesheetInfo.mediaText, mediaFontsDetails);
-				getFontsDetails(doc, cssRules, sheetIndex, mediaFontsDetails);
-			} else {
-				getFontsDetails(doc, cssRules, sheetIndex, fontsDetails);
-			}
-		}
-		sheetIndex++;
-	});
-	processFontDetails(fontsDetails);
-	await Promise.all([...stylesheets].map(async ([, stylesheetInfo], sheetIndex) => {
-		const cssRules = stylesheetInfo.stylesheet.children;
-		const media = stylesheetInfo.mediaText;
-		if (cssRules) {
-			if (media && media != MEDIA_ALL) {
-				await processFontFaceRules(cssRules, sheetIndex, fontsDetails.medias.get("media-" + sheetIndex + "-" + media), fontDeclarations, fontTests, stats);
-			} else {
-				await processFontFaceRules(cssRules, sheetIndex, fontsDetails, fontDeclarations, fontTests, stats);
-			}
-			stats.rules.discarded -= cssRules.size;
-		}
-	}));
-	return stats;
-}
-
-function getFontsDetails(doc, cssRules, sheetIndex, mediaFontsDetails) {
-	let mediaIndex = 0, supportsIndex = 0;
-	cssRules.forEach(ruleData => {
-		if (ruleData.type == "Atrule" && ruleData.name == "media" && ruleData.block && ruleData.block.children && ruleData.prelude) {
-			const mediaText = cssTree.generate(ruleData.prelude);
-			const fontsDetails = createFontsDetailsInfo();
-			mediaFontsDetails.medias.set("media-" + sheetIndex + "-" + mediaIndex + "-" + mediaText, fontsDetails);
-			mediaIndex++;
-			getFontsDetails(doc, ruleData.block.children, sheetIndex, fontsDetails);
-		} else if (ruleData.type == "Atrule" && ruleData.name == "supports" && ruleData.block && ruleData.block.children && ruleData.prelude) {
-			const supportsText = cssTree.generate(ruleData.prelude);
-			const fontsDetails = createFontsDetailsInfo();
-			mediaFontsDetails.supports.set("supports-" + sheetIndex + "-" + supportsIndex + "-" + supportsText, fontsDetails);
-			supportsIndex++;
-			getFontsDetails(doc, ruleData.block.children, sheetIndex, fontsDetails);
-		} else if (ruleData.type == "Atrule" && ruleData.name == "font-face" && ruleData.block && ruleData.block.children) {
-			const fontKey = getFontKey(ruleData);
-			let fontInfo = mediaFontsDetails.fonts.get(fontKey);
-			if (!fontInfo) {
-				fontInfo = [];
-				mediaFontsDetails.fonts.set(fontKey, fontInfo);
-			}
-			const src = getPropertyValue(ruleData, "src");
-			if (src) {
-				const fontSources = src.match(REGEXP_URL_FUNCTION);
-				if (fontSources) {
-					fontSources.forEach(source => fontInfo.unshift(source));
-				}
-			}
-		}
-	});
-}
-
-function processFontDetails(fontsDetails) {
-	fontsDetails.fonts.forEach((fontInfo, fontKey) => {
-		fontsDetails.fonts.set(fontKey, fontInfo.map(fontSource => {
-			const fontFormatMatch = fontSource.match(REGEXP_FONT_FORMAT_VALUE);
-			let fontFormat;
-			const fontUrl = getURL(fontSource);
-			if (fontFormatMatch && fontFormatMatch[1]) {
-				fontFormat = fontFormatMatch[1].replace(REGEXP_SIMPLE_QUOTES_STRING, "$1").replace(REGEXP_DOUBLE_QUOTES_STRING, "$1").toLowerCase();
-			}
-			if (!fontFormat) {
-				const fontFormatMatch = fontSource.match(REGEXP_URL_FUNCTION_WOFF);
-				if (fontFormatMatch && fontFormatMatch[1]) {
-					fontFormat = fontFormatMatch[1];
-				} else {
-					const fontFormatMatch = fontSource.match(REGEXP_URL_FUNCTION_WOFF_ALT);
-					if (fontFormatMatch && fontFormatMatch[1]) {
-						fontFormat = fontFormatMatch[1];
-					}
-				}
-			}
-			if (!fontFormat && fontUrl) {
-				const fontFormatMatch = fontUrl.match(REGEXP_FONT_FORMAT);
-				if (fontFormatMatch && fontFormatMatch[1]) {
-					fontFormat = fontFormatMatch[1];
-				}
-			}
-			return { src: fontSource.match(REGEXP_FONT_SRC)[1], fontUrl, format: fontFormat };
-		}));
-	});
-	fontsDetails.medias.forEach(mediaFontsDetails => processFontDetails(mediaFontsDetails));
-	fontsDetails.supports.forEach(supportsFontsDetails => processFontDetails(supportsFontsDetails));
-}
-
-async function processFontFaceRules(cssRules, sheetIndex, fontsDetails, fontDeclarations, fontTests, stats) {
-	const removedRules = [];
-	let mediaIndex = 0, supportsIndex = 0;
-	for (let cssRule = cssRules.head; cssRule; cssRule = cssRule.next) {
-		const ruleData = cssRule.data;
-		if (ruleData.type == "Atrule" && ruleData.name == "media" && ruleData.block && ruleData.block.children && ruleData.prelude) {
-			const mediaText = cssTree.generate(ruleData.prelude);
-			await processFontFaceRules(ruleData.block.children, sheetIndex, fontsDetails.medias.get("media-" + sheetIndex + "-" + mediaIndex + "-" + mediaText), fontDeclarations, fontTests, stats);
-			mediaIndex++;
-		} else if (ruleData.type == "Atrule" && ruleData.name == "supports" && ruleData.block && ruleData.block.children && ruleData.prelude) {
-			const supportsText = cssTree.generate(ruleData.prelude);
-			await processFontFaceRules(ruleData.block.children, sheetIndex, fontsDetails.supports.get("supports-" + sheetIndex + "-" + supportsIndex + "-" + supportsText), fontDeclarations, fontTests, stats);
-			supportsIndex++;
-		} else if (ruleData.type == "Atrule" && ruleData.name == "font-face") {
-			const key = getFontKey(ruleData);
-			const fontInfo = fontsDetails.fonts.get(key);
-			if (fontInfo) {
-				const processed = await processFontFaceRule(ruleData, fontInfo, fontDeclarations, fontTests, stats);
-				if (processed) {
-					fontsDetails.fonts.delete(key);
-				}
-			} else {
-				removedRules.push(cssRule);
-			}
-		}
-	}
-	removedRules.forEach(cssRule => cssRules.remove(cssRule));
-}
-
-async function processFontFaceRule(ruleData, fontInfo, fontDeclarations, fontTests, stats) {
-	const removedNodes = [];
-	for (let node = ruleData.block.children.head; node; node = node.next) {
-		if (node.data.property == "src") {
-			removedNodes.push(node);
-		}
-	}
-	removedNodes.pop();
-	removedNodes.forEach(node => ruleData.block.children.remove(node));
-	const srcDeclaration = ruleData.block.children.filter(node => node.property == "src").tail;
-	if (srcDeclaration) {
-		await Promise.all(fontInfo.map(async (source, sourceIndex) => {
-			if (fontTests.has(source.src)) {
-				source.valid = fontTests.get(source.src);
-			} else {
-				if (FontFace) {
-					const fontFace = new FontFace("test-font", source.src);
-					try {
-						let timeout;
-						await Promise.race([
-							fontFace.load().then(() => fontFace.loaded).then(() => { source.valid = true; globalThis.clearTimeout(timeout); }),
-							new Promise(resolve => timeout = globalThis.setTimeout(() => { source.valid = true; resolve(); }, FONT_MAX_LOAD_DELAY))
-						]);
-					} catch (error) {
-						const declarationFontURLs = fontDeclarations.get(srcDeclaration.data);
-						if (declarationFontURLs) {
-							const fontURL = declarationFontURLs[declarationFontURLs.length - sourceIndex - 1];
-							if (fontURL) {
-								const fontFace = new FontFace("test-font", "url(" + fontURL + ")");
-								try {
-									let timeout;
-									await Promise.race([
-										fontFace.load().then(() => fontFace.loaded).then(() => { source.valid = true; globalThis.clearTimeout(timeout); }),
-										new Promise(resolve => timeout = globalThis.setTimeout(() => { source.valid = true; resolve(); }, FONT_MAX_LOAD_DELAY))
-									]);
-								} catch (error) {
-									// ignored
-								}
-							}
-						} else {
-							source.valid = true;
-						}
-					}
-				} else {
-					source.valid = true;
-				}
-				fontTests.set(source.src, source.valid);
-			}
-		}));
-		const findSourceByFormat = (fontFormat, testValidity) => fontInfo.find(source => !source.src.match(EMPTY_URL_SOURCE) && source.format == fontFormat && (!testValidity || source.valid));
-		const filterSources = fontSource => fontInfo.filter(source => source == fontSource || source.src.startsWith(LOCAL_SOURCE));
-		stats.fonts.processed += fontInfo.length;
-		stats.fonts.discarded += fontInfo.length;
-		const woffFontFound =
-			findSourceByFormat("woff2-variations", true) || findSourceByFormat("woff2", true) || findSourceByFormat("woff", true);
-		if (woffFontFound) {
-			fontInfo = filterSources(woffFontFound);
-		} else {
-			const ttfFontFound =
-				findSourceByFormat("truetype-variations", true) || findSourceByFormat("truetype", true);
-			if (ttfFontFound) {
-				fontInfo = filterSources(ttfFontFound);
-			} else {
-				const otfFontFound =
-					findSourceByFormat("opentype") || findSourceByFormat("embedded-opentype");
-				if (otfFontFound) {
-					fontInfo = filterSources(otfFontFound);
-				} else {
-					fontInfo = fontInfo.filter(source => !source.src.match(EMPTY_URL_SOURCE) && (source.valid) || source.src.startsWith(LOCAL_SOURCE));
-				}
-			}
-		}
-		stats.fonts.discarded -= fontInfo.length;
-		fontInfo.reverse();
-		try {
-			srcDeclaration.data.value = cssTree.parse(fontInfo.map(fontSource => fontSource.src).join(","), { context: "value" });
-		}
-		catch (error) {
-			// ignored
-		}
-		return true;
-	} else {
-		return false;
-	}
-}
-
-function getPropertyValue(ruleData, propertyName) {
-	let property;
-	if (ruleData.block.children) {
-		property = ruleData.block.children.filter(node => {
-			try {
-				return node.property == propertyName && !cssTree.generate(node.value).match(/\\9$/);
-			} catch (error) {
-				return node.property == propertyName;
-			}
-		}).tail;
-	}
-	if (property) {
-		try {
-			return cssTree.generate(property.data.value);
-		} catch (error) {
-			// ignored
-		}
-	}
-}
-
-function getFontKey(ruleData) {
-	return JSON.stringify([
-		helper.normalizeFontFamily(getPropertyValue(ruleData, "font-family")),
-		helper.getFontWeight(getPropertyValue(ruleData, "font-weight") || "400"),
-		getPropertyValue(ruleData, "font-style") || "normal",
-		getPropertyValue(ruleData, "unicode-range"),
-		getFontStretch(getPropertyValue(ruleData, "font-stretch")),
-		getPropertyValue(ruleData, "font-variant") || "normal",
-		getPropertyValue(ruleData, "font-feature-settings"),
-		getPropertyValue(ruleData, "font-variation-settings")
-	]);
-}
-
-function getFontStretch(stretch) {
-	return FONT_STRETCHES[stretch] || stretch;
-}
-
-function createFontsDetailsInfo() {
-	return {
-		fonts: new Map(),
-		medias: new Map(),
-		supports: new Map()
-	};
-}
-
-function getURL(urlFunction) {
-	const urlMatch = urlFunction.match(REGEXP_URL_SIMPLE_QUOTES_FN) ||
-		urlFunction.match(REGEXP_URL_DOUBLE_QUOTES_FN) ||
-		urlFunction.match(REGEXP_URL_NO_QUOTES_FN);
-	return urlMatch && urlMatch[1];
-}

+ 0 - 371
src/single-file/modules/css-fonts-minifier.js

@@ -1,371 +0,0 @@
-/*
- * Copyright 2010-2020 Gildas Lormeau
- * contact : gildas.lormeau <at> gmail.com
- * 
- * This file is part of SingleFile.
- *
- *   The code in this file is free software: you can redistribute it and/or 
- *   modify it under the terms of the GNU Affero General Public License 
- *   (GNU AGPL) as published by the Free Software Foundation, either version 3
- *   of the License, or (at your option) any later version.
- * 
- *   The code in this file is distributed in the hope that it will be useful, 
- *   but WITHOUT ANY WARRANTY; without even the implied warranty of 
- *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero 
- *   General Public License for more details.
- *
- *   As additional permission under GNU AGPL version 3 section 7, you may 
- *   distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU 
- *   AGPL normally required by section 4, provided you include this license 
- *   notice and a URL through which recipients can access the Corresponding 
- *   Source.
- */
-
-/* global globalThis */
-
-import * as cssTree from "./../vendor/css-tree.js";
-import * as fontPropertyParser from "./../vendor/css-font-property-parser.js";
-import {
-	normalizeFontFamily,
-	flatten,
-	getFontWeight,
-	removeQuotes
-} from "./../single-file-helper.js";
-
-const helper = {
-	normalizeFontFamily,
-	flatten,
-	getFontWeight,
-	removeQuotes
-};
-
-const REGEXP_COMMA = /\s*,\s*/;
-const REGEXP_DASH = /-/;
-const REGEXP_QUESTION_MARK = /\?/g;
-const REGEXP_STARTS_U_PLUS = /^U\+/i;
-const VALID_FONT_STYLES = [/^normal$/, /^italic$/, /^oblique$/, /^oblique\s+/];
-
-export {
-	process
-};
-
-function process(doc, stylesheets, styles, options) {
-	const stats = { rules: { processed: 0, discarded: 0 }, fonts: { processed: 0, discarded: 0 } };
-	const fontsInfo = { declared: [], used: [] };
-	const workStyleElement = doc.createElement("style");
-	let docContent = "";
-	doc.body.appendChild(workStyleElement);
-	stylesheets.forEach(stylesheetInfo => {
-		const cssRules = stylesheetInfo.stylesheet.children;
-		if (cssRules) {
-			stats.processed += cssRules.size;
-			stats.discarded += cssRules.size;
-			getFontsInfo(cssRules, fontsInfo);
-			docContent = getRulesTextContent(doc, cssRules, workStyleElement, docContent);
-		}
-	});
-	styles.forEach(declarations => {
-		const fontFamilyNames = getFontFamilyNames(declarations);
-		if (fontFamilyNames.length) {
-			fontsInfo.used.push(fontFamilyNames);
-		}
-		docContent = getDeclarationsTextContent(declarations.children, workStyleElement, docContent);
-	});
-	workStyleElement.remove();
-	docContent += doc.body.innerText;
-	if (globalThis.getComputedStyle && options.doc) {
-		fontsInfo.used = fontsInfo.used.map(fontNames => fontNames.map(familyName => {
-			const matchedVar = familyName.match(/^var\((--.*)\)$/);
-			if (matchedVar && matchedVar[1]) {
-				const computedFamilyName = globalThis.getComputedStyle(options.doc.body).getPropertyValue(matchedVar[1]);
-				return (computedFamilyName && computedFamilyName.split(",").map(name => helper.normalizeFontFamily(name))) || familyName;
-			}
-			return familyName;
-		}));
-		fontsInfo.used = fontsInfo.used.map(fontNames => helper.flatten(fontNames));
-	}
-	const variableFound = fontsInfo.used.find(fontNames => fontNames.find(fontName => fontName.startsWith("var(--")));
-	let unusedFonts, filteredUsedFonts;
-	if (variableFound) {
-		unusedFonts = [];
-	} else {
-		filteredUsedFonts = new Map();
-		fontsInfo.used.forEach(fontNames => fontNames.forEach(familyName => {
-			if (fontsInfo.declared.find(fontInfo => fontInfo.fontFamily == familyName)) {
-				const optionalData = options.usedFonts && options.usedFonts.filter(fontInfo => fontInfo[0] == familyName);
-				if (optionalData && optionalData.length) {
-					filteredUsedFonts.set(familyName, optionalData);
-				}
-			}
-		}));
-		unusedFonts = fontsInfo.declared.filter(fontInfo => !filteredUsedFonts.has(fontInfo.fontFamily));
-	}
-	stylesheets.forEach(stylesheetInfo => {
-		const cssRules = stylesheetInfo.stylesheet.children;
-		if (cssRules) {
-			filterUnusedFonts(cssRules, fontsInfo.declared, unusedFonts, filteredUsedFonts, docContent);
-			stats.rules.discarded -= cssRules.size;
-		}
-	});
-	return stats;
-}
-
-function getFontsInfo(cssRules, fontsInfo) {
-	cssRules.forEach(ruleData => {
-		if (ruleData.type == "Atrule" && (ruleData.name == "media" || ruleData.name == "supports") && ruleData.block && ruleData.block.children) {
-			getFontsInfo(ruleData.block.children, fontsInfo);
-		} else if (ruleData.type == "Rule") {
-			const fontFamilyNames = getFontFamilyNames(ruleData.block);
-			if (fontFamilyNames.length) {
-				fontsInfo.used.push(fontFamilyNames);
-			}
-		} else {
-			if (ruleData.type == "Atrule" && ruleData.name == "font-face") {
-				const fontFamily = helper.normalizeFontFamily(getDeclarationValue(ruleData.block.children, "font-family"));
-				if (fontFamily) {
-					const fontWeight = getDeclarationValue(ruleData.block.children, "font-weight") || "400";
-					const fontStyle = getDeclarationValue(ruleData.block.children, "font-style") || "normal";
-					const fontVariant = getDeclarationValue(ruleData.block.children, "font-variant") || "normal";
-					fontWeight.split(",").forEach(weightValue =>
-						fontsInfo.declared.push({ fontFamily, fontWeight: helper.getFontWeight(helper.removeQuotes(weightValue)), fontStyle, fontVariant }));
-				}
-			}
-		}
-	});
-}
-
-function filterUnusedFonts(cssRules, declaredFonts, unusedFonts, filteredUsedFonts, docContent) {
-	const removedRules = [];
-	for (let cssRule = cssRules.head; cssRule; cssRule = cssRule.next) {
-		const ruleData = cssRule.data;
-		if (ruleData.type == "Atrule" && (ruleData.name == "media" || ruleData.name == "supports") && ruleData.block && ruleData.block.children) {
-			filterUnusedFonts(ruleData.block.children, declaredFonts, unusedFonts, filteredUsedFonts, docContent);
-		} else if (ruleData.type == "Atrule" && ruleData.name == "font-face") {
-			const fontFamily = helper.normalizeFontFamily(getDeclarationValue(ruleData.block.children, "font-family"));
-			if (fontFamily) {
-				const unicodeRange = getDeclarationValue(ruleData.block.children, "unicode-range");
-				if (unusedFonts.find(fontInfo => fontInfo.fontFamily == fontFamily) || !testUnicodeRange(docContent, unicodeRange) || !testUsedFont(ruleData, fontFamily, declaredFonts, filteredUsedFonts)) {
-					removedRules.push(cssRule);
-				}
-			}
-			const removedDeclarations = [];
-			for (let declaration = ruleData.block.children.head; declaration; declaration = declaration.next) {
-				if (declaration.data.property == "font-display") {
-					removedDeclarations.push(declaration);
-				}
-			}
-			if (removedDeclarations.length) {
-				removedDeclarations.forEach(removedDeclaration => ruleData.block.children.remove(removedDeclaration));
-			}
-		}
-	}
-	removedRules.forEach(cssRule => cssRules.remove(cssRule));
-}
-
-function testUsedFont(ruleData, familyName, declaredFonts, filteredUsedFonts) {
-	let test;
-	const optionalUsedFonts = filteredUsedFonts && filteredUsedFonts.get(familyName);
-	if (optionalUsedFonts && optionalUsedFonts.length) {
-		let fontStyle = getDeclarationValue(ruleData.block.children, "font-style") || "normal";
-		if (VALID_FONT_STYLES.find(rule => fontStyle.trim().match(rule))) {
-			const fontWeight = helper.getFontWeight(getDeclarationValue(ruleData.block.children, "font-weight") || "400");
-			const declaredFontsWeights = declaredFonts
-				.filter(fontInfo => fontInfo.fontFamily == familyName && fontInfo.fontStyle == fontStyle)
-				.map(fontInfo => fontInfo.fontWeight)
-				.sort((weight1, weight2) => Number.parseInt(weight1, 10) - Number.parseInt(weight2, 10));
-			let usedFontWeights = optionalUsedFonts.map(fontInfo => getUsedFontWeight(fontInfo, fontStyle, declaredFontsWeights));
-			test = testFontweight(fontWeight, usedFontWeights);
-			if (!test) {
-				usedFontWeights = optionalUsedFonts.map(fontInfo => {
-					fontInfo = Array.from(fontInfo);
-					fontInfo[2] = "normal";
-					return getUsedFontWeight(fontInfo, fontStyle, declaredFontsWeights);
-				});
-			}
-			test = testFontweight(fontWeight, usedFontWeights);
-		} else {
-			test = true;
-		}
-	} else {
-		test = true;
-	}
-	return test;
-}
-
-function testFontweight(fontWeight, usedFontWeights) {
-	let test;
-	for (const value of fontWeight.split(/[ ,]/)) {
-		test = test || usedFontWeights.includes(helper.getFontWeight(helper.removeQuotes(value)));
-	}
-	return test;
-}
-
-function getDeclarationValue(declarations, propertyName) {
-	let property;
-	if (declarations) {
-		property = declarations.filter(declaration => declaration.property == propertyName).tail;
-	}
-	if (property) {
-		try {
-			return helper.removeQuotes(cssTree.generate(property.data.value)).toLowerCase();
-		} catch (error) {
-			// ignored
-		}
-	}
-}
-
-function getFontFamilyNames(declarations) {
-	let fontFamilyName = declarations.children.filter(node => node.property == "font-family").tail;
-	let fontFamilyNames = [];
-	if (fontFamilyName) {
-		let familyName = "";
-		if (fontFamilyName.data.value.children) {
-			fontFamilyName.data.value.children.forEach(node => {
-				if (node.type == "Operator" && node.value == "," && familyName) {
-					fontFamilyNames.push(helper.normalizeFontFamily(familyName));
-					familyName = "";
-				} else {
-					familyName += cssTree.generate(node);
-				}
-			});
-		} else {
-			fontFamilyName = cssTree.generate(fontFamilyName.data.value);
-		}
-		if (familyName) {
-			fontFamilyNames.push(helper.normalizeFontFamily(familyName));
-		}
-	}
-	const font = declarations.children.filter(node => node.property == "font").tail;
-	if (font && font.data && font.data.value) {
-		try {
-			const parsedFont = fontPropertyParser.parse(cssTree.generate(font.data.value));
-			parsedFont.family.forEach(familyName => fontFamilyNames.push(helper.normalizeFontFamily(familyName)));
-		} catch (error) {
-			// ignored				
-		}
-	}
-	return fontFamilyNames;
-}
-
-function getUsedFontWeight(fontInfo, fontStyle, fontWeights) {
-	let foundWeight;
-	fontWeights = fontWeights.map(weight => String(Number.parseInt(weight, 10)));
-	if (fontInfo[2] == fontStyle) {
-		let fontWeight = Number(fontInfo[1]);
-		if (fontWeights.length > 1) {
-			if (fontWeight >= 400 && fontWeight <= 500) {
-				foundWeight = fontWeights.find(weight => weight >= fontWeight && weight <= 500);
-				if (!foundWeight) {
-					foundWeight = findDescendingFontWeight(fontWeight, fontWeights);
-				}
-				if (!foundWeight) {
-					foundWeight = findAscendingFontWeight(fontWeight, fontWeights);
-				}
-			}
-			if (fontWeight < 400) {
-				foundWeight = fontWeights.slice().reverse().find(weight => weight <= fontWeight);
-				if (!foundWeight) {
-					foundWeight = findAscendingFontWeight(fontWeight, fontWeights);
-				}
-			}
-			if (fontWeight > 500) {
-				foundWeight = fontWeights.find(weight => weight >= fontWeight);
-				if (!foundWeight) {
-					foundWeight = findDescendingFontWeight(fontWeight, fontWeights);
-				}
-			}
-		} else {
-			foundWeight = fontWeights[0];
-		}
-	}
-	return foundWeight;
-}
-
-function findDescendingFontWeight(fontWeight, fontWeights) {
-	return fontWeights.slice().reverse().find(weight => weight < fontWeight);
-}
-
-function findAscendingFontWeight(fontWeight, fontWeights) {
-	return fontWeights.find(weight => weight > fontWeight);
-}
-
-function getRulesTextContent(doc, cssRules, workStylesheet, content) {
-	cssRules.forEach(ruleData => {
-		if (ruleData.block && ruleData.block.children && ruleData.prelude && ruleData.prelude.children) {
-			if (ruleData.type == "Atrule" && (ruleData.name == "media" || ruleData.name == "supports")) {
-				content = getRulesTextContent(doc, ruleData.block.children, workStylesheet, content);
-			} else if (ruleData.type == "Rule") {
-				content = getDeclarationsTextContent(ruleData.block.children, workStylesheet, content);
-			}
-		}
-	});
-	return content;
-}
-
-function getDeclarationsTextContent(declarations, workStylesheet, content) {
-	const contentText = getDeclarationUnescapedValue(declarations, "content", workStylesheet);
-	const quotesText = getDeclarationUnescapedValue(declarations, "quotes", workStylesheet);
-	if (!content.includes(contentText)) {
-		content += contentText;
-	}
-	if (!content.includes(quotesText)) {
-		content += quotesText;
-	}
-	return content;
-}
-
-function getDeclarationUnescapedValue(declarations, property, workStylesheet) {
-	const rawValue = getDeclarationValue(declarations, property) || "";
-	if (rawValue) {
-		workStylesheet.textContent = "tmp { content:\"" + rawValue + "\"}";
-		if (workStylesheet.sheet && workStylesheet.sheet.cssRules) {
-			return helper.removeQuotes(workStylesheet.sheet.cssRules[0].style.getPropertyValue("content"));
-		} else {
-			return rawValue;
-		}
-	}
-	return "";
-}
-
-function testUnicodeRange(docContent, unicodeRange) {
-	if (unicodeRange) {
-		const unicodeRanges = unicodeRange.split(REGEXP_COMMA);
-		let invalid;
-		const result = unicodeRanges.filter(rangeValue => {
-			const range = rangeValue.split(REGEXP_DASH);
-			let regExpString;
-			if (range.length == 2) {
-				range[0] = transformRange(range[0]);
-				regExpString = "[" + range[0] + "-" + transformRange("U+" + range[1]) + "]";
-			}
-			if (range.length == 1) {
-				if (range[0].includes("?")) {
-					const firstRange = transformRange(range[0]);
-					const secondRange = firstRange;
-					regExpString = "[" + firstRange.replace(REGEXP_QUESTION_MARK, "0") + "-" + secondRange.replace(REGEXP_QUESTION_MARK, "F") + "]";
-				} else if (range[0]) {
-					regExpString = "[" + transformRange(range[0]) + "]";
-				}
-			}
-			if (regExpString) {
-				try {
-					return (new RegExp(regExpString, "u")).test(docContent);
-				} catch (error) {
-					invalid = true;
-					return false;
-				}
-			}
-			return true;
-		});
-		return !invalid && (!unicodeRanges.length || result.length);
-	}
-	return true;
-}
-
-function transformRange(range) {
-	range = range.replace(REGEXP_STARTS_U_PLUS, "");
-	while (range.length < 6) {
-		range = "0" + range;
-	}
-	return "\\u{" + range + "}";
-}

+ 0 - 350
src/single-file/modules/css-matched-rules.js

@@ -1,350 +0,0 @@
-/*
- * Copyright 2010-2020 Gildas Lormeau
- * contact : gildas.lormeau <at> gmail.com
- * 
- * This file is part of SingleFile.
- *
- *   The code in this file is free software: you can redistribute it and/or 
- *   modify it under the terms of the GNU Affero General Public License 
- *   (GNU AGPL) as published by the Free Software Foundation, either version 3
- *   of the License, or (at your option) any later version.
- * 
- *   The code in this file is distributed in the hope that it will be useful, 
- *   but WITHOUT ANY WARRANTY; without even the implied warranty of 
- *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero 
- *   General Public License for more details.
- *
- *   As additional permission under GNU AGPL version 3 section 7, you may 
- *   distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU 
- *   AGPL normally required by section 4, provided you include this license 
- *   notice and a URL through which recipients can access the Corresponding 
- *   Source.
- */
-
-import * as cssTree from "./../vendor/css-tree.js";
-
-const MEDIA_ALL = "all";
-const IGNORED_PSEUDO_ELEMENTS = ["after", "before", "first-letter", "first-line", "placeholder", "selection", "part", "marker"];
-const SINGLE_FILE_HIDDEN_CLASS_NAME = "sf-hidden";
-const DISPLAY_STYLE = "display";
-const REGEXP_VENDOR_IDENTIFIER = /-(ms|webkit|moz|o)-/;
-const DEBUG = false;
-
-class MatchedRules {
-	constructor(doc, stylesheets, styles) {
-		this.doc = doc;
-		this.mediaAllInfo = createMediaInfo(MEDIA_ALL);
-		const matchedElementsCache = new Map();
-		let sheetIndex = 0;
-		const workStyleElement = doc.createElement("style");
-		doc.body.appendChild(workStyleElement);
-		stylesheets.forEach(stylesheetInfo => {
-			if (!stylesheetInfo.scoped) {
-				const cssRules = stylesheetInfo.stylesheet.children;
-				if (cssRules) {
-					if (stylesheetInfo.mediaText && stylesheetInfo.mediaText != MEDIA_ALL) {
-						const mediaInfo = createMediaInfo(stylesheetInfo.mediaText);
-						this.mediaAllInfo.medias.set("style-" + sheetIndex + "-" + stylesheetInfo.mediaText, mediaInfo);
-						getMatchedElementsRules(doc, cssRules, mediaInfo, sheetIndex, styles, matchedElementsCache, workStyleElement);
-					} else {
-						getMatchedElementsRules(doc, cssRules, this.mediaAllInfo, sheetIndex, styles, matchedElementsCache, workStyleElement);
-					}
-				}
-			}
-			sheetIndex++;
-		});
-		let startTime;
-		if (DEBUG) {
-			startTime = Date.now();
-			log("  -- STARTED sortRules");
-		}
-		sortRules(this.mediaAllInfo);
-		if (DEBUG) {
-			log("  -- ENDED sortRules", Date.now() - startTime);
-			startTime = Date.now();
-			log("  -- STARTED computeCascade");
-		}
-		computeCascade(this.mediaAllInfo, [], this.mediaAllInfo, workStyleElement);
-		workStyleElement.remove();
-		if (DEBUG) {
-			log("  -- ENDED computeCascade", Date.now() - startTime);
-		}
-	}
-
-	getMediaAllInfo() {
-		return this.mediaAllInfo;
-	}
-}
-
-export {
-	getMediaAllInfo
-};
-
-function getMediaAllInfo(doc, stylesheets, styles) {
-	return new MatchedRules(doc, stylesheets, styles).getMediaAllInfo();
-}
-
-function createMediaInfo(media) {
-	const mediaInfo = { media: media, elements: new Map(), medias: new Map(), rules: new Map(), pseudoRules: new Map() };
-	if (media == MEDIA_ALL) {
-		mediaInfo.matchedStyles = new Map();
-	}
-	return mediaInfo;
-}
-
-function getMatchedElementsRules(doc, cssRules, mediaInfo, sheetIndex, styles, matchedElementsCache, workStylesheet) {
-	let mediaIndex = 0;
-	let ruleIndex = 0;
-	let startTime;
-	if (DEBUG && cssRules.length > 1) {
-		startTime = Date.now();
-		log("  -- STARTED getMatchedElementsRules", " index =", sheetIndex, "rules.length =", cssRules.length);
-	}
-	cssRules.forEach(ruleData => {
-		if (ruleData.block && ruleData.block.children && ruleData.prelude && ruleData.prelude.children) {
-			if (ruleData.type == "Atrule" && ruleData.name == "media") {
-				const mediaText = cssTree.generate(ruleData.prelude);
-				const ruleMediaInfo = createMediaInfo(mediaText);
-				mediaInfo.medias.set("rule-" + sheetIndex + "-" + mediaIndex + "-" + mediaText, ruleMediaInfo);
-				mediaIndex++;
-				getMatchedElementsRules(doc, ruleData.block.children, ruleMediaInfo, sheetIndex, styles, matchedElementsCache, workStylesheet);
-			} else if (ruleData.type == "Rule") {
-				const selectors = ruleData.prelude.children.toArray();
-				const selectorsText = ruleData.prelude.children.toArray().map(selector => cssTree.generate(selector));
-				const ruleInfo = { ruleData, mediaInfo, ruleIndex, sheetIndex, matchedSelectors: new Set(), declarations: new Set(), selectors, selectorsText };
-				if (!invalidSelector(selectorsText.join(","), workStylesheet)) {
-					for (let selector = ruleData.prelude.children.head, selectorIndex = 0; selector; selector = selector.next, selectorIndex++) {
-						const selectorText = selectorsText[selectorIndex];
-						const selectorInfo = { selector, selectorText, ruleInfo };
-						getMatchedElementsSelector(doc, selectorInfo, styles, matchedElementsCache);
-					}
-				}
-				ruleIndex++;
-			}
-		}
-	});
-	if (DEBUG && cssRules.length > 1) {
-		log("  -- ENDED   getMatchedElementsRules", "delay =", Date.now() - startTime);
-	}
-}
-
-function invalidSelector(selectorText, workStylesheet) {
-	workStylesheet.textContent = selectorText + "{}";
-	return workStylesheet.sheet ? !workStylesheet.sheet.cssRules.length : workStylesheet.sheet;
-}
-
-function getMatchedElementsSelector(doc, selectorInfo, styles, matchedElementsCache) {
-	const filteredSelectorText = getFilteredSelector(selectorInfo.selector, selectorInfo.selectorText);
-	const selectorText = filteredSelectorText != selectorInfo.selectorText ? filteredSelectorText : selectorInfo.selectorText;
-	const cachedMatchedElements = matchedElementsCache.get(selectorText);
-	let matchedElements = cachedMatchedElements;
-	if (!matchedElements) {
-		try {
-			matchedElements = doc.querySelectorAll(selectorText);
-			if (selectorText != "." + SINGLE_FILE_HIDDEN_CLASS_NAME) {
-				matchedElements = Array.from(doc.querySelectorAll(selectorText)).filter(matchedElement =>
-					!matchedElement.classList.contains(SINGLE_FILE_HIDDEN_CLASS_NAME) &&
-					(matchedElement.style.getPropertyValue(DISPLAY_STYLE) != "none" || matchedElement.style.getPropertyPriority("display") != "important")
-				);
-			}
-		} catch (error) {
-			// ignored				
-		}
-	}
-	if (matchedElements) {
-		if (!cachedMatchedElements) {
-			matchedElementsCache.set(selectorText, matchedElements);
-		}
-		if (matchedElements.length) {
-			if (filteredSelectorText == selectorInfo.selectorText) {
-				matchedElements.forEach(element => addRule(element, selectorInfo, styles));
-			} else {
-				let pseudoSelectors = selectorInfo.ruleInfo.mediaInfo.pseudoRules.get(selectorInfo.ruleInfo.ruleData);
-				if (!pseudoSelectors) {
-					pseudoSelectors = new Set();
-					selectorInfo.ruleInfo.mediaInfo.pseudoRules.set(selectorInfo.ruleInfo.ruleData, pseudoSelectors);
-				}
-				pseudoSelectors.add(selectorInfo.selectorText);
-			}
-		}
-	}
-}
-
-function getFilteredSelector(selector, selectorText) {
-	const removedSelectors = [];
-	selector = { data: cssTree.parse(cssTree.generate(selector.data), { context: "selector" }) };
-	filterPseudoClasses(selector);
-	if (removedSelectors.length) {
-		removedSelectors.forEach(({ parentSelector, selector }) => {
-			if (parentSelector.data.children.size == 0 || !selector.prev || selector.prev.data.type == "Combinator" || selector.prev.data.type == "WhiteSpace") {
-				parentSelector.data.children.replace(selector, cssTree.parse("*", { context: "selector" }).children.head);
-			} else {
-				parentSelector.data.children.remove(selector);
-			}
-		});
-		selectorText = cssTree.generate(selector.data).trim();
-	}
-	return selectorText;
-
-	function filterPseudoClasses(selector, parentSelector) {
-		if (selector.data.children) {
-			for (let childSelector = selector.data.children.head; childSelector; childSelector = childSelector.next) {
-				filterPseudoClasses(childSelector, selector);
-			}
-		}
-		if ((selector.data.type == "PseudoClassSelector") ||
-			(selector.data.type == "PseudoElementSelector" && (testVendorPseudo(selector) || IGNORED_PSEUDO_ELEMENTS.includes(selector.data.name)))) {
-			removedSelectors.push({ parentSelector, selector });
-		}
-	}
-
-	function testVendorPseudo(selector) {
-		const name = selector.data.name;
-		return name.startsWith("-") || name.startsWith("\\-");
-	}
-}
-
-function addRule(element, selectorInfo, styles) {
-	const mediaInfo = selectorInfo.ruleInfo.mediaInfo;
-	const elementStyle = styles.get(element);
-	let elementInfo = mediaInfo.elements.get(element);
-	if (!elementInfo) {
-		elementInfo = [];
-		if (elementStyle) {
-			elementInfo.push({ styleInfo: { styleData: elementStyle, declarations: new Set() } });
-		}
-		mediaInfo.elements.set(element, elementInfo);
-	}
-	const specificity = computeSpecificity(selectorInfo.selector.data);
-	specificity.ruleIndex = selectorInfo.ruleInfo.ruleIndex;
-	specificity.sheetIndex = selectorInfo.ruleInfo.sheetIndex;
-	selectorInfo.specificity = specificity;
-	elementInfo.push(selectorInfo);
-}
-
-function computeCascade(mediaInfo, parentMediaInfo, mediaAllInfo, workStylesheet) {
-	mediaInfo.elements.forEach((elementInfo/*, element*/) =>
-		getDeclarationsInfo(elementInfo, workStylesheet/*, element*/).forEach((declarationsInfo, property) => {
-			if (declarationsInfo.selectorInfo.ruleInfo || mediaInfo == mediaAllInfo) {
-				let info;
-				if (declarationsInfo.selectorInfo.ruleInfo) {
-					info = declarationsInfo.selectorInfo.ruleInfo;
-					const ruleData = info.ruleData;
-					const ascendantMedia = [mediaInfo, ...parentMediaInfo].find(media => media.rules.get(ruleData)) || mediaInfo;
-					ascendantMedia.rules.set(ruleData, info);
-					if (ruleData) {
-						info.matchedSelectors.add(declarationsInfo.selectorInfo.selectorText);
-					}
-				} else {
-					info = declarationsInfo.selectorInfo.styleInfo;
-					const styleData = info.styleData;
-					const matchedStyleInfo = mediaAllInfo.matchedStyles.get(styleData);
-					if (!matchedStyleInfo) {
-						mediaAllInfo.matchedStyles.set(styleData, info);
-					}
-				}
-				if (!info.declarations.has(property)) {
-					info.declarations.add(property);
-				}
-			}
-		}));
-	delete mediaInfo.elements;
-	mediaInfo.medias.forEach(childMediaInfo => computeCascade(childMediaInfo, [mediaInfo, ...parentMediaInfo], mediaAllInfo, workStylesheet));
-}
-
-function getDeclarationsInfo(elementInfo, workStylesheet/*, element*/) {
-	const declarationsInfo = new Map();
-	const processedProperties = new Set();
-	elementInfo.forEach(selectorInfo => {
-		let declarations;
-		if (selectorInfo.styleInfo) {
-			declarations = selectorInfo.styleInfo.styleData.children;
-		} else {
-			declarations = selectorInfo.ruleInfo.ruleData.block.children;
-		}
-		processDeclarations(declarationsInfo, declarations, selectorInfo, processedProperties, workStylesheet);
-	});
-	return declarationsInfo;
-}
-
-function processDeclarations(declarationsInfo, declarations, selectorInfo, processedProperties, workStylesheet) {
-	for (let declaration = declarations.tail; declaration; declaration = declaration.prev) {
-		const declarationData = declaration.data;
-		const declarationText = cssTree.generate(declarationData);
-		if (declarationData.type == "Declaration" &&
-			(declarationText.match(REGEXP_VENDOR_IDENTIFIER) || !processedProperties.has(declarationData.property) || declarationData.important) && !invalidDeclaration(declarationText, workStylesheet)) {
-			const declarationInfo = declarationsInfo.get(declarationData);
-			if (!declarationInfo || (declarationData.important && !declarationInfo.important)) {
-				declarationsInfo.set(declarationData, { selectorInfo, important: declarationData.important });
-				if (!declarationText.match(REGEXP_VENDOR_IDENTIFIER)) {
-					processedProperties.add(declarationData.property);
-				}
-			}
-		}
-	}
-}
-
-function invalidDeclaration(declarationText, workStylesheet) {
-	let invalidDeclaration;
-	workStylesheet.textContent = "single-file-declaration { " + declarationText + "}";
-	if (workStylesheet.sheet && !workStylesheet.sheet.cssRules[0].style.length) {
-		if (!declarationText.match(REGEXP_VENDOR_IDENTIFIER)) {
-			invalidDeclaration = true;
-		}
-	}
-	return invalidDeclaration;
-}
-
-function sortRules(media) {
-	media.elements.forEach(elementRules => elementRules.sort((ruleInfo1, ruleInfo2) =>
-		ruleInfo1.styleInfo && !ruleInfo2.styleInfo ? -1 :
-			!ruleInfo1.styleInfo && ruleInfo2.styleInfo ? 1 :
-				compareSpecificity(ruleInfo1.specificity, ruleInfo2.specificity)));
-	media.medias.forEach(sortRules);
-}
-
-function computeSpecificity(selector, specificity = { a: 0, b: 0, c: 0 }) {
-	if (selector.type == "IdSelector") {
-		specificity.a++;
-	}
-	if (selector.type == "ClassSelector" || selector.type == "AttributeSelector" || (selector.type == "PseudoClassSelector" && selector.name != "not")) {
-		specificity.b++;
-	}
-	if ((selector.type == "TypeSelector" && selector.name != "*") || selector.type == "PseudoElementSelector") {
-		specificity.c++;
-	}
-	if (selector.children) {
-		selector.children.forEach(selector => computeSpecificity(selector, specificity));
-	}
-	return specificity;
-}
-
-function compareSpecificity(specificity1, specificity2) {
-	if (specificity1.a > specificity2.a) {
-		return -1;
-	} else if (specificity1.a < specificity2.a) {
-		return 1;
-	} else if (specificity1.b > specificity2.b) {
-		return -1;
-	} else if (specificity1.b < specificity2.b) {
-		return 1;
-	} else if (specificity1.c > specificity2.c) {
-		return -1;
-	} else if (specificity1.c < specificity2.c) {
-		return 1;
-	} else if (specificity1.sheetIndex > specificity2.sheetIndex) {
-		return -1;
-	} else if (specificity1.sheetIndex < specificity2.sheetIndex) {
-		return 1;
-	} else if (specificity1.ruleIndex > specificity2.ruleIndex) {
-		return -1;
-	} else if (specificity1.ruleIndex < specificity2.ruleIndex) {
-		return 1;
-	} else {
-		return -1;
-	}
-}
-
-function log(...args) {
-	console.log("S-File <css-mat>", ...args); // eslint-disable-line no-console
-}

+ 0 - 91
src/single-file/modules/css-medias-alt-minifier.js

@@ -1,91 +0,0 @@
-/*
- * Copyright 2010-2020 Gildas Lormeau
- * contact : gildas.lormeau <at> gmail.com
- * 
- * This file is part of SingleFile.
- *
- *   The code in this file is free software: you can redistribute it and/or 
- *   modify it under the terms of the GNU Affero General Public License 
- *   (GNU AGPL) as published by the Free Software Foundation, either version 3
- *   of the License, or (at your option) any later version.
- * 
- *   The code in this file is distributed in the hope that it will be useful, 
- *   but WITHOUT ANY WARRANTY; without even the implied warranty of 
- *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero 
- *   General Public License for more details.
- *
- *   As additional permission under GNU AGPL version 3 section 7, you may 
- *   distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU 
- *   AGPL normally required by section 4, provided you include this license 
- *   notice and a URL through which recipients can access the Corresponding 
- *   Source.
- */
-
-import * as cssTree from "./../vendor/css-tree.js";
-import * as mediaQueryParser from "./../vendor/css-media-query-parser.js";
-import { flatten } from "./../single-file-helper.js";
-
-const helper = {
-	flatten
-};
-
-const MEDIA_ALL = "all";
-const MEDIA_SCREEN = "screen";
-
-export {
-	process
-};
-
-function process(stylesheets) {
-	const stats = { processed: 0, discarded: 0 };
-	stylesheets.forEach((stylesheetInfo, element) => {
-		if (matchesMediaType(stylesheetInfo.mediaText || MEDIA_ALL, MEDIA_SCREEN) && stylesheetInfo.stylesheet.children) {
-			const removedRules = processRules(stylesheetInfo.stylesheet.children, stats);
-			removedRules.forEach(({ cssRules, cssRule }) => cssRules.remove(cssRule));
-		} else {
-			stylesheets.delete(element);
-		}
-	});
-	return stats;
-}
-
-function processRules(cssRules, stats, removedRules = []) {
-	for (let cssRule = cssRules.head; cssRule; cssRule = cssRule.next) {
-		const ruleData = cssRule.data;
-		if (ruleData.type == "Atrule" && ruleData.name == "media" && ruleData.block && ruleData.block.children && ruleData.prelude && ruleData.prelude.children) {
-			stats.processed++;
-			if (matchesMediaType(cssTree.generate(ruleData.prelude), MEDIA_SCREEN)) {
-				processRules(ruleData.block.children, stats, removedRules);
-			} else {
-				removedRules.push({ cssRules, cssRule });
-				stats.discarded++;
-			}
-		}
-	}
-	return removedRules;
-}
-
-function matchesMediaType(mediaText, mediaType) {
-	const foundMediaTypes = helper.flatten(mediaQueryParser.parseMediaList(mediaText).map(node => getMediaTypes(node, mediaType)));
-	return foundMediaTypes.find(mediaTypeInfo => (!mediaTypeInfo.not && (mediaTypeInfo.value == mediaType || mediaTypeInfo.value == MEDIA_ALL))
-		|| (mediaTypeInfo.not && (mediaTypeInfo.value == MEDIA_ALL || mediaTypeInfo.value != mediaType)));
-}
-
-function getMediaTypes(parentNode, mediaType, mediaTypes = []) {
-	parentNode.nodes.map((node, indexNode) => {
-		if (node.type == "media-query") {
-			return getMediaTypes(node, mediaType, mediaTypes);
-		} else {
-			if (node.type == "media-type") {
-				const nodeMediaType = { not: Boolean(indexNode && parentNode.nodes[0].type == "keyword" && parentNode.nodes[0].value == "not"), value: node.value };
-				if (!mediaTypes.find(mediaType => nodeMediaType.not == mediaType.not && nodeMediaType.value == mediaType.value)) {
-					mediaTypes.push(nodeMediaType);
-				}
-			}
-		}
-	});
-	if (!mediaTypes.length) {
-		mediaTypes.push({ not: false, value: MEDIA_ALL });
-	}
-	return mediaTypes;
-}

+ 0 - 146
src/single-file/modules/css-rules-minifier.js

@@ -1,146 +0,0 @@
-/*
- * Copyright 2010-2020 Gildas Lormeau
- * contact : gildas.lormeau <at> gmail.com
- * 
- * This file is part of SingleFile.
- *
- *   The code in this file is free software: you can redistribute it and/or 
- *   modify it under the terms of the GNU Affero General Public License 
- *   (GNU AGPL) as published by the Free Software Foundation, either version 3
- *   of the License, or (at your option) any later version.
- * 
- *   The code in this file is distributed in the hope that it will be useful, 
- *   but WITHOUT ANY WARRANTY; without even the implied warranty of 
- *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero 
- *   General Public License for more details.
- *
- *   As additional permission under GNU AGPL version 3 section 7, you may 
- *   distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU 
- *   AGPL normally required by section 4, provided you include this license 
- *   notice and a URL through which recipients can access the Corresponding 
- *   Source.
- */
-
-import * as cssTree from "./../vendor/css-tree.js";
-
-const DEBUG = false;
-
-export {
-	process
-};
-
-function process(stylesheets, styles, mediaAllInfo) {
-	const stats = { processed: 0, discarded: 0 };
-	let sheetIndex = 0;
-	stylesheets.forEach(stylesheetInfo => {
-		if (!stylesheetInfo.scoped) {
-			const cssRules = stylesheetInfo.stylesheet.children;
-			if (cssRules) {
-				stats.processed += cssRules.size;
-				stats.discarded += cssRules.size;
-				let mediaInfo;
-				if (stylesheetInfo.mediaText && stylesheetInfo.mediaText != "all") {
-					mediaInfo = mediaAllInfo.medias.get("style-" + sheetIndex + "-" + stylesheetInfo.mediaText);
-				} else {
-					mediaInfo = mediaAllInfo;
-				}
-				processRules(cssRules, sheetIndex, mediaInfo);
-				stats.discarded -= cssRules.size;
-			}
-		}
-		sheetIndex++;
-	});
-	let startTime;
-	if (DEBUG) {
-		startTime = Date.now();
-		log("  -- STARTED processStyleAttribute");
-	}
-	styles.forEach(style => processStyleAttribute(style, mediaAllInfo));
-	if (DEBUG) {
-		log("  -- ENDED   processStyleAttribute delay =", Date.now() - startTime);
-	}
-	return stats;
-}
-
-function processRules(cssRules, sheetIndex, mediaInfo) {
-	let mediaRuleIndex = 0, startTime;
-	if (DEBUG && cssRules.size > 1) {
-		startTime = Date.now();
-		log("  -- STARTED processRules", "rules.length =", cssRules.size);
-	}
-	const removedCssRules = [];
-	for (let cssRule = cssRules.head; cssRule; cssRule = cssRule.next) {
-		const ruleData = cssRule.data;
-		if (ruleData.block && ruleData.block.children && ruleData.prelude && ruleData.prelude.children) {
-			if (ruleData.type == "Atrule" && ruleData.name == "media") {
-				const mediaText = cssTree.generate(ruleData.prelude);
-				processRules(ruleData.block.children, sheetIndex, mediaInfo.medias.get("rule-" + sheetIndex + "-" + mediaRuleIndex + "-" + mediaText));
-				if (!ruleData.prelude.children.size || !ruleData.block.children.size) {
-					removedCssRules.push(cssRule);
-				}
-				mediaRuleIndex++;
-			} else if (ruleData.type == "Rule") {
-				const ruleInfo = mediaInfo.rules.get(ruleData);
-				const pseudoSelectors = mediaInfo.pseudoRules.get(ruleData);
-				if (!ruleInfo && !pseudoSelectors) {
-					removedCssRules.push(cssRule);
-				} else if (ruleInfo) {
-					processRuleInfo(ruleData, ruleInfo, pseudoSelectors);
-					if (!ruleData.prelude.children.size || !ruleData.block.children.size) {
-						removedCssRules.push(cssRule);
-					}
-				}
-			}
-		} else {
-			if (!ruleData || ruleData.type == "Raw" || (ruleData.type == "Rule" && (!ruleData.prelude || ruleData.prelude.type == "Raw"))) {
-				removedCssRules.push(cssRule);
-			}
-		}
-	}
-	removedCssRules.forEach(cssRule => cssRules.remove(cssRule));
-	if (DEBUG && cssRules.size > 1) {
-		log("  -- ENDED   processRules delay =", Date.now() - startTime);
-	}
-}
-
-function processRuleInfo(ruleData, ruleInfo, pseudoSelectors) {
-	const removedDeclarations = [];
-	const removedSelectors = [];
-	let pseudoSelectorFound;
-	for (let selector = ruleData.prelude.children.head; selector; selector = selector.next) {
-		const selectorText = cssTree.generate(selector.data);
-		if (pseudoSelectors && pseudoSelectors.has(selectorText)) {
-			pseudoSelectorFound = true;
-		}
-		if (!ruleInfo.matchedSelectors.has(selectorText) && (!pseudoSelectors || !pseudoSelectors.has(selectorText))) {
-			removedSelectors.push(selector);
-		}
-	}
-	if (!pseudoSelectorFound) {
-		for (let declaration = ruleData.block.children.tail; declaration; declaration = declaration.prev) {
-			if (!ruleInfo.declarations.has(declaration.data)) {
-				removedDeclarations.push(declaration);
-			}
-		}
-	}
-	removedDeclarations.forEach(declaration => ruleData.block.children.remove(declaration));
-	removedSelectors.forEach(selector => ruleData.prelude.children.remove(selector));
-}
-
-function processStyleAttribute(styleData, mediaAllInfo) {
-	const removedDeclarations = [];
-	const styleInfo = mediaAllInfo.matchedStyles.get(styleData);
-	if (styleInfo) {
-		let propertyFound;
-		for (let declaration = styleData.children.head; declaration && !propertyFound; declaration = declaration.next) {
-			if (!styleInfo.declarations.has(declaration.data)) {
-				removedDeclarations.push(declaration);
-			}
-		}
-		removedDeclarations.forEach(declaration => styleData.children.remove(declaration));
-	}
-}
-
-function log(...args) {
-	console.log("S-File <css-min>", ...args); // eslint-disable-line no-console
-}

+ 0 - 109
src/single-file/modules/html-images-alt-minifier.js

@@ -1,109 +0,0 @@
-/*
- * Copyright 2010-2020 Gildas Lormeau
- * contact : gildas.lormeau <at> gmail.com
- * 
- * This file is part of SingleFile.
- *
- *   The code in this file is free software: you can redistribute it and/or 
- *   modify it under the terms of the GNU Affero General Public License 
- *   (GNU AGPL) as published by the Free Software Foundation, either version 3
- *   of the License, or (at your option) any later version.
- * 
- *   The code in this file is distributed in the hope that it will be useful, 
- *   but WITHOUT ANY WARRANTY; without even the implied warranty of 
- *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero 
- *   General Public License for more details.
- *
- *   As additional permission under GNU AGPL version 3 section 7, you may 
- *   distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU 
- *   AGPL normally required by section 4, provided you include this license 
- *   notice and a URL through which recipients can access the Corresponding 
- *   Source.
- */
-
-import * as srcsetParser from "./../vendor/html-srcset-parser.js";
-
-const EMPTY_RESOURCE = "data:,";
-
-export {
-	process
-};
-
-function process(doc) {
-	doc.querySelectorAll("picture").forEach(pictureElement => {
-		const imgElement = pictureElement.querySelector("img");
-		if (imgElement) {
-			let { src, srcset } = getImgSrcData(imgElement);
-			if (!src) {
-				const data = getSourceSrcData(Array.from(pictureElement.querySelectorAll("source")).reverse());
-				src = data.src;
-				if (!srcset) {
-					srcset = data.srcset;
-				}
-			}
-			setSrc({ src, srcset }, imgElement, pictureElement);
-		}
-	});
-	doc.querySelectorAll(":not(picture) > img[srcset]").forEach(imgElement => setSrc(getImgSrcData(imgElement), imgElement));
-}
-
-function getImgSrcData(imgElement) {
-	let src = imgElement.getAttribute("src");
-	if (src == EMPTY_RESOURCE) {
-		src = null;
-	}
-	let srcset = getSourceSrc(imgElement.getAttribute("srcset"));
-	if (srcset == EMPTY_RESOURCE) {
-		srcset = null;
-	}
-	return { src, srcset };
-}
-
-function getSourceSrcData(sources) {
-	let source = sources.find(source => source.src);
-	let src = source && source.src;
-	let srcset = source && source.srcset;
-	if (!src) {
-		source = sources.find(source => getSourceSrc(source.src));
-		src = source && source.src;
-		if (src == EMPTY_RESOURCE) {
-			src = null;
-		}
-	}
-	if (!srcset) {
-		source = sources.find(source => getSourceSrc(source.srcset));
-		srcset = source && source.srcset;
-		if (srcset == EMPTY_RESOURCE) {
-			srcset = null;
-		}
-	}
-	return { src, srcset };
-}
-
-function setSrc(srcData, imgElement, pictureElement) {
-	if (srcData.src) {
-		imgElement.setAttribute("src", srcData.src);
-		imgElement.setAttribute("srcset", "");
-		imgElement.setAttribute("sizes", "");
-	} else {
-		imgElement.setAttribute("src", EMPTY_RESOURCE);
-		if (srcData.srcset) {
-			imgElement.setAttribute("srcset", srcData.srcset);
-		} else {
-			imgElement.setAttribute("srcset", "");
-			imgElement.setAttribute("sizes", "");
-		}
-	}
-	if (pictureElement) {
-		pictureElement.querySelectorAll("source").forEach(sourceElement => sourceElement.remove());
-	}
-}
-
-function getSourceSrc(sourceSrcSet) {
-	if (sourceSrcSet) {
-		const srcset = srcsetParser.process(sourceSrcSet);
-		if (srcset.length) {
-			return (srcset.find(srcset => srcset.url)).url;
-		}
-	}
-}

+ 0 - 262
src/single-file/modules/html-minifier.js

@@ -1,262 +0,0 @@
-/*
- * Copyright 2010-2020 Gildas Lormeau
- * contact : gildas.lormeau <at> gmail.com
- * 
- * This file is part of SingleFile.
- *
- *   The code in this file is free software: you can redistribute it and/or 
- *   modify it under the terms of the GNU Affero General Public License 
- *   (GNU AGPL) as published by the Free Software Foundation, either version 3
- *   of the License, or (at your option) any later version.
- * 
- *   The code in this file is distributed in the hope that it will be useful, 
- *   but WITHOUT ANY WARRANTY; without even the implied warranty of 
- *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero 
- *   General Public License for more details.
- *
- *   As additional permission under GNU AGPL version 3 section 7, you may 
- *   distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU 
- *   AGPL normally required by section 4, provided you include this license 
- *   notice and a URL through which recipients can access the Corresponding 
- *   Source.
- */
-
-// Derived from the work of Kirill Maltsev - https://github.com/posthtml/htmlnano
-
-// Source: https://github.com/kangax/html-minifier/issues/63
-const booleanAttributes = [
-	"allowfullscreen",
-	"async",
-	"autofocus",
-	"autoplay",
-	"checked",
-	"compact",
-	"controls",
-	"declare",
-	"default",
-	"defaultchecked",
-	"defaultmuted",
-	"defaultselected",
-	"defer",
-	"disabled",
-	"enabled",
-	"formnovalidate",
-	"hidden",
-	"indeterminate",
-	"inert",
-	"ismap",
-	"itemscope",
-	"loop",
-	"multiple",
-	"muted",
-	"nohref",
-	"noresize",
-	"noshade",
-	"novalidate",
-	"nowrap",
-	"open",
-	"pauseonexit",
-	"readonly",
-	"required",
-	"reversed",
-	"scoped",
-	"seamless",
-	"selected",
-	"sortable",
-	"truespeed",
-	"typemustmatch",
-	"visible"
-];
-
-const noWhitespaceCollapseElements = ["script", "style", "pre", "textarea"];
-
-// Source: https://www.w3.org/TR/html4/sgml/dtd.html#events (Generic Attributes)
-const safeToRemoveAttrs = [
-	"id",
-	"class",
-	"style",
-	"lang",
-	"dir",
-	"onclick",
-	"ondblclick",
-	"onmousedown",
-	"onmouseup",
-	"onmouseover",
-	"onmousemove",
-	"onmouseout",
-	"onkeypress",
-	"onkeydown",
-	"onkeyup"
-];
-
-const redundantAttributes = {
-	"form": {
-		"method": "get"
-	},
-	"script": {
-		"language": "javascript",
-		"type": "text/javascript",
-		// Remove attribute if the function returns false
-		"charset": node => {
-			// The charset attribute only really makes sense on “external” SCRIPT elements:
-			// http://perfectionkills.com/optimizing-html/#8_script_charset
-			return !node.getAttribute("src");
-		}
-	},
-	"style": {
-		"media": "all",
-		"type": "text/css"
-	},
-	"link": {
-		"media": "all"
-	}
-};
-
-const REGEXP_WHITESPACE = /[ \t\f\r]+/g;
-const REGEXP_NEWLINE = /[\n]+/g;
-const REGEXP_ENDS_WHITESPACE = /^\s+$/;
-const NodeFilter_SHOW_ALL = 4294967295;
-const Node_ELEMENT_NODE = 1;
-const Node_TEXT_NODE = 3;
-const Node_COMMENT_NODE = 8;
-
-const modules = [
-	collapseBooleanAttributes,
-	mergeTextNodes,
-	collapseWhitespace,
-	removeComments,
-	removeEmptyAttributes,
-	removeRedundantAttributes,
-	compressJSONLD,
-	node => mergeElements(node, "style", (node, previousSibling) => node.parentElement && node.parentElement.tagName == "HEAD" && node.media == previousSibling.media && node.title == previousSibling.title)
-];
-
-export {
-	process
-};
-
-function process(doc, options) {
-	removeEmptyInlineElements(doc);
-	const nodesWalker = doc.createTreeWalker(doc.documentElement, NodeFilter_SHOW_ALL, null, false);
-	let node = nodesWalker.nextNode();
-	while (node) {
-		const deletedNode = modules.find(module => module(node, options));
-		const previousNode = node;
-		node = nodesWalker.nextNode();
-		if (deletedNode) {
-			previousNode.remove();
-		}
-	}
-}
-
-function collapseBooleanAttributes(node) {
-	if (node.nodeType == Node_ELEMENT_NODE) {
-		Array.from(node.attributes).forEach(attribute => {
-			if (booleanAttributes.includes(attribute.name)) {
-				node.setAttribute(attribute.name, "");
-			}
-		});
-	}
-}
-
-function mergeTextNodes(node) {
-	if (node.nodeType == Node_TEXT_NODE) {
-		if (node.previousSibling && node.previousSibling.nodeType == Node_TEXT_NODE) {
-			node.textContent = node.previousSibling.textContent + node.textContent;
-			node.previousSibling.remove();
-		}
-	}
-}
-
-function mergeElements(node, tagName, acceptMerge) {
-	if (node.nodeType == Node_ELEMENT_NODE && node.tagName.toLowerCase() == tagName.toLowerCase()) {
-		let previousSibling = node.previousSibling;
-		const previousSiblings = [];
-		while (previousSibling && previousSibling.nodeType == Node_TEXT_NODE && !previousSibling.textContent.trim()) {
-			previousSiblings.push(previousSibling);
-			previousSibling = previousSibling.previousSibling;
-		}
-		if (previousSibling && previousSibling.nodeType == Node_ELEMENT_NODE && previousSibling.tagName == node.tagName && acceptMerge(node, previousSibling)) {
-			node.textContent = previousSibling.textContent + node.textContent;
-			previousSiblings.forEach(node => node.remove());
-			previousSibling.remove();
-		}
-	}
-}
-
-function collapseWhitespace(node, options) {
-	if (node.nodeType == Node_TEXT_NODE) {
-		let element = node.parentElement;
-		const spacePreserved = element.getAttribute(options.PRESERVED_SPACE_ELEMENT_ATTRIBUTE_NAME) == "";
-		if (!spacePreserved) {
-			const textContent = node.textContent;
-			let noWhitespace = noWhitespaceCollapse(element);
-			while (noWhitespace) {
-				element = element.parentElement;
-				noWhitespace = element && noWhitespaceCollapse(element);
-			}
-			if ((!element || noWhitespace) && textContent.length > 1) {
-				node.textContent = textContent.replace(REGEXP_WHITESPACE, getWhiteSpace(node)).replace(REGEXP_NEWLINE, "\n");
-			}
-		}
-	}
-}
-
-function getWhiteSpace(node) {
-	return node.parentElement && node.parentElement.tagName == "HEAD" ? "\n" : " ";
-}
-
-function noWhitespaceCollapse(element) {
-	return element && !noWhitespaceCollapseElements.includes(element.tagName.toLowerCase());
-}
-
-function removeComments(node) {
-	if (node.nodeType == Node_COMMENT_NODE && node.parentElement.tagName != "HTML") {
-		return !node.textContent.toLowerCase().trim().startsWith("[if");
-	}
-}
-
-function removeEmptyAttributes(node) {
-	if (node.nodeType == Node_ELEMENT_NODE) {
-		Array.from(node.attributes).forEach(attribute => {
-			if (safeToRemoveAttrs.includes(attribute.name.toLowerCase())) {
-				const attributeValue = node.getAttribute(attribute.name);
-				if (attributeValue == "" || (attributeValue || "").match(REGEXP_ENDS_WHITESPACE)) {
-					node.removeAttribute(attribute.name);
-				}
-			}
-		});
-	}
-}
-
-function removeRedundantAttributes(node) {
-	if (node.nodeType == Node_ELEMENT_NODE) {
-		const tagRedundantAttributes = redundantAttributes[node.tagName.toLowerCase()];
-		if (tagRedundantAttributes) {
-			Object.keys(tagRedundantAttributes).forEach(redundantAttributeName => {
-				const tagRedundantAttributeValue = tagRedundantAttributes[redundantAttributeName];
-				if (typeof tagRedundantAttributeValue == "function" ? tagRedundantAttributeValue(node) : node.getAttribute(redundantAttributeName) == tagRedundantAttributeValue) {
-					node.removeAttribute(redundantAttributeName);
-				}
-			});
-		}
-	}
-}
-
-function compressJSONLD(node) {
-	if (node.nodeType == Node_ELEMENT_NODE && node.tagName == "SCRIPT" && node.type == "application/ld+json" && node.textContent.trim()) {
-		try {
-			node.textContent = JSON.stringify(JSON.parse(node.textContent));
-		} catch (error) {
-			// ignored
-		}
-	}
-}
-
-function removeEmptyInlineElements(doc) {
-	doc.querySelectorAll("style, script:not([src])").forEach(element => {
-		if (!element.textContent.trim()) {
-			element.remove();
-		}
-	});
-}

+ 0 - 180
src/single-file/modules/html-serializer.js

@@ -1,180 +0,0 @@
-/*
- * Copyright 2010-2020 Gildas Lormeau
- * contact : gildas.lormeau <at> gmail.com
- * 
- * This file is part of SingleFile.
- *
- *   The code in this file is free software: you can redistribute it and/or 
- *   modify it under the terms of the GNU Affero General Public License 
- *   (GNU AGPL) as published by the Free Software Foundation, either version 3
- *   of the License, or (at your option) any later version.
- * 
- *   The code in this file is distributed in the hope that it will be useful, 
- *   but WITHOUT ANY WARRANTY; without even the implied warranty of 
- *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero 
- *   General Public License for more details.
- *
- *   As additional permission under GNU AGPL version 3 section 7, you may 
- *   distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU 
- *   AGPL normally required by section 4, provided you include this license 
- *   notice and a URL through which recipients can access the Corresponding 
- *   Source.
- */
-
-const SELF_CLOSED_TAG_NAMES = ["area", "base", "br", "col", "command", "embed", "hr", "img", "input", "keygen", "link", "meta", "param", "source", "track", "wbr"];
-
-const Node_ELEMENT_NODE = 1;
-const Node_TEXT_NODE = 3;
-const Node_COMMENT_NODE = 8;
-
-// see https://www.w3.org/TR/html5/syntax.html#optional-tags
-const OMITTED_START_TAGS = [
-	{ tagName: "head", accept: element => !element.childNodes.length || element.childNodes[0].nodeType == Node_ELEMENT_NODE },
-	{ tagName: "body", accept: element => !element.childNodes.length }
-];
-const OMITTED_END_TAGS = [
-	{ tagName: "html", accept: next => !next || next.nodeType != Node_COMMENT_NODE },
-	{ tagName: "head", accept: next => !next || (next.nodeType != Node_COMMENT_NODE && (next.nodeType != Node_TEXT_NODE || !startsWithSpaceChar(next.textContent))) },
-	{ tagName: "body", accept: next => !next || next.nodeType != Node_COMMENT_NODE },
-	{ tagName: "li", accept: (next, element) => (!next && element.parentElement && (element.parentElement.tagName == "UL" || element.parentElement.tagName == "OL")) || (next && ["LI"].includes(next.tagName)) },
-	{ tagName: "dt", accept: next => !next || ["DT", "DD"].includes(next.tagName) },
-	{ tagName: "p", accept: next => next && ["ADDRESS", "ARTICLE", "ASIDE", "BLOCKQUOTE", "DETAILS", "DIV", "DL", "FIELDSET", "FIGCAPTION", "FIGURE", "FOOTER", "FORM", "H1", "H2", "H3", "H4", "H5", "H6", "HEADER", "HR", "MAIN", "NAV", "OL", "P", "PRE", "SECTION", "TABLE", "UL"].includes(next.tagName) },
-	{ tagName: "dd", accept: next => !next || ["DT", "DD"].includes(next.tagName) },
-	{ tagName: "rt", accept: next => !next || ["RT", "RP"].includes(next.tagName) },
-	{ tagName: "rp", accept: next => !next || ["RT", "RP"].includes(next.tagName) },
-	{ tagName: "optgroup", accept: next => !next || ["OPTGROUP"].includes(next.tagName) },
-	{ tagName: "option", accept: next => !next || ["OPTION", "OPTGROUP"].includes(next.tagName) },
-	{ tagName: "colgroup", accept: next => !next || (next.nodeType != Node_COMMENT_NODE && (next.nodeType != Node_TEXT_NODE || !startsWithSpaceChar(next.textContent))) },
-	{ tagName: "caption", accept: next => !next || (next.nodeType != Node_COMMENT_NODE && (next.nodeType != Node_TEXT_NODE || !startsWithSpaceChar(next.textContent))) },
-	{ tagName: "thead", accept: next => !next || ["TBODY", "TFOOT"].includes(next.tagName) },
-	{ tagName: "tbody", accept: next => !next || ["TBODY", "TFOOT"].includes(next.tagName) },
-	{ tagName: "tfoot", accept: next => !next },
-	{ tagName: "tr", accept: next => !next || ["TR"].includes(next.tagName) },
-	{ tagName: "td", accept: next => !next || ["TD", "TH"].includes(next.tagName) },
-	{ tagName: "th", accept: next => !next || ["TD", "TH"].includes(next.tagName) }
-];
-const TEXT_NODE_TAGS = ["style", "script", "xmp", "iframe", "noembed", "noframes", "plaintext", "noscript"];
-
-export {
-	process
-};
-
-function process(doc, compressHTML) {
-	const docType = doc.doctype;
-	let docTypeString = "";
-	if (docType) {
-		docTypeString = "<!DOCTYPE " + docType.nodeName;
-		if (docType.publicId) {
-			docTypeString += " PUBLIC \"" + docType.publicId + "\"";
-			if (docType.systemId)
-				docTypeString += " \"" + docType.systemId + "\"";
-		} else if (docType.systemId)
-			docTypeString += " SYSTEM \"" + docType.systemId + "\"";
-		if (docType.internalSubset)
-			docTypeString += " [" + docType.internalSubset + "]";
-		docTypeString += "> ";
-	}
-	return docTypeString + serialize(doc.documentElement, compressHTML);
-}
-
-function serialize(node, compressHTML, isSVG) {
-	if (node.nodeType == Node_TEXT_NODE) {
-		return serializeTextNode(node);
-	} else if (node.nodeType == Node_COMMENT_NODE) {
-		return serializeCommentNode(node);
-	} else if (node.nodeType == Node_ELEMENT_NODE) {
-		return serializeElement(node, compressHTML, isSVG);
-	}
-}
-
-function serializeTextNode(textNode) {
-	const parentNode = textNode.parentNode;
-	let parentTagName;
-	if (parentNode && parentNode.nodeType == Node_ELEMENT_NODE) {
-		parentTagName = parentNode.tagName.toLowerCase();
-	}
-	if (!parentTagName || TEXT_NODE_TAGS.includes(parentTagName)) {
-		if (parentTagName == "script") {
-			return textNode.textContent.replace(/<\//gi, "<\\/").replace(/\/>/gi, "\\/>");
-		}
-		return textNode.textContent;
-	} else {
-		return textNode.textContent.replace(/&/g, "&amp;").replace(/\u00a0/g, "&nbsp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
-	}
-}
-
-function serializeCommentNode(commentNode) {
-	return "<!--" + commentNode.textContent + "-->";
-}
-
-function serializeElement(element, compressHTML, isSVG) {
-	const tagName = element.tagName.toLowerCase();
-	const omittedStartTag = compressHTML && OMITTED_START_TAGS.find(omittedStartTag => tagName == omittedStartTag.tagName && omittedStartTag.accept(element));
-	let content = "";
-	if (!omittedStartTag || element.attributes.length) {
-		content = "<" + tagName;
-		Array.from(element.attributes).forEach(attribute => content += serializeAttribute(attribute, element, compressHTML));
-		content += ">";
-	}
-	if (element.tagName == "TEMPLATE" && !element.childNodes.length) {
-		content += element.innerHTML;
-	} else {
-		Array.from(element.childNodes).forEach(childNode => content += serialize(childNode, compressHTML, isSVG || tagName == "svg"));
-	}
-	const omittedEndTag = compressHTML && OMITTED_END_TAGS.find(omittedEndTag => tagName == omittedEndTag.tagName && omittedEndTag.accept(element.nextSibling, element));
-	if (isSVG || (!omittedEndTag && !SELF_CLOSED_TAG_NAMES.includes(tagName))) {
-		content += "</" + tagName + ">";
-	}
-	return content;
-}
-
-function serializeAttribute(attribute, element, compressHTML) {
-	const name = attribute.name;
-	let content = "";
-	if (!name.match(/["'>/=]/)) {
-		let value = attribute.value;
-		if (compressHTML && name == "class") {
-			value = Array.from(element.classList).map(className => className.trim()).join(" ");
-		}
-		let simpleQuotesValue;
-		value = value.replace(/&/g, "&amp;").replace(/\u00a0/g, "&nbsp;");
-		if (value.includes("\"")) {
-			if (value.includes("'") || !compressHTML) {
-				value = value.replace(/"/g, "&quot;");
-			} else {
-				simpleQuotesValue = true;
-			}
-		}
-		const invalidUnquotedValue = !compressHTML || !value.match(/^[^ \t\n\f\r'"`=<>]+$/);
-		content += " ";
-		if (!attribute.namespace) {
-			content += name;
-		} else if (attribute.namespaceURI == "http://www.w3.org/XML/1998/namespace") {
-			content += "xml:" + name;
-		} else if (attribute.namespaceURI == "http://www.w3.org/2000/xmlns/") {
-			if (name !== "xmlns") {
-				content += "xmlns:";
-			}
-			content += name;
-		} else if (attribute.namespaceURI == "http://www.w3.org/1999/xlink") {
-			content += "xlink:" + name;
-		} else {
-			content += name;
-		}
-		if (value != "") {
-			content += "=";
-			if (invalidUnquotedValue) {
-				content += simpleQuotesValue ? "'" : "\"";
-			}
-			content += value;
-			if (invalidUnquotedValue) {
-				content += simpleQuotesValue ? "'" : "\"";
-			}
-		}
-	}
-	return content;
-}
-
-function startsWithSpaceChar(textContent) {
-	return Boolean(textContent.match(/^[ \t\n\f\r]/));
-}

+ 0 - 42
src/single-file/modules/index.js

@@ -1,42 +0,0 @@
-/*
- * Copyright 2010-2020 Gildas Lormeau
- * contact : gildas.lormeau <at> gmail.com
- * 
- * This file is part of SingleFile.
- *
- *   The code in this file is free software: you can redistribute it and/or 
- *   modify it under the terms of the GNU Affero General Public License 
- *   (GNU AGPL) as published by the Free Software Foundation, either version 3
- *   of the License, or (at your option) any later version.
- * 
- *   The code in this file is distributed in the hope that it will be useful, 
- *   but WITHOUT ANY WARRANTY; without even the implied warranty of 
- *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero 
- *   General Public License for more details.
- *
- *   As additional permission under GNU AGPL version 3 section 7, you may 
- *   distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU 
- *   AGPL normally required by section 4, provided you include this license 
- *   notice and a URL through which recipients can access the Corresponding 
- *   Source.
- */
-
-import * as fontsAltMinifier from "./css-fonts-alt-minifier.js";
-import * as fontsMinifier from "./css-fonts-minifier";
-import * as matchedRules from "./css-matched-rules.js";
-import * as mediasAltMinifier from "./css-medias-alt-minifier.js";
-import * as cssRulesMinifier from "./css-rules-minifier.js";
-import * as imagesAltMinifier from "./html-images-alt-minifier.js";
-import * as htmlMinifier from "./html-minifier.js";
-import * as serializer from "./html-serializer.js";
-
-export {
-	fontsAltMinifier,
-	fontsMinifier,
-	matchedRules,
-	mediasAltMinifier,
-	cssRulesMinifier,
-	imagesAltMinifier,
-	htmlMinifier,
-	serializer
-};

+ 0 - 418
src/single-file/processors/frame-tree/content/content-frame-tree.js

@@ -1,418 +0,0 @@
-/*
- * Copyright 2010-2020 Gildas Lormeau
- * contact : gildas.lormeau <at> gmail.com
- * 
- * This file is part of SingleFile.
- *
- *   The code in this file is free software: you can redistribute it and/or 
- *   modify it under the terms of the GNU Affero General Public License 
- *   (GNU AGPL) as published by the Free Software Foundation, either version 3
- *   of the License, or (at your option) any later version.
- * 
- *   The code in this file is distributed in the hope that it will be useful, 
- *   but WITHOUT ANY WARRANTY; without even the implied warranty of 
- *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero 
- *   General Public License for more details.
- *
- *   As additional permission under GNU AGPL version 3 section 7, you may 
- *   distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU 
- *   AGPL normally required by section 4, provided you include this license 
- *   notice and a URL through which recipients can access the Corresponding 
- *   Source.
- */
-
-/* global globalThis */
-
-import * as lazy from "./../../lazy/content/content-lazy-loader.js";
-import {
-	ON_BEFORE_CAPTURE_EVENT_NAME,
-	ON_AFTER_CAPTURE_EVENT_NAME,
-	WIN_ID_ATTRIBUTE_NAME,
-	preProcessDoc,
-	serialize,
-	postProcessDoc,
-	getShadowRoot
-} from "./../../../single-file-helper.js";
-
-const helper = {
-	ON_BEFORE_CAPTURE_EVENT_NAME,
-	ON_AFTER_CAPTURE_EVENT_NAME,
-	WIN_ID_ATTRIBUTE_NAME,
-	preProcessDoc,
-	serialize,
-	postProcessDoc,
-	getShadowRoot
-};
-
-const MESSAGE_PREFIX = "__frameTree__::";
-const FRAMES_CSS_SELECTOR = "iframe, frame, object[type=\"text/html\"][data]";
-const ALL_ELEMENTS_CSS_SELECTOR = "*";
-const INIT_REQUEST_MESSAGE = "singlefile.frameTree.initRequest";
-const ACK_INIT_REQUEST_MESSAGE = "singlefile.frameTree.ackInitRequest";
-const CLEANUP_REQUEST_MESSAGE = "singlefile.frameTree.cleanupRequest";
-const INIT_RESPONSE_MESSAGE = "singlefile.frameTree.initResponse";
-const TARGET_ORIGIN = "*";
-const TIMEOUT_INIT_REQUEST_MESSAGE = 5000;
-const TIMEOUT_INIT_RESPONSE_MESSAGE = 10000;
-const TOP_WINDOW_ID = "0";
-const WINDOW_ID_SEPARATOR = ".";
-const TOP_WINDOW = globalThis.window == globalThis.top;
-
-const browser = globalThis.browser;
-const addEventListener = (type, listener, options) => globalThis.addEventListener(type, listener, options);
-const top = globalThis.top;
-const MessageChannel = globalThis.MessageChannel;
-const document = globalThis.document;
-
-let sessions = globalThis.sessions;
-if (!sessions) {
-	sessions = globalThis.sessions = new Map();
-}
-let windowId;
-if (TOP_WINDOW) {
-	windowId = TOP_WINDOW_ID;
-	if (browser && browser.runtime && browser.runtime.onMessage && browser.runtime.onMessage.addListener) {
-		browser.runtime.onMessage.addListener(message => {
-			if (message.method == INIT_RESPONSE_MESSAGE) {
-				initResponse(message);
-				return Promise.resolve({});
-			} else if (message.method == ACK_INIT_REQUEST_MESSAGE) {
-				clearFrameTimeout("requestTimeouts", message.sessionId, message.windowId);
-				createFrameResponseTimeout(message.sessionId, message.windowId);
-				return Promise.resolve({});
-			}
-		});
-	}
-}
-addEventListener("message", async event => {
-	if (typeof event.data == "string" && event.data.startsWith(MESSAGE_PREFIX)) {
-		event.preventDefault();
-		event.stopPropagation();
-		const message = JSON.parse(event.data.substring(MESSAGE_PREFIX.length));
-		if (message.method == INIT_REQUEST_MESSAGE) {
-			if (event.source) {
-				sendMessage(event.source, { method: ACK_INIT_REQUEST_MESSAGE, windowId: message.windowId, sessionId: message.sessionId });
-			}
-			if (!TOP_WINDOW) {
-				globalThis.stop();
-				if (message.options.loadDeferredImages) {
-					lazy.process(message.options);
-				}
-				await initRequestAsync(message);
-			}
-		} else if (message.method == ACK_INIT_REQUEST_MESSAGE) {
-			clearFrameTimeout("requestTimeouts", message.sessionId, message.windowId);
-			createFrameResponseTimeout(message.sessionId, message.windowId);
-		} else if (message.method == CLEANUP_REQUEST_MESSAGE) {
-			cleanupRequest(message);
-		} else if (message.method == INIT_RESPONSE_MESSAGE && sessions.get(message.sessionId)) {
-			const port = event.ports[0];
-			port.onmessage = event => initResponse(event.data);
-		}
-	}
-}, true);
-
-export {
-	getAsync,
-	getSync,
-	cleanup,
-	initResponse,
-	TIMEOUT_INIT_REQUEST_MESSAGE
-};
-
-function getAsync(options) {
-	const sessionId = getNewSessionId();
-	options = JSON.parse(JSON.stringify(options));
-	return new Promise(resolve => {
-		sessions.set(sessionId, {
-			frames: [],
-			requestTimeouts: {},
-			responseTimeouts: {},
-			resolve: frames => {
-				frames.sessionId = sessionId;
-				resolve(frames);
-			}
-		});
-		initRequestAsync({ windowId, sessionId, options });
-	});
-}
-
-function getSync(options) {
-	const sessionId = getNewSessionId();
-	options = JSON.parse(JSON.stringify(options));
-	sessions.set(sessionId, {
-		frames: [],
-		requestTimeouts: {},
-		responseTimeouts: {}
-	});
-	initRequestSync({ windowId, sessionId, options });
-	const frames = sessions.get(sessionId).frames;
-	frames.sessionId = sessionId;
-	return frames;
-}
-
-function cleanup(sessionId) {
-	sessions.delete(sessionId);
-	cleanupRequest({ windowId, sessionId, options: { sessionId } });
-}
-
-function getNewSessionId() {
-	return globalThis.crypto.getRandomValues(new Uint32Array(32)).join("");
-}
-
-function initRequestSync(message) {
-	const sessionId = message.sessionId;
-	const waitForUserScript = globalThis._singleFile_waitForUserScript;
-	delete globalThis._singleFile_cleaningUp;
-	if (!TOP_WINDOW) {
-		windowId = globalThis.frameId = message.windowId;
-	}
-	processFrames(document, message.options, windowId, sessionId);
-	if (!TOP_WINDOW) {
-		if (message.options.userScriptEnabled && waitForUserScript) {
-			waitForUserScript(helper.ON_BEFORE_CAPTURE_EVENT_NAME);
-		}
-		sendInitResponse({ frames: [getFrameData(document, globalThis, windowId, message.options)], sessionId, requestedFrameId: document.documentElement.dataset.requestedFrameId && windowId });
-		if (message.options.userScriptEnabled && waitForUserScript) {
-			waitForUserScript(helper.ON_AFTER_CAPTURE_EVENT_NAME);
-		}
-		delete document.documentElement.dataset.requestedFrameId;
-	}
-}
-
-async function initRequestAsync(message) {
-	const sessionId = message.sessionId;
-	const waitForUserScript = globalThis._singleFile_waitForUserScript;
-	delete globalThis._singleFile_cleaningUp;
-	if (!TOP_WINDOW) {
-		windowId = globalThis.frameId = message.windowId;
-	}
-	processFrames(document, message.options, windowId, sessionId);
-	if (!TOP_WINDOW) {
-		if (message.options.userScriptEnabled && waitForUserScript) {
-			await waitForUserScript(helper.ON_BEFORE_CAPTURE_EVENT_NAME);
-		}
-		sendInitResponse({ frames: [getFrameData(document, globalThis, windowId, message.options)], sessionId, requestedFrameId: document.documentElement.dataset.requestedFrameId && windowId });
-		if (message.options.userScriptEnabled && waitForUserScript) {
-			await waitForUserScript(helper.ON_AFTER_CAPTURE_EVENT_NAME);
-		}
-		delete document.documentElement.dataset.requestedFrameId;
-	}
-}
-
-function cleanupRequest(message) {
-	if (!globalThis._singleFile_cleaningUp) {
-		globalThis._singleFile_cleaningUp = true;
-		const sessionId = message.sessionId;
-		cleanupFrames(getFrames(document), message.windowId, sessionId);
-	}
-}
-
-function initResponse(message) {
-	message.frames.forEach(frameData => clearFrameTimeout("responseTimeouts", message.sessionId, frameData.windowId));
-	const windowData = sessions.get(message.sessionId);
-	if (windowData) {
-		if (message.requestedFrameId) {
-			windowData.requestedFrameId = message.requestedFrameId;
-		}
-		message.frames.forEach(messageFrameData => {
-			let frameData = windowData.frames.find(frameData => messageFrameData.windowId == frameData.windowId);
-			if (!frameData) {
-				frameData = { windowId: messageFrameData.windowId };
-				windowData.frames.push(frameData);
-			}
-			if (!frameData.processed) {
-				frameData.content = messageFrameData.content;
-				frameData.baseURI = messageFrameData.baseURI;
-				frameData.title = messageFrameData.title;
-				frameData.canvases = messageFrameData.canvases;
-				frameData.fonts = messageFrameData.fonts;
-				frameData.stylesheets = messageFrameData.stylesheets;
-				frameData.images = messageFrameData.images;
-				frameData.posters = messageFrameData.posters;
-				frameData.videos = messageFrameData.videos;
-				frameData.usedFonts = messageFrameData.usedFonts;
-				frameData.shadowRoots = messageFrameData.shadowRoots;
-				frameData.imports = messageFrameData.imports;
-				frameData.processed = messageFrameData.processed;
-			}
-		});
-		const remainingFrames = windowData.frames.filter(frameData => !frameData.processed).length;
-		if (!remainingFrames) {
-			windowData.frames = windowData.frames.sort((frame1, frame2) => frame2.windowId.split(WINDOW_ID_SEPARATOR).length - frame1.windowId.split(WINDOW_ID_SEPARATOR).length);
-			if (windowData.resolve) {
-				if (windowData.requestedFrameId) {
-					windowData.frames.forEach(frameData => {
-						if (frameData.windowId == windowData.requestedFrameId) {
-							frameData.requestedFrame = true;
-						}
-					});
-				}
-				windowData.resolve(windowData.frames);
-			}
-		}
-	}
-}
-function processFrames(doc, options, parentWindowId, sessionId) {
-	const frameElements = getFrames(doc);
-	processFramesAsync(doc, frameElements, options, parentWindowId, sessionId);
-	if (frameElements.length) {
-		processFramesSync(doc, frameElements, options, parentWindowId, sessionId);
-	}
-}
-
-function processFramesAsync(doc, frameElements, options, parentWindowId, sessionId) {
-	const frames = [];
-	let requestTimeouts;
-	if (sessions.get(sessionId)) {
-		requestTimeouts = sessions.get(sessionId).requestTimeouts;
-	} else {
-		requestTimeouts = {};
-		sessions.set(sessionId, { requestTimeouts });
-	}
-	frameElements.forEach((frameElement, frameIndex) => {
-		const windowId = parentWindowId + WINDOW_ID_SEPARATOR + frameIndex;
-		frameElement.setAttribute(helper.WIN_ID_ATTRIBUTE_NAME, windowId);
-		frames.push({ windowId });
-	});
-	sendInitResponse({ frames, sessionId, requestedFrameId: doc.documentElement.dataset.requestedFrameId && parentWindowId });
-	frameElements.forEach((frameElement, frameIndex) => {
-		const windowId = parentWindowId + WINDOW_ID_SEPARATOR + frameIndex;
-		try {
-			sendMessage(frameElement.contentWindow, { method: INIT_REQUEST_MESSAGE, windowId, sessionId, options });
-		} catch (error) {
-			// ignored
-		}
-		requestTimeouts[windowId] = globalThis.setTimeout(() => sendInitResponse({ frames: [{ windowId, processed: true }], sessionId }), TIMEOUT_INIT_REQUEST_MESSAGE);
-	});
-	delete doc.documentElement.dataset.requestedFrameId;
-}
-
-function processFramesSync(doc, frameElements, options, parentWindowId, sessionId) {
-	const frames = [];
-	frameElements.forEach((frameElement, frameIndex) => {
-		const windowId = parentWindowId + WINDOW_ID_SEPARATOR + frameIndex;
-		let frameDoc;
-		try {
-			frameDoc = frameElement.contentDocument;
-		} catch (error) {
-			// ignored
-		}
-		if (frameDoc) {
-			try {
-				const frameWindow = frameElement.contentWindow;
-				frameWindow.stop();
-				clearFrameTimeout("requestTimeouts", sessionId, windowId);
-				processFrames(frameDoc, options, windowId, sessionId);
-				frames.push(getFrameData(frameDoc, frameWindow, windowId, options));
-			} catch (error) {
-				frames.push({ windowId, processed: true });
-			}
-		}
-	});
-	sendInitResponse({ frames, sessionId, requestedFrameId: doc.documentElement.dataset.requestedFrameId && parentWindowId });
-	delete doc.documentElement.dataset.requestedFrameId;
-}
-
-function clearFrameTimeout(type, sessionId, windowId) {
-	const session = sessions.get(sessionId);
-	if (session && session[type]) {
-		const timeout = session[type][windowId];
-		if (timeout) {
-			globalThis.clearTimeout(timeout);
-			delete session[type][windowId];
-		}
-	}
-}
-
-function createFrameResponseTimeout(sessionId, windowId) {
-	const session = sessions.get(sessionId);
-	if (session && session.responseTimeouts) {
-		session.responseTimeouts[windowId] = globalThis.setTimeout(() => sendInitResponse({ frames: [{ windowId: windowId, processed: true }], sessionId: sessionId }), TIMEOUT_INIT_RESPONSE_MESSAGE);
-	}
-}
-
-function cleanupFrames(frameElements, parentWindowId, sessionId) {
-	frameElements.forEach((frameElement, frameIndex) => {
-		const windowId = parentWindowId + WINDOW_ID_SEPARATOR + frameIndex;
-		frameElement.removeAttribute(helper.WIN_ID_ATTRIBUTE_NAME);
-		try {
-			sendMessage(frameElement.contentWindow, { method: CLEANUP_REQUEST_MESSAGE, windowId, sessionId });
-		} catch (error) {
-			// ignored
-		}
-	});
-	frameElements.forEach((frameElement, frameIndex) => {
-		const windowId = parentWindowId + WINDOW_ID_SEPARATOR + frameIndex;
-		let frameDoc;
-		try {
-			frameDoc = frameElement.contentDocument;
-		} catch (error) {
-			// ignored
-		}
-		if (frameDoc) {
-			try {
-				cleanupFrames(getFrames(frameDoc), windowId, sessionId);
-			} catch (error) {
-				// ignored
-			}
-		}
-	});
-}
-
-function sendInitResponse(message) {
-	message.method = INIT_RESPONSE_MESSAGE;
-	try {
-		top.singlefile.processors.frameTree.initResponse(message);
-	} catch (error) {
-		sendMessage(top, message, true);
-	}
-}
-
-function sendMessage(targetWindow, message, useChannel) {
-	if (targetWindow == top && browser && browser.runtime && browser.runtime.sendMessage) {
-		browser.runtime.sendMessage(message);
-	} else {
-		if (useChannel) {
-			const channel = new MessageChannel();
-			targetWindow.postMessage(MESSAGE_PREFIX + JSON.stringify({ method: message.method, sessionId: message.sessionId }), TARGET_ORIGIN, [channel.port2]);
-			channel.port1.postMessage(message);
-		} else {
-			targetWindow.postMessage(MESSAGE_PREFIX + JSON.stringify(message), TARGET_ORIGIN);
-		}
-	}
-}
-
-function getFrameData(document, globalThis, windowId, options) {
-	const docData = helper.preProcessDoc(document, globalThis, options);
-	const content = helper.serialize(document);
-	helper.postProcessDoc(document, docData.markedElements, docData.invalidElements);
-	const baseURI = document.baseURI.split("#")[0];
-	return {
-		windowId,
-		content,
-		baseURI,
-		title: document.title,
-		canvases: docData.canvases,
-		fonts: docData.fonts,
-		stylesheets: docData.stylesheets,
-		images: docData.images,
-		posters: docData.posters,
-		videos: docData.videos,
-		usedFonts: docData.usedFonts,
-		shadowRoots: docData.shadowRoots,
-		imports: docData.imports,
-		processed: true
-	};
-}
-
-function getFrames(document) {
-	let frames = Array.from(document.querySelectorAll(FRAMES_CSS_SELECTOR));
-	document.querySelectorAll(ALL_ELEMENTS_CSS_SELECTOR).forEach(element => {
-		const shadowRoot = helper.getShadowRoot(element);
-		if (shadowRoot) {
-			frames = frames.concat(...shadowRoot.querySelectorAll(FRAMES_CSS_SELECTOR));
-		}
-	});
-	return frames;
-}

+ 0 - 404
src/single-file/processors/hooks/content/content-hooks-frames-web.js

@@ -1,404 +0,0 @@
-/*
- * Copyright 2010-2020 Gildas Lormeau
- * contact : gildas.lormeau <at> gmail.com
- * 
- * This file is part of SingleFile.
- *
- *   The code in this file is free software: you can redistribute it and/or 
- *   modify it under the terms of the GNU Affero General Public License 
- *   (GNU AGPL) as published by the Free Software Foundation, either version 3
- *   of the License, or (at your option) any later version.
- * 
- *   The code in this file is distributed in the hope that it will be useful, 
- *   but WITHOUT ANY WARRANTY; without even the implied warranty of 
- *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero 
- *   General Public License for more details.
- *
- *   As additional permission under GNU AGPL version 3 section 7, you may 
- *   distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU 
- *   AGPL normally required by section 4, provided you include this license 
- *   notice and a URL through which recipients can access the Corresponding 
- *   Source.
- */
-
-/* global window, globalThis */
-
-(globalThis => {
-
-	const LOAD_DEFERRED_IMAGES_START_EVENT = "single-file-load-deferred-images-start";
-	const LOAD_DEFERRED_IMAGES_END_EVENT = "single-file-load-deferred-images-end";
-	const LOAD_DEFERRED_IMAGES_KEEP_ZOOM_LEVEL_START_EVENT = "single-file-load-deferred-images-keep-zoom-level-start";
-	const LOAD_DEFERRED_IMAGES_KEEP_ZOOM_LEVEL_END_EVENT = "single-file-load-deferred-images-keep-zoom-level-end";
-	const LOAD_DEFERRED_IMAGES_RESET_ZOOM_LEVEL_EVENT = "single-file-load-deferred-images-keep-zoom-level-reset";
-	const LOAD_DEFERRED_IMAGES_RESET_EVENT = "single-file-load-deferred-images-reset";
-	const BLOCK_COOKIES_START_EVENT = "single-file-block-cookies-start";
-	const BLOCK_COOKIES_END_EVENT = "single-file-block-cookies-end";
-	const BLOCK_STORAGE_START_EVENT = "single-file-block-storage-start";
-	const BLOCK_STORAGE_END_EVENT = "single-file-block-storage-end";
-	const DISPATCH_SCROLL_START_EVENT = "single-file-dispatch-scroll-event-start";
-	const DISPATCH_SCROLL_END_EVENT = "single-file-dispatch-scroll-event-end";
-	const LAZY_LOAD_ATTRIBUTE = "single-file-lazy-load";
-	const LOAD_IMAGE_EVENT = "single-file-load-image";
-	const IMAGE_LOADED_EVENT = "single-file-image-loaded";
-	const NEW_FONT_FACE_EVENT = "single-file-new-font-face";
-	const DELETE_FONT_EVENT = "single-file-delete-font";
-	const CLEAR_FONTS_EVENT = "single-file-clear-fonts";
-	const FONT_STYLE_PROPERTIES = {
-		family: "font-family",
-		style: "font-style",
-		weight: "font-weight",
-		stretch: "font-stretch",
-		unicodeRange: "unicode-range",
-		variant: "font-variant",
-		featureSettings: "font-feature-settings"
-	};
-
-	const addEventListener = (type, listener, options) => globalThis.addEventListener(type, listener, options);
-	const dispatchEvent = event => { try { globalThis.dispatchEvent(event); } catch (error) {  /* ignored */ } };
-	const CustomEvent = globalThis.CustomEvent;
-	const document = globalThis.document;
-	const screen = globalThis.screen;
-	const Element = globalThis.Element;
-	const UIEvent = globalThis.UIEvent;
-	const FileReader = globalThis.FileReader;
-	const Blob = globalThis.Blob;
-	const console = globalThis.console;
-	const warn = (console && console.warn && ((...args) => console.warn(...args))) || (() => { });
-
-	const observers = new Map();
-	const observedElements = new Map();
-
-	let dispatchScrollEvent;
-	addEventListener(LOAD_DEFERRED_IMAGES_START_EVENT, () => loadDeferredImagesStart());
-	addEventListener(LOAD_DEFERRED_IMAGES_KEEP_ZOOM_LEVEL_START_EVENT, () => loadDeferredImagesStart(true));
-
-	function loadDeferredImagesStart(keepZoomLevel) {
-		const scrollingElement = document.scrollingElement || document.documentElement;
-		const clientHeight = scrollingElement.clientHeight;
-		const clientWidth = scrollingElement.clientWidth;
-		const scrollHeight = Math.max(scrollingElement.scrollHeight - clientHeight, clientHeight);
-		const scrollWidth = Math.max(scrollingElement.scrollWidth - clientWidth, clientWidth);
-		document.querySelectorAll("[loading=lazy]").forEach(element => {
-			element.loading = "eager";
-			element.setAttribute(LAZY_LOAD_ATTRIBUTE, "");
-		});
-		scrollingElement.__defineGetter__("clientHeight", () => scrollHeight);
-		scrollingElement.__defineGetter__("clientWidth", () => scrollWidth);
-		screen.__defineGetter__("height", () => scrollHeight);
-		screen.__defineGetter__("width", () => scrollWidth);
-		globalThis._singleFile_innerHeight = globalThis.innerHeight;
-		globalThis._singleFile_innerWidth = globalThis.innerWidth;
-		globalThis.__defineGetter__("innerHeight", () => scrollHeight);
-		globalThis.__defineGetter__("innerWidth", () => scrollWidth);
-		if (!keepZoomLevel) {
-			if (!globalThis._singleFile_getBoundingClientRect) {
-				globalThis._singleFile_getBoundingClientRect = Element.prototype.getBoundingClientRect;
-				Element.prototype.getBoundingClientRect = function () {
-					const boundingRect = globalThis._singleFile_getBoundingClientRect.call(this);
-					if (this == scrollingElement) {
-						boundingRect.__defineGetter__("height", () => scrollHeight);
-						boundingRect.__defineGetter__("bottom", () => scrollHeight + boundingRect.top);
-						boundingRect.__defineGetter__("width", () => scrollWidth);
-						boundingRect.__defineGetter__("right", () => scrollWidth + boundingRect.left);
-					}
-					return boundingRect;
-				};
-			}
-		}
-		if (!globalThis._singleFileImage) {
-			const Image = globalThis.Image;
-			globalThis._singleFileImage = globalThis.Image;
-			globalThis.__defineGetter__("Image", function () {
-				return function () {
-					const image = new Image(...arguments);
-					const result = new Image(...arguments);
-					result.__defineSetter__("src", function (value) {
-						image.src = value;
-						dispatchEvent(new CustomEvent(LOAD_IMAGE_EVENT, { detail: image.src }));
-					});
-					result.__defineGetter__("src", function () {
-						return image.src;
-					});
-					result.__defineSetter__("srcset", function (value) {
-						dispatchEvent(new CustomEvent(LOAD_IMAGE_EVENT));
-						image.srcset = value;
-					});
-					result.__defineGetter__("srcset", function () {
-						return image.srcset;
-					});
-					image.onload = image.onloadend = image.onerror = event => {
-						dispatchEvent(new CustomEvent(IMAGE_LOADED_EVENT, { detail: image.src }));
-						result.dispatchEvent(new UIEvent(event.type, event));
-					};
-					if (image.decode) {
-						result.decode = () => image.decode();
-					}
-					return result;
-				};
-			});
-		}
-		let zoomFactorX, zoomFactorY;
-		if (keepZoomLevel) {
-			zoomFactorX = clientHeight / scrollHeight;
-			zoomFactorY = clientWidth / scrollWidth;
-		} else {
-			zoomFactorX = (clientHeight + globalThis.scrollY) / scrollHeight;
-			zoomFactorY = (clientWidth + globalThis.scrollX) / scrollWidth;
-		}
-		const zoomFactor = Math.min(zoomFactorX, zoomFactorY);
-		if (zoomFactor < 1) {
-			const transform = document.documentElement.style.getPropertyValue("transform");
-			const transformPriority = document.documentElement.style.getPropertyPriority("transform");
-			const transformOrigin = document.documentElement.style.getPropertyValue("transform-origin");
-			const transformOriginPriority = document.documentElement.style.getPropertyPriority("transform-origin");
-			const minHeight = document.documentElement.style.getPropertyValue("min-height");
-			const minHeightPriority = document.documentElement.style.getPropertyPriority("min-height");
-			document.documentElement.style.setProperty("transform-origin", (zoomFactorX < 1 ? "50%" : "0") + " " + (zoomFactorY < 1 ? "50%" : "0") + " 0", "important");
-			document.documentElement.style.setProperty("transform", "scale3d(" + zoomFactor + ", " + zoomFactor + ", 1)", "important");
-			document.documentElement.style.setProperty("min-height", (100 / zoomFactor) + "vh", "important");
-			dispatchResizeEvent();
-			if (keepZoomLevel) {
-				document.documentElement.style.setProperty("-sf-transform", transform, transformPriority);
-				document.documentElement.style.setProperty("-sf-transform-origin", transformOrigin, transformOriginPriority);
-				document.documentElement.style.setProperty("-sf-min-height", minHeight, minHeightPriority);
-			} else {
-				document.documentElement.style.setProperty("transform", transform, transformPriority);
-				document.documentElement.style.setProperty("transform-origin", transformOrigin, transformOriginPriority);
-				document.documentElement.style.setProperty("min-height", minHeight, minHeightPriority);
-			}
-		}
-		if (!keepZoomLevel) {
-			dispatchResizeEvent();
-			const docBoundingRect = scrollingElement.getBoundingClientRect();
-			if (window == window.top) {
-				[...observers].forEach(([intersectionObserver, observer]) => {
-					const getBoundingClientRectDefined = observer.options && observer.options.root && observer.options.root.getBoundingClientRect;
-					const rootBoundingRect = getBoundingClientRectDefined && observer.options.root.getBoundingClientRect();
-					const targetElements = observedElements.get(intersectionObserver);
-					if (targetElements) {
-						const params = targetElements.map(target => {
-							const boundingClientRect = target.getBoundingClientRect();
-							const isIntersecting = true;
-							const intersectionRatio = 1;
-							const rootBounds = getBoundingClientRectDefined ? rootBoundingRect : docBoundingRect;
-							const time = 0;
-							return { target, intersectionRatio, boundingClientRect, intersectionRect: boundingClientRect, isIntersecting, rootBounds, time };
-						});
-						observer.callback(params, intersectionObserver);
-					}
-				});
-			}
-		}
-	}
-
-	addEventListener(LOAD_DEFERRED_IMAGES_END_EVENT, () => loadDeferredImagesEnd());
-	addEventListener(LOAD_DEFERRED_IMAGES_KEEP_ZOOM_LEVEL_END_EVENT, () => loadDeferredImagesEnd(true));
-	addEventListener(LOAD_DEFERRED_IMAGES_RESET_EVENT, resetScreenSize);
-	addEventListener(LOAD_DEFERRED_IMAGES_RESET_ZOOM_LEVEL_EVENT, () => {
-		const transform = document.documentElement.style.getPropertyValue("-sf-transform");
-		const transformPriority = document.documentElement.style.getPropertyPriority("-sf-transform");
-		const transformOrigin = document.documentElement.style.getPropertyValue("-sf-transform-origin");
-		const transformOriginPriority = document.documentElement.style.getPropertyPriority("-sf-transform-origin");
-		const minHeight = document.documentElement.style.getPropertyValue("-sf-min-height");
-		const minHeightPriority = document.documentElement.style.getPropertyPriority("-sf-min-height");
-		document.documentElement.style.setProperty("transform", transform, transformPriority);
-		document.documentElement.style.setProperty("transform-origin", transformOrigin, transformOriginPriority);
-		document.documentElement.style.setProperty("min-height", minHeight, minHeightPriority);
-		document.documentElement.style.removeProperty("-sf-transform");
-		document.documentElement.style.removeProperty("-sf-transform-origin");
-		document.documentElement.style.removeProperty("-sf-min-height");
-		resetScreenSize();
-	});
-
-	function loadDeferredImagesEnd(keepZoomLevel) {
-		document.querySelectorAll("[" + LAZY_LOAD_ATTRIBUTE + "]").forEach(element => {
-			element.loading = "lazy";
-			element.removeAttribute(LAZY_LOAD_ATTRIBUTE);
-		});
-		if (!keepZoomLevel) {
-			if (globalThis._singleFile_getBoundingClientRect) {
-				Element.prototype.getBoundingClientRect = globalThis._singleFile_getBoundingClientRect;
-				delete globalThis._singleFile_getBoundingClientRect;
-			}
-		}
-		if (globalThis._singleFileImage) {
-			delete globalThis.Image;
-			globalThis.Image = globalThis._singleFileImage;
-			delete globalThis._singleFileImage;
-		}
-		if (!keepZoomLevel) {
-			dispatchResizeEvent();
-		}
-	}
-
-	function resetScreenSize() {
-		const scrollingElement = document.scrollingElement || document.documentElement;
-		if (globalThis._singleFile_innerHeight != null) {
-			delete globalThis.innerHeight;
-			globalThis.innerHeight = globalThis._singleFile_innerHeight;
-			delete globalThis._singleFile_innerHeight;
-		}
-		if (globalThis._singleFile_innerWidth != null) {
-			delete globalThis.innerWidth;
-			globalThis.innerWidth = globalThis._singleFile_innerWidth;
-			delete globalThis._singleFile_innerWidth;
-		}
-		delete scrollingElement.clientHeight;
-		delete scrollingElement.clientWidth;
-		delete screen.height;
-		delete screen.width;
-	}
-
-	addEventListener(DISPATCH_SCROLL_START_EVENT, () => {
-		dispatchScrollEvent = true;
-	});
-
-	addEventListener(DISPATCH_SCROLL_END_EVENT, () => {
-		dispatchScrollEvent = false;
-	});
-
-	addEventListener(BLOCK_COOKIES_START_EVENT, () => {
-		try {
-			document.__defineGetter__("cookie", () => { throw new Error("document.cookie temporary blocked by SingleFile"); });
-		} catch (error) {
-			// ignored
-		}
-	});
-
-	addEventListener(BLOCK_COOKIES_END_EVENT, () => {
-		delete document.cookie;
-	});
-
-	addEventListener(BLOCK_STORAGE_START_EVENT, () => {
-		if (!globalThis._singleFile_localStorage) {
-			globalThis._singleFile_localStorage = globalThis.localStorage;
-			globalThis.__defineGetter__("localStorage", () => { throw new Error("localStorage temporary blocked by SingleFile"); });
-		}
-		if (!globalThis._singleFile_indexedDB) {
-			globalThis._singleFile_indexedDB = globalThis.indexedDB;
-			globalThis.__defineGetter__("indexedDB", () => { throw new Error("indexedDB temporary blocked by SingleFile"); });
-		}
-	});
-
-	addEventListener(BLOCK_STORAGE_END_EVENT, () => {
-		if (globalThis._singleFile_localStorage) {
-			delete globalThis.localStorage;
-			globalThis.localStorage = globalThis._singleFile_localStorage;
-			delete globalThis._singleFile_localStorage;
-		}
-		if (!globalThis._singleFile_indexedDB) {
-			delete globalThis.indexedDB;
-			globalThis.indexedDB = globalThis._singleFile_indexedDB;
-			delete globalThis._singleFile_indexedDB;
-		}
-	});
-
-	if (globalThis.FontFace) {
-		const FontFace = globalThis.FontFace;
-		let warningFontFaceDisplayed;
-		globalThis.FontFace = function () {
-			if (!warningFontFaceDisplayed) {
-				warn("SingleFile is hooking the FontFace constructor, document.fonts.delete and document.fonts.clear to handle dynamically loaded fonts.");
-				warningFontFaceDisplayed = true;
-			}
-			getDetailObject(...arguments).then(detail => dispatchEvent(new CustomEvent(NEW_FONT_FACE_EVENT, { detail })));
-			return new FontFace(...arguments);
-		};
-		globalThis.FontFace.toString = function () { return "function FontFace() { [native code] }"; };
-		const deleteFont = document.fonts.delete;
-		document.fonts.delete = function (fontFace) {
-			getDetailObject(fontFace.family).then(detail => dispatchEvent(new CustomEvent(DELETE_FONT_EVENT, { detail })));
-			return deleteFont.call(document.fonts, fontFace);
-		};
-		document.fonts.delete.toString = function () { return "function delete() { [native code] }"; };
-		const clearFonts = document.fonts.clear;
-		document.fonts.clear = function () {
-			dispatchEvent(new CustomEvent(CLEAR_FONTS_EVENT));
-			return clearFonts.call(document.fonts);
-		};
-		document.fonts.clear.toString = function () { return "function clear() { [native code] }"; };
-	}
-
-	if (globalThis.IntersectionObserver) {
-		const IntersectionObserver = globalThis.IntersectionObserver;
-		let warningIntersectionObserverDisplayed;
-		globalThis.IntersectionObserver = function () {
-			if (!warningIntersectionObserverDisplayed) {
-				warn("SingleFile is hooking the IntersectionObserver API to detect and load deferred images.");
-				warningIntersectionObserverDisplayed = true;
-			}
-			const intersectionObserver = new IntersectionObserver(...arguments);
-			const observeIntersection = IntersectionObserver.prototype.observe || intersectionObserver.observe;
-			const unobserveIntersection = IntersectionObserver.prototype.unobserve || intersectionObserver.unobserve;
-			const callback = arguments[0];
-			const options = arguments[1];
-			if (observeIntersection) {
-				intersectionObserver.observe = function (targetElement) {
-					let targetElements = observedElements.get(intersectionObserver);
-					if (!targetElements) {
-						targetElements = [];
-						observedElements.set(intersectionObserver, targetElements);
-					}
-					targetElements.push(targetElement);
-					return observeIntersection.call(intersectionObserver, targetElement);
-				};
-			}
-			if (unobserveIntersection) {
-				intersectionObserver.unobserve = function (targetElement) {
-					let targetElements = observedElements.get(intersectionObserver);
-					if (targetElements) {
-						targetElements = targetElements.filter(element => element != targetElement);
-						if (targetElements.length) {
-							observedElements.set(intersectionObserver, targetElements);
-						} else {
-							observedElements.delete(intersectionObserver);
-							observers.delete(intersectionObserver);
-						}
-					}
-					return unobserveIntersection.call(intersectionObserver, targetElement);
-				};
-			}
-			observers.set(intersectionObserver, { callback, options });
-			return intersectionObserver;
-		};
-		globalThis.IntersectionObserver.prototype = IntersectionObserver.prototype;
-		globalThis.IntersectionObserver.toString = function () { return "function IntersectionObserver() { [native code] }"; };
-	}
-
-	async function getDetailObject(fontFamily, src, descriptors) {
-		const detail = {};
-		detail["font-family"] = fontFamily;
-		detail.src = src;
-		if (descriptors) {
-			Object.keys(descriptors).forEach(descriptor => {
-				if (FONT_STYLE_PROPERTIES[descriptor]) {
-					detail[FONT_STYLE_PROPERTIES[descriptor]] = descriptors[descriptor];
-				}
-			});
-		}
-		return new Promise(resolve => {
-			if (detail.src instanceof ArrayBuffer) {
-				const reader = new FileReader();
-				reader.readAsDataURL(new Blob([detail.src]));
-				reader.addEventListener("load", () => {
-					detail.src = "url(" + reader.result + ")";
-					resolve(detail);
-				});
-			} else {
-				resolve(detail);
-			}
-		});
-	}
-
-	function dispatchResizeEvent() {
-		try {
-			dispatchEvent(new UIEvent("resize"));
-			if (dispatchScrollEvent) {
-				dispatchEvent(new UIEvent("scroll"));
-			}
-		} catch (error) {
-			// ignored
-		}
-	}
-
-})(typeof globalThis == "object" ? globalThis : window);

+ 0 - 214
src/single-file/processors/hooks/content/content-hooks-frames.js

@@ -1,214 +0,0 @@
-/*
- * Copyright 2010-2020 Gildas Lormeau
- * contact : gildas.lormeau <at> gmail.com
- * 
- * This file is part of SingleFile.
- *
- *   The code in this file is free software: you can redistribute it and/or 
- *   modify it under the terms of the GNU Affero General Public License 
- *   (GNU AGPL) as published by the Free Software Foundation, either version 3
- *   of the License, or (at your option) any later version.
- * 
- *   The code in this file is distributed in the hope that it will be useful, 
- *   but WITHOUT ANY WARRANTY; without even the implied warranty of 
- *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero 
- *   General Public License for more details.
- *
- *   As additional permission under GNU AGPL version 3 section 7, you may 
- *   distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU 
- *   AGPL normally required by section 4, provided you include this license 
- *   notice and a URL through which recipients can access the Corresponding 
- *   Source.
- */
-
-/* global globalThis, window */
-
-const LOAD_DEFERRED_IMAGES_START_EVENT = "single-file-load-deferred-images-start";
-const LOAD_DEFERRED_IMAGES_END_EVENT = "single-file-load-deferred-images-end";
-const LOAD_DEFERRED_IMAGES_KEEP_ZOOM_LEVEL_START_EVENT = "single-file-load-deferred-images-keep-zoom-level-start";
-const LOAD_DEFERRED_IMAGES_KEEP_ZOOM_LEVEL_END_EVENT = "single-file-load-deferred-images-keep-zoom-level-end";
-const LOAD_DEFERRED_IMAGES_RESET_ZOOM_LEVEL_EVENT = "single-file-load-deferred-images-keep-zoom-level-reset";
-const LOAD_DEFERRED_IMAGES_RESET_EVENT = "single-file-load-deferred-images-reset";
-const BLOCK_COOKIES_START_EVENT = "single-file-block-cookies-start";
-const BLOCK_COOKIES_END_EVENT = "single-file-block-cookies-end";
-const DISPATCH_SCROLL_START_EVENT = "single-file-dispatch-scroll-event-start";
-const DISPATCH_SCROLL_END_EVENT = "single-file-dispatch-scroll-event-end";
-const BLOCK_STORAGE_START_EVENT = "single-file-block-storage-start";
-const BLOCK_STORAGE_END_EVENT = "single-file-block-storage-end";
-const LOAD_IMAGE_EVENT = "single-file-load-image";
-const IMAGE_LOADED_EVENT = "single-file-image-loaded";
-const NEW_FONT_FACE_EVENT = "single-file-new-font-face";
-const DELETE_FONT_EVENT = "single-file-delete-font";
-const CLEAR_FONTS_EVENT = "single-file-clear-fonts";
-
-const browser = globalThis.browser;
-const addEventListener = (type, listener, options) => globalThis.addEventListener(type, listener, options);
-const dispatchEvent = event => { try { globalThis.dispatchEvent(event); } catch (error) {  /* ignored */ } };
-const CustomEvent = globalThis.CustomEvent;
-const document = globalThis.document;
-const Document = globalThis.Document;
-
-let fontFaces;
-if (window._singleFile_fontFaces) {
-	fontFaces = window._singleFile_fontFaces;
-} else {
-	fontFaces = window._singleFile_fontFaces = new Map();
-}
-
-if (document instanceof Document) {
-	if (browser && browser.runtime && browser.runtime.getURL) {
-		addEventListener(NEW_FONT_FACE_EVENT, event => {
-			const detail = event.detail;
-			const key = Object.assign({}, detail);
-			delete key.src;
-			fontFaces.set(JSON.stringify(key), detail);
-		});
-		addEventListener(DELETE_FONT_EVENT, event => {
-			const detail = event.detail;
-			const key = Object.assign({}, detail);
-			delete key.src;
-			fontFaces.delete(JSON.stringify(key));
-		});
-		addEventListener(CLEAR_FONTS_EVENT, () => fontFaces = new Map());
-		let scriptElement = document.createElement("script");
-		scriptElement.src = "data:," + "(" + injectedScript.toString() + ")()";
-		(document.documentElement || document).appendChild(scriptElement);
-		scriptElement.remove();
-		scriptElement = document.createElement("script");
-		scriptElement.src = browser.runtime.getURL("/lib/web/hooks/hooks-frames-web.js");
-		scriptElement.async = false;
-		(document.documentElement || document).appendChild(scriptElement);
-		scriptElement.remove();
-	}
-}
-
-export {
-	getFontsData,
-	loadDeferredImagesStart,
-	loadDeferredImagesEnd,
-	loadDeferredImagesResetZoomLevel,
-	LOAD_IMAGE_EVENT,
-	IMAGE_LOADED_EVENT
-};
-
-function getFontsData() {
-	return Array.from(fontFaces.values());
-}
-
-function loadDeferredImagesStart(options) {
-	if (options.loadDeferredImagesBlockCookies) {
-		dispatchEvent(new CustomEvent(BLOCK_COOKIES_START_EVENT));
-	}
-	if (options.loadDeferredImagesBlockStorage) {
-		dispatchEvent(new CustomEvent(BLOCK_STORAGE_START_EVENT));
-	}
-	if (options.loadDeferredImagesDispatchScrollEvent) {
-		dispatchEvent(new CustomEvent(DISPATCH_SCROLL_START_EVENT));
-	}
-	if (options.loadDeferredImagesKeepZoomLevel) {
-		dispatchEvent(new CustomEvent(LOAD_DEFERRED_IMAGES_KEEP_ZOOM_LEVEL_START_EVENT));
-	} else {
-		dispatchEvent(new CustomEvent(LOAD_DEFERRED_IMAGES_START_EVENT));
-	}
-}
-
-function loadDeferredImagesEnd(options) {
-	if (options.loadDeferredImagesBlockCookies) {
-		dispatchEvent(new CustomEvent(BLOCK_COOKIES_END_EVENT));
-	}
-	if (options.loadDeferredImagesBlockStorage) {
-		dispatchEvent(new CustomEvent(BLOCK_STORAGE_END_EVENT));
-	}
-	if (options.loadDeferredImagesDispatchScrollEvent) {
-		dispatchEvent(new CustomEvent(DISPATCH_SCROLL_END_EVENT));
-	}
-	if (options.loadDeferredImagesKeepZoomLevel) {
-		dispatchEvent(new CustomEvent(LOAD_DEFERRED_IMAGES_KEEP_ZOOM_LEVEL_END_EVENT));
-	} else {
-		dispatchEvent(new CustomEvent(LOAD_DEFERRED_IMAGES_END_EVENT));
-	}
-}
-
-function loadDeferredImagesResetZoomLevel(options) {
-	if (options.loadDeferredImagesKeepZoomLevel) {
-		dispatchEvent(new CustomEvent(LOAD_DEFERRED_IMAGES_RESET_ZOOM_LEVEL_EVENT));
-	} else {
-		dispatchEvent(new CustomEvent(LOAD_DEFERRED_IMAGES_RESET_EVENT));
-	}
-}
-
-function injectedScript() {
-	if (typeof globalThis == "undefined") {
-		window.globalThis = window;
-	}
-	const document = globalThis.document;
-	const console = globalThis.console;
-	const dispatchEvent = event => globalThis.dispatchEvent(event);
-	const CustomEvent = globalThis.CustomEvent;
-	const FileReader = globalThis.FileReader;
-	const Blob = globalThis.Blob;
-	const warn = (console && console.warn && ((...args) => console.warn(...args))) || (() => { });
-	const NEW_FONT_FACE_EVENT = "single-file-new-font-face";
-	const DELETE_FONT_EVENT = "single-file-delete-font";
-	const CLEAR_FONTS_EVENT = "single-file-clear-fonts";
-	const FONT_STYLE_PROPERTIES = {
-		family: "font-family",
-		style: "font-style",
-		weight: "font-weight",
-		stretch: "font-stretch",
-		unicodeRange: "unicode-range",
-		variant: "font-variant",
-		featureSettings: "font-feature-settings"
-	};
-
-	if (globalThis.FontFace) {
-		const FontFace = globalThis.FontFace;
-		let warningFontFaceDisplayed;
-		globalThis.FontFace = function () {
-			if (!warningFontFaceDisplayed) {
-				warn("SingleFile is hooking the FontFace constructor, document.fonts.delete and document.fonts.clear to handle dynamically loaded fonts.");
-				warningFontFaceDisplayed = true;
-			}
-			getDetailObject(...arguments).then(detail => dispatchEvent(new CustomEvent(NEW_FONT_FACE_EVENT, { detail })));
-			return new FontFace(...arguments);
-		};
-		globalThis.FontFace.toString = function () { return "function FontFace() { [native code] }"; };
-		const deleteFont = document.fonts.delete;
-		document.fonts.delete = function (fontFace) {
-			getDetailObject(fontFace.family).then(detail => dispatchEvent(new CustomEvent(DELETE_FONT_EVENT, { detail })));
-			return deleteFont.call(document.fonts, fontFace);
-		};
-		document.fonts.delete.toString = function () { return "function delete() { [native code] }"; };
-		const clearFonts = document.fonts.clear;
-		document.fonts.clear = function () {
-			dispatchEvent(new CustomEvent(CLEAR_FONTS_EVENT));
-			return clearFonts.call(document.fonts);
-		};
-		document.fonts.clear.toString = function () { return "function clear() { [native code] }"; };
-	}
-
-	async function getDetailObject(fontFamily, src, descriptors) {
-		const detail = {};
-		detail["font-family"] = fontFamily;
-		detail.src = src;
-		if (descriptors) {
-			Object.keys(descriptors).forEach(descriptor => {
-				if (FONT_STYLE_PROPERTIES[descriptor]) {
-					detail[FONT_STYLE_PROPERTIES[descriptor]] = descriptors[descriptor];
-				}
-			});
-		}
-		return new Promise(resolve => {
-			if (detail.src instanceof ArrayBuffer) {
-				const reader = new FileReader();
-				reader.readAsDataURL(new Blob([detail.src]));
-				reader.addEventListener("load", () => {
-					detail.src = "url(" + reader.result + ")";
-					resolve(detail);
-				});
-			} else {
-				resolve(detail);
-			}
-		});
-	}
-}

+ 0 - 48
src/single-file/processors/hooks/content/content-hooks-web.js

@@ -1,48 +0,0 @@
-/*
- * Copyright 2010-2020 Gildas Lormeau
- * contact : gildas.lormeau <at> gmail.com
- * 
- * This file is part of SingleFile.
- *
- *   The code in this file is free software: you can redistribute it and/or 
- *   modify it under the terms of the GNU Affero General Public License 
- *   (GNU AGPL) as published by the Free Software Foundation, either version 3
- *   of the License, or (at your option) any later version.
- * 
- *   The code in this file is distributed in the hope that it will be useful, 
- *   but WITHOUT ANY WARRANTY; without even the implied warranty of 
- *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero 
- *   General Public License for more details.
- *
- *   As additional permission under GNU AGPL version 3 section 7, you may 
- *   distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU 
- *   AGPL normally required by section 4, provided you include this license 
- *   notice and a URL through which recipients can access the Corresponding 
- *   Source.
- */
-
-/* global window, globalThis */
-
-(globalThis => {
-
-	const FETCH_REQUEST_EVENT = "single-file-request-fetch";
-	const FETCH_RESPONSE_EVENT = "single-file-response-fetch";	
-
-	const CustomEvent = globalThis.CustomEvent;
-	const fetch = globalThis.fetch;
-	const addEventListener = (type, listener, options) => globalThis.addEventListener(type, listener, options);
-	const dispatchEvent = event => { try { globalThis.dispatchEvent(event); } catch (error) {  /* ignored */ } };
-
-	addEventListener(FETCH_REQUEST_EVENT, async event => {
-		const url = event.detail;
-		let detail;
-		try {
-			const response = await fetch(url, { cache: "force-cache" });
-			detail = { url, response: await response.arrayBuffer(), headers: [...response.headers], status: response.status };
-		} catch (error) {
-			detail = { url, error: error && error.toString() };
-		}
-		dispatchEvent(new CustomEvent(FETCH_RESPONSE_EVENT, { detail }));
-	});
-
-})(typeof globalThis == "object" ? globalThis : window);

+ 0 - 39
src/single-file/processors/hooks/content/content-hooks.js

@@ -1,39 +0,0 @@
-/*
- * Copyright 2010-2020 Gildas Lormeau
- * contact : gildas.lormeau <at> gmail.com
- * 
- * This file is part of SingleFile.
- *
- *   The code in this file is free software: you can redistribute it and/or 
- *   modify it under the terms of the GNU Affero General Public License 
- *   (GNU AGPL) as published by the Free Software Foundation, either version 3
- *   of the License, or (at your option) any later version.
- * 
- *   The code in this file is distributed in the hope that it will be useful, 
- *   but WITHOUT ANY WARRANTY; without even the implied warranty of 
- *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero 
- *   General Public License for more details.
- *
- *   As additional permission under GNU AGPL version 3 section 7, you may 
- *   distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU 
- *   AGPL normally required by section 4, provided you include this license 
- *   notice and a URL through which recipients can access the Corresponding 
- *   Source.
- */
-
-/* global globalThis */
-
-const browser = globalThis.browser;
-const document = globalThis.document;
-const Document = globalThis.Document;
-
-if (document instanceof Document) {
-	const scriptElement = document.createElement("script");
-	scriptElement.async = false;
-	if (browser && browser.runtime && browser.runtime.getURL) {
-		scriptElement.src = browser.runtime.getURL("/lib/web/hooks/hooks-web.js");
-		scriptElement.async = false;
-	}
-	(document.documentElement || document).appendChild(scriptElement);
-	scriptElement.remove();
-}

+ 0 - 34
src/single-file/processors/index.js

@@ -1,34 +0,0 @@
-/*
- * Copyright 2010-2020 Gildas Lormeau
- * contact : gildas.lormeau <at> gmail.com
- * 
- * This file is part of SingleFile.
- *
- *   The code in this file is free software: you can redistribute it and/or 
- *   modify it under the terms of the GNU Affero General Public License 
- *   (GNU AGPL) as published by the Free Software Foundation, either version 3
- *   of the License, or (at your option) any later version.
- * 
- *   The code in this file is distributed in the hope that it will be useful, 
- *   but WITHOUT ANY WARRANTY; without even the implied warranty of 
- *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero 
- *   General Public License for more details.
- *
- *   As additional permission under GNU AGPL version 3 section 7, you may 
- *   distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU 
- *   AGPL normally required by section 4, provided you include this license 
- *   notice and a URL through which recipients can access the Corresponding 
- *   Source.
- */
-
-import * as frameTree from "./frame-tree/content/content-frame-tree.js";
-import * as hooks from "./hooks/content/content-hooks.js";
-import * as hooksFrames from "./hooks/content/content-hooks-frames.js";
-import * as lazy from "./lazy/content/content-lazy-loader.js";
-
-export {
-	frameTree,
-	hooks,
-	hooksFrames,
-	lazy
-};

+ 0 - 229
src/single-file/processors/lazy/content/content-lazy-loader.js

@@ -1,229 +0,0 @@
-/*
- * Copyright 2010-2020 Gildas Lormeau
- * contact : gildas.lormeau <at> gmail.com
- * 
- * This file is part of SingleFile.
- *
- *   The code in this file is free software: you can redistribute it and/or 
- *   modify it under the terms of the GNU Affero General Public License 
- *   (GNU AGPL) as published by the Free Software Foundation, either version 3
- *   of the License, or (at your option) any later version.
- * 
- *   The code in this file is distributed in the hope that it will be useful, 
- *   but WITHOUT ANY WARRANTY; without even the implied warranty of 
- *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero 
- *   General Public License for more details.
- *
- *   As additional permission under GNU AGPL version 3 section 7, you may 
- *   distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU 
- *   AGPL normally required by section 4, provided you include this license 
- *   notice and a URL through which recipients can access the Corresponding 
- *   Source.
- */
-
-/* global globalThis */
-
-import * as hooksFrames from "./../../hooks/content/content-hooks-frames";
-import {
-	LAZY_SRC_ATTRIBUTE_NAME,
-	SINGLE_FILE_UI_ELEMENT_CLASS
-} from "./../../../single-file-helper.js";
-const helper = {
-	LAZY_SRC_ATTRIBUTE_NAME,
-	SINGLE_FILE_UI_ELEMENT_CLASS
-};
-
-const MAX_IDLE_TIMEOUT_CALLS = 10;
-const ATTRIBUTES_MUTATION_TYPE = "attributes";
-
-const browser = globalThis.browser;
-const document = globalThis.document;
-const MutationObserver = globalThis.MutationObserver;
-const addEventListener = (type, listener, options) => globalThis.addEventListener(type, listener, options);
-const removeEventListener = (type, listener, options) => globalThis.removeEventListener(type, listener, options);
-const timeouts = new Map();
-
-let idleTimeoutCalls;
-
-if (browser && browser.runtime && browser.runtime.onMessage && browser.runtime.onMessage.addListener) {
-	browser.runtime.onMessage.addListener(message => {
-		if (message.method == "singlefile.lazyTimeout.onTimeout") {
-			const timeoutData = timeouts.get(message.type);
-			if (timeoutData) {
-				timeouts.delete(message.type);
-				try {
-					timeoutData.callback();
-				} catch (error) {
-					clearRegularTimeout(message.type);
-				}
-			}
-		}
-	});
-}
-
-export {
-	process,
-	resetZoomLevel
-};
-
-async function process(options) {
-	if (document.documentElement) {
-		timeouts.clear();
-		const maxScrollY = Math.max(document.documentElement.scrollHeight - (document.documentElement.clientHeight * 1.5), 0);
-		const maxScrollX = Math.max(document.documentElement.scrollWidth - (document.documentElement.clientWidth * 1.5), 0);
-		if (globalThis.scrollY <= maxScrollY && globalThis.scrollX <= maxScrollX) {
-			return triggerLazyLoading(options);
-		}
-	}
-}
-
-function resetZoomLevel(options) {
-	hooksFrames.loadDeferredImagesResetZoomLevel(options);
-}
-
-function triggerLazyLoading(options) {
-	idleTimeoutCalls = 0;
-	return new Promise(async resolve => { // eslint-disable-line  no-async-promise-executor
-		let loadingImages;
-		const pendingImages = new Set();
-		const observer = new MutationObserver(async mutations => {
-			mutations = mutations.filter(mutation => mutation.type == ATTRIBUTES_MUTATION_TYPE);
-			if (mutations.length) {
-				const updated = mutations.filter(mutation => {
-					if (mutation.attributeName == "src") {
-						mutation.target.setAttribute(helper.LAZY_SRC_ATTRIBUTE_NAME, mutation.target.src);
-						mutation.target.addEventListener("load", onResourceLoad);
-					}
-					if (mutation.attributeName == "src" || mutation.attributeName == "srcset" || mutation.target.tagName == "SOURCE") {
-						return !mutation.target.classList || !mutation.target.classList.contains(helper.SINGLE_FILE_UI_ELEMENT_CLASS);
-					}
-				});
-				if (updated.length) {
-					loadingImages = true;
-					await deferForceLazyLoadEnd(observer, options, cleanupAndResolve);
-					if (!pendingImages.size) {
-						await deferLazyLoadEnd(observer, options, cleanupAndResolve);
-					}
-				}
-			}
-		});
-		await setIdleTimeout(options.loadDeferredImagesMaxIdleTime * 2);
-		await deferForceLazyLoadEnd(observer, options, cleanupAndResolve);
-		observer.observe(document, { subtree: true, childList: true, attributes: true });
-		addEventListener(hooksFrames.LOAD_IMAGE_EVENT, onImageLoadEvent);
-		addEventListener(hooksFrames.IMAGE_LOADED_EVENT, onImageLoadedEvent);
-		hooksFrames.loadDeferredImagesStart(options);
-
-		async function setIdleTimeout(delay) {
-			await setAsyncTimeout("idleTimeout", async () => {
-				if (!loadingImages) {
-					clearAsyncTimeout("loadTimeout");
-					clearAsyncTimeout("maxTimeout");
-					lazyLoadEnd(observer, options, cleanupAndResolve);
-				} else if (idleTimeoutCalls < MAX_IDLE_TIMEOUT_CALLS) {
-					idleTimeoutCalls++;
-					clearAsyncTimeout("idleTimeout");
-					await setIdleTimeout(Math.max(500, delay / 2));
-				}
-			}, delay);
-		}
-
-		function onResourceLoad(event) {
-			const element = event.target;
-			element.removeAttribute(helper.LAZY_SRC_ATTRIBUTE_NAME);
-			element.removeEventListener("load", onResourceLoad);
-		}
-
-		async function onImageLoadEvent(event) {
-			loadingImages = true;
-			await deferForceLazyLoadEnd(observer, options, cleanupAndResolve);
-			await deferLazyLoadEnd(observer, options, cleanupAndResolve);
-			if (event.detail) {
-				pendingImages.add(event.detail);
-			}
-		}
-
-		async function onImageLoadedEvent(event) {
-			await deferForceLazyLoadEnd(observer, options, cleanupAndResolve);
-			await deferLazyLoadEnd(observer, options, cleanupAndResolve);
-			pendingImages.delete(event.detail);
-			if (!pendingImages.size) {
-				await deferLazyLoadEnd(observer, options, cleanupAndResolve);
-			}
-		}
-
-		function cleanupAndResolve(value) {
-			observer.disconnect();
-			removeEventListener(hooksFrames.LOAD_IMAGE_EVENT, onImageLoadEvent);
-			removeEventListener(hooksFrames.IMAGE_LOADED_EVENT, onImageLoadedEvent);
-			resolve(value);
-		}
-	});
-}
-
-async function deferLazyLoadEnd(observer, options, resolve) {
-	await setAsyncTimeout("loadTimeout", () => lazyLoadEnd(observer, options, resolve), options.loadDeferredImagesMaxIdleTime);
-}
-
-async function deferForceLazyLoadEnd(observer, options, resolve) {
-	await setAsyncTimeout("maxTimeout", async () => {
-		await clearAsyncTimeout("loadTimeout");
-		await lazyLoadEnd(observer, options, resolve);
-	}, options.loadDeferredImagesMaxIdleTime * 10);
-}
-
-async function lazyLoadEnd(observer, options, resolve) {
-	await clearAsyncTimeout("idleTimeout");
-	hooksFrames.loadDeferredImagesEnd(options);
-	await setAsyncTimeout("endTimeout", async () => {
-		await clearAsyncTimeout("maxTimeout");
-		resolve();
-	}, options.loadDeferredImagesMaxIdleTime / 2);
-	observer.disconnect();
-}
-
-async function setAsyncTimeout(type, callback, delay) {
-	if (browser && browser.runtime && browser.runtime.sendMessage) {
-		if (!timeouts.get(type) || !timeouts.get(type).pending) {
-			const timeoutData = { callback, pending: true };
-			timeouts.set(type, timeoutData);
-			try {
-				await browser.runtime.sendMessage({ method: "singlefile.lazyTimeout.setTimeout", type, delay });
-			} catch (error) {
-				setRegularTimeout(type, callback, delay);
-			}
-			timeoutData.pending = false;
-		}
-	} else {
-		setRegularTimeout(type, callback, delay);
-	}
-}
-
-function setRegularTimeout(type, callback, delay) {
-	const timeoutId = timeouts.get(type);
-	if (timeoutId) {
-		globalThis.clearTimeout(timeoutId);
-	}
-	timeouts.set(type, callback);
-	globalThis.setTimeout(callback, delay);
-}
-
-async function clearAsyncTimeout(type) {
-	if (browser && browser.runtime && browser.runtime.sendMessage) {
-		try {
-			await browser.runtime.sendMessage({ method: "singlefile.lazyTimeout.clearTimeout", type });
-		} catch (error) {
-			clearRegularTimeout(type);
-		}
-	} else {
-		clearRegularTimeout(type);
-	}
-}
-
-function clearRegularTimeout(type) {
-	const previousTimeoutId = timeouts.get(type);
-	timeouts.delete(type);
-	if (previousTimeoutId) {
-		globalThis.clearTimeout(previousTimeoutId);
-	}
-}

+ 1 - 33
src/single-file/single-file-bootstrap.js

@@ -1,33 +1 @@
-import * as frameTree from "./processors/frame-tree/content/content-frame-tree.js";
-import * as serializer from "./modules/html-serializer.js";
-import {
-	COMMENT_HEADER,
-	COMMENT_HEADER_LEGACY,
-	ON_BEFORE_CAPTURE_EVENT_NAME,
-	ON_AFTER_CAPTURE_EVENT_NAME,
-	initUserScriptHandler,
-	preProcessDoc,
-	postProcessDoc,
-	getShadowRoot
-} from "./single-file-helper.js";
-
-const processors = { frameTree };
-const helper = {
-	COMMENT_HEADER,
-	COMMENT_HEADER_LEGACY,
-	ON_BEFORE_CAPTURE_EVENT_NAME,
-	ON_AFTER_CAPTURE_EVENT_NAME,
-	preProcessDoc,
-	postProcessDoc,
-	serialize(doc, compressHTML) {
-		return serializer.process(doc, compressHTML);
-	},
-	getShadowRoot
-};
-
-initUserScriptHandler();
-
-export {
-	helper,
-	processors
-};
+export * from "single-file-core/single-file-bootstrap.js";

+ 0 - 2471
src/single-file/single-file-core.js

@@ -1,2471 +0,0 @@
-/*
- * Copyright 2010-2020 Gildas Lormeau
- * contact : gildas.lormeau <at> gmail.com
- * 
- * This file is part of SingleFile.
- *
- *   The code in this file is free software: you can redistribute it and/or 
- *   modify it under the terms of the GNU Affero General Public License 
- *   (GNU AGPL) as published by the Free Software Foundation, either version 3
- *   of the License, or (at your option) any later version.
- * 
- *   The code in this file is distributed in the hope that it will be useful, 
- *   but WITHOUT ANY WARRANTY; without even the implied warranty of 
- *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero 
- *   General Public License for more details.
- *
- *   As additional permission under GNU AGPL version 3 section 7, you may 
- *   distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU 
- *   AGPL normally required by section 4, provided you include this license 
- *   notice and a URL through which recipients can access the Corresponding 
- *   Source.
- */
-
-/* global globalThis */
-
-const DEBUG = false;
-
-const Set = globalThis.Set;
-const Map = globalThis.Map;
-
-let util, cssTree;
-
-function getClass(...args) {
-	[util, cssTree] = args;
-	return SingleFileClass;
-}
-
-class SingleFileClass {
-	constructor(options) {
-		this.options = options;
-	}
-	async run() {
-		const waitForUserScript = globalThis._singleFile_waitForUserScript;
-		if (this.options.userScriptEnabled && waitForUserScript) {
-			await waitForUserScript(util.ON_BEFORE_CAPTURE_EVENT_NAME);
-		}
-		this.runner = new Runner(this.options, true);
-		await this.runner.loadPage();
-		await this.runner.initialize();
-		if (this.options.userScriptEnabled && waitForUserScript) {
-			await waitForUserScript(util.ON_AFTER_CAPTURE_EVENT_NAME);
-		}
-		await this.runner.run();
-	}
-	cancel() {
-		this.cancelled = true;
-		if (this.runner) {
-			this.runner.cancel();
-		}
-	}
-	getPageData() {
-		return this.runner.getPageData();
-	}
-}
-
-// -------------
-// ProgressEvent
-// -------------
-const PAGE_LOADING = "page-loading";
-const PAGE_LOADED = "page-loaded";
-const RESOURCES_INITIALIZING = "resource-initializing";
-const RESOURCES_INITIALIZED = "resources-initialized";
-const RESOURCE_LOADED = "resource-loaded";
-const PAGE_ENDED = "page-ended";
-const STAGE_STARTED = "stage-started";
-const STAGE_ENDED = "stage-ended";
-const STAGE_TASK_STARTED = "stage-task-started";
-const STAGE_TASK_ENDED = "stage-task-ended";
-
-class ProgressEvent {
-	constructor(type, detail) {
-		return { type, detail, PAGE_LOADING, PAGE_LOADED, RESOURCES_INITIALIZING, RESOURCES_INITIALIZED, RESOURCE_LOADED, PAGE_ENDED, STAGE_STARTED, STAGE_ENDED, STAGE_TASK_STARTED, STAGE_TASK_ENDED };
-	}
-}
-
-// ------
-// Runner
-// ------
-const RESOLVE_URLS_STAGE = 0;
-const REPLACE_DATA_STAGE = 1;
-const REPLACE_DOCS_STAGE = 2;
-const POST_PROCESS_STAGE = 3;
-const STAGES = [{
-	sequential: [
-		{ action: "preProcessPage" },
-		{ option: "loadDeferredImagesKeepZoomLevel", action: "resetZoomLevel" },
-		{ action: "replaceStyleContents" },
-		{ action: "resetCharsetMeta" },
-		{ option: "saveFavicon", action: "saveFavicon" },
-		{ action: "replaceCanvasElements" },
-		{ action: "insertFonts" },
-		{ action: "insertShadowRootContents" },
-		{ action: "setInputValues" },
-		{ option: "moveStylesInHead", action: "moveStylesInHead" },
-		{ option: "blockScripts", action: "removeEmbedScripts" },
-		{ option: "selected", action: "removeUnselectedElements" },
-		{ option: "blockVideos", action: "insertVideoPosters" },
-		{ option: "blockVideos", action: "insertVideoLinks" },
-		{ option: "removeFrames", action: "removeFrames" },
-		{ action: "removeDiscardedResources" },
-		{ option: "removeHiddenElements", action: "removeHiddenElements" },
-		{ action: "resolveHrefs" },
-		{ action: "resolveStyleAttributeURLs" }
-	],
-	parallel: [
-		{ option: "blockVideos", action: "insertMissingVideoPosters" },
-		{ action: "resolveStylesheetURLs" },
-		{ option: "!removeFrames", action: "resolveFrameURLs" },
-		{ action: "resolveHtmlImportURLs" }
-	]
-}, {
-	sequential: [
-		{ option: "removeUnusedStyles", action: "removeUnusedStyles" },
-		{ option: "removeAlternativeMedias", action: "removeAlternativeMedias" },
-		{ option: "removeUnusedFonts", action: "removeUnusedFonts" }
-	],
-	parallel: [
-		{ action: "processStylesheets" },
-		{ action: "processStyleAttributes" },
-		{ action: "processPageResources" },
-		{ action: "processScripts" }
-	]
-}, {
-	sequential: [
-		{ option: "removeAlternativeImages", action: "removeAlternativeImages" }
-	],
-	parallel: [
-		{ option: "removeAlternativeFonts", action: "removeAlternativeFonts" },
-		{ option: "!removeFrames", action: "processFrames" },
-		{ option: "!removeImports", action: "processHtmlImports" },
-	]
-}, {
-	sequential: [
-		{ action: "replaceStylesheets" },
-		{ action: "replaceStyleAttributes" },
-		{ action: "insertVariables" },
-		{ option: "compressHTML", action: "compressHTML" },
-		{ action: "cleanupPage" }
-	],
-	parallel: [
-		{ option: "enableMaff", action: "insertMAFFMetaData" },
-		{ action: "setDocInfo" }
-	]
-}];
-
-class Runner {
-	constructor(options, root) {
-		const rootDocDefined = root && options.doc;
-		this.root = root;
-		this.options = options;
-		this.options.url = this.options.url || (rootDocDefined && this.options.doc.location.href);
-		const matchResourceReferrer = this.options.url.match(/^.*\//);
-		this.options.resourceReferrer = this.options.passReferrerOnError && matchResourceReferrer && matchResourceReferrer[0];
-		this.options.baseURI = rootDocDefined && this.options.doc.baseURI;
-		this.options.rootDocument = root;
-		this.options.updatedResources = this.options.updatedResources || {};
-		this.options.fontTests = new Map();
-		this.batchRequest = new BatchRequest();
-		this.processor = new Processor(options, this.batchRequest);
-		if (rootDocDefined) {
-			const docData = util.preProcessDoc(this.options.doc, this.options.win, this.options);
-			this.options.canvases = docData.canvases;
-			this.options.fonts = docData.fonts;
-			this.options.stylesheets = docData.stylesheets;
-			this.options.images = docData.images;
-			this.options.posters = docData.posters;
-			this.options.videos = docData.videos;
-			this.options.usedFonts = docData.usedFonts;
-			this.options.shadowRoots = docData.shadowRoots;
-			this.options.imports = docData.imports;
-			this.options.referrer = docData.referrer;
-			this.markedElements = docData.markedElements;
-			this.invalidElements = docData.invalidElements;
-		}
-		if (this.options.saveRawPage) {
-			this.options.removeFrames = true;
-		}
-		this.options.content = this.options.content || (rootDocDefined ? util.serialize(this.options.doc) : null);
-		this.onprogress = options.onprogress || (() => { });
-	}
-
-	async loadPage() {
-		this.onprogress(new ProgressEvent(PAGE_LOADING, { pageURL: this.options.url, frame: !this.root }));
-		await this.processor.loadPage(this.options.content);
-		this.onprogress(new ProgressEvent(PAGE_LOADED, { pageURL: this.options.url, frame: !this.root }));
-	}
-
-	async initialize() {
-		this.onprogress(new ProgressEvent(RESOURCES_INITIALIZING, { pageURL: this.options.url }));
-		await this.executeStage(RESOLVE_URLS_STAGE);
-		this.pendingPromises = this.executeStage(REPLACE_DATA_STAGE);
-		if (this.root && this.options.doc) {
-			util.postProcessDoc(this.options.doc, this.markedElements, this.invalidElements);
-		}
-	}
-
-	cancel() {
-		this.cancelled = true;
-		this.batchRequest.cancel();
-		if (this.root) {
-			if (this.options.frames) {
-				this.options.frames.forEach(cancelRunner);
-			}
-			if (this.options.imports) {
-				this.options.imports.forEach(cancelRunner);
-			}
-		}
-
-		function cancelRunner(resourceData) {
-			if (resourceData.runner) {
-				resourceData.runner.cancel();
-			}
-		}
-	}
-
-	async run() {
-		if (this.root) {
-			this.processor.initialize(this.batchRequest);
-			this.onprogress(new ProgressEvent(RESOURCES_INITIALIZED, { pageURL: this.options.url, max: this.processor.maxResources }));
-		}
-		await this.batchRequest.run(detail => {
-			detail.pageURL = this.options.url;
-			this.onprogress(new ProgressEvent(RESOURCE_LOADED, detail));
-		}, this.options);
-		await this.pendingPromises;
-		this.options.doc = null;
-		this.options.win = null;
-		await this.executeStage(REPLACE_DOCS_STAGE);
-		await this.executeStage(POST_PROCESS_STAGE);
-		this.processor.finalize();
-	}
-
-	getDocument() {
-		return this.processor.doc;
-	}
-
-	getStyleSheets() {
-		return this.processor.stylesheets;
-	}
-
-	getPageData() {
-		if (this.root) {
-			this.onprogress(new ProgressEvent(PAGE_ENDED, { pageURL: this.options.url }));
-		}
-		return this.processor.getPageData();
-	}
-
-	async executeStage(step) {
-		if (DEBUG) {
-			log("**** STARTED STAGE", step, "****");
-		}
-		const frame = !this.root;
-		this.onprogress(new ProgressEvent(STAGE_STARTED, { pageURL: this.options.url, step, frame }));
-		STAGES[step].sequential.forEach(task => {
-			let startTime;
-			if (DEBUG) {
-				startTime = Date.now();
-				log("  -- STARTED task =", task.action);
-			}
-			this.onprogress(new ProgressEvent(STAGE_TASK_STARTED, { pageURL: this.options.url, step, task: task.action, frame }));
-			if (!this.cancelled) {
-				this.executeTask(task);
-			}
-			this.onprogress(new ProgressEvent(STAGE_TASK_ENDED, { pageURL: this.options.url, step, task: task.action, frame }));
-			if (DEBUG) {
-				log("  -- ENDED   task =", task.action, "delay =", Date.now() - startTime);
-			}
-		});
-		let parallelTasksPromise;
-		if (STAGES[step].parallel) {
-			parallelTasksPromise = await Promise.all(STAGES[step].parallel.map(async task => {
-				let startTime;
-				if (DEBUG) {
-					startTime = Date.now();
-					log("  // STARTED task =", task.action);
-				}
-				this.onprogress(new ProgressEvent(STAGE_TASK_STARTED, { pageURL: this.options.url, step, task: task.action, frame }));
-				if (!this.cancelled) {
-					await this.executeTask(task);
-				}
-				this.onprogress(new ProgressEvent(STAGE_TASK_ENDED, { pageURL: this.options.url, step, task: task.action, frame }));
-				if (DEBUG) {
-					log("  // ENDED task =", task.action, "delay =", Date.now() - startTime);
-				}
-			}));
-		} else {
-			parallelTasksPromise = Promise.resolve();
-		}
-		this.onprogress(new ProgressEvent(STAGE_ENDED, { pageURL: this.options.url, step, frame }));
-		if (DEBUG) {
-			log("**** ENDED   STAGE", step, "****");
-		}
-		return parallelTasksPromise;
-	}
-
-	executeTask(task) {
-		if (!task.option || ((task.option.startsWith("!") && !this.options[task.option]) || this.options[task.option])) {
-			return this.processor[task.action]();
-		}
-	}
-}
-
-// ------------
-// BatchRequest
-// ------------
-class BatchRequest {
-	constructor() {
-		this.requests = new Map();
-		this.duplicates = new Map();
-	}
-
-	addURL(resourceURL, { asBinary, expectedType, groupDuplicates, baseURI, blockMixedContent } = {}) {
-		return new Promise((resolve, reject) => {
-			const requestKey = JSON.stringify([resourceURL, asBinary, expectedType, baseURI, blockMixedContent]);
-			let resourceRequests = this.requests.get(requestKey);
-			if (!resourceRequests) {
-				resourceRequests = [];
-				this.requests.set(requestKey, resourceRequests);
-			}
-			const callbacks = { resolve, reject };
-			resourceRequests.push(callbacks);
-			if (groupDuplicates) {
-				let duplicateRequests = this.duplicates.get(requestKey);
-				if (!duplicateRequests) {
-					duplicateRequests = [];
-					this.duplicates.set(requestKey, duplicateRequests);
-				}
-				duplicateRequests.push(callbacks);
-			}
-		});
-	}
-
-	getMaxResources() {
-		return this.requests.size;
-	}
-
-	run(onloadListener, options) {
-		const resourceURLs = [...this.requests.keys()];
-		let indexResource = 0;
-		return Promise.all(resourceURLs.map(async requestKey => {
-			const [resourceURL, asBinary, expectedType, baseURI, blockMixedContent] = JSON.parse(requestKey);
-			const resourceRequests = this.requests.get(requestKey);
-			try {
-				const currentIndexResource = indexResource;
-				indexResource = indexResource + 1;
-				const content = await util.getContent(resourceURL, {
-					asBinary,
-					expectedType,
-					maxResourceSize: options.maxResourceSize,
-					maxResourceSizeEnabled: options.maxResourceSizeEnabled,
-					frameId: options.windowId,
-					resourceReferrer: options.resourceReferrer,
-					baseURI,
-					blockMixedContent,
-					acceptHeaders: options.acceptHeaders,
-					networkTimeout: options.networkTimeout
-				});
-				onloadListener({ url: resourceURL });
-				if (!this.cancelled) {
-					resourceRequests.forEach(callbacks => {
-						const duplicateCallbacks = this.duplicates.get(requestKey);
-						const duplicate = duplicateCallbacks && duplicateCallbacks.length > 1 && duplicateCallbacks.includes(callbacks);
-						callbacks.resolve({ content: content.data, indexResource: currentIndexResource, duplicate });
-					});
-				}
-			} catch (error) {
-				indexResource = indexResource + 1;
-				onloadListener({ url: resourceURL });
-				resourceRequests.forEach(resourceRequest => resourceRequest.reject(error));
-			}
-			this.requests.delete(requestKey);
-		}));
-	}
-
-	cancel() {
-		this.cancelled = true;
-		const resourceURLs = [...this.requests.keys()];
-		resourceURLs.forEach(requestKey => {
-			const resourceRequests = this.requests.get(requestKey);
-			resourceRequests.forEach(callbacks => callbacks.reject());
-			this.requests.delete(requestKey);
-		});
-	}
-}
-
-// ---------
-// Processor
-// ---------
-const PREFIXES_FORBIDDEN_DATA_URI = ["data:text/"];
-const PREFIX_DATA_URI_IMAGE_SVG = "data:image/svg+xml";
-const SCRIPT_TAG_FOUND = /<script/gi;
-const NOSCRIPT_TAG_FOUND = /<noscript/gi;
-const CANVAS_TAG_FOUND = /<canvas/gi;
-const SHADOWROOT_ATTRIBUTE_NAME = "shadowroot";
-const SCRIPT_TEMPLATE_SHADOW_ROOT = "data-template-shadow-root";
-const UTF8_CHARSET = "utf-8";
-
-class Processor {
-	constructor(options, batchRequest) {
-		this.options = options;
-		this.stats = new Stats(options);
-		this.baseURI = normalizeURL(options.baseURI || options.url);
-		this.batchRequest = batchRequest;
-		this.stylesheets = new Map();
-		this.styles = new Map();
-		this.cssVariables = new Map();
-		this.fontTests = options.fontTests;
-	}
-
-	initialize() {
-		this.options.saveDate = new Date();
-		this.options.saveUrl = this.options.url;
-		if (this.options.enableMaff) {
-			this.maffMetaDataPromise = this.batchRequest.addURL(util.resolveURL("index.rdf", this.options.baseURI || this.options.url), { expectedType: "document" });
-		}
-		this.maxResources = this.batchRequest.getMaxResources();
-		if (!this.options.saveRawPage && !this.options.removeFrames && this.options.frames) {
-			this.options.frames.forEach(frameData => this.maxResources += frameData.maxResources || 0);
-		}
-		if (!this.options.removeImports && this.options.imports) {
-			this.options.imports.forEach(importData => this.maxResources += importData.maxResources || 0);
-		}
-		this.stats.set("processed", "resources", this.maxResources);
-	}
-
-	async loadPage(pageContent, charset) {
-		let content;
-		if (!pageContent || this.options.saveRawPage) {
-			content = await util.getContent(this.baseURI, {
-				maxResourceSize: this.options.maxResourceSize,
-				maxResourceSizeEnabled: this.options.maxResourceSizeEnabled,
-				charset,
-				frameId: this.options.windowId,
-				resourceReferrer: this.options.resourceReferrer,
-				expectedType: "document",
-				acceptHeaders: this.options.acceptHeaders,
-				networkTimeout: this.options.networkTimeout
-			});
-			pageContent = content.data;
-		}
-		this.doc = util.parseDocContent(pageContent, this.baseURI);
-		if (this.options.saveRawPage) {
-			let charset;
-			this.doc.querySelectorAll("meta[charset], meta[http-equiv=\"content-type\"]").forEach(element => {
-				const charsetDeclaration = element.content.split(";")[1];
-				if (charsetDeclaration && !charset) {
-					charset = charsetDeclaration.split("=")[1].trim().toLowerCase();
-				}
-			});
-			if (charset && content.charset && charset.toLowerCase() != content.charset.toLowerCase()) {
-				return this.loadPage(pageContent, charset);
-			}
-		}
-		this.workStyleElement = this.doc.createElement("style");
-		this.doc.body.appendChild(this.workStyleElement);
-		this.onEventAttributeNames = getOnEventAttributeNames(this.doc);
-	}
-
-	finalize() {
-		if (this.workStyleElement.parentNode) {
-			this.workStyleElement.remove();
-		}
-	}
-
-	async getPageData() {
-		util.postProcessDoc(this.doc);
-		const url = util.parseURL(this.baseURI);
-		if (this.options.insertSingleFileComment) {
-			const firstComment = this.doc.documentElement.firstChild;
-			let infobarURL = this.options.saveUrl, infobarSaveDate = this.options.saveDate;
-			if (firstComment.nodeType == 8 && (firstComment.textContent.includes(util.COMMENT_HEADER_LEGACY) || firstComment.textContent.includes(util.COMMENT_HEADER))) {
-				const info = this.doc.documentElement.firstChild.textContent.split("\n");
-				try {
-					const [, , url, saveDate] = info;
-					infobarURL = url.split("url: ")[1];
-					infobarSaveDate = saveDate.split("saved date: ")[1];
-					firstComment.remove();
-				} catch (error) {
-					// ignored
-				}
-			}
-			const infobarContent = (this.options.infobarContent || "").replace(/\\n/g, "\n").replace(/\\t/g, "\t");
-			const commentNode = this.doc.createComment("\n " + (this.options.useLegacyCommentHeader ? util.COMMENT_HEADER_LEGACY : util.COMMENT_HEADER) +
-				" \n url: " + infobarURL +
-				" \n saved date: " + infobarSaveDate +
-				(infobarContent ? " \n info: " + infobarContent : "") + "\n");
-			this.doc.documentElement.insertBefore(commentNode, this.doc.documentElement.firstChild);
-		}
-		if (this.options.insertCanonicalLink && this.options.saveUrl.match(HTTP_URI_PREFIX)) {
-			let canonicalLink = this.doc.querySelector("link[rel=canonical]");
-			if (!canonicalLink) {
-				canonicalLink = this.doc.createElement("link");
-				canonicalLink.setAttribute("rel", "canonical");
-				this.doc.head.appendChild(canonicalLink);
-			}
-			if (canonicalLink && !canonicalLink.href) {
-				canonicalLink.href = this.options.saveUrl;
-			}
-		}
-		if (this.options.insertMetaCSP) {
-			const metaTag = this.doc.createElement("meta");
-			metaTag.httpEquiv = "content-security-policy";
-			metaTag.content = "default-src 'none'; font-src 'self' data:; img-src 'self' data:; style-src 'unsafe-inline'; media-src 'self' data:; script-src 'unsafe-inline' data:;";
-			this.doc.head.appendChild(metaTag);
-		}
-		if (this.options.insertMetaNoIndex) {
-			let metaElement = this.doc.querySelector("meta[name=robots][content*=noindex]");
-			if (!metaElement) {
-				metaElement = this.doc.createElement("meta");
-				metaElement.setAttribute("name", "robots");
-				metaElement.setAttribute("content", "noindex");
-				this.doc.head.appendChild(metaElement);
-			}
-		}
-		const styleElement = this.doc.createElement("style");
-		styleElement.textContent = "img[src=\"data:,\"],source[src=\"data:,\"]{display:none!important}";
-		this.doc.head.appendChild(styleElement);
-		let size;
-		if (this.options.displayStats) {
-			size = util.getContentSize(this.doc.documentElement.outerHTML);
-		}
-		const content = util.serialize(this.doc, this.options.compressHTML);
-		if (this.options.displayStats) {
-			const contentSize = util.getContentSize(content);
-			this.stats.set("processed", "HTML bytes", contentSize);
-			this.stats.add("discarded", "HTML bytes", size - contentSize);
-		}
-		let filename = await ProcessorHelper.evalTemplate(this.options.filenameTemplate, this.options, content) || "";
-		const replacementCharacter = this.options.filenameReplacementCharacter;
-		filename = util.getValidFilename(filename, this.options.filenameReplacedCharacters, replacementCharacter);
-		if (!this.options.backgroundSave) {
-			filename = filename.replace(/\//g, replacementCharacter);
-		}
-		if (!this.options.saveToGDrive && !this.options.saveToGitHub && !this.options.saveWithCompanion &&
-			((this.options.filenameMaxLengthUnit == "bytes" && util.getContentSize(filename) > this.options.filenameMaxLength) || (filename.length > this.options.filenameMaxLength))) {
-			const extensionMatch = filename.match(/(\.[^.]{3,4})$/);
-			const extension = extensionMatch && extensionMatch[0] && extensionMatch[0].length > 1 ? extensionMatch[0] : "";
-			filename = this.options.filenameMaxLengthUnit == "bytes" ?
-				await util.truncateText(filename, this.options.filenameMaxLength - extension.length) :
-				filename.substring(0, this.options.filenameMaxLength - extension.length);
-			filename = filename + "…" + extension;
-		}
-		if (!filename) {
-			filename = "Unnamed page";
-		}
-		const matchTitle = this.baseURI.match(/([^/]*)\/?(\.html?.*)$/) || this.baseURI.match(/\/\/([^/]*)\/?$/);
-		const pageData = {
-			stats: this.stats.data,
-			title: this.options.title || (this.baseURI && matchTitle ? matchTitle[1] : (url.hostname ? url.hostname : "")),
-			filename,
-			content
-		};
-		if (this.options.addProof) {
-			pageData.hash = await util.digest("SHA-256", content);
-		}
-		if (this.options.retrieveLinks) {
-			pageData.links = Array.from(new Set(Array.from(this.doc.links).map(linkElement => linkElement.href)));
-		}
-		return pageData;
-	}
-
-	preProcessPage() {
-		if (this.options.win) {
-			this.doc.body.querySelectorAll(":not(svg) title, meta, link[href][rel*=\"icon\"]").forEach(element => element instanceof this.options.win.HTMLElement && this.doc.head.appendChild(element));
-		}
-		if (this.options.images && !this.options.saveRawPage) {
-			this.doc.querySelectorAll("img[" + util.IMAGE_ATTRIBUTE_NAME + "]").forEach(imgElement => {
-				const attributeValue = imgElement.getAttribute(util.IMAGE_ATTRIBUTE_NAME);
-				if (attributeValue) {
-					const imageData = this.options.images[Number(attributeValue)];
-					if (imageData) {
-						if (this.options.removeHiddenElements && (
-							(imageData.size && !imageData.size.pxWidth && !imageData.size.pxHeight) ||
-							(imgElement.getAttribute(util.HIDDEN_CONTENT_ATTRIBUTE_NAME) == "")
-						)) {
-							imgElement.setAttribute("src", util.EMPTY_RESOURCE);
-						} else {
-							if (imageData.currentSrc) {
-								imgElement.dataset.singleFileOriginURL = imgElement.getAttribute("src");
-								imgElement.setAttribute("src", imageData.currentSrc);
-							}
-							if (this.options.loadDeferredImages) {
-								if ((!imgElement.getAttribute("src") || imgElement.getAttribute("src") == util.EMPTY_RESOURCE) && imgElement.getAttribute("data-src")) {
-									imageData.src = imgElement.dataset.src;
-									imgElement.setAttribute("src", imgElement.dataset.src);
-									imgElement.removeAttribute("data-src");
-								}
-							}
-						}
-					}
-				}
-			});
-			if (this.options.loadDeferredImages) {
-				this.doc.querySelectorAll("img[data-srcset]").forEach(imgElement => {
-					if (!imgElement.getAttribute("srcset") && imgElement.getAttribute("data-srcset")) {
-						imgElement.setAttribute("srcset", imgElement.dataset.srcset);
-						imgElement.removeAttribute("data-srcset");
-					}
-				});
-			}
-		}
-	}
-
-	replaceStyleContents() {
-		if (this.options.stylesheets) {
-			this.doc.querySelectorAll("style").forEach((styleElement, styleIndex) => {
-				const attributeValue = styleElement.getAttribute(util.STYLESHEET_ATTRIBUTE_NAME);
-				if (attributeValue) {
-					const stylesheetContent = this.options.stylesheets[Number(styleIndex)];
-					if (stylesheetContent) {
-						styleElement.textContent = stylesheetContent;
-					}
-				}
-			});
-		}
-	}
-
-	removeUnselectedElements() {
-		removeUnmarkedElements(this.doc.body);
-		this.doc.body.removeAttribute(util.SELECTED_CONTENT_ATTRIBUTE_NAME);
-
-		function removeUnmarkedElements(element) {
-			let selectedElementFound = false;
-			Array.from(element.childNodes).forEach(node => {
-				if (node.nodeType == 1) {
-					const isSelectedElement = node.getAttribute(util.SELECTED_CONTENT_ATTRIBUTE_NAME) == "";
-					selectedElementFound = selectedElementFound || isSelectedElement;
-					if (isSelectedElement) {
-						node.removeAttribute(util.SELECTED_CONTENT_ATTRIBUTE_NAME);
-						removeUnmarkedElements(node);
-					} else if (selectedElementFound) {
-						removeNode(node);
-					} else {
-						hideNode(node);
-					}
-				}
-			});
-		}
-
-		function removeNode(node) {
-			if ((node.nodeType != 1 || !node.querySelector("svg,style,link")) && canHideNode(node)) {
-				node.remove();
-			} else {
-				hideNode(node);
-			}
-		}
-
-		function hideNode(node) {
-			if (canHideNode(node)) {
-				node.style.setProperty("display", "none", "important");
-				Array.from(node.childNodes).forEach(removeNode);
-			}
-		}
-
-		function canHideNode(node) {
-			if (node.nodeType == 1) {
-				const tagName = node.tagName && node.tagName.toLowerCase();
-				return (tagName != "svg" && tagName != "style" && tagName != "link");
-			}
-		}
-	}
-
-	insertVideoPosters() {
-		if (this.options.posters) {
-			this.doc.querySelectorAll("video[src], video > source[src]").forEach(element => {
-				let videoElement;
-				if (element.tagName == "VIDEO") {
-					videoElement = element;
-				} else {
-					videoElement = element.parentElement;
-				}
-				const attributeValue = element.getAttribute(util.POSTER_ATTRIBUTE_NAME);
-				if (attributeValue) {
-					const posterURL = this.options.posters[Number(attributeValue)];
-					if (!videoElement.poster && posterURL) {
-						videoElement.setAttribute("poster", posterURL);
-					}
-				}
-			});
-		}
-	}
-
-	insertVideoLinks() {
-		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";
-		const ICON_SIZE = "16px";
-		this.doc.querySelectorAll("video").forEach(videoElement => {
-			const attributeValue = videoElement.getAttribute(util.VIDEO_ATTRIBUTE_NAME);
-			if (attributeValue) {
-				const videoData = this.options.videos[Number(attributeValue)];
-				const src = videoData.src || videoElement.src;
-				if (videoElement && src) {
-					const linkElement = this.doc.createElement("a");
-					const imgElement = this.doc.createElement("img");
-					linkElement.href = src;
-					linkElement.target = "_blank";
-					linkElement.style.setProperty("z-index", 2147483647, "important");
-					linkElement.style.setProperty("position", "absolute", "important");
-					linkElement.style.setProperty("top", "8px", "important");
-					linkElement.style.setProperty("left", "8px", "important");
-					linkElement.style.setProperty("width", ICON_SIZE, "important");
-					linkElement.style.setProperty("height", ICON_SIZE, "important");
-					linkElement.style.setProperty("min-width", ICON_SIZE, "important");
-					linkElement.style.setProperty("min-height", ICON_SIZE, "important");
-					linkElement.style.setProperty("max-width", ICON_SIZE, "important");
-					linkElement.style.setProperty("max-height", ICON_SIZE, "important");
-					imgElement.src = LINK_ICON;
-					imgElement.style.setProperty("width", ICON_SIZE, "important");
-					imgElement.style.setProperty("height", ICON_SIZE, "important");
-					imgElement.style.setProperty("min-width", ICON_SIZE, "important");
-					imgElement.style.setProperty("min-height", ICON_SIZE, "important");
-					imgElement.style.setProperty("max-width", ICON_SIZE, "important");
-					imgElement.style.setProperty("max-height", ICON_SIZE, "important");
-					linkElement.appendChild(imgElement);
-					videoElement.insertAdjacentElement("afterend", linkElement);
-					const positionInlineParent = videoElement.parentNode.style.getPropertyValue("position");
-					if ((!videoData.positionParent && (!positionInlineParent || positionInlineParent != "static")) || videoData.positionParent == "static") {
-						videoElement.parentNode.style.setProperty("position", "relative", "important");
-					}
-				}
-			}
-		});
-	}
-
-	removeFrames() {
-		const frameElements = this.doc.querySelectorAll("iframe, frame, object[type=\"text/html\"][data]");
-		this.stats.set("discarded", "frames", frameElements.length);
-		this.stats.set("processed", "frames", frameElements.length);
-		this.doc.querySelectorAll("iframe, frame, object[type=\"text/html\"][data]").forEach(element => element.remove());
-	}
-
-	removeEmbedScripts() {
-		const JAVASCRIPT_URI_PREFIX = "javascript:";
-		this.onEventAttributeNames.forEach(attributeName => this.doc.querySelectorAll("[" + attributeName + "]").forEach(element => element.removeAttribute(attributeName)));
-		this.doc.querySelectorAll("[href]").forEach(element => {
-			if (element.href && element.href.match && element.href.trim().startsWith(JAVASCRIPT_URI_PREFIX)) {
-				element.removeAttribute("href");
-			}
-		});
-		this.doc.querySelectorAll("[src]").forEach(element => {
-			if (element.src && element.src.trim().startsWith(JAVASCRIPT_URI_PREFIX)) {
-				element.removeAttribute("src");
-			}
-		});
-		const scriptElements = this.doc.querySelectorAll("script:not([type=\"application/ld+json\"]):not([" + SCRIPT_TEMPLATE_SHADOW_ROOT + "])");
-		this.stats.set("discarded", "scripts", scriptElements.length);
-		this.stats.set("processed", "scripts", scriptElements.length);
-		scriptElements.forEach(element => element.remove());
-	}
-
-	removeDiscardedResources() {
-		this.doc.querySelectorAll("." + util.SINGLE_FILE_UI_ELEMENT_CLASS).forEach(element => element.remove());
-		const noscriptPlaceholders = new Map();
-		this.doc.querySelectorAll("noscript").forEach(noscriptElement => {
-			const placeholderElement = this.doc.createElement("div");
-			placeholderElement.innerHTML = noscriptElement.dataset.singleFileDisabledNoscript;
-			noscriptElement.replaceWith(placeholderElement);
-			noscriptPlaceholders.set(placeholderElement, noscriptElement);
-		});
-		this.doc.querySelectorAll("meta[http-equiv=refresh], meta[disabled-http-equiv]").forEach(element => element.remove());
-		Array.from(noscriptPlaceholders).forEach(([placeholderElement, noscriptElement]) => {
-			noscriptElement.dataset.singleFileDisabledNoscript = placeholderElement.innerHTML;
-			placeholderElement.replaceWith(noscriptElement);
-		});
-		this.doc.querySelectorAll("meta[http-equiv=\"content-security-policy\"]").forEach(element => element.remove());
-		const objectElements = this.doc.querySelectorAll("applet, object[data]:not([type=\"image/svg+xml\"]):not([type=\"image/svg-xml\"]):not([type=\"text/html\"]), embed[src]:not([src*=\".svg\"]):not([src*=\".pdf\"])");
-		this.stats.set("discarded", "objects", objectElements.length);
-		this.stats.set("processed", "objects", objectElements.length);
-		objectElements.forEach(element => element.remove());
-		const replacedAttributeValue = this.doc.querySelectorAll("link[rel~=preconnect], link[rel~=prerender], link[rel~=dns-prefetch], link[rel~=preload], link[rel~=manifest], link[rel~=prefetch]");
-		replacedAttributeValue.forEach(element => {
-			const relValue = element.getAttribute("rel").replace(/(preconnect|prerender|dns-prefetch|preload|prefetch|manifest)/g, "").trim();
-			if (relValue.length) {
-				element.setAttribute("rel", relValue);
-			} else {
-				element.remove();
-			}
-		});
-		this.doc.querySelectorAll("link[rel*=stylesheet][rel*=alternate][title],link[rel*=stylesheet]:not([href]),link[rel*=stylesheet][href=\"\"]").forEach(element => element.remove());
-		if (this.options.removeHiddenElements) {
-			this.doc.querySelectorAll("input[type=hidden]").forEach(element => element.remove());
-		}
-		if (!this.options.saveFavicon) {
-			this.doc.querySelectorAll("link[rel*=\"icon\"]").forEach(element => element.remove());
-		}
-		this.doc.querySelectorAll("a[ping]").forEach(element => element.removeAttribute("ping"));
-	}
-
-	resetCharsetMeta() {
-		let charset;
-		this.doc.querySelectorAll("meta[charset], meta[http-equiv=\"content-type\"]").forEach(element => {
-			const charsetDeclaration = element.content.split(";")[1];
-			if (charsetDeclaration && !charset) {
-				charset = charsetDeclaration.split("=")[1];
-				if (charset) {
-					this.charset = charset.trim().toLowerCase();
-				}
-			}
-			element.remove();
-		});
-		const metaElement = this.doc.createElement("meta");
-		metaElement.setAttribute("charset", UTF8_CHARSET);
-		if (this.doc.head.firstChild) {
-			this.doc.head.insertBefore(metaElement, this.doc.head.firstChild);
-		} else {
-			this.doc.head.appendChild(metaElement);
-		}
-	}
-
-	setInputValues() {
-		this.doc.querySelectorAll("input:not([type=radio]):not([type=checkbox])").forEach(input => {
-			const value = input.getAttribute(util.INPUT_VALUE_ATTRIBUTE_NAME);
-			input.setAttribute("value", value || "");
-		});
-		this.doc.querySelectorAll("input[type=radio], input[type=checkbox]").forEach(input => {
-			const value = input.getAttribute(util.INPUT_VALUE_ATTRIBUTE_NAME);
-			if (value == "true") {
-				input.setAttribute("checked", "");
-			}
-		});
-		this.doc.querySelectorAll("textarea").forEach(textarea => {
-			const value = textarea.getAttribute(util.INPUT_VALUE_ATTRIBUTE_NAME);
-			textarea.textContent = value || "";
-		});
-		this.doc.querySelectorAll("select").forEach(select => {
-			select.querySelectorAll("option").forEach(option => {
-				const selected = option.getAttribute(util.INPUT_VALUE_ATTRIBUTE_NAME) != null;
-				if (selected) {
-					option.setAttribute("selected", "");
-				}
-			});
-		});
-	}
-
-	moveStylesInHead() {
-		this.doc.querySelectorAll("style").forEach(stylesheet => {
-			if (stylesheet.getAttribute(util.STYLE_ATTRIBUTE_NAME) == "") {
-				this.doc.head.appendChild(stylesheet);
-			}
-		});
-	}
-
-	saveFavicon() {
-		let faviconElement = this.doc.querySelector("link[href][rel=\"icon\"]");
-		if (!faviconElement) {
-			faviconElement = this.doc.querySelector("link[href][rel=\"shortcut icon\"]");
-		}
-		if (!faviconElement) {
-			faviconElement = this.doc.createElement("link");
-			faviconElement.setAttribute("type", "image/x-icon");
-			faviconElement.setAttribute("rel", "shortcut icon");
-			faviconElement.setAttribute("href", "/favicon.ico");
-		}
-		this.doc.head.appendChild(faviconElement);
-	}
-
-	replaceCanvasElements() {
-		if (this.options.canvases) {
-			this.doc.querySelectorAll("canvas").forEach(canvasElement => {
-				const attributeValue = canvasElement.getAttribute(util.CANVAS_ATTRIBUTE_NAME);
-				if (attributeValue) {
-					const canvasData = this.options.canvases[Number(attributeValue)];
-					if (canvasData) {
-						ProcessorHelper.setBackgroundImage(canvasElement, "url(" + canvasData.dataURI + ")");
-						this.stats.add("processed", "canvas", 1);
-					}
-				}
-			});
-		}
-	}
-
-	insertFonts() {
-		if (this.options.fonts && this.options.fonts.length) {
-			let stylesheetContent = "";
-			this.options.fonts.forEach(fontData => {
-				if (fontData["font-family"] && fontData.src) {
-					stylesheetContent += "@font-face{";
-					let stylesContent = "";
-					Object.keys(fontData).forEach(fontStyle => {
-						if (stylesContent) {
-							stylesContent += ";";
-						}
-						stylesContent += fontStyle + ":" + fontData[fontStyle];
-					});
-					stylesheetContent += stylesContent + "}";
-				}
-			});
-			if (stylesheetContent) {
-				const styleElement = this.doc.createElement("style");
-				styleElement.textContent = stylesheetContent;
-				const existingStyleElement = this.doc.querySelector("style");
-				if (existingStyleElement) {
-					existingStyleElement.parentElement.insertBefore(styleElement, existingStyleElement);
-				} else {
-					this.doc.head.insertBefore(styleElement, this.doc.head.firstChild);
-				}
-			}
-		}
-	}
-
-	removeHiddenElements() {
-		const hiddenElements = this.doc.querySelectorAll("[" + util.HIDDEN_CONTENT_ATTRIBUTE_NAME + "]");
-		const removedElements = this.doc.querySelectorAll("[" + util.REMOVED_CONTENT_ATTRIBUTE_NAME + "]");
-		this.stats.set("discarded", "hidden elements", removedElements.length);
-		this.stats.set("processed", "hidden elements", removedElements.length);
-		if (hiddenElements.length) {
-			const styleElement = this.doc.createElement("style");
-			styleElement.textContent = ".sf-hidden{display:none!important;}";
-			this.doc.head.appendChild(styleElement);
-			hiddenElements.forEach(element => {
-				if (element.style.getPropertyValue("display") != "none") {
-					if (element.style.getPropertyPriority("display") == "important") {
-						element.style.setProperty("display", "none", "important");
-					} else {
-						element.classList.add("sf-hidden");
-					}
-				}
-			});
-		}
-		removedElements.forEach(element => element.remove());
-	}
-
-	resolveHrefs() {
-		this.doc.querySelectorAll("a[href], area[href], link[href]").forEach(element => {
-			const href = element.getAttribute("href").trim();
-			if (element.tagName == "LINK" && element.rel.includes("stylesheet")) {
-				if (this.options.saveOriginalURLs && !isDataURL(href)) {
-					element.setAttribute("data-sf-original-href", href);
-				}
-			}
-			if (!testIgnoredPath(href)) {
-				let resolvedURL;
-				try {
-					resolvedURL = util.resolveURL(href, this.options.baseURI || this.options.url);
-				} catch (error) {
-					// ignored
-				}
-				if (resolvedURL) {
-					const url = normalizeURL(this.options.url);
-					if (resolvedURL.startsWith(url + "#") && !resolvedURL.startsWith(url + "#!") && !this.options.resolveFragmentIdentifierURLs) {
-						resolvedURL = resolvedURL.substring(url.length);
-					}
-					try {
-						element.setAttribute("href", resolvedURL);
-					} catch (error) {
-						// ignored
-					}
-				}
-			}
-		});
-	}
-
-	async insertMissingVideoPosters() {
-		await Promise.all(Array.from(this.doc.querySelectorAll("video[src], video > source[src]")).map(async element => {
-			let videoElement;
-			if (element.tagName == "VIDEO") {
-				videoElement = element;
-			} else {
-				videoElement = element.parentElement;
-			}
-			if (!videoElement.poster) {
-				const attributeValue = videoElement.getAttribute(util.VIDEO_ATTRIBUTE_NAME);
-				if (attributeValue) {
-					const videoData = this.options.videos[Number(attributeValue)];
-					const src = videoData.src || videoElement.src;
-					if (src) {
-						const temporaryVideoElement = this.doc.createElement("video");
-						temporaryVideoElement.src = src;
-						temporaryVideoElement.style.setProperty("width", videoData.size.pxWidth + "px", "important");
-						temporaryVideoElement.style.setProperty("height", videoData.size.pxHeight + "px", "important");
-						temporaryVideoElement.style.setProperty("display", "none", "important");
-						temporaryVideoElement.crossOrigin = "anonymous";
-						const canvasElement = this.doc.createElement("canvas");
-						const context = canvasElement.getContext("2d");
-						this.options.doc.body.appendChild(temporaryVideoElement);
-						return new Promise(resolve => {
-							temporaryVideoElement.currentTime = videoData.currentTime;
-							temporaryVideoElement.oncanplay = () => {
-								canvasElement.width = videoData.size.pxWidth;
-								canvasElement.height = videoData.size.pxHeight;
-								context.drawImage(temporaryVideoElement, 0, 0, canvasElement.width, canvasElement.height);
-								try {
-									videoElement.poster = canvasElement.toDataURL("image/png", "");
-								} catch (error) {
-									// ignored
-								}
-								temporaryVideoElement.remove();
-								resolve();
-							};
-							temporaryVideoElement.onerror = () => {
-								temporaryVideoElement.remove();
-								resolve();
-							};
-						});
-					}
-				}
-			}
-		}));
-	}
-
-	resolveStyleAttributeURLs() {
-		this.doc.querySelectorAll("[style]").forEach(element => {
-			let styleContent = element.getAttribute("style");
-			if (this.options.compressCSS) {
-				styleContent = util.compressCSS(styleContent);
-			}
-			styleContent = ProcessorHelper.resolveStylesheetURLs(styleContent, this.baseURI, this.workStyleElement, this.options.saveOriginalURLs);
-			const declarationList = cssTree.parse(styleContent, { context: "declarationList" });
-			this.styles.set(element, declarationList);
-		});
-	}
-
-	async resolveStylesheetURLs() {
-		await Promise.all(Array.from(this.doc.querySelectorAll("style, link[rel*=stylesheet]")).map(async element => {
-			const options = Object.assign({}, this.options, { charset: this.charset });
-			let mediaText;
-			if (element.media) {
-				mediaText = element.media.toLowerCase();
-			}
-			const stylesheetInfo = { mediaText };
-			if (element.closest("[" + SHADOWROOT_ATTRIBUTE_NAME + "]")) {
-				stylesheetInfo.scoped = true;
-			}
-			if (element.tagName == "LINK" && element.charset) {
-				options.charset = element.charset;
-			}
-			await processElement(element, stylesheetInfo, this.stylesheets, this.baseURI, options, this.workStyleElement);
-		}));
-		if (this.options.rootDocument) {
-			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]);
-			await Promise.all(newResources.map(async resource => {
-				resource.retrieved = true;
-				const stylesheetInfo = {};
-				const element = this.doc.createElement("style");
-				this.doc.body.appendChild(element);
-				element.textContent = resource.content;
-				await processElement(element, stylesheetInfo, this.stylesheets, this.baseURI, this.options, this.workStyleElement);
-			}));
-		}
-
-		async function processElement(element, stylesheetInfo, stylesheets, baseURI, options, workStyleElement) {
-			let stylesheet;
-			stylesheets.set(element, stylesheetInfo);
-			if (!options.blockStylesheets) {
-				let stylesheetContent = await getStylesheetContent(element, baseURI, options, workStyleElement);
-				if (!matchCharsetEquals(stylesheetContent, options.charset)) {
-					options = Object.assign({}, options, { charset: getCharset(stylesheetContent) });
-					stylesheetContent = await getStylesheetContent(element, baseURI, options, workStyleElement);
-				}
-				try {
-					stylesheet = cssTree.parse(removeCssComments(stylesheetContent));
-				} catch (error) {
-					// ignored
-				}
-			}
-			if (stylesheet && stylesheet.children) {
-				if (options.compressCSS) {
-					ProcessorHelper.removeSingleLineCssComments(stylesheet);
-				}
-				stylesheetInfo.stylesheet = stylesheet;
-			} else {
-				stylesheets.delete(element);
-			}
-		}
-
-		async function getStylesheetContent(element, baseURI, options, workStyleElement) {
-			let content;
-			if (!options.blockStylesheets) {
-				if (element.tagName == "LINK") {
-					content = await ProcessorHelper.resolveLinkStylesheetURLs(element.href, baseURI, options, workStyleElement);
-				} else {
-					content = await ProcessorHelper.resolveImportURLs(element.textContent, baseURI, options, workStyleElement);
-				}
-			}
-			return content || "";
-		}
-	}
-
-	async resolveFrameURLs() {
-		if (!this.options.saveRawPage) {
-			const frameElements = Array.from(this.doc.querySelectorAll("iframe, frame, object[type=\"text/html\"][data]"));
-			await Promise.all(frameElements.map(async frameElement => {
-				if (frameElement.tagName == "OBJECT") {
-					frameElement.setAttribute("data", "data:text/html,");
-				} else {
-					const src = frameElement.getAttribute("src");
-					if (this.options.saveOriginalURLs && !isDataURL(src)) {
-						frameElement.setAttribute("data-sf-original-src", src);
-					}
-					frameElement.removeAttribute("src");
-					frameElement.removeAttribute("srcdoc");
-				}
-				Array.from(frameElement.childNodes).forEach(node => node.remove());
-				const frameWindowId = frameElement.getAttribute(util.WIN_ID_ATTRIBUTE_NAME);
-				if (this.options.frames && frameWindowId) {
-					const frameData = this.options.frames.find(frame => frame.windowId == frameWindowId);
-					if (frameData) {
-						await initializeProcessor(frameData, frameElement, frameWindowId, this.batchRequest, Object.create(this.options));
-					}
-				}
-			}));
-		}
-
-		async function initializeProcessor(frameData, frameElement, frameWindowId, batchRequest, options) {
-			options.insertSingleFileComment = false;
-			options.insertCanonicalLink = false;
-			options.insertMetaNoIndex = false;
-			options.saveFavicon = false;
-			options.url = frameData.baseURI;
-			options.windowId = frameWindowId;
-			if (frameData.content) {
-				options.content = frameData.content;
-				options.canvases = frameData.canvases;
-				options.fonts = frameData.fonts;
-				options.stylesheets = frameData.stylesheets;
-				options.images = frameData.images;
-				options.posters = frameData.posters;
-				options.videos = frameData.videos;
-				options.usedFonts = frameData.usedFonts;
-				options.shadowRoots = frameData.shadowRoots;
-				options.imports = frameData.imports;
-				frameData.runner = new Runner(options);
-				frameData.frameElement = frameElement;
-				await frameData.runner.loadPage();
-				await frameData.runner.initialize();
-				frameData.maxResources = batchRequest.getMaxResources();
-			}
-		}
-	}
-
-	insertShadowRootContents() {
-		const doc = this.doc;
-		const options = this.options;
-		if (options.shadowRoots && options.shadowRoots.length) {
-			processElement(this.doc);
-			if (options.blockScripts) {
-				this.doc.querySelectorAll("script[" + SCRIPT_TEMPLATE_SHADOW_ROOT + "]").forEach(element => element.remove());
-			}
-			const scriptElement = doc.createElement("script");
-			scriptElement.setAttribute(SCRIPT_TEMPLATE_SHADOW_ROOT, "");
-			scriptElement.textContent = `(()=>{document.currentScript.remove();processNode(document);function processNode(node){node.querySelectorAll("template[${SHADOWROOT_ATTRIBUTE_NAME}]").forEach(element=>{let shadowRoot = element.parentElement.shadowRoot;if (!shadowRoot) {try {shadowRoot=element.parentElement.attachShadow({mode:element.getAttribute("${SHADOWROOT_ATTRIBUTE_NAME}")});shadowRoot.innerHTML=element.innerHTML;element.remove()} catch (error) {} if (shadowRoot) {processNode(shadowRoot)}}})}})()`;
-			doc.body.appendChild(scriptElement);
-		}
-
-		function processElement(element) {
-			const shadowRootElements = Array.from((element.querySelectorAll("[" + util.SHADOW_ROOT_ATTRIBUTE_NAME + "]")));
-			shadowRootElements.forEach(element => {
-				const attributeValue = element.getAttribute(util.SHADOW_ROOT_ATTRIBUTE_NAME);
-				if (attributeValue) {
-					const shadowRootData = options.shadowRoots[Number(attributeValue)];
-					if (shadowRootData) {
-						const templateElement = doc.createElement("template");
-						templateElement.setAttribute(SHADOWROOT_ATTRIBUTE_NAME, shadowRootData.mode);
-						if (shadowRootData.adoptedStyleSheets) {
-							shadowRootData.adoptedStyleSheets.forEach(stylesheetContent => {
-								const styleElement = doc.createElement("style");
-								styleElement.textContent = stylesheetContent;
-								templateElement.appendChild(styleElement);
-							});
-						}
-						const shadowDoc = util.parseDocContent(shadowRootData.content);
-						if (shadowDoc.head) {
-							const metaCharset = shadowDoc.head.querySelector("meta[charset]");
-							if (metaCharset) {
-								metaCharset.remove();
-							}
-							shadowDoc.head.childNodes.forEach(node => templateElement.appendChild(shadowDoc.importNode(node, true)));
-						}
-						if (shadowDoc.body) {
-							shadowDoc.body.childNodes.forEach(node => templateElement.appendChild(shadowDoc.importNode(node, true)));
-						}
-						processElement(templateElement);
-						if (element.firstChild) {
-							element.insertBefore(templateElement, element.firstChild);
-						} else {
-							element.appendChild(templateElement);
-						}
-					}
-				}
-			});
-		}
-	}
-
-	async resolveHtmlImportURLs() {
-		const linkElements = Array.from(this.doc.querySelectorAll("link[rel=import][href]"));
-		await Promise.all(linkElements.map(async linkElement => {
-			const resourceURL = linkElement.href;
-			if (this.options.saveOriginalURLs && !isDataURL(resourceURL)) {
-				linkElement.setAttribute("data-sf-original-href", resourceURL);
-			}
-			linkElement.removeAttribute("href");
-			const options = Object.create(this.options);
-			options.insertSingleFileComment = false;
-			options.insertCanonicalLink = false;
-			options.insertMetaNoIndex = false;
-			options.saveFavicon = false;
-			options.removeUnusedStyles = false;
-			options.removeAlternativeMedias = false;
-			options.removeUnusedFonts = false;
-			options.removeHiddenElements = false;
-			options.url = resourceURL;
-			const attributeValue = linkElement.getAttribute(util.HTML_IMPORT_ATTRIBUTE_NAME);
-			if (attributeValue) {
-				const importData = options.imports[Number(attributeValue)];
-				if (importData) {
-					options.content = importData.content;
-					importData.runner = new Runner(options);
-					await importData.runner.loadPage();
-					await importData.runner.initialize();
-					if (!options.removeImports) {
-						importData.maxResources = importData.runner.batchRequest.getMaxResources();
-					}
-					importData.runner.getStyleSheets().forEach(stylesheet => {
-						const importedStyleElement = this.doc.createElement("style");
-						linkElement.insertAdjacentElement("afterEnd", importedStyleElement);
-						this.stylesheets.set(importedStyleElement, stylesheet);
-					});
-				}
-			}
-			if (options.removeImports) {
-				linkElement.remove();
-				this.stats.add("discarded", "HTML imports", 1);
-			}
-		}));
-	}
-
-	removeUnusedStyles() {
-		if (!this.mediaAllInfo) {
-			this.mediaAllInfo = util.getMediaAllInfo(this.doc, this.stylesheets, this.styles);
-		}
-		const stats = util.minifyCSSRules(this.stylesheets, this.styles, this.mediaAllInfo);
-		this.stats.set("processed", "CSS rules", stats.processed);
-		this.stats.set("discarded", "CSS rules", stats.discarded);
-	}
-
-	removeUnusedFonts() {
-		util.removeUnusedFonts(this.doc, this.stylesheets, this.styles, this.options);
-	}
-
-	removeAlternativeMedias() {
-		const stats = util.minifyMedias(this.stylesheets);
-		this.stats.set("processed", "medias", stats.processed);
-		this.stats.set("discarded", "medias", stats.discarded);
-	}
-
-	async processStylesheets() {
-		this.options.fontDeclarations = new Map();
-		await Promise.all([...this.stylesheets].map(([, stylesheetInfo]) =>
-			ProcessorHelper.processStylesheet(stylesheetInfo.stylesheet.children, this.baseURI, this.options, this.cssVariables, this.batchRequest)
-		));
-	}
-
-	async processStyleAttributes() {
-		return Promise.all([...this.styles].map(([, declarationList]) =>
-			ProcessorHelper.processStyle(declarationList.children.toArray(), this.baseURI, this.options, this.cssVariables, this.batchRequest)
-		));
-	}
-
-	async processPageResources() {
-		const processAttributeArgs = [
-			["link[href][rel*=\"icon\"]", "href", false, true],
-			["object[type=\"image/svg+xml\"], object[type=\"image/svg-xml\"]", "data"],
-			["img[src], input[src][type=image]", "src", true],
-			["embed[src*=\".svg\"], embed[src*=\".pdf\"]", "src"],
-			["video[poster]", "poster"],
-			["*[background]", "background"],
-			["image", "xlink:href"],
-			["image", "href"]
-		];
-		if (this.options.blockImages) {
-			this.doc.querySelectorAll("svg").forEach(element => element.remove());
-		}
-		let resourcePromises = processAttributeArgs.map(([selector, attributeName, processDuplicates, removeElementIfMissing]) =>
-			ProcessorHelper.processAttribute(this.doc.querySelectorAll(selector), attributeName, this.baseURI, this.options, "image", this.cssVariables, this.styles, this.batchRequest, processDuplicates, removeElementIfMissing)
-		);
-		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)
-		]);
-		resourcePromises.push(ProcessorHelper.processAttribute(this.doc.querySelectorAll("audio[src], audio > source[src]"), "src", this.baseURI, this.options, "audio", this.cssVariables, this.styles, this.batchRequest));
-		resourcePromises.push(ProcessorHelper.processAttribute(this.doc.querySelectorAll("video[src], video > source[src]"), "src", this.baseURI, this.options, "video", this.cssVariables, this.styles, this.batchRequest));
-		await Promise.all(resourcePromises);
-		if (this.options.saveFavicon) {
-			ProcessorHelper.processShortcutIcons(this.doc);
-		}
-	}
-
-	async processScripts() {
-		await Promise.all(Array.from(this.doc.querySelectorAll("script[src]")).map(async element => {
-			let resourceURL;
-			let scriptSrc;
-			scriptSrc = element.getAttribute("src");
-			if (this.options.saveOriginalURLs && !isDataURL(scriptSrc)) {
-				element.setAttribute("data-sf-original-src", scriptSrc);
-			}
-			element.removeAttribute("integrity");
-			if (!this.options.blockScripts) {
-				element.textContent = "";
-				try {
-					resourceURL = util.resolveURL(scriptSrc, this.baseURI);
-				} catch (error) {
-					// ignored
-				}
-				if (testValidURL(resourceURL)) {
-					element.removeAttribute("src");
-					const content = await util.getContent(resourceURL, {
-						asBinary: true,
-						charset: this.charset != UTF8_CHARSET && this.charset,
-						maxResourceSize: this.options.maxResourceSize,
-						maxResourceSizeEnabled: this.options.maxResourceSizeEnabled,
-						frameId: this.options.windowId,
-						resourceReferrer: this.options.resourceReferrer,
-						baseURI: this.options.baseURI,
-						blockMixedContent: this.options.blockMixedContent,
-						expectedType: "script",
-						acceptHeaders: this.options.acceptHeaders,
-						networkTimeout: this.options.networkTimeout
-					});
-					content.data = getUpdatedResourceContent(resourceURL, content, this.options);
-					element.setAttribute("src", content.data);
-					if (element.getAttribute("async") == "async" || element.getAttribute(util.ASYNC_SCRIPT_ATTRIBUTE_NAME) == "") {
-						element.setAttribute("async", "");
-					}
-				}
-			} else {
-				element.removeAttribute("src");
-			}
-			this.stats.add("processed", "scripts", 1);
-		}));
-	}
-
-	removeAlternativeImages() {
-		util.removeAlternativeImages(this.doc);
-	}
-
-	async removeAlternativeFonts() {
-		await util.removeAlternativeFonts(this.doc, this.stylesheets, this.options.fontDeclarations, this.options.fontTests);
-	}
-
-	async processFrames() {
-		if (this.options.frames) {
-			const frameElements = Array.from(this.doc.querySelectorAll("iframe, frame, object[type=\"text/html\"][data]"));
-			await Promise.all(frameElements.map(async frameElement => {
-				const frameWindowId = frameElement.getAttribute(util.WIN_ID_ATTRIBUTE_NAME);
-				if (frameWindowId) {
-					const frameData = this.options.frames.find(frame => frame.windowId == frameWindowId);
-					if (frameData) {
-						this.options.frames = this.options.frames.filter(frame => frame.windowId != frameWindowId);
-						if (frameData.runner && frameElement.getAttribute(util.HIDDEN_FRAME_ATTRIBUTE_NAME) != "") {
-							this.stats.add("processed", "frames", 1);
-							await frameData.runner.run();
-							const pageData = await frameData.runner.getPageData();
-							frameElement.removeAttribute(util.WIN_ID_ATTRIBUTE_NAME);
-							let sandbox = "allow-popups allow-top-navigation allow-top-navigation-by-user-activation";
-							if (pageData.content.match(NOSCRIPT_TAG_FOUND) || pageData.content.match(CANVAS_TAG_FOUND) || pageData.content.match(SCRIPT_TAG_FOUND)) {
-								sandbox += " allow-scripts allow-same-origin";
-							}
-							frameElement.setAttribute("sandbox", sandbox);
-							if (frameElement.tagName == "OBJECT") {
-								frameElement.setAttribute("data", "data:text/html," + pageData.content);
-							} else {
-								if (frameElement.tagName == "FRAME") {
-									frameElement.setAttribute("src", "data:text/html," + pageData.content.replace(/%/g, "%25").replace(/#/g, "%23"));
-								} else {
-									frameElement.setAttribute("srcdoc", pageData.content);
-									frameElement.removeAttribute("src");
-								}
-							}
-							this.stats.addAll(pageData);
-						} else {
-							frameElement.removeAttribute(util.WIN_ID_ATTRIBUTE_NAME);
-							this.stats.add("discarded", "frames", 1);
-						}
-					}
-				}
-			}));
-		}
-	}
-
-	async processHtmlImports() {
-		const linkElements = Array.from(this.doc.querySelectorAll("link[rel=import]"));
-		await Promise.all(linkElements.map(async linkElement => {
-			const attributeValue = linkElement.getAttribute(util.HTML_IMPORT_ATTRIBUTE_NAME);
-			if (attributeValue) {
-				const importData = this.options.imports[Number(attributeValue)];
-				if (importData.runner) {
-					this.stats.add("processed", "HTML imports", 1);
-					await importData.runner.run();
-					const pageData = await importData.runner.getPageData();
-					linkElement.removeAttribute(util.HTML_IMPORT_ATTRIBUTE_NAME);
-					linkElement.setAttribute("href", "data:text/html," + pageData.content);
-					this.stats.addAll(pageData);
-				} else {
-					this.stats.add("discarded", "HTML imports", 1);
-				}
-			}
-		}));
-	}
-
-	replaceStylesheets() {
-		this.doc.querySelectorAll("style").forEach(styleElement => {
-			const stylesheetInfo = this.stylesheets.get(styleElement);
-			if (stylesheetInfo) {
-				this.stylesheets.delete(styleElement);
-				let stylesheetContent = cssTree.generate(stylesheetInfo.stylesheet);
-				if (this.options.saveOriginalURLs) {
-					stylesheetContent = replaceOriginalURLs(stylesheetContent);
-				}
-				styleElement.textContent = stylesheetContent;
-				if (stylesheetInfo.mediaText) {
-					styleElement.media = stylesheetInfo.mediaText;
-				}
-			} else {
-				styleElement.remove();
-			}
-		});
-		this.doc.querySelectorAll("link[rel*=stylesheet]").forEach(linkElement => {
-			const stylesheetInfo = this.stylesheets.get(linkElement);
-			if (stylesheetInfo) {
-				this.stylesheets.delete(linkElement);
-				const styleElement = this.doc.createElement("style");
-				if (stylesheetInfo.mediaText) {
-					styleElement.media = stylesheetInfo.mediaText;
-				}
-				let stylesheetContent = cssTree.generate(stylesheetInfo.stylesheet);
-				if (this.options.saveOriginalURLs) {
-					stylesheetContent = replaceOriginalURLs(stylesheetContent);
-					styleElement.setAttribute("data-sf-original-href", linkElement.getAttribute("data-sf-original-href"));
-				}
-				styleElement.textContent = stylesheetContent;
-				linkElement.parentElement.replaceChild(styleElement, linkElement);
-			} else {
-				linkElement.remove();
-			}
-		});
-	}
-
-	replaceStyleAttributes() {
-		this.doc.querySelectorAll("[style]").forEach(element => {
-			const declarations = this.styles.get(element);
-			if (declarations) {
-				this.styles.delete(element);
-				let styleContent = cssTree.generate(declarations);
-				if (this.options.saveOriginalURLs) {
-					styleContent = replaceOriginalURLs(styleContent);
-				}
-				element.setAttribute("style", styleContent);
-			} else {
-				element.setAttribute("style", "");
-			}
-		});
-	}
-
-	insertVariables() {
-		if (this.cssVariables.size) {
-			const styleElement = this.doc.createElement("style");
-			const firstStyleElement = this.doc.head.querySelector("style");
-			if (firstStyleElement) {
-				this.doc.head.insertBefore(styleElement, firstStyleElement);
-			} else {
-				this.doc.head.appendChild(styleElement);
-			}
-			let stylesheetContent = "";
-			this.cssVariables.forEach(({ content, url }, indexResource) => {
-				this.cssVariables.delete(indexResource);
-				if (stylesheetContent) {
-					stylesheetContent += ";";
-				}
-				stylesheetContent += `${SINGLE_FILE_VARIABLE_NAME_PREFIX + indexResource}: `;
-				if (this.options.saveOriginalURLs) {
-					stylesheetContent += `/* original URL: ${url} */ `;
-				}
-				stylesheetContent += `url("${content}")`;
-			});
-			styleElement.textContent = ":root{" + stylesheetContent + "}";
-		}
-	}
-
-	compressHTML() {
-		let size;
-		if (this.options.displayStats) {
-			size = util.getContentSize(this.doc.documentElement.outerHTML);
-		}
-		util.minifyHTML(this.doc, { PRESERVED_SPACE_ELEMENT_ATTRIBUTE_NAME: util.PRESERVED_SPACE_ELEMENT_ATTRIBUTE_NAME });
-		if (this.options.displayStats) {
-			this.stats.add("discarded", "HTML bytes", size - util.getContentSize(this.doc.documentElement.outerHTML));
-		}
-	}
-
-	cleanupPage() {
-		this.doc.querySelectorAll("base").forEach(element => element.remove());
-		const metaCharset = this.doc.head.querySelector("meta[charset]");
-		if (metaCharset) {
-			this.doc.head.insertBefore(metaCharset, this.doc.head.firstChild);
-			if (this.doc.head.querySelectorAll("*").length == 1 && this.doc.body.childNodes.length == 0) {
-				this.doc.head.querySelector("meta[charset]").remove();
-			}
-		}
-	}
-
-	resetZoomLevel() {
-		const transform = this.doc.documentElement.style.getPropertyValue("-sf-transform");
-		const transformPriority = this.doc.documentElement.style.getPropertyPriority("-sf-transform");
-		const transformOrigin = this.doc.documentElement.style.getPropertyValue("-sf-transform-origin");
-		const transformOriginPriority = this.doc.documentElement.style.getPropertyPriority("-sf-transform-origin");
-		const minHeight = this.doc.documentElement.style.getPropertyValue("-sf-min-height");
-		const minHeightPriority = this.doc.documentElement.style.getPropertyPriority("-sf-min-height");
-		this.doc.documentElement.style.setProperty("transform", transform, transformPriority);
-		this.doc.documentElement.style.setProperty("transform-origin", transformOrigin, transformOriginPriority);
-		this.doc.documentElement.style.setProperty("min-height", minHeight, minHeightPriority);
-		this.doc.documentElement.style.removeProperty("-sf-transform");
-		this.doc.documentElement.style.removeProperty("-sf-transform-origin");
-		this.doc.documentElement.style.removeProperty("-sf-min-height");
-	}
-
-	async insertMAFFMetaData() {
-		const maffMetaData = await this.maffMetaDataPromise;
-		if (maffMetaData && maffMetaData.content) {
-			const NAMESPACE_RDF = "http://www.w3.org/1999/02/22-rdf-syntax-ns#";
-			const maffDoc = util.parseXMLContent(maffMetaData.content);
-			const originalURLElement = maffDoc.querySelector("RDF > Description > originalurl");
-			const archiveTimeElement = maffDoc.querySelector("RDF > Description > archivetime");
-			if (originalURLElement) {
-				this.options.saveUrl = originalURLElement.getAttributeNS(NAMESPACE_RDF, "resource");
-			}
-			if (archiveTimeElement) {
-				const value = archiveTimeElement.getAttributeNS(NAMESPACE_RDF, "resource");
-				if (value) {
-					const date = new Date(value);
-					if (!isNaN(date.getTime())) {
-						this.options.saveDate = new Date(value);
-					}
-				}
-			}
-		}
-	}
-
-	async setDocInfo() {
-		const titleElement = this.doc.querySelector("title");
-		const descriptionElement = this.doc.querySelector("meta[name=description]");
-		const authorElement = this.doc.querySelector("meta[name=author]");
-		const creatorElement = this.doc.querySelector("meta[name=creator]");
-		const publisherElement = this.doc.querySelector("meta[name=publisher]");
-		const headingElement = this.doc.querySelector("h1");
-		this.options.title = titleElement ? titleElement.textContent.trim() : "";
-		this.options.info = {
-			description: descriptionElement && descriptionElement.content ? descriptionElement.content.trim() : "",
-			lang: this.doc.documentElement.lang,
-			author: authorElement && authorElement.content ? authorElement.content.trim() : "",
-			creator: creatorElement && creatorElement.content ? creatorElement.content.trim() : "",
-			publisher: publisherElement && publisherElement.content ? publisherElement.content.trim() : "",
-			heading: headingElement && headingElement.textContent ? headingElement.textContent.trim() : ""
-		};
-		this.options.infobarContent = await ProcessorHelper.evalTemplate(this.options.infobarTemplate, this.options, null, true);
-	}
-}
-
-// ---------------
-// ProcessorHelper
-// ---------------
-const DATA_URI_PREFIX = "data:";
-const ABOUT_BLANK_URI = "about:blank";
-const REGEXP_URL_HASH = /(#.+?)$/;
-const SINGLE_FILE_VARIABLE_NAME_PREFIX = "--sf-img-";
-const SINGLE_FILE_VARIABLE_MAX_SIZE = 512 * 1024;
-
-class ProcessorHelper {
-	static async evalTemplate(template = "", options, content, dontReplaceSlash) {
-		const url = util.parseURL(options.saveUrl);
-		template = await evalTemplateVariable(template, "page-title", () => options.title || "No title", dontReplaceSlash, options.filenameReplacementCharacter);
-		template = await evalTemplateVariable(template, "page-heading", () => options.info.heading || "No heading", dontReplaceSlash, options.filenameReplacementCharacter);
-		template = await evalTemplateVariable(template, "page-language", () => options.info.lang || "No language", dontReplaceSlash, options.filenameReplacementCharacter);
-		template = await evalTemplateVariable(template, "page-description", () => options.info.description || "No description", dontReplaceSlash, options.filenameReplacementCharacter);
-		template = await evalTemplateVariable(template, "page-author", () => options.info.author || "No author", dontReplaceSlash, options.filenameReplacementCharacter);
-		template = await evalTemplateVariable(template, "page-creator", () => options.info.creator || "No creator", dontReplaceSlash, options.filenameReplacementCharacter);
-		template = await evalTemplateVariable(template, "page-publisher", () => options.info.publisher || "No publisher", dontReplaceSlash, options.filenameReplacementCharacter);
-		await evalDate(options.saveDate);
-		await evalDate(options.visitDate, "visit-");
-		template = await evalTemplateVariable(template, "url-hash", () => url.hash.substring(1) || "No hash", dontReplaceSlash, options.filenameReplacementCharacter);
-		template = await evalTemplateVariable(template, "url-host", () => url.host.replace(/\/$/, "") || "No host", dontReplaceSlash, options.filenameReplacementCharacter);
-		template = await evalTemplateVariable(template, "url-hostname", () => url.hostname.replace(/\/$/, "") || "No hostname", dontReplaceSlash, options.filenameReplacementCharacter);
-		const urlHref = decode(url.href);
-		template = await evalTemplateVariable(template, "url-href", () => urlHref || "No href", dontReplaceSlash === undefined ? true : dontReplaceSlash, options.filenameReplacementCharacter);
-		template = await evalTemplateVariable(template, "url-href-digest-sha-1", urlHref ? async () => util.digest("SHA-1", urlHref) : "No href", dontReplaceSlash, options.filenameReplacementCharacter);
-		template = await evalTemplateVariable(template, "url-href-flat", () => decode(url.href) || "No href", false, options.filenameReplacementCharacter);
-		template = await evalTemplateVariable(template, "url-referrer", () => decode(options.referrer) || "No referrer", dontReplaceSlash === undefined ? true : dontReplaceSlash, options.filenameReplacementCharacter);
-		template = await evalTemplateVariable(template, "url-referrer-flat", () => decode(options.referrer) || "No referrer", false, options.filenameReplacementCharacter);
-		template = await evalTemplateVariable(template, "url-password", () => url.password || "No password", dontReplaceSlash, options.filenameReplacementCharacter);
-		template = await evalTemplateVariable(template, "url-pathname", () => decode(url.pathname).replace(/^\//, "").replace(/\/$/, "") || "No pathname", dontReplaceSlash === undefined ? true : dontReplaceSlash, options.filenameReplacementCharacter);
-		template = await evalTemplateVariable(template, "url-pathname-flat", () => decode(url.pathname) || "No pathname", false, options.filenameReplacementCharacter);
-		template = await evalTemplateVariable(template, "url-port", () => url.port || "No port", dontReplaceSlash, options.filenameReplacementCharacter);
-		template = await evalTemplateVariable(template, "url-protocol", () => url.protocol || "No protocol", dontReplaceSlash, options.filenameReplacementCharacter);
-		template = await evalTemplateVariable(template, "url-search", () => url.search.substring(1) || "No search", dontReplaceSlash, options.filenameReplacementCharacter);
-		const params = util.getSearchParams(url.search);
-		for (const [name, value] of params) {
-			template = await evalTemplateVariable(template, "url-search-" + name, () => value || "", dontReplaceSlash, options.filenameReplacementCharacter);
-		}
-		template = template.replace(/{\s*url-search-[^}\s]*\s*}/gi, "");
-		template = await evalTemplateVariable(template, "url-username", () => url.username || "No username", dontReplaceSlash, options.filenameReplacementCharacter);
-		template = await evalTemplateVariable(template, "tab-id", () => String(options.tabId || "No tab id"), dontReplaceSlash, options.filenameReplacementCharacter);
-		template = await evalTemplateVariable(template, "tab-index", () => String(options.tabIndex || "No tab index"), dontReplaceSlash, options.filenameReplacementCharacter);
-		template = await evalTemplateVariable(template, "url-last-segment", () => decode(getLastSegment(url, options.filenameReplacementCharacter)) || "No last segment", dontReplaceSlash, options.filenameReplacementCharacter);
-		if (content) {
-			template = await evalTemplateVariable(template, "digest-sha-256", async () => util.digest("SHA-256", content), dontReplaceSlash, options.filenameReplacementCharacter);
-			template = await evalTemplateVariable(template, "digest-sha-384", async () => util.digest("SHA-384", content), dontReplaceSlash, options.filenameReplacementCharacter);
-			template = await evalTemplateVariable(template, "digest-sha-512", async () => util.digest("SHA-512", content), dontReplaceSlash, options.filenameReplacementCharacter);
-		}
-		const bookmarkFolder = (options.bookmarkFolders && options.bookmarkFolders.join("/")) || "";
-		template = await evalTemplateVariable(template, "bookmark-pathname", () => bookmarkFolder, dontReplaceSlash === undefined ? true : dontReplaceSlash, options.filenameReplacementCharacter);
-		template = await evalTemplateVariable(template, "bookmark-pathname-flat", () => bookmarkFolder, false, options.filenameReplacementCharacter);
-		template = await evalTemplateVariable(template, "profile-name", () => options.profileName, dontReplaceSlash, options.filenameReplacementCharacter);
-		return template.trim();
-
-		function decode(value) {
-			try {
-				return decodeURI(value);
-			} catch (error) {
-				return value;
-			}
-		}
-
-		async function evalDate(date, prefix = "") {
-			if (date) {
-				template = await evalTemplateVariable(template, prefix + "datetime-iso", () => date.toISOString(), dontReplaceSlash, options.filenameReplacementCharacter);
-				template = await evalTemplateVariable(template, prefix + "date-iso", () => date.toISOString().split("T")[0], dontReplaceSlash, options.filenameReplacementCharacter);
-				template = await evalTemplateVariable(template, prefix + "time-iso", () => date.toISOString().split("T")[1].split("Z")[0], dontReplaceSlash, options.filenameReplacementCharacter);
-				template = await evalTemplateVariable(template, prefix + "date-locale", () => date.toLocaleDateString(), dontReplaceSlash, options.filenameReplacementCharacter);
-				template = await evalTemplateVariable(template, prefix + "time-locale", () => date.toLocaleTimeString(), dontReplaceSlash, options.filenameReplacementCharacter);
-				template = await evalTemplateVariable(template, prefix + "day-locale", () => String(date.getDate()).padStart(2, "0"), dontReplaceSlash, options.filenameReplacementCharacter);
-				template = await evalTemplateVariable(template, prefix + "month-locale", () => String(date.getMonth() + 1).padStart(2, "0"), dontReplaceSlash, options.filenameReplacementCharacter);
-				template = await evalTemplateVariable(template, prefix + "year-locale", () => String(date.getFullYear()), dontReplaceSlash, options.filenameReplacementCharacter);
-				template = await evalTemplateVariable(template, prefix + "datetime-locale", () => date.toLocaleString(), dontReplaceSlash, options.filenameReplacementCharacter);
-				template = await evalTemplateVariable(template, prefix + "datetime-utc", () => date.toUTCString(), dontReplaceSlash, options.filenameReplacementCharacter);
-				template = await evalTemplateVariable(template, prefix + "day-utc", () => String(date.getUTCDate()).padStart(2, "0"), dontReplaceSlash, options.filenameReplacementCharacter);
-				template = await evalTemplateVariable(template, prefix + "month-utc", () => String(date.getUTCMonth() + 1).padStart(2, "0"), dontReplaceSlash, options.filenameReplacementCharacter);
-				template = await evalTemplateVariable(template, prefix + "year-utc", () => String(date.getUTCFullYear()), dontReplaceSlash, options.filenameReplacementCharacter);
-				template = await evalTemplateVariable(template, prefix + "hours-locale", () => String(date.getHours()).padStart(2, "0"), dontReplaceSlash, options.filenameReplacementCharacter);
-				template = await evalTemplateVariable(template, prefix + "minutes-locale", () => String(date.getMinutes()).padStart(2, "0"), dontReplaceSlash, options.filenameReplacementCharacter);
-				template = await evalTemplateVariable(template, prefix + "seconds-locale", () => String(date.getSeconds()).padStart(2, "0"), dontReplaceSlash, options.filenameReplacementCharacter);
-				template = await evalTemplateVariable(template, prefix + "hours-utc", () => String(date.getUTCHours()).padStart(2, "0"), dontReplaceSlash, options.filenameReplacementCharacter);
-				template = await evalTemplateVariable(template, prefix + "minutes-utc", () => String(date.getUTCMinutes()).padStart(2, "0"), dontReplaceSlash, options.filenameReplacementCharacter);
-				template = await evalTemplateVariable(template, prefix + "seconds-utc", () => String(date.getUTCSeconds()).padStart(2, "0"), dontReplaceSlash, options.filenameReplacementCharacter);
-				template = await evalTemplateVariable(template, prefix + "time-ms", () => String(date.getTime()), dontReplaceSlash, options.filenameReplacementCharacter);
-			}
-		}
-	}
-
-	static setBackgroundImage(element, url, style) {
-		element.style.setProperty("background-blend-mode", "normal", "important");
-		element.style.setProperty("background-clip", "content-box", "important");
-		element.style.setProperty("background-position", style && style["background-position"] ? style["background-position"] : "center", "important");
-		element.style.setProperty("background-color", style && style["background-color"] ? style["background-color"] : "transparent", "important");
-		element.style.setProperty("background-image", url, "important");
-		element.style.setProperty("background-size", style && style["background-size"] ? style["background-size"] : "100% 100%", "important");
-		element.style.setProperty("background-origin", "content-box", "important");
-		element.style.setProperty("background-repeat", "no-repeat", "important");
-	}
-
-	static processShortcutIcons(doc) {
-		let shortcutIcon = findShortcutIcon(Array.from(doc.querySelectorAll("link[href][rel=\"icon\"], link[href][rel=\"shortcut icon\"]")));
-		if (!shortcutIcon) {
-			shortcutIcon = findShortcutIcon(Array.from(doc.querySelectorAll("link[href][rel*=\"icon\"]")));
-			if (shortcutIcon) {
-				shortcutIcon.rel = "icon";
-			}
-		}
-		if (shortcutIcon) {
-			doc.querySelectorAll("link[href][rel*=\"icon\"]").forEach(linkElement => {
-				if (linkElement != shortcutIcon) {
-					linkElement.remove();
-				}
-			});
-		}
-	}
-
-	static removeSingleLineCssComments(stylesheet) {
-		const removedRules = [];
-		for (let cssRule = stylesheet.children.head; cssRule; cssRule = cssRule.next) {
-			const ruleData = cssRule.data;
-			if (ruleData.type == "Raw" && ruleData.value && ruleData.value.trim().startsWith("//")) {
-				removedRules.push(cssRule);
-			}
-		}
-		removedRules.forEach(cssRule => stylesheet.children.remove(cssRule));
-	}
-
-	static async resolveImportURLs(stylesheetContent, baseURI, options, workStylesheet, importedStyleSheets = new Set()) {
-		stylesheetContent = ProcessorHelper.resolveStylesheetURLs(stylesheetContent, baseURI, workStylesheet, options.saveOriginalURLs);
-		const imports = getImportFunctions(stylesheetContent);
-		await Promise.all(imports.map(async cssImport => {
-			const match = matchImport(cssImport);
-			if (match) {
-				const regExpCssImport = getRegExp(cssImport);
-				let resourceURL = normalizeURL(match.resourceURL);
-				if (!testIgnoredPath(resourceURL) && testValidPath(resourceURL)) {
-					try {
-						resourceURL = util.resolveURL(match.resourceURL, baseURI);
-					} catch (error) {
-						// ignored
-					}
-					if (testValidURL(resourceURL) && !importedStyleSheets.has(resourceURL)) {
-						const content = await getStylesheetContent(resourceURL);
-						resourceURL = content.resourceURL;
-						content.data = getUpdatedResourceContent(resourceURL, content, options);
-						if (content.data && content.data.match(/^<!doctype /i)) {
-							content.data = "";
-						}
-						let importedStylesheetContent = removeCssComments(content.data);
-						if (options.compressCSS) {
-							importedStylesheetContent = util.compressCSS(importedStylesheetContent);
-						}
-						importedStylesheetContent = wrapMediaQuery(importedStylesheetContent, match.media);
-						if (stylesheetContent.includes(cssImport)) {
-							const ancestorStyleSheets = new Set(importedStyleSheets);
-							ancestorStyleSheets.add(resourceURL);
-							importedStylesheetContent = await ProcessorHelper.resolveImportURLs(importedStylesheetContent, resourceURL, options, workStylesheet, ancestorStyleSheets);
-							workStylesheet.textContent = importedStylesheetContent;
-							if ((workStylesheet.sheet && workStylesheet.sheet.cssRules.length) || (!workStylesheet.sheet && importedStylesheetContent)) {
-								stylesheetContent = stylesheetContent.replace(regExpCssImport, importedStylesheetContent);
-							} else {
-								stylesheetContent = stylesheetContent.replace(regExpCssImport, "");
-							}
-						}
-					} else {
-						stylesheetContent = stylesheetContent.replace(regExpCssImport, "");
-					}
-				} else {
-					stylesheetContent = stylesheetContent.replace(regExpCssImport, "");
-				}
-			}
-		}));
-		return stylesheetContent;
-
-		async function getStylesheetContent(resourceURL) {
-			const content = await util.getContent(resourceURL, {
-				maxResourceSize: options.maxResourceSize,
-				maxResourceSizeEnabled: options.maxResourceSizeEnabled,
-				validateTextContentType: true,
-				frameId: options.frameId,
-				charset: options.charset,
-				resourceReferrer: options.resourceReferrer,
-				baseURI: options.baseURI,
-				blockMixedContent: options.blockMixedContent,
-				expectedType: "stylesheet",
-				acceptHeaders: options.acceptHeaders,
-				networkTimeout: options.networkTimeout
-			});
-			if (!(matchCharsetEquals(content.data, content.charset) || matchCharsetEquals(content.data, options.charset))) {
-				options = Object.assign({}, options, { charset: getCharset(content.data) });
-				return util.getContent(resourceURL, {
-					maxResourceSize: options.maxResourceSize,
-					maxResourceSizeEnabled: options.maxResourceSizeEnabled,
-					validateTextContentType: true,
-					frameId: options.frameId,
-					charset: options.charset,
-					resourceReferrer: options.resourceReferrer,
-					baseURI: options.baseURI,
-					blockMixedContent: options.blockMixedContent,
-					expectedType: "stylesheet",
-					acceptHeaders: options.acceptHeaders,
-					networkTimeout: options.networkTimeout
-				});
-			} else {
-				return content;
-			}
-		}
-	}
-
-	static resolveStylesheetURLs(stylesheetContent, baseURI, workStylesheet, saveOriginalURLs) {
-		const urlFunctions = getUrlFunctions(stylesheetContent, true);
-		if (saveOriginalURLs) {
-			stylesheetContent = addOriginalURLs(stylesheetContent);
-		}
-		urlFunctions.map(urlFunction => {
-			const originalResourceURL = matchURL(urlFunction);
-			let resourceURL = normalizeURL(originalResourceURL);
-			workStylesheet.textContent = "tmp { content:\"" + resourceURL + "\"}";
-			if (workStylesheet.sheet && workStylesheet.sheet.cssRules) {
-				resourceURL = util.removeQuotes(workStylesheet.sheet.cssRules[0].style.getPropertyValue("content"));
-			}
-			if (!testIgnoredPath(resourceURL)) {
-				if (!resourceURL || testValidPath(resourceURL)) {
-					let resolvedURL;
-					if (!originalResourceURL.startsWith("#")) {
-						try {
-							resolvedURL = util.resolveURL(resourceURL, baseURI);
-						} catch (error) {
-							// ignored
-						}
-					}
-					if (testValidURL(resolvedURL) && originalResourceURL != resolvedURL && stylesheetContent.includes(urlFunction)) {
-						try {
-							stylesheetContent = stylesheetContent.replace(getRegExp(urlFunction), originalResourceURL ? urlFunction.replace(originalResourceURL, resolvedURL) : "url(" + resolvedURL + ")");
-						} catch (error) {
-							// ignored
-						}
-					}
-				} else {
-					let newUrlFunction;
-					if (originalResourceURL) {
-						newUrlFunction = urlFunction.replace(originalResourceURL, util.EMPTY_RESOURCE);
-					} else {
-						newUrlFunction = "url(" + util.EMPTY_RESOURCE + ")";
-					}
-					stylesheetContent = stylesheetContent.replace(getRegExp(urlFunction), newUrlFunction);
-				}
-			}
-		});
-		return stylesheetContent;
-	}
-
-	static async resolveLinkStylesheetURLs(resourceURL, baseURI, options, workStylesheet) {
-		resourceURL = normalizeURL(resourceURL);
-		if (resourceURL && resourceURL != baseURI && resourceURL != ABOUT_BLANK_URI) {
-			const content = await util.getContent(resourceURL, {
-				maxResourceSize: options.maxResourceSize,
-				maxResourceSizeEnabled: options.maxResourceSizeEnabled,
-				charset: options.charset,
-				frameId: options.frameId,
-				resourceReferrer: options.resourceReferrer,
-				validateTextContentType: true,
-				baseURI: baseURI,
-				blockMixedContent: options.blockMixedContent,
-				expectedType: "stylesheet",
-				acceptHeaders: options.acceptHeaders,
-				networkTimeout: options.networkTimeout
-			});
-			if (!(matchCharsetEquals(content.data, content.charset) || matchCharsetEquals(content.data, options.charset))) {
-				options = Object.assign({}, options, { charset: getCharset(content.data) });
-				return ProcessorHelper.resolveLinkStylesheetURLs(resourceURL, baseURI, options, workStylesheet);
-			}
-			resourceURL = content.resourceURL;
-			content.data = getUpdatedResourceContent(content.resourceURL, content, options);
-			if (content.data && content.data.match(/^<!doctype /i)) {
-				content.data = "";
-			}
-			let stylesheetContent = removeCssComments(content.data);
-			if (options.compressCSS) {
-				stylesheetContent = util.compressCSS(stylesheetContent);
-			}
-			stylesheetContent = await ProcessorHelper.resolveImportURLs(stylesheetContent, resourceURL, options, workStylesheet);
-			return stylesheetContent;
-		}
-	}
-
-	static async processStylesheet(cssRules, baseURI, options, cssVariables, batchRequest) {
-		const promises = [];
-		const removedRules = [];
-		for (let cssRule = cssRules.head; cssRule; cssRule = cssRule.next) {
-			const ruleData = cssRule.data;
-			if (ruleData.type == "Atrule" && ruleData.name == "charset") {
-				removedRules.push(cssRule);
-			} else if (ruleData.block && ruleData.block.children) {
-				if (ruleData.type == "Rule") {
-					promises.push(this.processStyle(ruleData.block.children.toArray(), baseURI, options, cssVariables, batchRequest));
-				} else if (ruleData.type == "Atrule" && (ruleData.name == "media" || ruleData.name == "supports")) {
-					promises.push(this.processStylesheet(ruleData.block.children, baseURI, options, cssVariables, batchRequest));
-				} else if (ruleData.type == "Atrule" && ruleData.name == "font-face") {
-					promises.push(processFontFaceRule(ruleData));
-				}
-			}
-		}
-		removedRules.forEach(cssRule => cssRules.remove(cssRule));
-		await Promise.all(promises);
-
-		async function processFontFaceRule(ruleData) {
-			await Promise.all(ruleData.block.children.toArray().map(async declaration => {
-				if (declaration.type == "Declaration" && declaration.value.children) {
-					const urlFunctions = getUrlFunctions(getCSSValue(declaration.value), true);
-					await Promise.all(urlFunctions.map(async urlFunction => {
-						const originalResourceURL = matchURL(urlFunction);
-						if (!options.blockFonts) {
-							const resourceURL = normalizeURL(originalResourceURL);
-							if (!testIgnoredPath(resourceURL) && testValidURL(resourceURL)) {
-								let { content } = await batchRequest.addURL(resourceURL,
-									{ asBinary: true, expectedType: "font", baseURI, blockMixedContent: options.blockMixedContent });
-								let resourceURLs = options.fontDeclarations.get(declaration);
-								if (!resourceURLs) {
-									resourceURLs = [];
-									options.fontDeclarations.set(declaration, resourceURLs);
-								}
-								resourceURLs.push(resourceURL);
-								replaceURLs(declaration, originalResourceURL, content);
-							}
-						} else {
-							replaceURLs(declaration, originalResourceURL, util.EMPTY_RESOURCE);
-						}
-					}));
-				}
-			}));
-
-			function replaceURLs(declaration, oldURL, newURL) {
-				declaration.value.children.forEach(token => {
-					if (token.type == "Url" && util.removeQuotes(getCSSValue(token.value)) == oldURL) {
-						token.value = newURL;
-					}
-				});
-			}
-		}
-	}
-
-	static async processStyle(declarations, baseURI, options, cssVariables, batchRequest) {
-		await Promise.all(declarations.map(async declaration => {
-			if (declaration.value && !declaration.value.children && declaration.value.type == "Raw") {
-				try {
-					declaration.value = cssTree.parse(declaration.value.value, { context: "value" });
-				} catch (error) {
-					// ignored
-				}
-			}
-			if (declaration.type == "Declaration" && declaration.value.children) {
-				const urlFunctions = getUrlFunctions(getCSSValue(declaration.value));
-				await Promise.all(urlFunctions.map(async urlFunction => {
-					const originalResourceURL = matchURL(urlFunction);
-					if (!options.blockImages) {
-						const resourceURL = normalizeURL(originalResourceURL);
-						if (!testIgnoredPath(resourceURL) && testValidURL(resourceURL)) {
-							let { content, indexResource, duplicate } = await batchRequest.addURL(resourceURL,
-								{ asBinary: true, expectedType: "image", groupDuplicates: options.groupDuplicateImages });
-							let variableDefined;
-							const tokens = [];
-							findURLToken(originalResourceURL, declaration.value.children, (token, parent, rootFunction) => {
-								if (!originalResourceURL.startsWith("#")) {
-									if (duplicate && options.groupDuplicateImages && rootFunction && util.getContentSize(content) < SINGLE_FILE_VARIABLE_MAX_SIZE) {
-										const value = cssTree.parse("var(" + SINGLE_FILE_VARIABLE_NAME_PREFIX + indexResource + ")", { context: "value" }).children.head;
-										tokens.push({ parent, token, value });
-										variableDefined = true;
-									} else {
-										token.data.value = content;
-									}
-								}
-							});
-							if (variableDefined) {
-								cssVariables.set(indexResource, { content, url: originalResourceURL });
-								tokens.forEach(({ parent, token, value }) => parent.replace(token, value));
-							}
-						}
-					} else {
-						findURLToken(originalResourceURL, declaration.value.children, token => token.data.value = util.EMPTY_RESOURCE);
-					}
-				}));
-			}
-		}));
-
-		function findURLToken(url, children, callback, depth = 0) {
-			for (let token = children.head; token; token = token.next) {
-				if (token.data.children) {
-					findURLToken(url, token.data.children, callback, depth + 1);
-				}
-				if (token.data.type == "Url" && util.removeQuotes(getCSSValue(token.data.value)) == url) {
-					callback(token, children, depth == 0);
-				}
-			}
-		}
-	}
-
-	static async processAttribute(resourceElements, attributeName, baseURI, options, expectedType, cssVariables, styles, batchRequest, processDuplicates, removeElementIfMissing) {
-		await Promise.all(Array.from(resourceElements).map(async resourceElement => {
-			let resourceURL = resourceElement.getAttribute(attributeName);
-			if (resourceURL != null) {
-				resourceURL = normalizeURL(resourceURL);
-				let originURL = resourceElement.dataset.singleFileOriginURL;
-				if (options.saveOriginalURLs && !isDataURL(resourceURL)) {
-					resourceElement.setAttribute("data-sf-original-" + attributeName, resourceURL);
-				}
-				delete resourceElement.dataset.singleFileOriginURL;
-				if (!options["block" + expectedType.charAt(0).toUpperCase() + expectedType.substring(1) + "s"]) {
-					if (!testIgnoredPath(resourceURL)) {
-						setAttributeEmpty(resourceElement, attributeName, expectedType);
-						if (testValidPath(resourceURL)) {
-							try {
-								resourceURL = util.resolveURL(resourceURL, baseURI);
-							} catch (error) {
-								// ignored
-							}
-							if (testValidURL(resourceURL)) {
-								let { content, indexResource, duplicate } = await batchRequest.addURL(resourceURL,
-									{ asBinary: true, expectedType, groupDuplicates: options.groupDuplicateImages && resourceElement.tagName == "IMG" && attributeName == "src" });
-								if (originURL) {
-									if (content == util.EMPTY_RESOURCE) {
-										try {
-											originURL = util.resolveURL(originURL, baseURI);
-										} catch (error) {
-											// ignored
-										}
-										try {
-											resourceURL = originURL;
-											content = (await util.getContent(resourceURL, {
-												asBinary: true,
-												expectedType,
-												maxResourceSize: options.maxResourceSize,
-												maxResourceSizeEnabled: options.maxResourceSizeEnabled,
-												frameId: options.windowId,
-												resourceReferrer: options.resourceReferrer,
-												acceptHeaders: options.acceptHeaders,
-												networkTimeout: options.networkTimeout
-											})).data;
-										} catch (error) {
-											// ignored
-										}
-									}
-								}
-								if (removeElementIfMissing && content == util.EMPTY_RESOURCE) {
-									resourceElement.remove();
-								} else if (content !== util.EMPTY_RESOURCE) {
-									const forbiddenPrefixFound = PREFIXES_FORBIDDEN_DATA_URI.filter(prefixDataURI => content.startsWith(prefixDataURI)).length;
-									if (!forbiddenPrefixFound) {
-										const isSVG = content.startsWith(PREFIX_DATA_URI_IMAGE_SVG);
-										if (expectedType == "image" && processDuplicates && duplicate && !isSVG && util.getContentSize(content) < SINGLE_FILE_VARIABLE_MAX_SIZE) {
-											if (ProcessorHelper.replaceImageSource(resourceElement, SINGLE_FILE_VARIABLE_NAME_PREFIX + indexResource, options)) {
-												cssVariables.set(indexResource, { content, url: originURL });
-												const declarationList = cssTree.parse(resourceElement.getAttribute("style"), { context: "declarationList" });
-												styles.set(resourceElement, declarationList);
-											} else {
-												resourceElement.setAttribute(attributeName, content);
-											}
-										} else {
-											resourceElement.setAttribute(attributeName, content);
-										}
-									}
-								}
-							}
-						}
-					}
-				} else {
-					setAttributeEmpty(resourceElement, attributeName, expectedType);
-				}
-			}
-		}));
-
-		function setAttributeEmpty(resourceElement, attributeName, expectedType) {
-			if (expectedType == "video" || expectedType == "audio") {
-				resourceElement.removeAttribute(attributeName);
-			} else {
-				resourceElement.setAttribute(attributeName, util.EMPTY_RESOURCE);
-			}
-		}
-	}
-
-	static async processXLinks(resourceElements, doc, baseURI, options, batchRequest) {
-		let attributeName = "xlink:href";
-		await Promise.all(Array.from(resourceElements).map(async resourceElement => {
-			let originalResourceURL = resourceElement.getAttribute(attributeName);
-			if (originalResourceURL == null) {
-				attributeName = "href";
-				originalResourceURL = resourceElement.getAttribute(attributeName);
-			}
-			if (options.saveOriginalURLs && !isDataURL(originalResourceURL)) {
-				resourceElement.setAttribute("data-sf-original-href", originalResourceURL);
-			}
-			let resourceURL = normalizeURL(originalResourceURL);
-			if (!options.blockImages) {
-				if (testValidPath(resourceURL) && !testIgnoredPath(resourceURL)) {
-					resourceElement.setAttribute(attributeName, util.EMPTY_RESOURCE);
-					try {
-						resourceURL = util.resolveURL(resourceURL, baseURI);
-					} catch (error) {
-						// ignored
-					}
-					if (testValidURL(resourceURL)) {
-						const hashMatch = originalResourceURL.match(REGEXP_URL_HASH);
-						if (originalResourceURL.startsWith(baseURI + "#")) {
-							resourceElement.setAttribute(attributeName, hashMatch[0]);
-						} else {
-							const response = await batchRequest.addURL(resourceURL, { expectedType: "image" });
-							const svgDoc = util.parseSVGContent(response.content);
-							if (hashMatch && hashMatch[0]) {
-								let symbolElement;
-								try {
-									symbolElement = svgDoc.querySelector(hashMatch[0]);
-								} catch (error) {
-									// ignored
-								}
-								if (symbolElement) {
-									resourceElement.setAttribute(attributeName, hashMatch[0]);
-									resourceElement.parentElement.insertBefore(symbolElement, resourceElement.parentElement.firstChild);
-								}
-							} else {
-								const content = await batchRequest.addURL(resourceURL, { expectedType: "image" });
-								resourceElement.setAttribute(attributeName, PREFIX_DATA_URI_IMAGE_SVG + "," + content);
-							}
-						}
-					}
-				} else if (resourceURL == options.url) {
-					resourceElement.setAttribute(attributeName, originalResourceURL.substring(resourceURL.length));
-				}
-			} else {
-				resourceElement.setAttribute(attributeName, util.EMPTY_RESOURCE);
-			}
-		}));
-	}
-
-	static async processSrcset(resourceElements, baseURI, options, batchRequest) {
-		await Promise.all(Array.from(resourceElements).map(async resourceElement => {
-			const originSrcset = resourceElement.getAttribute("srcset");
-			const srcset = util.parseSrcset(originSrcset);
-			if (options.saveOriginalURLs && !isDataURL(originSrcset)) {
-				resourceElement.setAttribute("data-sf-original-srcset", originSrcset);
-			}
-			if (!options.blockImages) {
-				const srcsetValues = await Promise.all(srcset.map(async srcsetValue => {
-					let resourceURL = normalizeURL(srcsetValue.url);
-					if (!testIgnoredPath(resourceURL)) {
-						if (testValidPath(resourceURL)) {
-							try {
-								resourceURL = util.resolveURL(resourceURL, baseURI);
-							} catch (error) {
-								// ignored
-							}
-							if (testValidURL(resourceURL)) {
-								const { content } = await batchRequest.addURL(resourceURL, { asBinary: true, expectedType: "image" });
-								const forbiddenPrefixFound = PREFIXES_FORBIDDEN_DATA_URI.filter(prefixDataURI => content.startsWith(prefixDataURI)).length;
-								if (forbiddenPrefixFound) {
-									return "";
-								}
-								return content + (srcsetValue.w ? " " + srcsetValue.w + "w" : srcsetValue.d ? " " + srcsetValue.d + "x" : "");
-							} else {
-								return "";
-							}
-						} else {
-							return "";
-						}
-					} else {
-						return resourceURL + (srcsetValue.w ? " " + srcsetValue.w + "w" : srcsetValue.d ? " " + srcsetValue.d + "x" : "");
-					}
-				}));
-				resourceElement.setAttribute("srcset", srcsetValues.join(", "));
-			} else {
-				resourceElement.setAttribute("srcset", "");
-			}
-		}));
-	}
-
-	static replaceImageSource(imgElement, variableName, options) {
-		const attributeValue = imgElement.getAttribute(util.IMAGE_ATTRIBUTE_NAME);
-		if (attributeValue) {
-			const imageData = options.images[Number(imgElement.getAttribute(util.IMAGE_ATTRIBUTE_NAME))];
-			if (imageData && imageData.replaceable) {
-				imgElement.setAttribute("src", `${PREFIX_DATA_URI_IMAGE_SVG},<svg xmlns="http://www.w3.org/2000/svg" width="${imageData.size.pxWidth}" height="${imageData.size.pxHeight}"><rect fill-opacity="0"/></svg>`);
-				const backgroundStyle = {};
-				const backgroundSize = (imageData.objectFit == "content" || imageData.objectFit == "cover") && imageData.objectFit;
-				if (backgroundSize) {
-					backgroundStyle["background-size"] = imageData.objectFit;
-				}
-				if (imageData.objectPosition) {
-					backgroundStyle["background-position"] = imageData.objectPosition;
-				}
-				if (imageData.backgroundColor) {
-					backgroundStyle["background-color"] = imageData.backgroundColor;
-				}
-				ProcessorHelper.setBackgroundImage(imgElement, "var(" + variableName + ")", backgroundStyle);
-				imgElement.removeAttribute(util.IMAGE_ATTRIBUTE_NAME);
-				return true;
-			}
-		}
-	}
-}
-
-// ----
-// Util
-// ----
-const BLOB_URI_PREFIX = "blob:";
-const HTTP_URI_PREFIX = /^https?:\/\//;
-const FILE_URI_PREFIX = /^file:\/\//;
-const EMPTY_URL = /^https?:\/\/+\s*$/;
-const NOT_EMPTY_URL = /^(https?:\/\/|file:\/\/|blob:).+/;
-const REGEXP_URL_FN = /(url\s*\(\s*'(.*?)'\s*\))|(url\s*\(\s*"(.*?)"\s*\))|(url\s*\(\s*(.*?)\s*\))/gi;
-const REGEXP_URL_SIMPLE_QUOTES_FN = /^url\s*\(\s*'(.*?)'\s*\)$/i;
-const REGEXP_URL_DOUBLE_QUOTES_FN = /^url\s*\(\s*"(.*?)"\s*\)$/i;
-const REGEXP_URL_NO_QUOTES_FN = /^url\s*\(\s*(.*?)\s*\)$/i;
-const REGEXP_IMPORT_FN = /(@import\s*url\s*\(\s*'(.*?)'\s*\)\s*(.*?)(;|$|}))|(@import\s*url\s*\(\s*"(.*?)"\s*\)\s*(.*?)(;|$|}))|(@import\s*url\s*\(\s*(.*?)\s*\)\s*(.*?)(;|$|}))|(@import\s*'(.*?)'\s*(.*?)(;|$|}))|(@import\s*"(.*?)"\s*(.*?)(;|$|}))|(@import\s*(.*?)\s*(.*?)(;|$|}))/gi;
-const REGEXP_IMPORT_URL_SIMPLE_QUOTES_FN = /@import\s*url\s*\(\s*'(.*?)'\s*\)\s*(.*?)(;|$|})/i;
-const REGEXP_IMPORT_URL_DOUBLE_QUOTES_FN = /@import\s*url\s*\(\s*"(.*?)"\s*\)\s*(.*?)(;|$|})/i;
-const REGEXP_IMPORT_URL_NO_QUOTES_FN = /@import\s*url\s*\(\s*(.*?)\s*\)\s*(.*?)(;|$|})/i;
-const REGEXP_IMPORT_SIMPLE_QUOTES_FN = /@import\s*'(.*?)'\s*(.*?)(;|$|})/i;
-const REGEXP_IMPORT_DOUBLE_QUOTES_FN = /@import\s*"(.*?)"\s*(.*?)(;|$|})/i;
-const REGEXP_IMPORT_NO_QUOTES_FN = /@import\s*(.*?)\s*(.*?)(;|$|})/i;
-const REGEXP_ESCAPE = /([{}()^$&.*?/+|[\\\\]|\]|-)/g;
-
-function getUpdatedResourceContent(resourceURL, content, options) {
-	if (options.rootDocument && options.updatedResources[resourceURL]) {
-		options.updatedResources[resourceURL].retrieved = true;
-		return options.updatedResources[resourceURL].content;
-	} else {
-		return content.data || "";
-	}
-}
-
-function normalizeURL(url) {
-	if (!url || url.startsWith(DATA_URI_PREFIX)) {
-		return url;
-	} else {
-		return url.split("#")[0];
-	}
-}
-
-function getCSSValue(value) {
-	if (typeof value == "string") {
-		return value;
-	} else {
-		let result = "";
-		try {
-			result = cssTree.generate(value);
-		} catch (error) {
-			// ignored
-		}
-		return result;
-	}
-}
-
-function matchCharsetEquals(stylesheetContent, charset = UTF8_CHARSET) {
-	const stylesheetCharset = getCharset(stylesheetContent);
-	if (stylesheetCharset) {
-		return stylesheetCharset == charset.toLowerCase();
-	} else {
-		return true;
-	}
-}
-
-function getCharset(stylesheetContent) {
-	const match = stylesheetContent.match(/^@charset\s+"([^"]*)";/i);
-	if (match && match[1]) {
-		return match[1].toLowerCase().trim();
-	}
-}
-
-function getOnEventAttributeNames(doc) {
-	const element = doc.createElement("div");
-	const attributeNames = [];
-	for (const propertyName in element) {
-		if (propertyName.startsWith("on")) {
-			attributeNames.push(propertyName);
-		}
-	}
-	attributeNames.push("onunload");
-	return attributeNames;
-}
-
-async function evalTemplateVariable(template, variableName, valueGetter, dontReplaceSlash, replacementCharacter) {
-	let maxLength, maxCharLength;
-	if (template) {
-		const regExpVariable = "{\\s*" + variableName.replace(/\W|_/g, "[$&]") + "\\s*}";
-		let replaceRegExp = new RegExp(regExpVariable + "\\[\\d+(ch)?\\]", "g");
-		if (template.match(replaceRegExp)) {
-			const matchedLength = template.match(replaceRegExp)[0];
-			if (matchedLength.match(/\[(\d+)\]$/)) {
-				maxLength = Number(matchedLength.match(/\[(\d+)\]$/)[1]);
-				if (isNaN(maxLength) || maxLength <= 0) {
-					maxLength = null;
-				}
-			} else {
-				maxCharLength = Number(matchedLength.match(/\[(\d+)ch\]$/)[1]);
-				if (isNaN(maxCharLength) || maxCharLength <= 0) {
-					maxCharLength = null;
-				}
-			}
-		} else {
-			replaceRegExp = new RegExp(regExpVariable, "g");
-		}
-		if (template.match(replaceRegExp)) {
-			let value = await valueGetter();
-			if (!dontReplaceSlash) {
-				value = value.replace(/\/+/g, replacementCharacter);
-			}
-			if (maxLength) {
-				value = await util.truncateText(value, maxLength);
-			} else if (maxCharLength) {
-				value = value.substring(0, maxCharLength);
-			}
-			return template.replace(replaceRegExp, value);
-		}
-	}
-	return template;
-}
-
-function getLastSegment(url, replacementCharacter) {
-	let lastSegmentMatch = url.pathname.match(/\/([^/]+)$/), lastSegment = lastSegmentMatch && lastSegmentMatch[0];
-	if (!lastSegment) {
-		lastSegmentMatch = url.href.match(/([^/]+)\/?$/);
-		lastSegment = lastSegmentMatch && lastSegmentMatch[0];
-	}
-	if (!lastSegment) {
-		lastSegmentMatch = lastSegment.match(/(.*)\.[^.]+$/);
-		lastSegment = lastSegmentMatch && lastSegmentMatch[0];
-	}
-	if (!lastSegment) {
-		lastSegment = url.hostname.replace(/\/+/g, replacementCharacter).replace(/\/$/, "");
-	}
-	lastSegmentMatch = lastSegment.match(/(.*)\.[^.]+$/);
-	if (lastSegmentMatch && lastSegmentMatch[1]) {
-		lastSegment = lastSegmentMatch[1];
-	}
-	lastSegment = lastSegment.replace(/\/$/, "").replace(/^\//, "");
-	return lastSegment;
-}
-
-function getRegExp(string) {
-	return new RegExp(string.replace(REGEXP_ESCAPE, "\\$1"), "gi");
-}
-
-function getUrlFunctions(stylesheetContent, unique) {
-	const result = stylesheetContent.match(REGEXP_URL_FN) || [];
-	if (unique) {
-		return [...new Set(result)];
-	} else {
-		return result;
-	}
-}
-
-function getImportFunctions(stylesheetContent) {
-	return stylesheetContent.match(REGEXP_IMPORT_FN) || [];
-}
-
-function findShortcutIcon(shortcutIcons) {
-	shortcutIcons = shortcutIcons.filter(linkElement => linkElement.href != util.EMPTY_RESOURCE);
-	shortcutIcons.sort((linkElement1, linkElement2) => (parseInt(linkElement2.sizes, 10) || 16) - (parseInt(linkElement1.sizes, 10) || 16));
-	return shortcutIcons[0];
-}
-
-function matchURL(stylesheetContent) {
-	const match = stylesheetContent.match(REGEXP_URL_SIMPLE_QUOTES_FN) ||
-		stylesheetContent.match(REGEXP_URL_DOUBLE_QUOTES_FN) ||
-		stylesheetContent.match(REGEXP_URL_NO_QUOTES_FN);
-	return match && match[1];
-}
-
-function addOriginalURLs(stylesheetContent) {
-	return stylesheetContent.replace(REGEXP_URL_FN, function (match, _0, url, _1, url2, _2, url3) {
-		url = url || url2 || url3;
-		if (isDataURL(url)) {
-			return match;
-		} else {
-			return "-sf-url-original(" + JSON.stringify(url) + ") " + match;
-		}
-	});
-}
-
-function isDataURL(url) {
-	return url && (url.startsWith(DATA_URI_PREFIX) || url.startsWith(BLOB_URI_PREFIX));
-}
-
-function replaceOriginalURLs(stylesheetContent) {
-	return stylesheetContent.replace(/-sf-url-original\("(.*?)"\)/g, "/* original URL: $1 */");
-}
-
-function testIgnoredPath(resourceURL) {
-	return resourceURL && (resourceURL.startsWith(DATA_URI_PREFIX) || resourceURL == ABOUT_BLANK_URI);
-}
-
-function testValidPath(resourceURL) {
-	return resourceURL && !resourceURL.match(EMPTY_URL);
-}
-
-function testValidURL(resourceURL) {
-	return testValidPath(resourceURL) && (resourceURL.match(HTTP_URI_PREFIX) || resourceURL.match(FILE_URI_PREFIX) || resourceURL.startsWith(BLOB_URI_PREFIX)) && resourceURL.match(NOT_EMPTY_URL);
-}
-
-function matchImport(stylesheetContent) {
-	const match = stylesheetContent.match(REGEXP_IMPORT_URL_SIMPLE_QUOTES_FN) ||
-		stylesheetContent.match(REGEXP_IMPORT_URL_DOUBLE_QUOTES_FN) ||
-		stylesheetContent.match(REGEXP_IMPORT_URL_NO_QUOTES_FN) ||
-		stylesheetContent.match(REGEXP_IMPORT_SIMPLE_QUOTES_FN) ||
-		stylesheetContent.match(REGEXP_IMPORT_DOUBLE_QUOTES_FN) ||
-		stylesheetContent.match(REGEXP_IMPORT_NO_QUOTES_FN);
-	if (match) {
-		const [, resourceURL, media] = match;
-		return { resourceURL, media };
-	}
-}
-
-function removeCssComments(stylesheetContent) {
-	try {
-		return stylesheetContent.replace(/\/\*(.|[\r\n])*?\*\//g, "");
-	} catch (error) {
-		let start, end;
-		do {
-			start = stylesheetContent.indexOf("/*");
-			end = stylesheetContent.indexOf("*/", start + 2);
-			if (start != -1 && end != -1) {
-				stylesheetContent = stylesheetContent.substring(0, start) + stylesheetContent.substr(end + 2);
-			}
-		} while (start != -1 && end != -1);
-		return stylesheetContent;
-	}
-}
-
-function wrapMediaQuery(stylesheetContent, mediaQuery) {
-	if (mediaQuery) {
-		return "@media " + mediaQuery + "{ " + stylesheetContent + " }";
-	} else {
-		return stylesheetContent;
-	}
-}
-
-function log(...args) {
-	console.log("S-File <core>   ", ...args); // eslint-disable-line no-console
-}
-
-// -----
-// Stats
-// -----
-const STATS_DEFAULT_VALUES = {
-	discarded: {
-		"HTML bytes": 0,
-		"hidden elements": 0,
-		"HTML imports": 0,
-		scripts: 0,
-		objects: 0,
-		"audio sources": 0,
-		"video sources": 0,
-		frames: 0,
-		"CSS rules": 0,
-		canvas: 0,
-		stylesheets: 0,
-		resources: 0,
-		medias: 0
-	},
-	processed: {
-		"HTML bytes": 0,
-		"hidden elements": 0,
-		"HTML imports": 0,
-		scripts: 0,
-		objects: 0,
-		"audio sources": 0,
-		"video sources": 0,
-		frames: 0,
-		"CSS rules": 0,
-		canvas: 0,
-		stylesheets: 0,
-		resources: 0,
-		medias: 0
-	}
-};
-
-class Stats {
-	constructor(options) {
-		this.options = options;
-		if (options.displayStats) {
-			this.data = JSON.parse(JSON.stringify(STATS_DEFAULT_VALUES));
-		}
-	}
-	set(type, subType, value) {
-		if (this.options.displayStats) {
-			this.data[type][subType] = value;
-		}
-	}
-	add(type, subType, value) {
-		if (this.options.displayStats) {
-			this.data[type][subType] += value;
-		}
-	}
-	addAll(pageData) {
-		if (this.options.displayStats) {
-			Object.keys(this.data.discarded).forEach(key => this.add("discarded", key, pageData.stats.discarded[key] || 0));
-			Object.keys(this.data.processed).forEach(key => this.add("processed", key, pageData.stats.processed[key] || 0));
-		}
-	}
-}
-
-export {
-	getClass
-};

+ 1 - 1
src/single-file/single-file-frames.js

@@ -1 +1 @@
-import "./processors/frame-tree/content/content-frame-tree.js";
+export * from  "single-file-core/single-file-frames.js";

+ 0 - 557
src/single-file/single-file-helper.js

@@ -1,557 +0,0 @@
-/*
- * Copyright 2010-2020 Gildas Lormeau
- * contact : gildas.lormeau <at> gmail.com
- * 
- * This file is part of SingleFile.
- *
- *   The code in this file is free software: you can redistribute it and/or 
- *   modify it under the terms of the GNU Affero General Public License 
- *   (GNU AGPL) as published by the Free Software Foundation, either version 3
- *   of the License, or (at your option) any later version.
- * 
- *   The code in this file is distributed in the hope that it will be useful, 
- *   but WITHOUT ANY WARRANTY; without even the implied warranty of 
- *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero 
- *   General Public License for more details.
- *
- *   As additional permission under GNU AGPL version 3 section 7, you may 
- *   distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU 
- *   AGPL normally required by section 4, provided you include this license 
- *   notice and a URL through which recipients can access the Corresponding 
- *   Source.
- */
-
-/* global globalThis, CustomEvent */
-
-import * as cssUnescape from "./vendor/css-unescape.js";
-import * as hooksFrames from "./processors/hooks/content/content-hooks-frames";
-
-const ON_BEFORE_CAPTURE_EVENT_NAME = "single-file-on-before-capture";
-const ON_AFTER_CAPTURE_EVENT_NAME = "single-file-on-after-capture";
-const REMOVED_CONTENT_ATTRIBUTE_NAME = "data-single-file-removed-content";
-const HIDDEN_CONTENT_ATTRIBUTE_NAME = "data-single-file-hidden-content";
-const KEPT_CONTENT_ATTRIBUTE_NAME = "data-single-file-kept-content";
-const HIDDEN_FRAME_ATTRIBUTE_NAME = "data-single-file-hidden-frame";
-const PRESERVED_SPACE_ELEMENT_ATTRIBUTE_NAME = "data-single-file-preserved-space-element";
-const SHADOW_ROOT_ATTRIBUTE_NAME = "data-single-file-shadow-root-element";
-const WIN_ID_ATTRIBUTE_NAME = "data-single-file-win-id";
-const IMAGE_ATTRIBUTE_NAME = "data-single-file-image";
-const POSTER_ATTRIBUTE_NAME = "data-single-file-poster";
-const VIDEO_ATTRIBUTE_NAME = "data-single-file-video";
-const CANVAS_ATTRIBUTE_NAME = "data-single-file-canvas";
-const HTML_IMPORT_ATTRIBUTE_NAME = "data-single-file-import";
-const STYLE_ATTRIBUTE_NAME = "data-single-file-movable-style";
-const INPUT_VALUE_ATTRIBUTE_NAME = "data-single-file-input-value";
-const LAZY_SRC_ATTRIBUTE_NAME = "data-single-file-lazy-loaded-src";
-const STYLESHEET_ATTRIBUTE_NAME = "data-single-file-stylesheet";
-const DISABLED_NOSCRIPT_ATTRIBUTE_NAME = "data-single-file-disabled-noscript";
-const SELECTED_CONTENT_ATTRIBUTE_NAME = "data-single-file-selected-content";
-const ASYNC_SCRIPT_ATTRIBUTE_NAME = "data-single-file-async-script";
-const FLOW_ELEMENTS_SELECTOR = "*:not(base):not(link):not(meta):not(noscript):not(script):not(style):not(template):not(title)";
-const KEPT_TAG_NAMES = ["NOSCRIPT", "DISABLED-NOSCRIPT", "META", "LINK", "STYLE", "TITLE", "TEMPLATE", "SOURCE", "OBJECT", "SCRIPT", "HEAD"];
-const REGEXP_SIMPLE_QUOTES_STRING = /^'(.*?)'$/;
-const REGEXP_DOUBLE_QUOTES_STRING = /^"(.*?)"$/;
-const FONT_WEIGHTS = {
-	regular: "400",
-	normal: "400",
-	bold: "700",
-	bolder: "700",
-	lighter: "100"
-};
-const COMMENT_HEADER = "Page saved with SingleFile";
-const COMMENT_HEADER_LEGACY = "Archive processed by SingleFile";
-const SINGLE_FILE_UI_ELEMENT_CLASS = "single-file-ui-element";
-const EMPTY_RESOURCE = "data:,";
-const addEventListener = (type, listener, options) => globalThis.addEventListener(type, listener, options);
-const dispatchEvent = event => { try { globalThis.dispatchEvent(event); } catch (error) {  /* ignored */ } };
-
-export {
-	initUserScriptHandler,
-	initDoc,
-	preProcessDoc,
-	postProcessDoc,
-	serialize,
-	removeQuotes,
-	flatten,
-	getFontWeight,
-	normalizeFontFamily,
-	getShadowRoot,
-	ON_BEFORE_CAPTURE_EVENT_NAME,
-	ON_AFTER_CAPTURE_EVENT_NAME,
-	WIN_ID_ATTRIBUTE_NAME,
-	PRESERVED_SPACE_ELEMENT_ATTRIBUTE_NAME,
-	REMOVED_CONTENT_ATTRIBUTE_NAME,
-	HIDDEN_CONTENT_ATTRIBUTE_NAME,
-	HIDDEN_FRAME_ATTRIBUTE_NAME,
-	IMAGE_ATTRIBUTE_NAME,
-	POSTER_ATTRIBUTE_NAME,
-	VIDEO_ATTRIBUTE_NAME,
-	CANVAS_ATTRIBUTE_NAME,
-	INPUT_VALUE_ATTRIBUTE_NAME,
-	SHADOW_ROOT_ATTRIBUTE_NAME,
-	HTML_IMPORT_ATTRIBUTE_NAME,
-	STYLE_ATTRIBUTE_NAME,
-	LAZY_SRC_ATTRIBUTE_NAME,
-	STYLESHEET_ATTRIBUTE_NAME,
-	SELECTED_CONTENT_ATTRIBUTE_NAME,
-	ASYNC_SCRIPT_ATTRIBUTE_NAME,
-	COMMENT_HEADER,
-	COMMENT_HEADER_LEGACY,
-	SINGLE_FILE_UI_ELEMENT_CLASS,
-	EMPTY_RESOURCE
-};
-
-function initUserScriptHandler() {
-	addEventListener("single-file-user-script-init", () => globalThis._singleFile_waitForUserScript = async eventPrefixName => {
-		const event = new CustomEvent(eventPrefixName + "-request", { cancelable: true });
-		const promiseResponse = new Promise(resolve => addEventListener(eventPrefixName + "-response", resolve));
-		dispatchEvent(event);
-		if (event.defaultPrevented) {
-			await promiseResponse;
-		}
-	});
-}
-
-function initDoc(doc) {
-	doc.querySelectorAll("meta[http-equiv=refresh]").forEach(element => {
-		element.removeAttribute("http-equiv");
-		element.setAttribute("disabled-http-equiv", "refresh");
-	});
-}
-
-function preProcessDoc(doc, win, options) {
-	doc.querySelectorAll("noscript:not([" + DISABLED_NOSCRIPT_ATTRIBUTE_NAME + "])").forEach(element => {
-		element.setAttribute(DISABLED_NOSCRIPT_ATTRIBUTE_NAME, element.textContent);
-		element.textContent = "";
-	});
-	initDoc(doc);
-	if (doc.head) {
-		doc.head.querySelectorAll(FLOW_ELEMENTS_SELECTOR).forEach(element => element.hidden = true);
-	}
-	doc.querySelectorAll("svg foreignObject").forEach(element => {
-		const flowElements = element.querySelectorAll("html > head > " + FLOW_ELEMENTS_SELECTOR + ", html > body > " + FLOW_ELEMENTS_SELECTOR);
-		if (flowElements.length) {
-			Array.from(element.childNodes).forEach(node => node.remove());
-			flowElements.forEach(flowElement => element.appendChild(flowElement));
-		}
-	});
-	const invalidElements = new Map();
-	let elementsInfo;
-	if (win && doc.documentElement) {
-		doc.querySelectorAll("button button").forEach(element => {
-			const placeHolderElement = doc.createComment("");
-			invalidElements.set(element, placeHolderElement);
-			element.replaceWith(placeHolderElement);
-		});
-		elementsInfo = getElementsInfo(win, doc, doc.documentElement, options);
-		if (options.moveStylesInHead) {
-			doc.querySelectorAll("body style, body ~ style").forEach(element => {
-				const computedStyle = win.getComputedStyle(element);
-				if (computedStyle && testHiddenElement(element, computedStyle)) {
-					element.setAttribute(STYLE_ATTRIBUTE_NAME, "");
-					elementsInfo.markedElements.push(element);
-				}
-			});
-		}
-	} else {
-		elementsInfo = {
-			canvases: [],
-			images: [],
-			posters: [],
-			videos: [],
-			usedFonts: [],
-			shadowRoots: [],
-			imports: [],
-			markedElements: []
-		};
-	}
-	return {
-		canvases: elementsInfo.canvases,
-		fonts: getFontsData(doc),
-		stylesheets: getStylesheetsData(doc),
-		images: elementsInfo.images,
-		posters: elementsInfo.posters,
-		videos: elementsInfo.videos,
-		usedFonts: Array.from(elementsInfo.usedFonts.values()),
-		shadowRoots: elementsInfo.shadowRoots,
-		imports: elementsInfo.imports,
-		referrer: doc.referrer,
-		markedElements: elementsInfo.markedElements,
-		invalidElements
-	};
-}
-
-function getElementsInfo(win, doc, element, options, data = { usedFonts: new Map(), canvases: [], images: [], posters: [], videos: [], shadowRoots: [], imports: [], markedElements: [] }, ascendantHidden) {
-	const elements = Array.from(element.childNodes).filter(node => (node instanceof win.HTMLElement) || (node instanceof win.SVGElement));
-	elements.forEach(element => {
-		let elementHidden, elementKept, computedStyle;
-		if (!options.autoSaveExternalSave && (options.removeHiddenElements || options.removeUnusedFonts || options.compressHTML)) {
-			computedStyle = win.getComputedStyle(element);
-			if (element instanceof win.HTMLElement) {
-				if (options.removeHiddenElements) {
-					elementKept = ((ascendantHidden || element.closest("html > head")) && KEPT_TAG_NAMES.includes(element.tagName)) || element.closest("details");
-					if (!elementKept) {
-						elementHidden = ascendantHidden || testHiddenElement(element, computedStyle);
-						if (elementHidden) {
-							element.setAttribute(HIDDEN_CONTENT_ATTRIBUTE_NAME, "");
-							data.markedElements.push(element);
-						}
-					}
-				}
-			}
-			if (!elementHidden) {
-				if (options.compressHTML && computedStyle) {
-					const whiteSpace = computedStyle.getPropertyValue("white-space");
-					if (whiteSpace && whiteSpace.startsWith("pre")) {
-						element.setAttribute(PRESERVED_SPACE_ELEMENT_ATTRIBUTE_NAME, "");
-						data.markedElements.push(element);
-					}
-				}
-				if (options.removeUnusedFonts) {
-					getUsedFont(computedStyle, options, data.usedFonts);
-					getUsedFont(win.getComputedStyle(element, ":first-letter"), options, data.usedFonts);
-					getUsedFont(win.getComputedStyle(element, ":before"), options, data.usedFonts);
-					getUsedFont(win.getComputedStyle(element, ":after"), options, data.usedFonts);
-				}
-			}
-		}
-		getResourcesInfo(win, doc, element, options, data, elementHidden, computedStyle);
-		const shadowRoot = !(element instanceof win.SVGElement) && getShadowRoot(element);
-		if (shadowRoot && !element.classList.contains(SINGLE_FILE_UI_ELEMENT_CLASS)) {
-			const shadowRootInfo = {};
-			element.setAttribute(SHADOW_ROOT_ATTRIBUTE_NAME, data.shadowRoots.length);
-			data.markedElements.push(element);
-			data.shadowRoots.push(shadowRootInfo);
-			getElementsInfo(win, doc, shadowRoot, options, data, elementHidden);
-			shadowRootInfo.content = shadowRoot.innerHTML;
-			shadowRootInfo.mode = shadowRoot.mode;
-			try {
-				if (shadowRoot.adoptedStyleSheets && shadowRoot.adoptedStyleSheets.length) {
-					shadowRootInfo.adoptedStyleSheets = Array.from(shadowRoot.adoptedStyleSheets).map(stylesheet => Array.from(stylesheet.cssRules).map(cssRule => cssRule.cssText).join("\n"));
-				}
-			} catch (error) {
-				// ignored
-			}
-		}
-		getElementsInfo(win, doc, element, options, data, elementHidden);
-		if (!options.autoSaveExternalSave && options.removeHiddenElements && ascendantHidden) {
-			if (elementKept || element.getAttribute(KEPT_CONTENT_ATTRIBUTE_NAME) == "") {
-				if (element.parentElement) {
-					element.parentElement.setAttribute(KEPT_CONTENT_ATTRIBUTE_NAME, "");
-					data.markedElements.push(element.parentElement);
-				}
-			} else if (elementHidden) {
-				element.setAttribute(REMOVED_CONTENT_ATTRIBUTE_NAME, "");
-				data.markedElements.push(element);
-			}
-		}
-	});
-	return data;
-}
-
-function getResourcesInfo(win, doc, element, options, data, elementHidden, computedStyle) {
-	if (element.tagName == "CANVAS") {
-		try {
-			data.canvases.push({ dataURI: element.toDataURL("image/png", "") });
-			element.setAttribute(CANVAS_ATTRIBUTE_NAME, data.canvases.length - 1);
-			data.markedElements.push(element);
-		} catch (error) {
-			// ignored
-		}
-	}
-	if (element.tagName == "IMG") {
-		const imageData = {
-			currentSrc: elementHidden ?
-				EMPTY_RESOURCE :
-				(options.loadDeferredImages && element.getAttribute(LAZY_SRC_ATTRIBUTE_NAME)) || element.currentSrc
-		};
-		data.images.push(imageData);
-		element.setAttribute(IMAGE_ATTRIBUTE_NAME, data.images.length - 1);
-		data.markedElements.push(element);
-		element.removeAttribute(LAZY_SRC_ATTRIBUTE_NAME);
-		computedStyle = computedStyle || win.getComputedStyle(element);
-		if (computedStyle) {
-			imageData.size = getSize(win, element, computedStyle);
-			const boxShadow = computedStyle.getPropertyValue("box-shadow");
-			const backgroundImage = computedStyle.getPropertyValue("background-image");
-			if ((!boxShadow || boxShadow == "none") &&
-				(!backgroundImage || backgroundImage == "none") &&
-				(imageData.size.pxWidth > 1 || imageData.size.pxHeight > 1)) {
-				imageData.replaceable = true;
-				imageData.backgroundColor = computedStyle.getPropertyValue("background-color");
-				imageData.objectFit = computedStyle.getPropertyValue("object-fit");
-				imageData.boxSizing = computedStyle.getPropertyValue("box-sizing");
-				imageData.objectPosition = computedStyle.getPropertyValue("object-position");
-			}
-		}
-	}
-	if (element.tagName == "VIDEO") {
-		const src = element.currentSrc;
-		if (src && !src.startsWith("blob:") && !src.startsWith("data:")) {
-			const positionParent = win.getComputedStyle(element.parentNode).getPropertyValue("position");
-			data.videos.push({
-				positionParent,
-				src,
-				size: {
-					pxWidth: element.clientWidth,
-					pxHeight: element.clientHeight
-				},
-				currentTime: element.currentTime
-			});
-			element.setAttribute(VIDEO_ATTRIBUTE_NAME, data.videos.length - 1);
-		}
-		if (!element.poster) {
-			const canvasElement = doc.createElement("canvas");
-			const context = canvasElement.getContext("2d");
-			canvasElement.width = element.clientWidth;
-			canvasElement.height = element.clientHeight;
-			try {
-				context.drawImage(element, 0, 0, canvasElement.width, canvasElement.height);
-				data.posters.push(canvasElement.toDataURL("image/png", ""));
-				element.setAttribute(POSTER_ATTRIBUTE_NAME, data.posters.length - 1);
-				data.markedElements.push(element);
-			} catch (error) {
-				// ignored
-			}
-		}
-	}
-	if (element.tagName == "IFRAME") {
-		if (elementHidden && options.removeHiddenElements) {
-			element.setAttribute(HIDDEN_FRAME_ATTRIBUTE_NAME, "");
-			data.markedElements.push(element);
-		}
-	}
-	if (element.tagName == "LINK") {
-		if (element.import && element.import.documentElement) {
-			data.imports.push({ content: serialize(element.import) });
-			element.setAttribute(HTML_IMPORT_ATTRIBUTE_NAME, data.imports.length - 1);
-			data.markedElements.push(element);
-		}
-	}
-	if (element.tagName == "INPUT") {
-		if (element.type != "password") {
-			element.setAttribute(INPUT_VALUE_ATTRIBUTE_NAME, element.value);
-			data.markedElements.push(element);
-		}
-		if (element.type == "radio" || element.type == "checkbox") {
-			element.setAttribute(INPUT_VALUE_ATTRIBUTE_NAME, element.checked);
-			data.markedElements.push(element);
-		}
-	}
-	if (element.tagName == "TEXTAREA") {
-		element.setAttribute(INPUT_VALUE_ATTRIBUTE_NAME, element.value);
-		data.markedElements.push(element);
-	}
-	if (element.tagName == "SELECT") {
-		element.querySelectorAll("option").forEach(option => {
-			if (option.selected) {
-				option.setAttribute(INPUT_VALUE_ATTRIBUTE_NAME, "");
-				data.markedElements.push(option);
-			}
-		});
-	}
-	if (element.tagName == "SCRIPT") {
-		if (element.async && element.getAttribute("async") != "" && element.getAttribute("async") != "async") {
-			element.setAttribute(ASYNC_SCRIPT_ATTRIBUTE_NAME, "");
-			data.markedElements.push(element);
-		}
-		element.textContent = element.textContent.replace(/<\/script>/gi, "<\\/script>");
-	}
-}
-
-function getUsedFont(computedStyle, options, usedFonts) {
-	if (computedStyle) {
-		const fontStyle = computedStyle.getPropertyValue("font-style") || "normal";
-		computedStyle.getPropertyValue("font-family").split(",").forEach(fontFamilyName => {
-			fontFamilyName = normalizeFontFamily(fontFamilyName);
-			if (!options.loadedFonts || options.loadedFonts.find(font => normalizeFontFamily(font.family) == fontFamilyName && font.style == fontStyle)) {
-				const fontWeight = getFontWeight(computedStyle.getPropertyValue("font-weight"));
-				const fontVariant = computedStyle.getPropertyValue("font-variant") || "normal";
-				const value = [fontFamilyName, fontWeight, fontStyle, fontVariant];
-				usedFonts.set(JSON.stringify(value), [fontFamilyName, fontWeight, fontStyle, fontVariant]);
-			}
-		});
-	}
-}
-
-function getShadowRoot(element) {
-	const chrome = globalThis.chrome;
-	if (element.openOrClosedShadowRoot) {
-		return element.openOrClosedShadowRoot;
-	} else if (chrome && chrome.dom && chrome.dom.openOrClosedShadowRoot) {
-		try {
-			return chrome.dom.openOrClosedShadowRoot(element);
-		} catch (error) {
-			return element.shadowRoot;
-		}
-	} else {
-		return element.shadowRoot;
-	}
-}
-
-function normalizeFontFamily(fontFamilyName = "") {
-	return removeQuotes(cssUnescape.process(fontFamilyName.trim())).toLowerCase();
-}
-
-function testHiddenElement(element, computedStyle) {
-	let hidden = false;
-	if (computedStyle) {
-		const display = computedStyle.getPropertyValue("display");
-		const opacity = computedStyle.getPropertyValue("opacity");
-		const visibility = computedStyle.getPropertyValue("visibility");
-		hidden = display == "none";
-		if (!hidden && (opacity == "0" || visibility == "hidden") && element.getBoundingClientRect) {
-			const boundingRect = element.getBoundingClientRect();
-			hidden = !boundingRect.width && !boundingRect.height;
-		}
-	}
-	return Boolean(hidden);
-}
-
-function postProcessDoc(doc, markedElements, invalidElements) {
-	doc.querySelectorAll("[" + DISABLED_NOSCRIPT_ATTRIBUTE_NAME + "]").forEach(element => {
-		element.textContent = element.getAttribute(DISABLED_NOSCRIPT_ATTRIBUTE_NAME);
-		element.removeAttribute(DISABLED_NOSCRIPT_ATTRIBUTE_NAME);
-	});
-	doc.querySelectorAll("meta[disabled-http-equiv]").forEach(element => {
-		element.setAttribute("http-equiv", element.getAttribute("disabled-http-equiv"));
-		element.removeAttribute("disabled-http-equiv");
-	});
-	if (doc.head) {
-		doc.head.querySelectorAll("*:not(base):not(link):not(meta):not(noscript):not(script):not(style):not(template):not(title)").forEach(element => element.removeAttribute("hidden"));
-	}
-	if (!markedElements) {
-		const singleFileAttributes = [REMOVED_CONTENT_ATTRIBUTE_NAME, HIDDEN_FRAME_ATTRIBUTE_NAME, HIDDEN_CONTENT_ATTRIBUTE_NAME, PRESERVED_SPACE_ELEMENT_ATTRIBUTE_NAME, IMAGE_ATTRIBUTE_NAME, POSTER_ATTRIBUTE_NAME, VIDEO_ATTRIBUTE_NAME, CANVAS_ATTRIBUTE_NAME, INPUT_VALUE_ATTRIBUTE_NAME, SHADOW_ROOT_ATTRIBUTE_NAME, HTML_IMPORT_ATTRIBUTE_NAME, STYLESHEET_ATTRIBUTE_NAME, ASYNC_SCRIPT_ATTRIBUTE_NAME];
-		markedElements = doc.querySelectorAll(singleFileAttributes.map(name => "[" + name + "]").join(","));
-	}
-	markedElements.forEach(element => {
-		element.removeAttribute(REMOVED_CONTENT_ATTRIBUTE_NAME);
-		element.removeAttribute(HIDDEN_CONTENT_ATTRIBUTE_NAME);
-		element.removeAttribute(KEPT_CONTENT_ATTRIBUTE_NAME);
-		element.removeAttribute(HIDDEN_FRAME_ATTRIBUTE_NAME);
-		element.removeAttribute(PRESERVED_SPACE_ELEMENT_ATTRIBUTE_NAME);
-		element.removeAttribute(IMAGE_ATTRIBUTE_NAME);
-		element.removeAttribute(POSTER_ATTRIBUTE_NAME);
-		element.removeAttribute(VIDEO_ATTRIBUTE_NAME);
-		element.removeAttribute(CANVAS_ATTRIBUTE_NAME);
-		element.removeAttribute(INPUT_VALUE_ATTRIBUTE_NAME);
-		element.removeAttribute(SHADOW_ROOT_ATTRIBUTE_NAME);
-		element.removeAttribute(HTML_IMPORT_ATTRIBUTE_NAME);
-		element.removeAttribute(STYLESHEET_ATTRIBUTE_NAME);
-		element.removeAttribute(ASYNC_SCRIPT_ATTRIBUTE_NAME);
-		element.removeAttribute(STYLE_ATTRIBUTE_NAME);
-	});
-	if (invalidElements) {
-		Array.from(invalidElements.entries()).forEach(([element, placeholderElement]) => placeholderElement.replaceWith(element));
-	}
-}
-
-function getStylesheetsData(doc) {
-	if (doc) {
-		const contents = [];
-		doc.querySelectorAll("style").forEach((styleElement, styleIndex) => {
-			try {
-				const tempStyleElement = doc.createElement("style");
-				tempStyleElement.textContent = styleElement.textContent;
-				doc.body.appendChild(tempStyleElement);
-				const stylesheet = tempStyleElement.sheet;
-				tempStyleElement.remove();
-				if (!stylesheet || stylesheet.cssRules.length != styleElement.sheet.cssRules.length) {
-					styleElement.setAttribute(STYLESHEET_ATTRIBUTE_NAME, styleIndex);
-					contents[styleIndex] = Array.from(styleElement.sheet.cssRules).map(cssRule => cssRule.cssText).join("\n");
-				}
-			} catch (error) {
-				// ignored
-			}
-		});
-		return contents;
-	}
-}
-
-function getSize(win, imageElement, computedStyle) {
-	let pxWidth = imageElement.naturalWidth;
-	let pxHeight = imageElement.naturalHeight;
-	if (!pxWidth && !pxHeight) {
-		const noStyleAttribute = imageElement.getAttribute("style") == null;
-		computedStyle = computedStyle || win.getComputedStyle(imageElement);
-		let removeBorderWidth = false;
-		if (computedStyle.getPropertyValue("box-sizing") == "content-box") {
-			const boxSizingValue = imageElement.style.getPropertyValue("box-sizing");
-			const boxSizingPriority = imageElement.style.getPropertyPriority("box-sizing");
-			const clientWidth = imageElement.clientWidth;
-			imageElement.style.setProperty("box-sizing", "border-box", "important");
-			removeBorderWidth = imageElement.clientWidth != clientWidth;
-			if (boxSizingValue) {
-				imageElement.style.setProperty("box-sizing", boxSizingValue, boxSizingPriority);
-			} else {
-				imageElement.style.removeProperty("box-sizing");
-			}
-		}
-		let paddingLeft, paddingRight, paddingTop, paddingBottom, borderLeft, borderRight, borderTop, borderBottom;
-		paddingLeft = getWidth("padding-left", computedStyle);
-		paddingRight = getWidth("padding-right", computedStyle);
-		paddingTop = getWidth("padding-top", computedStyle);
-		paddingBottom = getWidth("padding-bottom", computedStyle);
-		if (removeBorderWidth) {
-			borderLeft = getWidth("border-left-width", computedStyle);
-			borderRight = getWidth("border-right-width", computedStyle);
-			borderTop = getWidth("border-top-width", computedStyle);
-			borderBottom = getWidth("border-bottom-width", computedStyle);
-		} else {
-			borderLeft = borderRight = borderTop = borderBottom = 0;
-		}
-		pxWidth = Math.max(0, imageElement.clientWidth - paddingLeft - paddingRight - borderLeft - borderRight);
-		pxHeight = Math.max(0, imageElement.clientHeight - paddingTop - paddingBottom - borderTop - borderBottom);
-		if (noStyleAttribute) {
-			imageElement.removeAttribute("style");
-		}
-	}
-	return { pxWidth, pxHeight };
-}
-
-function getWidth(styleName, computedStyle) {
-	if (computedStyle.getPropertyValue(styleName).endsWith("px")) {
-		return parseFloat(computedStyle.getPropertyValue(styleName));
-	}
-}
-
-function getFontsData() {
-	return hooksFrames.getFontsData();
-}
-
-function serialize(doc) {
-	const docType = doc.doctype;
-	let docTypeString = "";
-	if (docType) {
-		docTypeString = "<!DOCTYPE " + docType.nodeName;
-		if (docType.publicId) {
-			docTypeString += " PUBLIC \"" + docType.publicId + "\"";
-			if (docType.systemId) {
-				docTypeString += " \"" + docType.systemId + "\"";
-			}
-		} else if (docType.systemId) {
-			docTypeString += " SYSTEM \"" + docType.systemId + "\"";
-		} if (docType.internalSubset) {
-			docTypeString += " [" + docType.internalSubset + "]";
-		}
-		docTypeString += "> ";
-	}
-	return docTypeString + doc.documentElement.outerHTML;
-}
-
-function removeQuotes(string) {
-	if (string.match(REGEXP_SIMPLE_QUOTES_STRING)) {
-		string = string.replace(REGEXP_SIMPLE_QUOTES_STRING, "$1");
-	} else {
-		string = string.replace(REGEXP_DOUBLE_QUOTES_STRING, "$1");
-	}
-	return string.trim();
-}
-
-function getFontWeight(weight) {
-	return FONT_WEIGHTS[weight.toLowerCase().trim()] || weight;
-}
-
-function flatten(array) {
-	return array.flat ? array.flat() : array.reduce((a, b) => a.concat(Array.isArray(b) ? flatten(b) : b), []);
-}

+ 1 - 0
src/single-file/single-file-hooks-frames-web.js

@@ -0,0 +1 @@
+import "single-file-core/processors/hooks/content/content-hooks-frames-web.js";

+ 1 - 0
src/single-file/single-file-hooks-web.js

@@ -0,0 +1 @@
+import "single-file-core/processors/hooks/content/content-hooks-web.js";

+ 0 - 412
src/single-file/single-file-util.js

@@ -1,412 +0,0 @@
-/*
- * Copyright 2010-2020 Gildas Lormeau
- * contact : gildas.lormeau <at> gmail.com
- * 
- * This file is part of SingleFile.
- *
- *   The code in this file is free software: you can redistribute it and/or 
- *   modify it under the terms of the GNU Affero General Public License 
- *   (GNU AGPL) as published by the Free Software Foundation, either version 3
- *   of the License, or (at your option) any later version.
- * 
- *   The code in this file is distributed in the hope that it will be useful, 
- *   but WITHOUT ANY WARRANTY; without even the implied warranty of 
- *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero 
- *   General Public License for more details.
- *
- *   As additional permission under GNU AGPL version 3 section 7, you may 
- *   distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU 
- *   AGPL normally required by section 4, provided you include this license 
- *   notice and a URL through which recipients can access the Corresponding 
- *   Source.
- */
-
-/* global globalThis, URLSearchParams */
-
-import * as vendor from "./vendor/index.js";
-import * as modules from "./modules/index.js";
-import * as helper from "./single-file-helper.js";
-
-const DEBUG = false;
-const ONE_MB = 1024 * 1024;
-const PREFIX_CONTENT_TYPE_TEXT = "text/";
-const DEFAULT_REPLACED_CHARACTERS = ["~", "+", "\\\\", "?", "%", "*", ":", "|", "\"", "<", ">", "\x00-\x1f", "\x7F"];
-const DEFAULT_REPLACEMENT_CHARACTER = "_";
-
-const URL = globalThis.URL;
-const DOMParser = globalThis.DOMParser;
-const Blob = globalThis.Blob;
-const FileReader = globalThis.FileReader;
-const fetch = (url, options) => globalThis.fetch(url, options);
-const crypto = globalThis.crypto;
-const TextDecoder = globalThis.TextDecoder;
-const TextEncoder = globalThis.TextEncoder;
-
-export {
-	getInstance
-};
-
-function getInstance(utilOptions) {
-	utilOptions = utilOptions || {};
-	utilOptions.fetch = utilOptions.fetch || fetch;
-	utilOptions.frameFetch = utilOptions.frameFetch || utilOptions.fetch || fetch;
-	return {
-		getContent,
-		parseURL(resourceURL, baseURI) {
-			if (baseURI === undefined) {
-				return new URL(resourceURL);
-			} else {
-				return new URL(resourceURL, baseURI);
-			}
-		},
-		resolveURL(resourceURL, baseURI) {
-			return this.parseURL(resourceURL, baseURI).href;
-		},
-		getSearchParams(searchParams) {
-			return Array.from(new URLSearchParams(searchParams));
-		},
-		getValidFilename(filename, replacedCharacters = DEFAULT_REPLACED_CHARACTERS, replacementCharacter = DEFAULT_REPLACEMENT_CHARACTER) {
-			replacedCharacters.forEach(replacedCharacter => filename = filename.replace(new RegExp("[" + replacedCharacter + "]+", "g"), replacementCharacter));
-			filename = filename
-				.replace(/\.\.\//g, "")
-				.replace(/^\/+/, "")
-				.replace(/\/+/g, "/")
-				.replace(/\/$/, "")
-				.replace(/\.$/, "")
-				.replace(/\.\//g, "." + replacementCharacter)
-				.replace(/\/\./g, "/" + replacementCharacter);
-			return filename;
-		},
-		parseDocContent(content, baseURI) {
-			const doc = (new DOMParser()).parseFromString(content, "text/html");
-			if (!doc.head) {
-				doc.documentElement.insertBefore(doc.createElement("HEAD"), doc.body);
-			}
-			let baseElement = doc.querySelector("base");
-			if (!baseElement || !baseElement.getAttribute("href")) {
-				if (baseElement) {
-					baseElement.remove();
-				}
-				baseElement = doc.createElement("base");
-				baseElement.setAttribute("href", baseURI);
-				doc.head.insertBefore(baseElement, doc.head.firstChild);
-			}
-			return doc;
-		},
-		parseXMLContent(content) {
-			return (new DOMParser()).parseFromString(content, "text/xml");
-		},
-		parseSVGContent(content) {
-			const doc = (new DOMParser()).parseFromString(content, "image/svg+xml");
-			if (doc.querySelector("parsererror")) {
-				return (new DOMParser()).parseFromString(content, "text/html");
-			} else {
-				return doc;
-			}
-		},
-		async digest(algo, text) {
-			try {
-				const hash = await crypto.subtle.digest(algo, new TextEncoder("utf-8").encode(text));
-				return hex(hash);
-			} catch (error) {
-				return "";
-			}
-		},
-		getContentSize(content) {
-			return new Blob([content]).size;
-		},
-		truncateText(content, maxSize) {
-			const blob = new Blob([content]);
-			const reader = new FileReader();
-			reader.readAsText(blob.slice(0, maxSize));
-			return new Promise((resolve, reject) => {
-				reader.addEventListener("load", () => {
-					if (content.startsWith(reader.result)) {
-						resolve(reader.result);
-					} else {
-						this.truncateText(content, maxSize - 1).then(resolve).catch(reject);
-					}
-				}, false);
-				reader.addEventListener("error", reject, false);
-			});
-		},
-		minifyHTML(doc, options) {
-			return modules.htmlMinifier.process(doc, options);
-		},
-		minifyCSSRules(stylesheets, styles, mediaAllInfo) {
-			return modules.cssRulesMinifier.process(stylesheets, styles, mediaAllInfo);
-		},
-		removeUnusedFonts(doc, stylesheets, styles, options) {
-			return modules.fontsMinifier.process(doc, stylesheets, styles, options);
-		},
-		removeAlternativeFonts(doc, stylesheets, fontDeclarations, fontTests) {
-			return modules.fontsAltMinifier.process(doc, stylesheets, fontDeclarations, fontTests);
-		},
-		getMediaAllInfo(doc, stylesheets, styles) {
-			return modules.matchedRules.getMediaAllInfo(doc, stylesheets, styles);
-		},
-		compressCSS(content, options) {
-			return vendor.cssMinifier.processString(content, options);
-		},
-		minifyMedias(stylesheets) {
-			return modules.mediasAltMinifier.process(stylesheets);
-		},
-		removeAlternativeImages(doc) {
-			return modules.imagesAltMinifier.process(doc);
-		},
-		parseSrcset(srcset) {
-			return vendor.srcsetParser.process(srcset);
-		},
-		preProcessDoc(doc, win, options) {
-			return helper.preProcessDoc(doc, win, options);
-		},
-		postProcessDoc(doc, markedElements, invalidElements) {
-			helper.postProcessDoc(doc, markedElements, invalidElements);
-		},
-		serialize(doc, compressHTML) {
-			return modules.serializer.process(doc, compressHTML);
-		},
-		removeQuotes(string) {
-			return helper.removeQuotes(string);
-		},
-		ON_BEFORE_CAPTURE_EVENT_NAME: helper.ON_BEFORE_CAPTURE_EVENT_NAME,
-		ON_AFTER_CAPTURE_EVENT_NAME: helper.ON_AFTER_CAPTURE_EVENT_NAME,
-		WIN_ID_ATTRIBUTE_NAME: helper.WIN_ID_ATTRIBUTE_NAME,
-		REMOVED_CONTENT_ATTRIBUTE_NAME: helper.REMOVED_CONTENT_ATTRIBUTE_NAME,
-		HIDDEN_CONTENT_ATTRIBUTE_NAME: helper.HIDDEN_CONTENT_ATTRIBUTE_NAME,
-		HIDDEN_FRAME_ATTRIBUTE_NAME: helper.HIDDEN_FRAME_ATTRIBUTE_NAME,
-		IMAGE_ATTRIBUTE_NAME: helper.IMAGE_ATTRIBUTE_NAME,
-		POSTER_ATTRIBUTE_NAME: helper.POSTER_ATTRIBUTE_NAME,
-		VIDEO_ATTRIBUTE_NAME: helper.VIDEO_ATTRIBUTE_NAME,
-		CANVAS_ATTRIBUTE_NAME: helper.CANVAS_ATTRIBUTE_NAME,
-		HTML_IMPORT_ATTRIBUTE_NAME: helper.HTML_IMPORT_ATTRIBUTE_NAME,
-		STYLE_ATTRIBUTE_NAME: helper.STYLE_ATTRIBUTE_NAME,
-		INPUT_VALUE_ATTRIBUTE_NAME: helper.INPUT_VALUE_ATTRIBUTE_NAME,
-		SHADOW_ROOT_ATTRIBUTE_NAME: helper.SHADOW_ROOT_ATTRIBUTE_NAME,
-		PRESERVED_SPACE_ELEMENT_ATTRIBUTE_NAME: helper.PRESERVED_SPACE_ELEMENT_ATTRIBUTE_NAME,
-		STYLESHEET_ATTRIBUTE_NAME: helper.STYLESHEET_ATTRIBUTE_NAME,
-		SELECTED_CONTENT_ATTRIBUTE_NAME: helper.SELECTED_CONTENT_ATTRIBUTE_NAME,
-		COMMENT_HEADER: helper.COMMENT_HEADER,
-		COMMENT_HEADER_LEGACY: helper.COMMENT_HEADER_LEGACY,
-		SINGLE_FILE_UI_ELEMENT_CLASS: helper.SINGLE_FILE_UI_ELEMENT_CLASS,
-		EMPTY_RESOURCE: helper.EMPTY_RESOURCE
-	};
-
-	async function getContent(resourceURL, options) {
-		let response, startTime, networkTimeoutId, networkTimeoutPromise, resolveNetworkTimeoutPromise;
-		const fetchResource = utilOptions.fetch;
-		const fetchFrameResource = utilOptions.frameFetch;
-		if (DEBUG) {
-			startTime = Date.now();
-			log("  // STARTED download url =", resourceURL, "asBinary =", options.asBinary);
-		}
-		if (options.blockMixedContent && /^https:/i.test(options.baseURI) && !/^https:/i.test(resourceURL)) {
-			return getFetchResponse(resourceURL, options);
-		}
-		if (options.networkTimeout) {
-			networkTimeoutPromise = new Promise((resolve, reject) => {
-				resolveNetworkTimeoutPromise = resolve;
-				networkTimeoutId = globalThis.setTimeout(() => reject(new Error("network timeout")), options.networkTimeout);
-			});
-		} else {
-			networkTimeoutPromise = new Promise(resolve => {
-				resolveNetworkTimeoutPromise = resolve;
-			});
-		}
-		try {
-			const accept = options.acceptHeaders ? options.acceptHeaders[options.expectedType] : "*/*";
-			if (options.frameId) {
-				try {
-					response = await Promise.race([
-						fetchFrameResource(resourceURL, { frameId: options.frameId, referrer: options.resourceReferrer, headers: { accept } }),
-						networkTimeoutPromise
-					]);
-				} catch (error) {
-					response = await Promise.race([
-						fetchResource(resourceURL, { headers: { accept } }),
-						networkTimeoutPromise
-					]);
-				}
-			} else {
-				response = await Promise.race([
-					fetchResource(resourceURL, { referrer: options.resourceReferrer, headers: { accept } }),
-					networkTimeoutPromise
-				]);
-			}
-		} catch (error) {
-			return getFetchResponse(resourceURL, options);
-		} finally {
-			resolveNetworkTimeoutPromise();
-			if (options.networkTimeout) {
-				globalThis.clearTimeout(networkTimeoutId);
-			}
-		}
-		let buffer;
-		try {
-			buffer = await response.arrayBuffer();
-		} catch (error) {
-			return { data: options.asBinary ? helper.EMPTY_RESOURCE : "", resourceURL };
-		}
-		resourceURL = response.url || resourceURL;
-		let contentType = "", charset;
-		try {
-			const mimeType = new vendor.MIMEType(response.headers.get("content-type"));
-			contentType = mimeType.type + "/" + mimeType.subtype;
-			charset = mimeType.parameters.get("charset");
-		} catch (error) {
-			// ignored
-		}
-		if (!contentType) {
-			contentType = guessMIMEType(options.expectedType, buffer);
-		}
-		if (!charset && options.charset) {
-			charset = options.charset;
-		}
-		if (options.asBinary) {
-			if (response.status >= 400) {
-				return getFetchResponse(resourceURL, options);
-			}
-			try {
-				if (DEBUG) {
-					log("  // ENDED   download url =", resourceURL, "delay =", Date.now() - startTime);
-				}
-				if (options.maxResourceSizeEnabled && buffer.byteLength > options.maxResourceSize * ONE_MB) {
-					return getFetchResponse(resourceURL, options);
-				} else {
-					return getFetchResponse(resourceURL, options, buffer, null, contentType);
-				}
-			} catch (error) {
-				return getFetchResponse(resourceURL, options);
-			}
-		} else {
-			if (response.status >= 400 || (options.validateTextContentType && contentType && !contentType.startsWith(PREFIX_CONTENT_TYPE_TEXT))) {
-				return getFetchResponse(resourceURL, options);
-			}
-			if (!charset) {
-				charset = "utf-8";
-			}
-			if (DEBUG) {
-				log("  // ENDED   download url =", resourceURL, "delay =", Date.now() - startTime);
-			}
-			if (options.maxResourceSizeEnabled && buffer.byteLength > options.maxResourceSize * ONE_MB) {
-				return getFetchResponse(resourceURL, options, null, charset);
-			} else {
-				try {
-					return getFetchResponse(resourceURL, options, buffer, charset, contentType);
-				} catch (error) {
-					return getFetchResponse(resourceURL, options, null, charset);
-				}
-			}
-		}
-	}
-}
-
-async function getFetchResponse(resourceURL, options, data, charset, contentType) {
-	if (data) {
-		if (options.asBinary) {
-			const reader = new FileReader();
-			reader.readAsDataURL(new Blob([data], { type: contentType + (options.charset ? ";charset=" + options.charset : "") }));
-			data = await new Promise((resolve, reject) => {
-				reader.addEventListener("load", () => resolve(reader.result), false);
-				reader.addEventListener("error", reject, false);
-			});
-		} else {
-			const firstBytes = new Uint8Array(data.slice(0, 4));
-			if (firstBytes[0] == 132 && firstBytes[1] == 49 && firstBytes[2] == 149 && firstBytes[3] == 51) {
-				charset = "gb18030";
-			} else if (firstBytes[0] == 255 && firstBytes[1] == 254) {
-				charset = "utf-16le";
-			} else if (firstBytes[0] == 254 && firstBytes[1] == 255) {
-				charset = "utf-16be";
-			}
-			try {
-				data = new TextDecoder(charset).decode(data);
-			} catch (error) {
-				charset = "utf-8";
-				data = new TextDecoder(charset).decode(data);
-			}
-		}
-	} else {
-		data = options.asBinary ? helper.EMPTY_RESOURCE : "";
-	}
-	return { data, resourceURL, charset };
-}
-
-function guessMIMEType(expectedType, buffer) {
-	if (expectedType == "image") {
-		if (compareBytes([255, 255, 255, 255], [0, 0, 1, 0])) {
-			return "image/x-icon";
-		}
-		if (compareBytes([255, 255, 255, 255], [0, 0, 2, 0])) {
-			return "image/x-icon";
-		}
-		if (compareBytes([255, 255], [78, 77])) {
-			return "image/bmp";
-		}
-		if (compareBytes([255, 255, 255, 255, 255, 255], [71, 73, 70, 56, 57, 97])) {
-			return "image/gif";
-		}
-		if (compareBytes([255, 255, 255, 255, 255, 255], [71, 73, 70, 56, 59, 97])) {
-			return "image/gif";
-		}
-		if (compareBytes([255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255], [82, 73, 70, 70, 0, 0, 0, 0, 87, 69, 66, 80, 86, 80])) {
-			return "image/webp";
-		}
-		if (compareBytes([255, 255, 255, 255, 255, 255, 255, 255], [137, 80, 78, 71, 13, 10, 26, 10])) {
-			return "image/png";
-		}
-		if (compareBytes([255, 255, 255], [255, 216, 255])) {
-			return "image/jpeg";
-		}
-	}
-	if (expectedType == "font") {
-		if (compareBytes([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255],
-			[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 76, 80])) {
-			return "application/vnd.ms-fontobject";
-		}
-		if (compareBytes([255, 255, 255, 255], [0, 1, 0, 0])) {
-			return "font/ttf";
-		}
-		if (compareBytes([255, 255, 255, 255], [79, 84, 84, 79])) {
-			return "font/otf";
-		}
-		if (compareBytes([255, 255, 255, 255], [116, 116, 99, 102])) {
-			return "font/collection";
-		}
-		if (compareBytes([255, 255, 255, 255], [119, 79, 70, 70])) {
-			return "font/woff";
-		}
-		if (compareBytes([255, 255, 255, 255], [119, 79, 70, 50])) {
-			return "font/woff2";
-		}
-	}
-
-	function compareBytes(mask, pattern) {
-		let patternMatch = true, index = 0;
-		if (buffer.byteLength >= pattern.length) {
-			const value = new Uint8Array(buffer, 0, mask.length);
-			for (index = 0; index < mask.length && patternMatch; index++) {
-				patternMatch = patternMatch && ((value[index] & mask[index]) == pattern[index]);
-			}
-			return patternMatch;
-		}
-	}
-}
-
-// https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/digest
-function hex(buffer) {
-	const hexCodes = [];
-	const view = new DataView(buffer);
-	for (let i = 0; i < view.byteLength; i += 4) {
-		const value = view.getUint32(i);
-		const stringValue = value.toString(16);
-		const padding = "00000000";
-		const paddedValue = (padding + stringValue).slice(-padding.length);
-		hexCodes.push(paddedValue);
-	}
-	return hexCodes.join("");
-}
-
-function log(...args) {
-	console.log("S-File <browser>", ...args); // eslint-disable-line no-console
-}

+ 1 - 0
src/single-file/single-file.js

@@ -0,0 +1 @@
+export * from "single-file-core/index.js";

+ 0 - 343
src/single-file/vendor/css-font-property-parser.js

@@ -1,343 +0,0 @@
-/*
- * The MIT License (MIT)
- * 
- * Author: Gildas Lormeau
- * 
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-
-// derived from https://github.com/jedmao/parse-css-font/
-
-/*
- * The MIT License (MIT)
- * 
- * Copyright (c) 2015 Jed Mao
- * 
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
-
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
-
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-
-const REGEXP_SIMPLE_QUOTES_STRING = /^'(.*?)'$/;
-const REGEXP_DOUBLE_QUOTES_STRING = /^"(.*?)"$/;
-
-const globalKeywords = [
-	"inherit",
-	"initial",
-	"unset"
-];
-
-const systemFontKeywords = [
-	"caption",
-	"icon",
-	"menu",
-	"message-box",
-	"small-caption",
-	"status-bar"
-];
-
-const fontWeightKeywords = [
-	"normal",
-	"bold",
-	"bolder",
-	"lighter",
-	"100",
-	"200",
-	"300",
-	"400",
-	"500",
-	"600",
-	"700",
-	"800",
-	"900"
-];
-
-const fontStyleKeywords = [
-	"normal",
-	"italic",
-	"oblique"
-];
-
-const fontStretchKeywords = [
-	"normal",
-	"condensed",
-	"semi-condensed",
-	"extra-condensed",
-	"ultra-condensed",
-	"expanded",
-	"semi-expanded",
-	"extra-expanded",
-	"ultra-expanded"
-];
-
-const cssFontSizeKeywords = [
-	"xx-small",
-	"x-small",
-	"small",
-	"medium",
-	"large",
-	"x-large",
-	"xx-large",
-	"larger",
-	"smaller"
-];
-
-const cssListHelpers = {
-	splitBySpaces,
-	split,
-	splitByCommas
-};
-
-const helpers = {
-	isSize
-};
-
-const errorPrefix = "[parse-css-font] ";
-
-export {
-	parse
-};
-
-function parse(value) {
-	if (typeof value !== "string") {
-		throw new TypeError(errorPrefix + "Expected a string.");
-	}
-	if (value === "") {
-		throw error("Cannot parse an empty string.");
-	}
-	if (systemFontKeywords.indexOf(value) !== -1) {
-		return { system: value };
-	}
-
-	const font = {
-		lineHeight: "normal",
-		stretch: "normal",
-		style: "normal",
-		variant: "normal",
-		weight: "normal",
-	};
-
-	let isLocked = false;
-	const tokens = cssListHelpers.splitBySpaces(value);
-	let token = tokens.shift();
-	for (; token; token = tokens.shift()) {
-
-		if (token === "normal" || globalKeywords.indexOf(token) !== -1) {
-			["style", "variant", "weight", "stretch"].forEach((prop) => {
-				font[prop] = token;
-			});
-			isLocked = true;
-			continue;
-		}
-
-		if (fontWeightKeywords.indexOf(token) !== -1) {
-			if (isLocked) {
-				continue;
-			}
-			font.weight = token;
-			continue;
-		}
-
-		if (fontStyleKeywords.indexOf(token) !== -1) {
-			if (isLocked) {
-				continue;
-			}
-			font.style = token;
-			continue;
-		}
-
-		if (fontStretchKeywords.indexOf(token) !== -1) {
-			if (isLocked) {
-				continue;
-			}
-			font.stretch = token;
-			continue;
-		}
-
-		if (helpers.isSize(token)) {
-			const parts = cssListHelpers.split(token, ["/"]);
-			font.size = parts[0];
-			if (parts[1]) {
-				font.lineHeight = parseLineHeight(parts[1]);
-			} else if (tokens[0] === "/") {
-				tokens.shift();
-				font.lineHeight = parseLineHeight(tokens.shift());
-			}
-			if (!tokens.length) {
-				throw error("Missing required font-family.");
-			}
-			font.family = cssListHelpers.splitByCommas(tokens.join(" ")).map(removeQuotes);
-			return font;
-		}
-
-		if (font.variant !== "normal") {
-			throw error("Unknown or unsupported font token: " + font.variant);
-		}
-
-		if (isLocked) {
-			continue;
-		}
-		font.variant = token;
-	}
-
-	throw error("Missing required font-size.");
-}
-
-function error(message) {
-	return new Error(errorPrefix + message);
-}
-
-function parseLineHeight(value) {
-	const parsed = parseFloat(value);
-	if (parsed.toString() === value) {
-		return parsed;
-	}
-	return value;
-}
-
-/**
- * Splits a CSS declaration value (shorthand) using provided separators
- * as the delimiters.
- */
-function split(
-	/**
-	 * A CSS declaration value (shorthand).
-	 */
-	value,
-	/**
-	 * Any number of separator characters used for splitting.
-	 */
-	separators,
-	{
-		last = false,
-	} = {},
-) {
-	if (typeof value !== "string") {
-		throw new TypeError("expected a string");
-	}
-	if (!Array.isArray(separators)) {
-		throw new TypeError("expected a string array of separators");
-	}
-	if (typeof last !== "boolean") {
-		throw new TypeError("expected a Boolean value for options.last");
-	}
-	const array = [];
-	let current = "";
-	let splitMe = false;
-
-	let func = 0;
-	let quote = false;
-	let escape = false;
-
-	for (const char of value) {
-
-		if (quote) {
-			if (escape) {
-				escape = false;
-			} else if (char === "\\") {
-				escape = true;
-			} else if (char === quote) {
-				quote = false;
-			}
-		} else if (char === "\"" || char === "'") {
-			quote = char;
-		} else if (char === "(") {
-			func += 1;
-		} else if (char === ")") {
-			if (func > 0) {
-				func -= 1;
-			}
-		} else if (func === 0) {
-			if (separators.indexOf(char) !== -1) {
-				splitMe = true;
-			}
-		}
-
-		if (splitMe) {
-			if (current !== "") {
-				array.push(current.trim());
-			}
-			current = "";
-			splitMe = false;
-		} else {
-			current += char;
-		}
-	}
-
-	if (last || current !== "") {
-		array.push(current.trim());
-	}
-	return array;
-}
-
-/**
- * Splits a CSS declaration value (shorthand) using whitespace characters
- * as the delimiters.
- */
-function splitBySpaces(
-	/**
-	 * A CSS declaration value (shorthand).
-	 */
-	value,
-) {
-	const spaces = [" ", "\n", "\t"];
-	return split(value, spaces);
-}
-
-/**
- * Splits a CSS declaration value (shorthand) using commas as the delimiters.
- */
-function splitByCommas(
-	/**
-	 * A CSS declaration value (shorthand).
-	 */
-	value,
-) {
-	const comma = ",";
-	return split(value, [comma], { last: true });
-}
-
-function isSize(value) {
-	return !isNaN(parseFloat(value))
-		|| value.indexOf("/") !== -1
-		|| cssFontSizeKeywords.indexOf(value) !== -1;
-}
-
-function removeQuotes(string) {
-	if (string.match(REGEXP_SIMPLE_QUOTES_STRING)) {
-		string = string.replace(REGEXP_SIMPLE_QUOTES_STRING, "$1");
-	} else {
-		string = string.replace(REGEXP_DOUBLE_QUOTES_STRING, "$1");
-	}
-	return string.trim();
-}

+ 0 - 474
src/single-file/vendor/css-media-query-parser.js

@@ -1,474 +0,0 @@
-/*
- * The MIT License (MIT)
- * 
- * Author: Gildas Lormeau
- * 
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-
-// derived from https://github.com/dryoma/postcss-media-query-parser
-
-/*
- * The MIT License (MIT)
- * 
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- * 
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- * 
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
-*/
-
-/**
- * Parses a media feature expression, e.g. `max-width: 10px`, `(color)`
- *
- * @param {string} string - the source expression string, can be inside parens
- * @param {Number} index - the index of `string` in the overall input
- *
- * @return {Array} an array of Nodes, the first element being a media feature,
- *    the second - its value (may be missing)
- */
-
-function parseMediaFeature(string, index = 0) {
-	const modesEntered = [{
-		mode: "normal",
-		character: null,
-	}];
-	const result = [];
-	let lastModeIndex = 0, mediaFeature = "", colon = null, mediaFeatureValue = null, indexLocal = index;
-
-	let stringNormalized = string;
-	// Strip trailing parens (if any), and correct the starting index
-	if (string[0] === "(" && string[string.length - 1] === ")") {
-		stringNormalized = string.substring(1, string.length - 1);
-		indexLocal++;
-	}
-
-	for (let i = 0; i < stringNormalized.length; i++) {
-		const character = stringNormalized[i];
-
-		// If entering/exiting a string
-		if (character === "'" || character === "\"") {
-			if (modesEntered[lastModeIndex].isCalculationEnabled === true) {
-				modesEntered.push({
-					mode: "string",
-					isCalculationEnabled: false,
-					character,
-				});
-				lastModeIndex++;
-			} else if (modesEntered[lastModeIndex].mode === "string" &&
-				modesEntered[lastModeIndex].character === character &&
-				stringNormalized[i - 1] !== "\\"
-			) {
-				modesEntered.pop();
-				lastModeIndex--;
-			}
-		}
-
-		// If entering/exiting interpolation
-		if (character === "{") {
-			modesEntered.push({
-				mode: "interpolation",
-				isCalculationEnabled: true,
-			});
-			lastModeIndex++;
-		} else if (character === "}") {
-			modesEntered.pop();
-			lastModeIndex--;
-		}
-
-		// If a : is met outside of a string, function call or interpolation, than
-		// this : separates a media feature and a value
-		if (modesEntered[lastModeIndex].mode === "normal" && character === ":") {
-			const mediaFeatureValueStr = stringNormalized.substring(i + 1);
-			mediaFeatureValue = {
-				type: "value",
-				before: /^(\s*)/.exec(mediaFeatureValueStr)[1],
-				after: /(\s*)$/.exec(mediaFeatureValueStr)[1],
-				value: mediaFeatureValueStr.trim(),
-			};
-			// +1 for the colon
-			mediaFeatureValue.sourceIndex =
-				mediaFeatureValue.before.length + i + 1 + indexLocal;
-			colon = {
-				type: "colon",
-				sourceIndex: i + indexLocal,
-				after: mediaFeatureValue.before,
-				value: ":", // for consistency only
-			};
-			break;
-		}
-
-		mediaFeature += character;
-	}
-
-	// Forming a media feature node
-	mediaFeature = {
-		type: "media-feature",
-		before: /^(\s*)/.exec(mediaFeature)[1],
-		after: /(\s*)$/.exec(mediaFeature)[1],
-		value: mediaFeature.trim(),
-	};
-	mediaFeature.sourceIndex = mediaFeature.before.length + indexLocal;
-	result.push(mediaFeature);
-
-	if (colon !== null) {
-		colon.before = mediaFeature.after;
-		result.push(colon);
-	}
-
-	if (mediaFeatureValue !== null) {
-		result.push(mediaFeatureValue);
-	}
-
-	return result;
-}
-
-/**
- * Parses a media query, e.g. `screen and (color)`, `only tv`
- *
- * @param {string} string - the source media query string
- * @param {Number} index - the index of `string` in the overall input
- *
- * @return {Array} an array of Nodes and Containers
- */
-
-function parseMediaQuery(string, index = 0) {
-	const result = [];
-
-	// How many times the parser entered parens/curly braces
-	let localLevel = 0;
-	// Has any keyword, media type, media feature expression or interpolation
-	// ('element' hereafter) started
-	let insideSomeValue = false, node;
-
-	function resetNode() {
-		return {
-			before: "",
-			after: "",
-			value: "",
-		};
-	}
-
-	node = resetNode();
-
-	for (let i = 0; i < string.length; i++) {
-		const character = string[i];
-		// If not yet entered any element
-		if (!insideSomeValue) {
-			if (character.search(/\s/) !== -1) {
-				// A whitespace
-				// Don't form 'after' yet; will do it later
-				node.before += character;
-			} else {
-				// Not a whitespace - entering an element
-				// Expression start
-				if (character === "(") {
-					node.type = "media-feature-expression";
-					localLevel++;
-				}
-				node.value = character;
-				node.sourceIndex = index + i;
-				insideSomeValue = true;
-			}
-		} else {
-			// Already in the middle of some element
-			node.value += character;
-
-			// Here parens just increase localLevel and don't trigger a start of
-			// a media feature expression (since they can't be nested)
-			// Interpolation start
-			if (character === "{" || character === "(") { localLevel++; }
-			// Interpolation/function call/media feature expression end
-			if (character === ")" || character === "}") { localLevel--; }
-		}
-
-		// If exited all parens/curlies and the next symbol
-		if (insideSomeValue && localLevel === 0 &&
-			(character === ")" || i === string.length - 1 ||
-				string[i + 1].search(/\s/) !== -1)
-		) {
-			if (["not", "only", "and"].indexOf(node.value) !== -1) {
-				node.type = "keyword";
-			}
-			// if it's an expression, parse its contents
-			if (node.type === "media-feature-expression") {
-				node.nodes = parseMediaFeature(node.value, node.sourceIndex);
-			}
-			result.push(Array.isArray(node.nodes) ?
-				new Container(node) : new Node(node));
-			node = resetNode();
-			insideSomeValue = false;
-		}
-	}
-
-	// Now process the result array - to specify undefined types of the nodes
-	// and specify the `after` prop
-	for (let i = 0; i < result.length; i++) {
-		node = result[i];
-		if (i > 0) { result[i - 1].after = node.before; }
-
-		// Node types. Might not be set because contains interpolation/function
-		// calls or fully consists of them
-		if (node.type === undefined) {
-			if (i > 0) {
-				// only `and` can follow an expression
-				if (result[i - 1].type === "media-feature-expression") {
-					node.type = "keyword";
-					continue;
-				}
-				// Anything after 'only|not' is a media type
-				if (result[i - 1].value === "not" || result[i - 1].value === "only") {
-					node.type = "media-type";
-					continue;
-				}
-				// Anything after 'and' is an expression
-				if (result[i - 1].value === "and") {
-					node.type = "media-feature-expression";
-					continue;
-				}
-
-				if (result[i - 1].type === "media-type") {
-					// if it is the last element - it might be an expression
-					// or 'and' depending on what is after it
-					if (!result[i + 1]) {
-						node.type = "media-feature-expression";
-					} else {
-						node.type = result[i + 1].type === "media-feature-expression" ?
-							"keyword" : "media-feature-expression";
-					}
-				}
-			}
-
-			if (i === 0) {
-				// `screen`, `fn( ... )`, `#{ ... }`. Not an expression, since then
-				// its type would have been set by now
-				if (!result[i + 1]) {
-					node.type = "media-type";
-					continue;
-				}
-
-				// `screen and` or `#{...} (max-width: 10px)`
-				if (result[i + 1] &&
-					(result[i + 1].type === "media-feature-expression" ||
-						result[i + 1].type === "keyword")
-				) {
-					node.type = "media-type";
-					continue;
-				}
-				if (result[i + 2]) {
-					// `screen and (color) ...`
-					if (result[i + 2].type === "media-feature-expression") {
-						node.type = "media-type";
-						result[i + 1].type = "keyword";
-						continue;
-					}
-					// `only screen and ...`
-					if (result[i + 2].type === "keyword") {
-						node.type = "keyword";
-						result[i + 1].type = "media-type";
-						continue;
-					}
-				}
-				if (result[i + 3]) {
-					// `screen and (color) ...`
-					if (result[i + 3].type === "media-feature-expression") {
-						node.type = "keyword";
-						result[i + 1].type = "media-type";
-						result[i + 2].type = "keyword";
-						continue;
-					}
-				}
-			}
-		}
-	}
-	return result;
-}
-
-/**
- * Parses a media query list. Takes a possible `url()` at the start into
- * account, and divides the list into media queries that are parsed separately
- *
- * @param {string} string - the source media query list string
- *
- * @return {Array} an array of Nodes/Containers
- */
-
-function parseMediaList(string) {
-	const result = [];
-	let interimIndex = 0, levelLocal = 0;
-
-	// Check for a `url(...)` part (if it is contents of an @import rule)
-	const doesHaveUrl = /^(\s*)url\s*\(/.exec(string);
-	if (doesHaveUrl !== null) {
-		let i = doesHaveUrl[0].length;
-		let parenthesesLv = 1;
-		while (parenthesesLv > 0) {
-			const character = string[i];
-			if (character === "(") { parenthesesLv++; }
-			if (character === ")") { parenthesesLv--; }
-			i++;
-		}
-		result.unshift(new Node({
-			type: "url",
-			value: string.substring(0, i).trim(),
-			sourceIndex: doesHaveUrl[1].length,
-			before: doesHaveUrl[1],
-			after: /^(\s*)/.exec(string.substring(i))[1],
-		}));
-		interimIndex = i;
-	}
-
-	// Start processing the media query list
-	for (let i = interimIndex; i < string.length; i++) {
-		const character = string[i];
-
-		// Dividing the media query list into comma-separated media queries
-		// Only count commas that are outside of any parens
-		// (i.e., not part of function call params list, etc.)
-		if (character === "(") { levelLocal++; }
-		if (character === ")") { levelLocal--; }
-		if (levelLocal === 0 && character === ",") {
-			const mediaQueryString = string.substring(interimIndex, i);
-			const spaceBefore = /^(\s*)/.exec(mediaQueryString)[1];
-			result.push(new Container({
-				type: "media-query",
-				value: mediaQueryString.trim(),
-				sourceIndex: interimIndex + spaceBefore.length,
-				nodes: parseMediaQuery(mediaQueryString, interimIndex),
-				before: spaceBefore,
-				after: /(\s*)$/.exec(mediaQueryString)[1],
-			}));
-			interimIndex = i + 1;
-		}
-	}
-
-	const mediaQueryString = string.substring(interimIndex);
-	const spaceBefore = /^(\s*)/.exec(mediaQueryString)[1];
-	result.push(new Container({
-		type: "media-query",
-		value: mediaQueryString.trim(),
-		sourceIndex: interimIndex + spaceBefore.length,
-		nodes: parseMediaQuery(mediaQueryString, interimIndex),
-		before: spaceBefore,
-		after: /(\s*)$/.exec(mediaQueryString)[1],
-	}));
-
-	return result;
-}
-
-function Container(opts) {
-	this.constructor(opts);
-
-	this.nodes = opts.nodes;
-
-	if (this.after === undefined) {
-		this.after = this.nodes.length > 0 ?
-			this.nodes[this.nodes.length - 1].after : "";
-	}
-
-	if (this.before === undefined) {
-		this.before = this.nodes.length > 0 ?
-			this.nodes[0].before : "";
-	}
-
-	if (this.sourceIndex === undefined) {
-		this.sourceIndex = this.before.length;
-	}
-
-	this.nodes.forEach(node => {
-		node.parent = this; // eslint-disable-line no-param-reassign
-	});
-}
-
-Container.prototype = Object.create(Node.prototype);
-Container.constructor = Node;
-
-/**
- * Iterate over descendant nodes of the node
- *
- * @param {RegExp|string} filter - Optional. Only nodes with node.type that
- *    satisfies the filter will be traversed over
- * @param {function} cb - callback to call on each node. Takes these params:
- *    node - the node being processed, i - it's index, nodes - the array
- *    of all nodes
- *    If false is returned, the iteration breaks
- *
- * @return (boolean) false, if the iteration was broken
- */
-Container.prototype.walk = function walk(filter, cb) {
-	const hasFilter = typeof filter === "string" || filter instanceof RegExp;
-	const callback = hasFilter ? cb : filter;
-	const filterReg = typeof filter === "string" ? new RegExp(filter) : filter;
-
-	for (let i = 0; i < this.nodes.length; i++) {
-		const node = this.nodes[i];
-		const filtered = hasFilter ? filterReg.test(node.type) : true;
-		if (filtered && callback && callback(node, i, this.nodes) === false) {
-			return false;
-		}
-		if (node.nodes && node.walk(filter, cb) === false) { return false; }
-	}
-	return true;
-};
-
-/**
- * Iterate over immediate children of the node
- *
- * @param {function} cb - callback to call on each node. Takes these params:
- *    node - the node being processed, i - it's index, nodes - the array
- *    of all nodes
- *    If false is returned, the iteration breaks
- *
- * @return (boolean) false, if the iteration was broken
- */
-Container.prototype.each = function each(cb = () => { }) {
-	for (let i = 0; i < this.nodes.length; i++) {
-		const node = this.nodes[i];
-		if (cb(node, i, this.nodes) === false) { return false; }
-	}
-	return true;
-};
-
-/**
- * A very generic node. Pretty much any element of a media query
- */
-
-function Node(opts) {
-	this.after = opts.after;
-	this.before = opts.before;
-	this.type = opts.type;
-	this.value = opts.value;
-	this.sourceIndex = opts.sourceIndex;
-}
-
-export {
-	parseMediaList
-};

+ 0 - 789
src/single-file/vendor/css-minifier.js

@@ -1,789 +0,0 @@
-/*
- * The MIT License (MIT)
- * 
- * Author: Gildas Lormeau
- * 
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-
-// derived from https://github.com/fmarcia/UglifyCSS
-
-/**
- * UglifyCSS
- * Port of YUI CSS Compressor to NodeJS
- * Author: Franck Marcia - https://github.com/fmarcia
- * MIT licenced
- */
-
-/**
- * cssmin.js
- * Author: Stoyan Stefanov - http://phpied.com/
- * This is a JavaScript port of the CSS minification tool
- * distributed with YUICompressor, itself a port
- * of the cssmin utility by Isaac Schlueter - http://foohack.com/
- * Permission is hereby granted to use the JavaScript version under the same
- * conditions as the YUICompressor (original YUICompressor note below).
- */
-
-/**
- * YUI Compressor
- * http://developer.yahoo.com/yui/compressor/
- * Author: Julien Lecomte - http://www.julienlecomte.net/
- * Copyright (c) 2011 Yahoo! Inc. All rights reserved.
- * The copyrights embodied in the content of this file are licensed
- * by Yahoo! Inc. under the BSD (revised) open source license.
- */
-
-/**
- * @type {string} - placeholder prefix
- */
-
-const ___PRESERVED_TOKEN_ = "___PRESERVED_TOKEN_";
-
-/**
- * @typedef {object} options - UglifyCSS options
- * @property {number} [maxLineLen=0] - Maximum line length of uglified CSS
- * @property {boolean} [expandVars=false] - Expand variables
- * @property {boolean} [uglyComments=false] - Removes newlines within preserved comments
- * @property {boolean} [cuteComments=false] - Preserves newlines within and around preserved comments
- * @property {boolean} [debug=false] - Prints full error stack on error
- * @property {string} [output=''] - Output file name
- */
-
-/**
- * @type {options} - UglifyCSS options
- */
-
-const defaultOptions = {
-	maxLineLen: 0,
-	expandVars: false,
-	uglyComments: false,
-	cuteComments: false,
-	debug: false,
-	output: ""
-};
-
-const REGEXP_DATA_URI = /url\(\s*(["']?)data:/g;
-const REGEXP_WHITE_SPACES = /\s+/g;
-const REGEXP_NEW_LINE = /\n/g;
-
-/**
- * extractDataUrls replaces all data urls with tokens before we start
- * compressing, to avoid performance issues running some of the subsequent
- * regexes against large strings chunks.
- *
- * @param {string} css - CSS content
- * @param {string[]} preservedTokens - Global array of tokens to preserve
- *
- * @return {string} Processed CSS
- */
-
-function extractDataUrls(css, preservedTokens) {
-
-	// Leave data urls alone to increase parse performance.
-	const pattern = REGEXP_DATA_URI;
-	const maxIndex = css.length - 1;
-	const sb = [];
-
-	let appendIndex = 0, match;
-
-	// Since we need to account for non-base64 data urls, we need to handle
-	// ' and ) being part of the data string. Hence switching to indexOf,
-	// to determine whether or not we have matching string terminators and
-	// handling sb appends directly, instead of using matcher.append* methods.
-
-	while ((match = pattern.exec(css)) !== null) {
-
-		const startIndex = match.index + 4;  // 'url('.length()
-		let terminator = match[1];         // ', " or empty (not quoted)
-
-		if (terminator.length === 0) {
-			terminator = ")";
-		}
-
-		let foundTerminator = false, endIndex = pattern.lastIndex - 1;
-
-		while (foundTerminator === false && endIndex + 1 <= maxIndex && endIndex != -1) {
-			endIndex = css.indexOf(terminator, endIndex + 1);
-
-			// endIndex == 0 doesn't really apply here
-			if ((endIndex > 0) && (css.charAt(endIndex - 1) !== "\\")) {
-				foundTerminator = true;
-				if (")" != terminator) {
-					endIndex = css.indexOf(")", endIndex);
-				}
-			}
-		}
-
-		// Enough searching, start moving stuff over to the buffer
-		sb.push(css.substring(appendIndex, match.index));
-
-		if (foundTerminator) {
-
-			let token = css.substring(startIndex, endIndex);
-			const parts = token.split(",");
-			if (parts.length > 1 && parts[0].slice(-7) == ";base64") {
-				token = token.replace(REGEXP_WHITE_SPACES, "");
-			} else {
-				token = token.replace(REGEXP_NEW_LINE, " ");
-				token = token.replace(REGEXP_WHITE_SPACES, " ");
-				token = token.replace(REGEXP_PRESERVE_HSLA1, "");
-			}
-
-			preservedTokens.push(token);
-
-			const preserver = "url(" + ___PRESERVED_TOKEN_ + (preservedTokens.length - 1) + "___)";
-			sb.push(preserver);
-
-			appendIndex = endIndex + 1;
-		} else {
-			// No end terminator found, re-add the whole match. Should we throw/warn here?
-			sb.push(css.substring(match.index, pattern.lastIndex));
-			appendIndex = pattern.lastIndex;
-		}
-	}
-
-	sb.push(css.substring(appendIndex));
-
-	return sb.join("");
-}
-
-const REGEXP_HEX_COLORS = /(=\s*?["']?)?#([0-9a-f])([0-9a-f])([0-9a-f])([0-9a-f])([0-9a-f])([0-9a-f])(\}|[^0-9a-f{][^{]*?\})/gi;
-
-/**
- * compressHexColors compresses hex color values of the form #AABBCC to #ABC.
- *
- * DOES NOT compress CSS ID selectors which match the above pattern (which would
- * break things), like #AddressForm { ... }
- *
- * DOES NOT compress IE filters, which have hex color values (which would break
- * things), like chroma(color='#FFFFFF');
- *
- * DOES NOT compress invalid hex values, like background-color: #aabbccdd
- *
- * @param {string} css - CSS content
- *
- * @return {string} Processed CSS
- */
-
-function compressHexColors(css) {
-
-	// Look for hex colors inside { ... } (to avoid IDs) and which don't have a =, or a " in front of them (to avoid filters)
-
-	const pattern = REGEXP_HEX_COLORS;
-	const sb = [];
-
-	let index = 0, match;
-
-	while ((match = pattern.exec(css)) !== null) {
-
-		sb.push(css.substring(index, match.index));
-
-		const isFilter = match[1];
-
-		if (isFilter) {
-			// Restore, maintain case, otherwise filter will break
-			sb.push(match[1] + "#" + (match[2] + match[3] + match[4] + match[5] + match[6] + match[7]));
-		} else {
-			if (match[2].toLowerCase() == match[3].toLowerCase() &&
-				match[4].toLowerCase() == match[5].toLowerCase() &&
-				match[6].toLowerCase() == match[7].toLowerCase()) {
-
-				// Compress.
-				sb.push("#" + (match[3] + match[5] + match[7]).toLowerCase());
-			} else {
-				// Non compressible color, restore but lower case.
-				sb.push("#" + (match[2] + match[3] + match[4] + match[5] + match[6] + match[7]).toLowerCase());
-			}
-		}
-
-		index = pattern.lastIndex = pattern.lastIndex - match[8].length;
-	}
-
-	sb.push(css.substring(index));
-
-	return sb.join("");
-}
-
-const REGEXP_KEYFRAMES = /@[a-z0-9-_]*keyframes\s+[a-z0-9-_]+\s*{/gi;
-const REGEXP_WHITE_SPACE = /(^\s|\s$)/g;
-
-/** keyframes preserves 0 followed by unit in keyframes steps
- *
- * @param {string} content - CSS content
- * @param {string[]} preservedTokens - Global array of tokens to preserve
- *
- * @return {string} Processed CSS
- */
-
-function keyframes(content, preservedTokens) {
-
-	const pattern = REGEXP_KEYFRAMES;
-
-	let index = 0, buffer;
-
-	const preserve = (part, i) => {
-		part = part.replace(REGEXP_WHITE_SPACE, "");
-		if (part.charAt(0) === "0") {
-			preservedTokens.push(part);
-			buffer[i] = ___PRESERVED_TOKEN_ + (preservedTokens.length - 1) + "___";
-		}
-	};
-
-	while (true) { // eslint-disable-line no-constant-condition
-
-		let level = 0;
-		buffer = "";
-
-		let startIndex = content.slice(index).search(pattern);
-		if (startIndex < 0) {
-			break;
-		}
-
-		index += startIndex;
-		startIndex = index;
-
-		const len = content.length;
-		const buffers = [];
-
-		for (; index < len; ++index) {
-
-			const ch = content.charAt(index);
-
-			if (ch === "{") {
-
-				if (level === 0) {
-					buffers.push(buffer.replace(REGEXP_WHITE_SPACE, ""));
-
-				} else if (level === 1) {
-
-					buffer = buffer.split(",");
-
-					buffer.forEach(preserve);
-
-					buffers.push(buffer.join(",").replace(REGEXP_WHITE_SPACE, ""));
-				}
-
-				buffer = "";
-				level += 1;
-
-			} else if (ch === "}") {
-
-				if (level === 2) {
-					buffers.push("{" + buffer.replace(REGEXP_WHITE_SPACE, "") + "}");
-					buffer = "";
-
-				} else if (level === 1) {
-					content = content.slice(0, startIndex) +
-						buffers.shift() + "{" +
-						buffers.join("") +
-						content.slice(index);
-					break;
-				}
-
-				level -= 1;
-			}
-
-			if (level < 0) {
-				break;
-
-			} else if (ch !== "{" && ch !== "}") {
-				buffer += ch;
-			}
-		}
-	}
-
-	return content;
-}
-
-/**
- * collectComments collects all comment blocks and return new content with comment placeholders
- *
- * @param {string} content - CSS content
- * @param {string[]} comments - Global array of extracted comments
- *
- * @return {string} Processed CSS
- */
-
-function collectComments(content, comments) {
-
-	const table = [];
-
-	let from = 0, end;
-
-	while (true) { // eslint-disable-line no-constant-condition
-
-		const start = content.indexOf("/*", from);
-
-		if (start > -1) {
-
-			end = content.indexOf("*/", start + 2);
-
-			if (end > -1) {
-				comments.push(content.slice(start + 2, end));
-				table.push(content.slice(from, start));
-				table.push("/*___PRESERVE_CANDIDATE_COMMENT_" + (comments.length - 1) + "___*/");
-				from = end + 2;
-
-			} else {
-				// unterminated comment
-				end = -2;
-				break;
-			}
-
-		} else {
-			break;
-		}
-	}
-
-	table.push(content.slice(end + 2));
-
-	return table.join("");
-}
-
-/**
- * processString uglifies a CSS string
- *
- * @param {string} content - CSS string
- * @param {options} options - UglifyCSS options
- *
- * @return {string} Uglified result
- */
-
-// const REGEXP_EMPTY_RULES = /[^};{/]+\{\}/g;
-const REGEXP_PRESERVE_STRING = /"([^\\"]|\\.|\\)*"/g;
-const REGEXP_PRESERVE_STRING2 = /'([^\\']|\\.|\\)*'/g;
-const REGEXP_MINIFY_ALPHA = /progid:DXImageTransform.Microsoft.Alpha\(Opacity=/gi;
-const REGEXP_PRESERVE_TOKEN1 = /\r\n/g;
-const REGEXP_PRESERVE_TOKEN2 = /[\r\n]/g;
-const REGEXP_VARIABLES = /@variables\s*\{\s*([^}]+)\s*\}/g;
-const REGEXP_VARIABLE = /\s*([a-z0-9-]+)\s*:\s*([^;}]+)\s*/gi;
-const REGEXP_VARIABLE_VALUE = /var\s*\(\s*([^)]+)\s*\)/g;
-const REGEXP_PRESERVE_CALC = /calc\(([^;}]*)\)/g;
-const REGEXP_TRIM = /(^\s*|\s*$)/g;
-const REGEXP_PRESERVE_CALC2 = /\( /g;
-const REGEXP_PRESERVE_CALC3 = / \)/g;
-const REGEXP_PRESERVE_MATRIX = /\s*filter:\s*progid:DXImageTransform.Microsoft.Matrix\(([^)]+)\);/g;
-const REGEXP_REMOVE_SPACES = /(^|\})(([^{:])+:)+([^{]*{)/g;
-const REGEXP_REMOVE_SPACES2 = /\s+([!{;:>+()\],])/g;
-const REGEXP_REMOVE_SPACES2_BIS = /([^\\])\s+([}])/g;
-const REGEXP_RESTORE_SPACE_IMPORTANT = /!important/g;
-const REGEXP_PSEUDOCLASSCOLON = /___PSEUDOCLASSCOLON___/g;
-const REGEXP_COLUMN = /:/g;
-const REGEXP_PRESERVE_ZERO_UNIT = /\s*(animation|animation-delay|animation-duration|transition|transition-delay|transition-duration):\s*([^;}]+)/gi;
-const REGEXP_PRESERVE_ZERO_UNIT1 = /(^|\D)0?\.?0(m?s)/gi;
-const REGEXP_PRESERVE_FLEX = /\s*(flex|flex-basis):\s*([^;}]+)/gi;
-const REGEXP_SPACES = /\s+/;
-const REGEXP_PRESERVE_HSLA = /(hsla?)\(([^)]+)\)/g;
-const REGEXP_PRESERVE_HSLA1 = /(^\s+|\s+$)/g;
-const REGEXP_RETAIN_SPACE_IE6 = /:first-(line|letter)(\{|,)/gi;
-const REGEXP_CHARSET = /^(.*)(@charset)( "[^"]*";)/gi;
-const REGEXP_REMOVE_SECOND_CHARSET = /^((\s*)(@charset)( [^;]+;\s*))+/gi;
-const REGEXP_LOWERCASE_DIRECTIVES = /@(font-face|import|(?:-(?:atsc|khtml|moz|ms|o|wap|webkit)-)?keyframe|media|page|namespace)/gi;
-const REGEXP_LOWERCASE_PSEUDO_ELEMENTS = /:(active|after|before|checked|disabled|empty|enabled|first-(?:child|of-type)|focus|hover|last-(?:child|of-type)|link|only-(?:child|of-type)|root|:selection|target|visited)/gi;
-const REGEXP_CHARSET2 = /^(.*)(@charset "[^"]*";)/g;
-const REGEXP_CHARSET3 = /^(\s*@charset [^;]+;\s*)+/g;
-const REGEXP_LOWERCASE_FUNCTIONS = /:(lang|not|nth-child|nth-last-child|nth-last-of-type|nth-of-type|(?:-(?:atsc|khtml|moz|ms|o|wap|webkit)-)?any)\(/gi;
-const REGEXP_LOWERCASE_FUNCTIONS2 = /([:,( ]\s*)(attr|color-stop|from|rgba|to|url|(?:-(?:atsc|khtml|moz|ms|o|wap|webkit)-)?(?:calc|max|min|(?:repeating-)?(?:linear|radial)-gradient)|-webkit-gradient)/gi;
-const REGEXP_NEWLINE1 = /\s*\/\*/g;
-const REGEXP_NEWLINE2 = /\*\/\s*/g;
-const REGEXP_RESTORE_SPACE1 = /\band\(/gi;
-const REGEXP_RESTORE_SPACE2 = /([^:])not\(/gi;
-const REGEXP_RESTORE_SPACE3 = /\bor\(/gi;
-const REGEXP_REMOVE_SPACES3 = /([!{}:;>+([,])\s+/g;
-const REGEXP_REMOVE_SEMI_COLUMNS = /;+\}/g;
-// const REGEXP_REPLACE_ZERO = /(^|[^.0-9\\])(?:0?\.)?0(?:ex|ch|r?em|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|g?rad|turn|ms|k?Hz|dpi|dpcm|dppx|%)(?![a-z0-9])/gi;
-const REGEXP_REPLACE_ZERO_DOT = /([0-9])\.0(ex|ch|r?em|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|g?rad|turn|m?s|k?Hz|dpi|dpcm|dppx|%| |;)/gi;
-const REGEXP_REPLACE_4_ZEROS = /:0 0 0 0(;|\})/g;
-const REGEXP_REPLACE_3_ZEROS = /:0 0 0(;|\})/g;
-// const REGEXP_REPLACE_2_ZEROS = /:0 0(;|\})/g;
-const REGEXP_REPLACE_1_ZERO = /(transform-origin|webkit-transform-origin|moz-transform-origin|o-transform-origin|ms-transform-origin|box-shadow):0(;|\})/gi;
-const REGEXP_REPLACE_ZERO_DOT_DECIMAL = /(:|\s)0+\.(\d+)/g;
-const REGEXP_REPLACE_RGB = /rgb\s*\(\s*([0-9,\s]+)\s*\)/gi;
-const REGEXP_REPLACE_BORDER_ZERO = /(border|border-top|border-right|border-bottom|border-left|outline|background):none(;|\})/gi;
-const REGEXP_REPLACE_IE_OPACITY = /progid:DXImageTransform\.Microsoft\.Alpha\(Opacity=/gi;
-const REGEXP_REPLACE_QUERY_FRACTION = /\(([-A-Za-z]+):([0-9]+)\/([0-9]+)\)/g;
-const REGEXP_QUERY_FRACTION = /___QUERY_FRACTION___/g;
-const REGEXP_REPLACE_SEMI_COLUMNS = /;;+/g;
-const REGEXP_REPLACE_HASH_COLOR = /(:|\s)(#f00)(;|})/g;
-const REGEXP_PRESERVED_NEWLINE = /___PRESERVED_NEWLINE___/g;
-const REGEXP_REPLACE_HASH_COLOR_SHORT1 = /(:|\s)(#000080)(;|})/g;
-const REGEXP_REPLACE_HASH_COLOR_SHORT2 = /(:|\s)(#808080)(;|})/g;
-const REGEXP_REPLACE_HASH_COLOR_SHORT3 = /(:|\s)(#808000)(;|})/g;
-const REGEXP_REPLACE_HASH_COLOR_SHORT4 = /(:|\s)(#800080)(;|})/g;
-const REGEXP_REPLACE_HASH_COLOR_SHORT5 = /(:|\s)(#c0c0c0)(;|})/g;
-const REGEXP_REPLACE_HASH_COLOR_SHORT6 = /(:|\s)(#008080)(;|})/g;
-const REGEXP_REPLACE_HASH_COLOR_SHORT7 = /(:|\s)(#ffa500)(;|})/g;
-const REGEXP_REPLACE_HASH_COLOR_SHORT8 = /(:|\s)(#800000)(;|})/g;
-
-function processString(content = "", options = defaultOptions) {
-
-	const comments = [];
-	const preservedTokens = [];
-
-	let pattern;
-
-	const originalContent = content;
-	content = extractDataUrls(content, preservedTokens);
-	content = collectComments(content, comments);
-
-	// preserve strings so their content doesn't get accidentally minified
-	preserveString(REGEXP_PRESERVE_STRING);
-	preserveString(REGEXP_PRESERVE_STRING2);
-
-	function preserveString(pattern) {
-		content = content.replace(pattern, token => {
-			const quote = token.substring(0, 1);
-			token = token.slice(1, -1);
-			// maybe the string contains a comment-like substring or more? put'em back then
-			if (token.indexOf("___PRESERVE_CANDIDATE_COMMENT_") >= 0) {
-				for (let i = 0, len = comments.length; i < len; i += 1) {
-					token = token.replace("___PRESERVE_CANDIDATE_COMMENT_" + i + "___", comments[i]);
-				}
-			}
-			// minify alpha opacity in filter strings
-			token = token.replace(REGEXP_MINIFY_ALPHA, "alpha(opacity=");
-			preservedTokens.push(token);
-			return quote + ___PRESERVED_TOKEN_ + (preservedTokens.length - 1) + "___" + quote;
-		});
-	}
-
-	// strings are safe, now wrestle the comments
-	for (let i = 0, len = comments.length; i < len; i += 1) {
-
-		const token = comments[i];
-		const placeholder = "___PRESERVE_CANDIDATE_COMMENT_" + i + "___";
-
-		// ! in the first position of the comment means preserve
-		// so push to the preserved tokens keeping the !
-		if (token.charAt(0) === "!") {
-			if (options.cuteComments) {
-				preservedTokens.push(token.substring(1).replace(REGEXP_PRESERVE_TOKEN1, "\n"));
-			} else if (options.uglyComments) {
-				preservedTokens.push(token.substring(1).replace(REGEXP_PRESERVE_TOKEN2, ""));
-			} else {
-				preservedTokens.push(token);
-			}
-			content = content.replace(placeholder, ___PRESERVED_TOKEN_ + (preservedTokens.length - 1) + "___");
-			continue;
-		}
-
-		// \ in the last position looks like hack for Mac/IE5
-		// shorten that to /*\*/ and the next one to /**/
-		if (token.charAt(token.length - 1) === "\\") {
-			preservedTokens.push("\\");
-			content = content.replace(placeholder, ___PRESERVED_TOKEN_ + (preservedTokens.length - 1) + "___");
-			i = i + 1; // attn: advancing the loop
-			preservedTokens.push("");
-			content = content.replace(
-				"___PRESERVE_CANDIDATE_COMMENT_" + i + "___",
-				___PRESERVED_TOKEN_ + (preservedTokens.length - 1) + "___"
-			);
-			continue;
-		}
-
-		// keep empty comments after child selectors (IE7 hack)
-		// e.g. html >/**/ body
-		if (token.length === 0) {
-			const startIndex = content.indexOf(placeholder);
-			if (startIndex > 2) {
-				if (content.charAt(startIndex - 3) === ">") {
-					preservedTokens.push("");
-					content = content.replace(placeholder, ___PRESERVED_TOKEN_ + (preservedTokens.length - 1) + "___");
-				}
-			}
-		}
-
-		// in all other cases kill the comment
-		content = content.replace(`/*${placeholder}*/`, "");
-	}
-
-	// parse simple @variables blocks and remove them
-	if (options.expandVars) {
-		const vars = {};
-		pattern = REGEXP_VARIABLES;
-		content = content.replace(pattern, (_, f1) => {
-			pattern = REGEXP_VARIABLE;
-			f1.replace(pattern, (_, f1, f2) => {
-				if (f1 && f2) {
-					vars[f1] = f2;
-				}
-				return "";
-			});
-			return "";
-		});
-
-		// replace var(x) with the value of x
-		pattern = REGEXP_VARIABLE_VALUE;
-		content = content.replace(pattern, (_, f1) => {
-			return vars[f1] || "none";
-		});
-	}
-
-	// normalize all whitespace strings to single spaces. Easier to work with that way.
-	content = content.replace(REGEXP_WHITE_SPACES, " ");
-
-	// preserve formulas in calc() before removing spaces
-	pattern = REGEXP_PRESERVE_CALC;
-	content = content.replace(pattern, (_, f1) => {
-		preservedTokens.push(
-			"calc(" +
-			f1.replace(REGEXP_TRIM, "")
-				.replace(REGEXP_PRESERVE_CALC2, "(")
-				.replace(REGEXP_PRESERVE_CALC3, ")") +
-			")"
-		);
-		return ___PRESERVED_TOKEN_ + (preservedTokens.length - 1) + "___";
-	});
-
-	// preserve matrix
-	pattern = REGEXP_PRESERVE_MATRIX;
-	content = content.replace(pattern, (_, f1) => {
-		preservedTokens.push(f1);
-		return "filter:progid:DXImageTransform.Microsoft.Matrix(" + ___PRESERVED_TOKEN_ + (preservedTokens.length - 1) + "___);";
-	});
-
-	// remove the spaces before the things that should not have spaces before them.
-	// but, be careful not to turn 'p :link {...}' into 'p:link{...}'
-	// swap out any pseudo-class colons with the token, and then swap back.
-	pattern = REGEXP_REMOVE_SPACES;
-	content = content.replace(pattern, token => token.replace(REGEXP_COLUMN, "___PSEUDOCLASSCOLON___"));
-
-	// remove spaces before the things that should not have spaces before them.
-	content = content.replace(REGEXP_REMOVE_SPACES2, "$1");
-	content = content.replace(REGEXP_REMOVE_SPACES2_BIS, "$1$2");
-
-	// restore spaces for !important
-	content = content.replace(REGEXP_RESTORE_SPACE_IMPORTANT, " !important");
-
-	// bring back the colon
-	content = content.replace(REGEXP_PSEUDOCLASSCOLON, ":");
-
-	// preserve 0 followed by a time unit for properties using time units
-	pattern = REGEXP_PRESERVE_ZERO_UNIT;
-	content = content.replace(pattern, (_, f1, f2) => {
-
-		f2 = f2.replace(REGEXP_PRESERVE_ZERO_UNIT1, (_, g1, g2) => {
-			preservedTokens.push("0" + g2);
-			return g1 + ___PRESERVED_TOKEN_ + (preservedTokens.length - 1) + "___";
-		});
-
-		return f1 + ":" + f2;
-	});
-
-	// preserve unit for flex-basis within flex and flex-basis (ie10 bug)
-	pattern = REGEXP_PRESERVE_FLEX;
-	content = content.replace(pattern, (_, f1, f2) => {
-		let f2b = f2.split(REGEXP_SPACES);
-		preservedTokens.push(f2b.pop());
-		f2b.push(___PRESERVED_TOKEN_ + (preservedTokens.length - 1) + "___");
-		f2b = f2b.join(" ");
-		return `${f1}:${f2b}`;
-	});
-
-	// preserve 0% in hsl and hsla color definitions
-	content = content.replace(REGEXP_PRESERVE_HSLA, (_, f1, f2) => {
-		const f0 = [];
-		f2.split(",").forEach(part => {
-			part = part.replace(REGEXP_PRESERVE_HSLA1, "");
-			if (part === "0%") {
-				preservedTokens.push("0%");
-				f0.push(___PRESERVED_TOKEN_ + (preservedTokens.length - 1) + "___");
-			} else {
-				f0.push(part);
-			}
-		});
-		return f1 + "(" + f0.join(",") + ")";
-	});
-
-	// preserve 0 followed by unit in keyframes steps (WIP)
-	content = keyframes(content, preservedTokens);
-
-	// retain space for special IE6 cases
-	content = content.replace(REGEXP_RETAIN_SPACE_IE6, (_, f1, f2) => ":first-" + f1.toLowerCase() + " " + f2);
-
-	// newlines before and after the end of a preserved comment
-	if (options.cuteComments) {
-		content = content.replace(REGEXP_NEWLINE1, "___PRESERVED_NEWLINE___/*");
-		content = content.replace(REGEXP_NEWLINE2, "*/___PRESERVED_NEWLINE___");
-		// no space after the end of a preserved comment
-	} else {
-		content = content.replace(REGEXP_NEWLINE2, "*/");
-	}
-
-	// If there are multiple @charset directives, push them to the top of the file.
-	pattern = REGEXP_CHARSET;
-	content = content.replace(pattern, (_, f1, f2, f3) => f2.toLowerCase() + f3 + f1);
-
-	// When all @charset are at the top, remove the second and after (as they are completely ignored).
-	pattern = REGEXP_REMOVE_SECOND_CHARSET;
-	content = content.replace(pattern, (_, __, f2, f3, f4) => f2 + f3.toLowerCase() + f4);
-
-	// lowercase some popular @directives (@charset is done right above)
-	pattern = REGEXP_LOWERCASE_DIRECTIVES;
-	content = content.replace(pattern, (_, f1) => "@" + f1.toLowerCase());
-
-	// lowercase some more common pseudo-elements
-	pattern = REGEXP_LOWERCASE_PSEUDO_ELEMENTS;
-	content = content.replace(pattern, (_, f1) => ":" + f1.toLowerCase());
-
-	// if there is a @charset, then only allow one, and push to the top of the file.
-	content = content.replace(REGEXP_CHARSET2, "$2$1");
-	content = content.replace(REGEXP_CHARSET3, "$1");
-
-	// lowercase some more common functions
-	pattern = REGEXP_LOWERCASE_FUNCTIONS;
-	content = content.replace(pattern, (_, f1) => ":" + f1.toLowerCase() + "(");
-
-	// lower case some common function that can be values
-	// NOTE: rgb() isn't useful as we replace with #hex later, as well as and() is already done for us right after this
-	pattern = REGEXP_LOWERCASE_FUNCTIONS2;
-	content = content.replace(pattern, (_, f1, f2) => f1 + f2.toLowerCase());
-
-	// put the space back in some cases, to support stuff like
-	// @media screen and (-webkit-min-device-pixel-ratio:0){
-	content = content.replace(REGEXP_RESTORE_SPACE1, "and (");
-	content = content.replace(REGEXP_RESTORE_SPACE2, "$1not (");
-	content = content.replace(REGEXP_RESTORE_SPACE3, "or (");
-
-	// remove the spaces after the things that should not have spaces after them.
-	content = content.replace(REGEXP_REMOVE_SPACES3, "$1");
-
-	// remove unnecessary semicolons
-	content = content.replace(REGEXP_REMOVE_SEMI_COLUMNS, "}");
-
-	// replace 0(px,em,%) with 0.
-	// content = content.replace(REGEXP_REPLACE_ZERO, "$10");
-
-	// Replace x.0(px,em,%) with x(px,em,%).
-	content = content.replace(REGEXP_REPLACE_ZERO_DOT, "$1$2");
-
-	// replace 0 0 0 0; with 0.
-	content = content.replace(REGEXP_REPLACE_4_ZEROS, ":0$1");
-	content = content.replace(REGEXP_REPLACE_3_ZEROS, ":0$1");
-	// content = content.replace(REGEXP_REPLACE_2_ZEROS, ":0$1");
-
-	// replace background-position:0; with background-position:0 0;
-	// same for transform-origin and box-shadow
-	pattern = REGEXP_REPLACE_1_ZERO;
-	content = content.replace(pattern, (_, f1, f2) => f1.toLowerCase() + ":0 0" + f2);
-
-	// replace 0.6 to .6, but only when preceded by : or a white-space
-	content = content.replace(REGEXP_REPLACE_ZERO_DOT_DECIMAL, "$1.$2");
-
-	// shorten colors from rgb(51,102,153) to #336699
-	// this makes it more likely that it'll get further compressed in the next step.
-	pattern = REGEXP_REPLACE_RGB;
-	content = content.replace(pattern, (_, f1) => {
-		const rgbcolors = f1.split(",");
-		let hexcolor = "#";
-		for (let i = 0; i < rgbcolors.length; i += 1) {
-			let val = parseInt(rgbcolors[i], 10);
-			if (val < 16) {
-				hexcolor += "0";
-			}
-			if (val > 255) {
-				val = 255;
-			}
-			hexcolor += val.toString(16);
-		}
-		return hexcolor;
-	});
-
-	// Shorten colors from #AABBCC to #ABC.
-	content = compressHexColors(content);
-
-	// Replace #f00 -> red
-	content = content.replace(REGEXP_REPLACE_HASH_COLOR, "$1red$3");
-
-	// Replace other short color keywords
-	content = content.replace(REGEXP_REPLACE_HASH_COLOR_SHORT1, "$1navy$3");
-	content = content.replace(REGEXP_REPLACE_HASH_COLOR_SHORT2, "$1gray$3");
-	content = content.replace(REGEXP_REPLACE_HASH_COLOR_SHORT3, "$1olive$3");
-	content = content.replace(REGEXP_REPLACE_HASH_COLOR_SHORT4, "$1purple$3");
-	content = content.replace(REGEXP_REPLACE_HASH_COLOR_SHORT5, "$1silver$3");
-	content = content.replace(REGEXP_REPLACE_HASH_COLOR_SHORT6, "$1teal$3");
-	content = content.replace(REGEXP_REPLACE_HASH_COLOR_SHORT7, "$1orange$3");
-	content = content.replace(REGEXP_REPLACE_HASH_COLOR_SHORT8, "$1maroon$3");
-
-	// border: none -> border:0
-	pattern = REGEXP_REPLACE_BORDER_ZERO;
-	content = content.replace(pattern, (_, f1, f2) => f1.toLowerCase() + ":0" + f2);
-
-	// shorter opacity IE filter
-	content = content.replace(REGEXP_REPLACE_IE_OPACITY, "alpha(opacity=");
-
-	// Find a fraction that is used for Opera's -o-device-pixel-ratio query
-	// Add token to add the '\' back in later
-	content = content.replace(REGEXP_REPLACE_QUERY_FRACTION, "($1:$2___QUERY_FRACTION___$3)");
-
-	// remove empty rules.
-	// content = content.replace(REGEXP_EMPTY_RULES, "");
-
-	// Add '\' back to fix Opera -o-device-pixel-ratio query
-	content = content.replace(REGEXP_QUERY_FRACTION, "/");
-
-	// some source control tools don't like it when files containing lines longer
-	// than, say 8000 characters, are checked in. The linebreak option is used in
-	// that case to split long lines after a specific column.
-	if (options.maxLineLen > 0) {
-		const lines = [];
-		let line = [];
-		for (let i = 0, len = content.length; i < len; i += 1) {
-			const ch = content.charAt(i);
-			line.push(ch);
-			if (ch === "}" && line.length > options.maxLineLen) {
-				lines.push(line.join(""));
-				line = [];
-			}
-		}
-		if (line.length) {
-			lines.push(line.join(""));
-		}
-
-		content = lines.join("\n");
-	}
-
-	// replace multiple semi-colons in a row by a single one
-	// see SF bug #1980989
-	content = content.replace(REGEXP_REPLACE_SEMI_COLUMNS, ";");
-
-	// trim the final string (for any leading or trailing white spaces)
-	content = content.replace(REGEXP_TRIM, "");
-
-	if (preservedTokens.length > 1000) {
-		return originalContent;
-	}
-
-	// restore preserved tokens
-	for (let i = preservedTokens.length - 1; i >= 0; i--) {
-		content = content.replace(___PRESERVED_TOKEN_ + i + "___", preservedTokens[i], "g");
-	}
-
-	// restore preserved newlines
-	content = content.replace(REGEXP_PRESERVED_NEWLINE, "\n");
-
-	// return
-	return content;
-}
-
-export {
-	defaultOptions,
-	processString
-};

Plik diff jest za duży
+ 0 - 24
src/single-file/vendor/css-tree.js


+ 0 - 72
src/single-file/vendor/css-unescape.js

@@ -1,72 +0,0 @@
-/*
- * The MIT License (MIT)
- *
- * Author: Gildas Lormeau
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
-
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
-
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-
-// derived from https://github.com/postcss/postcss-selector-parser/blob/master/src/util/unesc.js
-
-/*
- * The MIT License (MIT)
- * Copyright (c) Ben Briggs <beneb.info@gmail.com> (http://beneb.info)
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- */
-
-const whitespace = "[\\x20\\t\\r\\n\\f]";
-const unescapeRegExp = new RegExp("\\\\([\\da-f]{1,6}" + whitespace + "?|(" + whitespace + ")|.)", "ig");
-
-export {
-	process
-};
-
-function process(str) {
-	return str.replace(unescapeRegExp, (_, escaped, escapedWhitespace) => {
-		const high = "0x" + escaped - 0x10000;
-
-		// NaN means non-codepoint
-		// Workaround erroneous numeric interpretation of +"0x"
-		// eslint-disable-next-line no-self-compare
-		return high !== high || escapedWhitespace
-			? escaped
-			: high < 0
-				? // BMP codepoint
-				String.fromCharCode(high + 0x10000)
-				: // Supplemental Plane codepoint (surrogate pair)
-				String.fromCharCode((high >> 10) | 0xd800, (high & 0x3ff) | 0xdc00);
-	});
-}

+ 0 - 339
src/single-file/vendor/html-srcset-parser.js

@@ -1,339 +0,0 @@
-/*
- * The MIT License (MIT)
- * 
- * Author: Gildas Lormeau
- * 
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-
-// derived from https://github.com/albell/parse-srcset
-
-/**
- * Srcset Parser
- *
- * By Alex Bell |  MIT License
- *
- * JS Parser for the string value that appears in markup <img srcset="here">
- *
- * @returns Array [{url: _, d: _, w: _, h:_}, ...]
- *
- * Based super duper closely on the reference algorithm at:
- * https://html.spec.whatwg.org/multipage/embedded-content.html#parse-a-srcset-attribute
- *
- * Most comments are copied in directly from the spec
- * (except for comments in parens).
- */
-
-export {
-	process
-};
-
-// 1. Let input be the value passed to this algorithm.
-function process(input) {
-
-	// UTILITY FUNCTIONS
-
-	// Manual is faster than RegEx
-	// http://bjorn.tipling.com/state-and-regular-expressions-in-javascript
-	// http://jsperf.com/whitespace-character/5
-	function isSpace(c) {
-		return (c === "\u0020" || // space
-			c === "\u0009" || // horizontal tab
-			c === "\u000A" || // new line
-			c === "\u000C" || // form feed
-			c === "\u000D");  // carriage return
-	}
-
-	function collectCharacters(regEx) {
-		let chars;
-		const match = regEx.exec(input.substring(pos));
-		if (match) {
-			chars = match[0];
-			pos += chars.length;
-			return chars;
-		}
-	}
-
-	const inputLength = input.length;
-
-	// (Don"t use \s, to avoid matching non-breaking space)
-	/* eslint-disable no-control-regex */
-	const regexLeadingSpaces = /^[ \t\n\r\u000c]+/;
-	const regexLeadingCommasOrSpaces = /^[, \t\n\r\u000c]+/;
-	const regexLeadingNotSpaces = /^[^ \t\n\r\u000c]+/;
-	const regexTrailingCommas = /[,]+$/;
-	const regexNonNegativeInteger = /^\d+$/;
-	/* eslint-enable no-control-regex */
-
-	// ( Positive or negative or unsigned integers or decimals, without or without exponents.
-	// Must include at least one digit.
-	// According to spec tests any decimal point must be followed by a digit.
-	// No leading plus sign is allowed.)
-	// https://html.spec.whatwg.org/multipage/infrastructure.html#valid-floating-point-number
-	const regexFloatingPoint = /^-?(?:[0-9]+|[0-9]*\.[0-9]+)(?:[eE][+-]?[0-9]+)?$/;
-
-	let url, descriptors, currentDescriptor, state, c,
-		// 2. Let position be a pointer into input, initially pointing at the start
-		//    of the string.
-		pos = 0;
-	// 3. Let candidates be an initially empty source set.
-	const candidates = [];
-
-	// 4. Splitting loop: Collect a sequence of characters that are space
-	//    characters or U+002C COMMA characters. If any U+002C COMMA characters
-	//    were collected, that is a parse error.		
-	while (true) { // eslint-disable-line no-constant-condition
-		collectCharacters(regexLeadingCommasOrSpaces);
-
-		// 5. If position is past the end of input, return candidates and abort these steps.
-		if (pos >= inputLength) {
-			return candidates; // (we"re done, this is the sole return path)
-		}
-
-		// 6. Collect a sequence of characters that are not space characters,
-		//    and let that be url.
-		url = collectCharacters(regexLeadingNotSpaces);
-
-		// 7. Let descriptors be a new empty list.
-		descriptors = [];
-
-		// 8. If url ends with a U+002C COMMA character (,), follow these substeps:
-		//		(1). Remove all trailing U+002C COMMA characters from url. If this removed
-		//         more than one character, that is a parse error.
-		if (url.slice(-1) === ",") {
-			url = url.replace(regexTrailingCommas, "");
-			// (Jump ahead to step 9 to skip tokenization and just push the candidate).
-			parseDescriptors();
-
-			//	Otherwise, follow these substeps:
-		} else {
-			tokenize();
-		} // (close else of step 8)
-
-		// 16. Return to the step labeled splitting loop.
-	} // (Close of big while loop.)
-
-	/**
-	 * Tokenizes descriptor properties prior to parsing
-	 * Returns undefined.
-	 */
-	function tokenize() {
-
-		// 8.1. Descriptor tokeniser: Skip whitespace
-		collectCharacters(regexLeadingSpaces);
-
-		// 8.2. Let current descriptor be the empty string.
-		currentDescriptor = "";
-
-		// 8.3. Let state be in descriptor.
-		state = "in descriptor";
-
-		while (true) { // eslint-disable-line no-constant-condition
-
-			// 8.4. Let c be the character at position.
-			c = input.charAt(pos);
-
-			//  Do the following depending on the value of state.
-			//  For the purpose of this step, "EOF" is a special character representing
-			//  that position is past the end of input.
-
-			// In descriptor
-			if (state === "in descriptor") {
-				// Do the following, depending on the value of c:
-
-				// Space character
-				// If current descriptor is not empty, append current descriptor to
-				// descriptors and let current descriptor be the empty string.
-				// Set state to after descriptor.
-				if (isSpace(c)) {
-					if (currentDescriptor) {
-						descriptors.push(currentDescriptor);
-						currentDescriptor = "";
-						state = "after descriptor";
-					}
-
-					// U+002C COMMA (,)
-					// Advance position to the next character in input. If current descriptor
-					// is not empty, append current descriptor to descriptors. Jump to the step
-					// labeled descriptor parser.
-				} else if (c === ",") {
-					pos += 1;
-					if (currentDescriptor) {
-						descriptors.push(currentDescriptor);
-					}
-					parseDescriptors();
-					return;
-
-					// U+0028 LEFT PARENTHESIS (()
-					// Append c to current descriptor. Set state to in parens.
-				} else if (c === "\u0028") {
-					currentDescriptor = currentDescriptor + c;
-					state = "in parens";
-
-					// EOF
-					// If current descriptor is not empty, append current descriptor to
-					// descriptors. Jump to the step labeled descriptor parser.
-				} else if (c === "") {
-					if (currentDescriptor) {
-						descriptors.push(currentDescriptor);
-					}
-					parseDescriptors();
-					return;
-
-					// Anything else
-					// Append c to current descriptor.
-				} else {
-					currentDescriptor = currentDescriptor + c;
-				}
-				// (end "in descriptor"
-
-				// In parens
-			} else if (state === "in parens") {
-
-				// U+0029 RIGHT PARENTHESIS ())
-				// Append c to current descriptor. Set state to in descriptor.
-				if (c === ")") {
-					currentDescriptor = currentDescriptor + c;
-					state = "in descriptor";
-
-					// EOF
-					// Append current descriptor to descriptors. Jump to the step labeled
-					// descriptor parser.
-				} else if (c === "") {
-					descriptors.push(currentDescriptor);
-					parseDescriptors();
-					return;
-
-					// Anything else
-					// Append c to current descriptor.
-				} else {
-					currentDescriptor = currentDescriptor + c;
-				}
-
-				// After descriptor
-			} else if (state === "after descriptor") {
-
-				// Do the following, depending on the value of c:
-				// Space character: Stay in this state.
-				if (isSpace(c)) {
-
-					// EOF: Jump to the step labeled descriptor parser.
-				} else if (c === "") {
-					parseDescriptors();
-					return;
-
-					// Anything else
-					// Set state to in descriptor. Set position to the previous character in input.
-				} else {
-					state = "in descriptor";
-					pos -= 1;
-
-				}
-			}
-
-			// Advance position to the next character in input.
-			pos += 1;
-
-			// Repeat this step.
-		} // (close while true loop)
-	}
-
-	/**
-	 * Adds descriptor properties to a candidate, pushes to the candidates array
-	 * @return undefined
-	 */
-	// Declared outside of the while loop so that it"s only created once.
-	function parseDescriptors() {
-
-		// 9. Descriptor parser: Let error be no.
-		let pError = false,
-
-			// 10. Let width be absent.
-			// 11. Let density be absent.
-			// 12. Let future-compat-h be absent. (We"re implementing it now as h)
-			w, d, h, i,
-			desc, lastChar, value, intVal, floatVal;
-		const candidate = {};
-
-		// 13. For each descriptor in descriptors, run the appropriate set of steps
-		// from the following list:
-		for (i = 0; i < descriptors.length; i++) {
-			desc = descriptors[i];
-
-			lastChar = desc[desc.length - 1];
-			value = desc.substring(0, desc.length - 1);
-			intVal = parseInt(value, 10);
-			floatVal = parseFloat(value);
-
-			// If the descriptor consists of a valid non-negative integer followed by
-			// a U+0077 LATIN SMALL LETTER W character
-			if (regexNonNegativeInteger.test(value) && (lastChar === "w")) {
-
-				// If width and density are not both absent, then let error be yes.
-				if (w || d) { pError = true; }
-
-				// Apply the rules for parsing non-negative integers to the descriptor.
-				// If the result is zero, let error be yes.
-				// Otherwise, let width be the result.
-				if (intVal === 0) { pError = true; } else { w = intVal; }
-
-				// If the descriptor consists of a valid floating-point number followed by
-				// a U+0078 LATIN SMALL LETTER X character
-			} else if (regexFloatingPoint.test(value) && (lastChar === "x")) {
-
-				// If width, density and future-compat-h are not all absent, then let error
-				// be yes.
-				if (w || d || h) { pError = true; }
-
-				// Apply the rules for parsing floating-point number values to the descriptor.
-				// If the result is less than zero, let error be yes. Otherwise, let density
-				// be the result.
-				if (floatVal < 0) { pError = true; } else { d = floatVal; }
-
-				// If the descriptor consists of a valid non-negative integer followed by
-				// a U+0068 LATIN SMALL LETTER H character
-			} else if (regexNonNegativeInteger.test(value) && (lastChar === "h")) {
-
-				// If height and density are not both absent, then let error be yes.
-				if (h || d) { pError = true; }
-
-				// Apply the rules for parsing non-negative integers to the descriptor.
-				// If the result is zero, let error be yes. Otherwise, let future-compat-h
-				// be the result.
-				if (intVal === 0) { pError = true; } else { h = intVal; }
-
-				// Anything else, Let error be yes.
-			} else { pError = true; }
-		} // (close step 13 for loop)
-
-		// 15. If error is still no, then append a new image source to candidates whose
-		// URL is url, associated with a width width if not absent and a pixel
-		// density density if not absent. Otherwise, there is a parse error.
-		if (!pError) {
-			candidate.url = url;
-			if (w) { candidate.w = w; }
-			if (d) { candidate.d = d; }
-			if (h) { candidate.h = h; }
-			candidates.push(candidate);
-		} else if (console && console.log) {  // eslint-disable-line no-console
-			console.log("Invalid srcset descriptor found in \"" + input + "\" at \"" + desc + "\"."); // eslint-disable-line no-console
-		}
-	} // (close parseDescriptors fn)
-
-}

+ 0 - 40
src/single-file/vendor/index.js

@@ -1,40 +0,0 @@
-/*
- * Copyright 2010-2020 Gildas Lormeau
- * contact : gildas.lormeau <at> gmail.com
- * 
- * This file is part of SingleFile.
- *
- *   The code in this file is free software: you can redistribute it and/or 
- *   modify it under the terms of the GNU Affero General Public License 
- *   (GNU AGPL) as published by the Free Software Foundation, either version 3
- *   of the License, or (at your option) any later version.
- * 
- *   The code in this file is distributed in the hope that it will be useful, 
- *   but WITHOUT ANY WARRANTY; without even the implied warranty of 
- *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero 
- *   General Public License for more details.
- *
- *   As additional permission under GNU AGPL version 3 section 7, you may 
- *   distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU 
- *   AGPL normally required by section 4, provided you include this license 
- *   notice and a URL through which recipients can access the Corresponding 
- *   Source.
- */
-
-import * as fontPropertyParser from "./css-font-property-parser.js";
-import * as mediaQueryParser from "./css-media-query-parser.js";
-import * as cssMinifier from "./css-minifier.js";
-import * as cssTree from "./css-tree.js";
-import * as cssUnescape from "./css-unescape.js";
-import * as srcsetParser from "./html-srcset-parser";
-import { MIMEType } from "./mime-type-parser";
-
-export {
-	fontPropertyParser,
-	mediaQueryParser,
-	cssMinifier,
-	cssTree,
-	cssUnescape,
-	srcsetParser,
-	MIMEType
-};

+ 0 - 446
src/single-file/vendor/mime-type-parser.js

@@ -1,446 +0,0 @@
-/*
- * The MIT License (MIT)
- * 
- * Author: Gildas Lormeau
- * 
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
-
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
-
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-
-// derived from https://github.com/jsdom/whatwg-mimetype
-
-/* 
- * Copyright © 2017–2018 Domenic Denicola <d@domenic.me>
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
-
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
-
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-
-let utils, parser, serializer, MIMEType;
-
-// lib/utils.js
-{
-	utils = {};
-	utils.removeLeadingAndTrailingHTTPWhitespace = string => {
-		return string.replace(/^[ \t\n\r]+/, "").replace(/[ \t\n\r]+$/, "");
-	};
-
-	utils.removeTrailingHTTPWhitespace = string => {
-		return string.replace(/[ \t\n\r]+$/, "");
-	};
-
-	utils.isHTTPWhitespaceChar = char => {
-		return char === " " || char === "\t" || char === "\n" || char === "\r";
-	};
-
-	utils.solelyContainsHTTPTokenCodePoints = string => {
-		return /^[-!#$%&'*+.^_`|~A-Za-z0-9]*$/.test(string);
-	};
-
-	utils.soleyContainsHTTPQuotedStringTokenCodePoints = string => {
-		return /^[\t\u0020-\u007E\u0080-\u00FF]*$/.test(string);
-	};
-
-	utils.asciiLowercase = string => {
-		return string.replace(/[A-Z]/g, l => l.toLowerCase());
-	};
-
-	// This variant only implements it with the extract-value flag set.
-	utils.collectAnHTTPQuotedString = (input, position) => {
-		let value = "";
-
-		position++;
-
-		// eslint-disable-next-line no-constant-condition
-		while (true) {
-			while (position < input.length && input[position] !== "\"" && input[position] !== "\\") {
-				value += input[position];
-				++position;
-			}
-
-			if (position >= input.length) {
-				break;
-			}
-
-			const quoteOrBackslash = input[position];
-			++position;
-
-			if (quoteOrBackslash === "\\") {
-				if (position >= input.length) {
-					value += "\\";
-					break;
-				}
-
-				value += input[position];
-				++position;
-			} else {
-				break;
-			}
-		}
-
-		return [value, position];
-	};
-}
-
-// lib/serializer.js
-{
-	const { solelyContainsHTTPTokenCodePoints } = utils;
-	serializer = mimeType => {
-		let serialization = `${mimeType.type}/${mimeType.subtype}`;
-
-		if (mimeType.parameters.size === 0) {
-			return serialization;
-		}
-
-		for (let [name, value] of mimeType.parameters) {
-			serialization += ";";
-			serialization += name;
-			serialization += "=";
-
-			if (!solelyContainsHTTPTokenCodePoints(value) || value.length === 0) {
-				value = value.replace(/(["\\])/g, "\\$1");
-				value = `"${value}"`;
-			}
-
-			serialization += value;
-		}
-
-		return serialization;
-	};
-}
-
-// lib/parser.js
-{
-	const {
-		removeLeadingAndTrailingHTTPWhitespace,
-		removeTrailingHTTPWhitespace,
-		isHTTPWhitespaceChar,
-		solelyContainsHTTPTokenCodePoints,
-		soleyContainsHTTPQuotedStringTokenCodePoints,
-		asciiLowercase,
-		collectAnHTTPQuotedString
-	} = utils;
-
-	parser = input => {
-		input = removeLeadingAndTrailingHTTPWhitespace(input);
-
-		let position = 0;
-		let type = "";
-		while (position < input.length && input[position] !== "/") {
-			type += input[position];
-			++position;
-		}
-
-		if (type.length === 0 || !solelyContainsHTTPTokenCodePoints(type)) {
-			return null;
-		}
-
-		if (position >= input.length) {
-			return null;
-		}
-
-		// Skips past "/"
-		++position;
-
-		let subtype = "";
-		while (position < input.length && input[position] !== ";") {
-			subtype += input[position];
-			++position;
-		}
-
-		subtype = removeTrailingHTTPWhitespace(subtype);
-
-		if (subtype.length === 0 || !solelyContainsHTTPTokenCodePoints(subtype)) {
-			return null;
-		}
-
-		const mimeType = {
-			type: asciiLowercase(type),
-			subtype: asciiLowercase(subtype),
-			parameters: new Map()
-		};
-
-		while (position < input.length) {
-			// Skip past ";"
-			++position;
-
-			while (isHTTPWhitespaceChar(input[position])) {
-				++position;
-			}
-
-			let parameterName = "";
-			while (position < input.length && input[position] !== ";" && input[position] !== "=") {
-				parameterName += input[position];
-				++position;
-			}
-			parameterName = asciiLowercase(parameterName);
-
-			if (position < input.length) {
-				if (input[position] === ";") {
-					continue;
-				}
-
-				// Skip past "="
-				++position;
-			}
-
-			let parameterValue = null;
-			if (input[position] === "\"") {
-				[parameterValue, position] = collectAnHTTPQuotedString(input, position);
-
-				while (position < input.length && input[position] !== ";") {
-					++position;
-				}
-			} else {
-				parameterValue = "";
-				while (position < input.length && input[position] !== ";") {
-					parameterValue += input[position];
-					++position;
-				}
-
-				parameterValue = removeTrailingHTTPWhitespace(parameterValue);
-
-				if (parameterValue === "") {
-					continue;
-				}
-			}
-
-			if (parameterName.length > 0 &&
-				solelyContainsHTTPTokenCodePoints(parameterName) &&
-				soleyContainsHTTPQuotedStringTokenCodePoints(parameterValue) &&
-				!mimeType.parameters.has(parameterName)) {
-				mimeType.parameters.set(parameterName, parameterValue);
-			}
-		}
-
-		return mimeType;
-	};
-}
-
-// lib/mime-type.js
-{
-	const parse = parser;
-	const serialize = serializer;
-	const {
-		asciiLowercase,
-		solelyContainsHTTPTokenCodePoints,
-		soleyContainsHTTPQuotedStringTokenCodePoints
-	} = utils;
-
-	MIMEType = class MIMEType {
-		constructor(string) {
-			string = String(string);
-			const result = parse(string);
-			if (result === null) {
-				throw new Error(`Could not parse MIME type string "${string}"`);
-			}
-
-			this._type = result.type;
-			this._subtype = result.subtype;
-			this._parameters = new MIMETypeParameters(result.parameters);
-		}
-
-		static parse(string) {
-			try {
-				return new this(string);
-			} catch (e) {
-				return null;
-			}
-		}
-
-		get essence() {
-			return `${this.type}/${this.subtype}`;
-		}
-
-		get type() {
-			return this._type;
-		}
-
-		set type(value) {
-			value = asciiLowercase(String(value));
-
-			if (value.length === 0) {
-				throw new Error("Invalid type: must be a non-empty string");
-			}
-			if (!solelyContainsHTTPTokenCodePoints(value)) {
-				throw new Error(`Invalid type ${value}: must contain only HTTP token code points`);
-			}
-
-			this._type = value;
-		}
-
-		get subtype() {
-			return this._subtype;
-		}
-
-		set subtype(value) {
-			value = asciiLowercase(String(value));
-
-			if (value.length === 0) {
-				throw new Error("Invalid subtype: must be a non-empty string");
-			}
-			if (!solelyContainsHTTPTokenCodePoints(value)) {
-				throw new Error(`Invalid subtype ${value}: must contain only HTTP token code points`);
-			}
-
-			this._subtype = value;
-		}
-
-		get parameters() {
-			return this._parameters;
-		}
-
-		toString() {
-			// The serialize function works on both "MIME type records" (i.e. the results of parse) and on this class, since
-			// this class's interface is identical.
-			return serialize(this);
-		}
-
-		isJavaScript({ allowParameters = false } = {}) {
-			switch (this._type) {
-				case "text": {
-					switch (this._subtype) {
-						case "ecmascript":
-						case "javascript":
-						case "javascript1.0":
-						case "javascript1.1":
-						case "javascript1.2":
-						case "javascript1.3":
-						case "javascript1.4":
-						case "javascript1.5":
-						case "jscript":
-						case "livescript":
-						case "x-ecmascript":
-						case "x-javascript": {
-							return allowParameters || this._parameters.size === 0;
-						}
-						default: {
-							return false;
-						}
-					}
-				}
-				case "application": {
-					switch (this._subtype) {
-						case "ecmascript":
-						case "javascript":
-						case "x-ecmascript":
-						case "x-javascript": {
-							return allowParameters || this._parameters.size === 0;
-						}
-						default: {
-							return false;
-						}
-					}
-				}
-				default: {
-					return false;
-				}
-			}
-		}
-		isXML() {
-			return (this._subtype === "xml" && (this._type === "text" || this._type === "application")) ||
-				this._subtype.endsWith("+xml");
-		}
-		isHTML() {
-			return this._subtype === "html" && this._type === "text";
-		}
-	};
-
-	class MIMETypeParameters {
-		constructor(map) {
-			this._map = map;
-		}
-
-		get size() {
-			return this._map.size;
-		}
-
-		get(name) {
-			name = asciiLowercase(String(name));
-			return this._map.get(name);
-		}
-
-		has(name) {
-			name = asciiLowercase(String(name));
-			return this._map.has(name);
-		}
-
-		set(name, value) {
-			name = asciiLowercase(String(name));
-			value = String(value);
-
-			if (!solelyContainsHTTPTokenCodePoints(name)) {
-				throw new Error(`Invalid MIME type parameter name "${name}": only HTTP token code points are valid.`);
-			}
-			if (!soleyContainsHTTPQuotedStringTokenCodePoints(value)) {
-				throw new Error(`Invalid MIME type parameter value "${value}": only HTTP quoted-string token code points are valid.`);
-			}
-
-			return this._map.set(name, value);
-		}
-
-		clear() {
-			this._map.clear();
-		}
-
-		delete(name) {
-			name = asciiLowercase(String(name));
-			return this._map.delete(name);
-		}
-
-		forEach(callbackFn, thisArg) {
-			this._map.forEach(callbackFn, thisArg);
-		}
-
-		keys() {
-			return this._map.keys();
-		}
-
-		values() {
-			return this._map.values();
-		}
-
-		entries() {
-			return this._map.entries();
-		}
-
-		[Symbol.iterator]() {
-			return this._map[Symbol.iterator]();
-		}
-	}
-
-}
-
-export {
-	MIMEType
-};

Niektóre pliki nie zostały wyświetlone z powodu dużej ilości zmienionych plików