single-file-browser.js 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. /*
  2. * Copyright 2010-2019 Gildas Lormeau
  3. * contact : gildas.lormeau <at> gmail.com
  4. *
  5. * This file is part of SingleFile.
  6. *
  7. * SingleFile is free software: you can redistribute it and/or modify
  8. * it under the terms of the GNU Lesser General Public License as published by
  9. * the Free Software Foundation, either version 3 of the License, or
  10. * (at your option) any later version.
  11. *
  12. * SingleFile is distributed in the hope that it will be useful,
  13. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. * GNU Lesser General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU Lesser General Public License
  18. * along with SingleFile. If not, see <http://www.gnu.org/licenses/>.
  19. */
  20. /* global
  21. URL,
  22. docUtil,
  23. cssTree,
  24. docHelper,
  25. crypto,
  26. fetch,
  27. setTimeout,
  28. superFetch,
  29. Blob,
  30. DOMParser,
  31. FileReader,
  32. FontFace
  33. SingleFileCore,
  34. TextDecoder,
  35. TextEncoder */
  36. this.SingleFileBrowser = this.SingleFileBrowser || (() => {
  37. const FONT_FACE_TEST_MAX_DELAY = 1000;
  38. const modules = {
  39. srcsetParser: this.srcsetParser,
  40. cssMinifier: this.cssMinifier,
  41. docHelper: docHelper,
  42. htmlMinifier: this.htmlMinifier,
  43. serializer: this.serializer,
  44. fontsMinifier: this.fontsMinifier && this.fontsMinifier.getInstance(cssTree, this.fontPropertyParser, docHelper),
  45. fontsAltMinifier: this.fontsAltMinifier && this.fontsAltMinifier.getInstance(cssTree),
  46. cssRulesMinifier: this.cssRulesMinifier && this.cssRulesMinifier.getInstance(cssTree),
  47. matchedRules: this.matchedRules && this.matchedRules.getInstance(cssTree),
  48. mediasMinifier: this.mediasMinifier && this.mediasMinifier.getInstance(cssTree, this.mediaQueryParser),
  49. imagesAltMinifier: this.imagesAltMinifier && this.imagesAltMinifier.getInstance(this.srcsetParser)
  50. };
  51. const domUtil = {
  52. getResourceContent,
  53. parseDocContent,
  54. parseSVGContent,
  55. isValidFontUrl,
  56. getContentSize,
  57. digestText,
  58. parseURL
  59. };
  60. const SingleFile = SingleFileCore.getClass(docUtil.getInstance(modules, domUtil), cssTree);
  61. let fetchResource;
  62. return {
  63. getClass: () => SingleFile
  64. };
  65. async function getResourceContent(resourceURL) {
  66. if (!fetchResource) {
  67. fetchResource = typeof superFetch == "undefined" ? fetch : superFetch.fetch;
  68. }
  69. const resourceContent = await fetchResource(resourceURL);
  70. const buffer = await resourceContent.arrayBuffer();
  71. return {
  72. getUrl() {
  73. return resourceContent.url || resourceURL;
  74. },
  75. getContentType() {
  76. return resourceContent.headers && resourceContent.headers.get("content-type");
  77. },
  78. getStatusCode() {
  79. return resourceContent.status;
  80. },
  81. getSize() {
  82. return buffer.byteLength;
  83. },
  84. getText(charset) {
  85. return new TextDecoder(charset).decode(buffer);
  86. },
  87. async getDataUri(contentType) {
  88. const reader = new FileReader();
  89. reader.readAsDataURL(new Blob([buffer], { type: contentType || this.getContentType() }));
  90. return await new Promise((resolve, reject) => {
  91. reader.addEventListener("load", () => resolve(reader.result), false);
  92. reader.addEventListener("error", reject, false);
  93. });
  94. }
  95. };
  96. }
  97. // https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/digest
  98. function hex(buffer) {
  99. const hexCodes = [];
  100. const view = new DataView(buffer);
  101. for (let i = 0; i < view.byteLength; i += 4) {
  102. const value = view.getUint32(i);
  103. const stringValue = value.toString(16);
  104. const padding = "00000000";
  105. const paddedValue = (padding + stringValue).slice(-padding.length);
  106. hexCodes.push(paddedValue);
  107. }
  108. return hexCodes.join("");
  109. }
  110. function parseDocContent(content) {
  111. return (new DOMParser()).parseFromString(content, "text/html");
  112. }
  113. function parseSVGContent(content) {
  114. return (new DOMParser()).parseFromString(content, "image/svg+xml");
  115. }
  116. async function digestText(algo, text) {
  117. const hash = await crypto.subtle.digest(algo, new TextEncoder("utf-8").encode(text));
  118. return hex(hash);
  119. }
  120. function getContentSize(content) {
  121. return new Blob([content]).size;
  122. }
  123. async function isValidFontUrl(urlFunction) {
  124. try {
  125. const font = new FontFace("font-test", urlFunction);
  126. await Promise.race([font.load(), new Promise(resolve => setTimeout(() => resolve(true), FONT_FACE_TEST_MAX_DELAY))]);
  127. return true;
  128. } catch (error) {
  129. return false;
  130. }
  131. }
  132. function parseURL(resourceURL, baseURI) {
  133. return new URL(resourceURL, baseURI);
  134. }
  135. })();