download.js 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  1. /*
  2. * Copyright 2010-2020 Gildas Lormeau
  3. * contact : gildas.lormeau <at> gmail.com
  4. *
  5. * This file is part of SingleFile.
  6. *
  7. * The code in this file is free software: you can redistribute it and/or
  8. * modify it under the terms of the GNU Affero General Public License
  9. * (GNU AGPL) as published by the Free Software Foundation, either version 3
  10. * of the License, or (at your option) any later version.
  11. *
  12. * The code in this file is distributed in the hope that it will be useful,
  13. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
  15. * General Public License for more details.
  16. *
  17. * As additional permission under GNU AGPL version 3 section 7, you may
  18. * distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU
  19. * AGPL normally required by section 4, provided you include this license
  20. * notice and a URL through which recipients can access the Corresponding
  21. * Source.
  22. */
  23. /* global browser, document, URL, Blob, MouseEvent, setTimeout, open, navigator, File */
  24. import * as yabson from "./../../lib/yabson/yabson.js";
  25. import { getSharePageBar, setLabels } from "./../../ui/common/common-content-ui.js";
  26. const MAX_CONTENT_SIZE = 16 * (1024 * 1024);
  27. let EMBEDDED_IMAGE_BUTTON_MESSAGE, SHARE_PAGE_BUTTON_MESSAGE, ERROR_TITLE_MESSAGE;
  28. try {
  29. EMBEDDED_IMAGE_BUTTON_MESSAGE = browser.i18n.getMessage("topPanelEmbeddedImageButton");
  30. SHARE_PAGE_BUTTON_MESSAGE = browser.i18n.getMessage("topPanelSharePageButton");
  31. ERROR_TITLE_MESSAGE = browser.i18n.getMessage("topPanelError");
  32. } catch (error) {
  33. // ignored
  34. }
  35. let sharePageBar;
  36. setLabels({
  37. EMBEDDED_IMAGE_BUTTON_MESSAGE,
  38. SHARE_PAGE_BUTTON_MESSAGE,
  39. ERROR_TITLE_MESSAGE
  40. });
  41. export {
  42. downloadPage,
  43. downloadPageForeground
  44. };
  45. async function downloadPage(pageData, options) {
  46. if (options.includeBOM) {
  47. pageData.content = "\ufeff" + pageData.content;
  48. }
  49. const embeddedImage = options.embeddedImage;
  50. const message = {
  51. method: "downloads.download",
  52. taskId: options.taskId,
  53. insertTextBody: options.insertTextBody,
  54. confirmFilename: options.confirmFilename,
  55. filenameConflictAction: options.filenameConflictAction,
  56. filename: pageData.filename,
  57. saveToClipboard: options.saveToClipboard,
  58. saveToGDrive: options.saveToGDrive,
  59. saveToDropbox: options.saveToDropbox,
  60. saveWithWebDAV: options.saveWithWebDAV,
  61. webDAVURL: options.webDAVURL,
  62. webDAVUser: options.webDAVUser,
  63. webDAVPassword: options.webDAVPassword,
  64. saveToGitHub: options.saveToGitHub,
  65. githubToken: options.githubToken,
  66. githubUser: options.githubUser,
  67. githubRepository: options.githubRepository,
  68. githubBranch: options.githubBranch,
  69. saveWithCompanion: options.saveWithCompanion,
  70. forceWebAuthFlow: options.forceWebAuthFlow,
  71. filenameReplacementCharacter: options.filenameReplacementCharacter,
  72. openEditor: options.openEditor,
  73. openSavedPage: options.openSavedPage,
  74. compressHTML: options.compressHTML,
  75. backgroundSave: options.backgroundSave,
  76. bookmarkId: options.bookmarkId,
  77. replaceBookmarkURL: options.replaceBookmarkURL,
  78. applySystemTheme: options.applySystemTheme,
  79. defaultEditorMode: options.defaultEditorMode,
  80. includeInfobar: options.includeInfobar,
  81. warnUnsavedPage: options.warnUnsavedPage,
  82. createRootDirectory: options.createRootDirectory,
  83. selfExtractingArchive: options.selfExtractingArchive,
  84. embeddedImage: embeddedImage ? Array.from(embeddedImage) : null,
  85. preventAppendedData: options.preventAppendedData,
  86. extractDataFromPage: options.extractDataFromPage,
  87. insertCanonicalLink: options.insertCanonicalLink,
  88. insertMetaNoIndex: options.insertMetaNoIndex,
  89. insertMetaCSP: options.insertMetaCSP,
  90. password: options.password,
  91. compressContent: options.compressContent,
  92. foregroundSave: options.foregroundSave,
  93. sharePage: options.sharePage
  94. };
  95. if (options.compressContent) {
  96. const blob = new Blob([await yabson.serialize(pageData)], { type: "application/octet-stream" });
  97. const blobURL = URL.createObjectURL(blob);
  98. message.blobURL = blobURL;
  99. const result = await browser.runtime.sendMessage(message);
  100. URL.revokeObjectURL(blobURL);
  101. if (result.error) {
  102. message.embeddedImage = embeddedImage;
  103. message.blobURL = null;
  104. message.pageData = pageData;
  105. let data, indexData = 0;
  106. const dataArray = await yabson.serialize(message);
  107. do {
  108. data = Array.from(dataArray.slice(indexData, indexData + MAX_CONTENT_SIZE));
  109. indexData += MAX_CONTENT_SIZE;
  110. await browser.runtime.sendMessage({
  111. method: "downloads.download",
  112. compressContent: true,
  113. data
  114. });
  115. } while (data.length);
  116. await browser.runtime.sendMessage({
  117. method: "downloads.download",
  118. compressContent: true
  119. });
  120. }
  121. if (options.backgroundSave) {
  122. await browser.runtime.sendMessage({ method: "downloads.end", taskId: options.taskId });
  123. }
  124. } else {
  125. if ((options.backgroundSave && !options.sharePage) || options.openEditor || options.saveToGDrive || options.saveToGitHub || options.saveWithCompanion || options.saveWithWebDAV || options.saveToDropbox) {
  126. const blobURL = URL.createObjectURL(new Blob([pageData.content], { type: "text/html" }));
  127. message.blobURL = blobURL;
  128. const result = await browser.runtime.sendMessage(message);
  129. URL.revokeObjectURL(blobURL);
  130. if (result.error) {
  131. message.blobURL = null;
  132. for (let blockIndex = 0; blockIndex * MAX_CONTENT_SIZE < pageData.content.length; blockIndex++) {
  133. message.truncated = pageData.content.length > MAX_CONTENT_SIZE;
  134. if (message.truncated) {
  135. message.finished = (blockIndex + 1) * MAX_CONTENT_SIZE > pageData.content.length;
  136. message.content = pageData.content.substring(blockIndex * MAX_CONTENT_SIZE, (blockIndex + 1) * MAX_CONTENT_SIZE);
  137. } else {
  138. message.content = pageData.content;
  139. }
  140. await browser.runtime.sendMessage(message);
  141. }
  142. }
  143. } else {
  144. if (options.saveToClipboard) {
  145. saveToClipboard(pageData);
  146. } else {
  147. await downloadPageForeground(pageData, options);
  148. }
  149. if (options.openSavedPage) {
  150. open(URL.createObjectURL(new Blob([pageData.content], { type: "text/html" })));
  151. }
  152. browser.runtime.sendMessage({ method: "ui.processEnd" });
  153. }
  154. await browser.runtime.sendMessage({ method: "downloads.end", taskId: options.taskId, hash: pageData.hash, woleetKey: options.woleetKey });
  155. }
  156. }
  157. async function downloadPageForeground(pageData, options) {
  158. if (options.sharePage && navigator.share) {
  159. await sharePage(pageData);
  160. } else {
  161. if (pageData.filename && pageData.filename.length) {
  162. const link = document.createElement("a");
  163. link.download = pageData.filename;
  164. link.href = URL.createObjectURL(new Blob([pageData.content], { type: "text/html" }));
  165. link.dispatchEvent(new MouseEvent("click"));
  166. return new Promise(resolve => setTimeout(() => { URL.revokeObjectURL(link.href); resolve(); }, 1000));
  167. }
  168. }
  169. }
  170. async function sharePage(pageData) {
  171. sharePageBar = getSharePageBar();
  172. const cancelled = await sharePageBar.display();
  173. if (!cancelled) {
  174. const data = { files: [new File([pageData.content], pageData.filename)] };
  175. try {
  176. await navigator.share(data);
  177. sharePageBar.hide();
  178. } catch (error) {
  179. sharePageBar.hide();
  180. if (error.name === "AbortError") {
  181. await sharePage(pageData);
  182. } else {
  183. throw error;
  184. }
  185. }
  186. }
  187. }
  188. function saveToClipboard(page) {
  189. const command = "copy";
  190. document.addEventListener(command, listener);
  191. document.execCommand(command);
  192. document.removeEventListener(command, listener);
  193. function listener(event) {
  194. event.clipboardData.setData("text/html", page.content);
  195. event.clipboardData.setData("text/plain", page.content);
  196. event.preventDefault();
  197. }
  198. }