download.js 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  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, SHARE_SELECTION_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. SHARE_SELECTION_BUTTON_MESSAGE = browser.i18n.getMessage("topPanelShareSelectionButton");
  32. ERROR_TITLE_MESSAGE = browser.i18n.getMessage("topPanelError");
  33. } catch (error) {
  34. // ignored
  35. }
  36. let sharePageBar;
  37. setLabels({
  38. EMBEDDED_IMAGE_BUTTON_MESSAGE,
  39. SHARE_PAGE_BUTTON_MESSAGE,
  40. SHARE_SELECTION_BUTTON_MESSAGE,
  41. ERROR_TITLE_MESSAGE
  42. });
  43. export {
  44. downloadPage,
  45. downloadPageForeground
  46. };
  47. async function downloadPage(pageData, options) {
  48. if (options.includeBOM) {
  49. pageData.content = "\ufeff" + pageData.content;
  50. }
  51. const embeddedImage = options.embeddedImage;
  52. const message = {
  53. method: "downloads.download",
  54. taskId: options.taskId,
  55. insertTextBody: options.insertTextBody,
  56. confirmFilename: options.confirmFilename,
  57. filenameConflictAction: options.filenameConflictAction,
  58. filename: pageData.filename,
  59. saveToClipboard: options.saveToClipboard,
  60. saveToGDrive: options.saveToGDrive,
  61. saveToDropbox: options.saveToDropbox,
  62. saveWithWebDAV: options.saveWithWebDAV,
  63. webDAVURL: options.webDAVURL,
  64. webDAVUser: options.webDAVUser,
  65. webDAVPassword: options.webDAVPassword,
  66. saveToGitHub: options.saveToGitHub,
  67. githubToken: options.githubToken,
  68. githubUser: options.githubUser,
  69. githubRepository: options.githubRepository,
  70. githubBranch: options.githubBranch,
  71. saveWithCompanion: options.saveWithCompanion,
  72. forceWebAuthFlow: options.forceWebAuthFlow,
  73. filenameReplacementCharacter: options.filenameReplacementCharacter,
  74. openEditor: options.openEditor,
  75. openSavedPage: options.openSavedPage,
  76. compressHTML: options.compressHTML,
  77. backgroundSave: options.backgroundSave,
  78. bookmarkId: options.bookmarkId,
  79. replaceBookmarkURL: options.replaceBookmarkURL,
  80. applySystemTheme: options.applySystemTheme,
  81. defaultEditorMode: options.defaultEditorMode,
  82. includeInfobar: options.includeInfobar,
  83. warnUnsavedPage: options.warnUnsavedPage,
  84. createRootDirectory: options.createRootDirectory,
  85. selfExtractingArchive: options.selfExtractingArchive,
  86. embeddedImage: embeddedImage ? Array.from(embeddedImage) : null,
  87. preventAppendedData: options.preventAppendedData,
  88. extractDataFromPage: options.extractDataFromPage,
  89. insertCanonicalLink: options.insertCanonicalLink,
  90. insertMetaNoIndex: options.insertMetaNoIndex,
  91. insertMetaCSP: options.insertMetaCSP,
  92. password: options.password,
  93. compressContent: options.compressContent,
  94. foregroundSave: options.foregroundSave,
  95. sharePage: options.sharePage
  96. };
  97. if (options.compressContent) {
  98. const blob = new Blob([await yabson.serialize(pageData)], { type: "application/octet-stream" });
  99. const blobURL = URL.createObjectURL(blob);
  100. message.blobURL = blobURL;
  101. const result = await browser.runtime.sendMessage(message);
  102. URL.revokeObjectURL(blobURL);
  103. if (result.error) {
  104. message.embeddedImage = embeddedImage;
  105. message.blobURL = null;
  106. message.pageData = pageData;
  107. let data, indexData = 0;
  108. const dataArray = await yabson.serialize(message);
  109. do {
  110. data = Array.from(dataArray.slice(indexData, indexData + MAX_CONTENT_SIZE));
  111. indexData += MAX_CONTENT_SIZE;
  112. await browser.runtime.sendMessage({
  113. method: "downloads.download",
  114. compressContent: true,
  115. data
  116. });
  117. } while (data.length);
  118. await browser.runtime.sendMessage({
  119. method: "downloads.download",
  120. compressContent: true
  121. });
  122. }
  123. if (options.backgroundSave) {
  124. await browser.runtime.sendMessage({ method: "downloads.end", taskId: options.taskId });
  125. }
  126. } else {
  127. if ((options.backgroundSave && !options.sharePage) || options.openEditor || options.saveToGDrive || options.saveToGitHub || options.saveWithCompanion || options.saveWithWebDAV || options.saveToDropbox) {
  128. const blobURL = URL.createObjectURL(new Blob([pageData.content], { type: "text/html" }));
  129. message.blobURL = blobURL;
  130. const result = await browser.runtime.sendMessage(message);
  131. URL.revokeObjectURL(blobURL);
  132. if (result.error) {
  133. message.blobURL = null;
  134. for (let blockIndex = 0; blockIndex * MAX_CONTENT_SIZE < pageData.content.length; blockIndex++) {
  135. message.truncated = pageData.content.length > MAX_CONTENT_SIZE;
  136. if (message.truncated) {
  137. message.finished = (blockIndex + 1) * MAX_CONTENT_SIZE > pageData.content.length;
  138. message.content = pageData.content.substring(blockIndex * MAX_CONTENT_SIZE, (blockIndex + 1) * MAX_CONTENT_SIZE);
  139. } else {
  140. message.content = pageData.content;
  141. }
  142. await browser.runtime.sendMessage(message);
  143. }
  144. }
  145. } else {
  146. if (options.saveToClipboard) {
  147. saveToClipboard(pageData);
  148. } else {
  149. await downloadPageForeground(pageData, options);
  150. }
  151. if (options.openSavedPage) {
  152. open(URL.createObjectURL(new Blob([pageData.content], { type: "text/html" })));
  153. }
  154. browser.runtime.sendMessage({ method: "ui.processEnd" });
  155. }
  156. await browser.runtime.sendMessage({ method: "downloads.end", taskId: options.taskId, hash: pageData.hash, woleetKey: options.woleetKey });
  157. }
  158. }
  159. async function downloadPageForeground(pageData, options) {
  160. if (options.sharePage && navigator.share) {
  161. await sharePage(pageData, options);
  162. } else {
  163. if (pageData.filename && pageData.filename.length) {
  164. const link = document.createElement("a");
  165. link.download = pageData.filename;
  166. link.href = URL.createObjectURL(new Blob([pageData.content], { type: "text/html" }));
  167. link.dispatchEvent(new MouseEvent("click"));
  168. return new Promise(resolve => setTimeout(() => { URL.revokeObjectURL(link.href); resolve(); }, 1000));
  169. }
  170. }
  171. }
  172. async function sharePage(pageData, options) {
  173. sharePageBar = getSharePageBar();
  174. debugger;
  175. const cancelled = await sharePageBar.display(options.selected);
  176. if (!cancelled) {
  177. const data = { files: [new File([pageData.content], pageData.filename, { type: "text/html" })] };
  178. try {
  179. await navigator.share(data);
  180. sharePageBar.hide();
  181. } catch (error) {
  182. sharePageBar.hide();
  183. if (error.name === "AbortError") {
  184. await sharePage(pageData, options);
  185. } else {
  186. throw error;
  187. }
  188. }
  189. }
  190. }
  191. function saveToClipboard(page) {
  192. const command = "copy";
  193. document.addEventListener(command, listener);
  194. document.execCommand(command);
  195. document.removeEventListener(command, listener);
  196. function listener(event) {
  197. event.clipboardData.setData("text/html", page.content);
  198. event.clipboardData.setData("text/plain", page.content);
  199. event.preventDefault();
  200. }
  201. }