autosave.js 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263
  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, URL, Blob */
  24. import * as config from "./config.js";
  25. import * as business from "./business.js";
  26. import * as companion from "./companion.js";
  27. import * as downloads from "./downloads.js";
  28. import * as tabsData from "./tabs-data.js";
  29. import * as ui from "./../../ui/bg/index.js";
  30. import { getPageData } from "./../../index.js";
  31. import * as woleet from "./../../lib/woleet/woleet.js";
  32. import { autoSaveIsEnabled } from "./autosave-util.js";
  33. import { enableReferrerOnError } from "./requests.js";
  34. import { fetchResource } from "./../../lib/single-file/fetch/bg/fetch.js";
  35. const pendingMessages = {};
  36. const replacedTabIds = {};
  37. export {
  38. onMessage,
  39. onMessageExternal,
  40. onInit,
  41. onTabUpdated,
  42. onTabRemoved,
  43. onTabDiscarded,
  44. onTabReplaced
  45. };
  46. async function onMessage(message, sender) {
  47. if (message.method.endsWith(".save")) {
  48. if (message.autoSaveDiscard || message.autoSaveRemove) {
  49. if (sender.tab) {
  50. message.tab = sender.tab;
  51. pendingMessages[sender.tab.id] = message;
  52. } else if (pendingMessages[message.tabId] &&
  53. ((pendingMessages[message.tabId].removed && message.autoSaveRemove) ||
  54. (pendingMessages[message.tabId].discarded && message.autoSaveDiscard))
  55. ) {
  56. delete pendingMessages[message.tabId];
  57. await saveContent(message, { id: message.tabId, index: message.tabIndex, url: sender.url });
  58. }
  59. if (message.autoSaveUnload) {
  60. delete pendingMessages[message.tabId];
  61. await saveContent(message, sender.tab);
  62. }
  63. } else {
  64. delete pendingMessages[message.tabId];
  65. await saveContent(message, sender.tab);
  66. }
  67. return {};
  68. }
  69. }
  70. function onTabUpdated(tabId) {
  71. delete pendingMessages[tabId];
  72. }
  73. async function onTabRemoved(tabId) {
  74. const message = pendingMessages[tabId];
  75. if (message) {
  76. if (message.autoSaveRemove) {
  77. delete pendingMessages[tabId];
  78. await saveContent(message, message.tab);
  79. }
  80. } else {
  81. pendingMessages[tabId] = { removed: true };
  82. }
  83. }
  84. async function onTabDiscarded(tabId) {
  85. const message = pendingMessages[tabId];
  86. if (message) {
  87. delete pendingMessages[tabId];
  88. await saveContent(message, message.tab);
  89. } else {
  90. pendingMessages[tabId] = { discarded: true };
  91. }
  92. }
  93. async function onTabReplaced(addedTabId, removedTabId) {
  94. if (pendingMessages[removedTabId] && !pendingMessages[addedTabId]) {
  95. pendingMessages[addedTabId] = pendingMessages[removedTabId];
  96. delete pendingMessages[removedTabId];
  97. replacedTabIds[removedTabId] = addedTabId;
  98. }
  99. }
  100. async function onMessageExternal(message, currentTab) {
  101. if (message.method == "enableAutoSave") {
  102. const allTabsData = await tabsData.get(currentTab.id);
  103. allTabsData[currentTab.id].autoSave = message.enabled;
  104. await tabsData.set(allTabsData);
  105. ui.refreshTab(currentTab);
  106. }
  107. if (message.method == "isAutoSaveEnabled") {
  108. return autoSaveIsEnabled(currentTab);
  109. }
  110. }
  111. async function onInit(tab) {
  112. const [options, autoSaveEnabled] = await Promise.all([config.getOptions(tab.url, true), autoSaveIsEnabled(tab)]);
  113. if (options && ((options.autoSaveLoad || options.autoSaveLoadOrUnload) && autoSaveEnabled)) {
  114. business.saveTabs([tab], { autoSave: true });
  115. }
  116. }
  117. async function saveContent(message, tab) {
  118. const tabId = tab.id;
  119. const options = await config.getOptions(tab.url, true);
  120. if (options) {
  121. ui.onStart(tabId, 1, true);
  122. options.content = message.content;
  123. options.url = message.url;
  124. options.frames = message.frames;
  125. options.canvases = message.canvases;
  126. options.fonts = message.fonts;
  127. options.stylesheets = message.stylesheets;
  128. options.images = message.images;
  129. options.posters = message.posters;
  130. options.videos = message.videos;
  131. options.usedFonts = message.usedFonts;
  132. options.shadowRoots = message.shadowRoots;
  133. options.referrer = message.referrer;
  134. options.updatedResources = message.updatedResources;
  135. options.adoptedStyleSheets = message.adoptedStyleSheets;
  136. options.visitDate = new Date(message.visitDate);
  137. options.backgroundTab = true;
  138. options.autoSave = true;
  139. options.incognito = tab.incognito;
  140. options.tabId = tabId;
  141. options.tabIndex = tab.index;
  142. options.keepFilename = options.saveToGDrive || options.saveToGitHub || options.saveWithWebDAV || options.saveToDropbox || options.saveToRestFormApi;
  143. let pageData;
  144. try {
  145. if (options.autoSaveExternalSave) {
  146. await companion.externalSave(options);
  147. } else {
  148. if (options.passReferrerOnError) {
  149. enableReferrerOnError();
  150. }
  151. options.tabId = tabId;
  152. pageData = await getPageData(options, null, null, { fetch });
  153. let skipped;
  154. if (!options.saveToGDrive && !options.saveWithWebDAV && !options.saveToGitHub && !options.saveToDropbox && !options.saveWithCompanion && !options.saveToRestFormApi) {
  155. const testSkip = await downloads.testSkipSave(pageData.filename, options);
  156. skipped = testSkip.skipped;
  157. options.filenameConflictAction = testSkip.filenameConflictAction;
  158. }
  159. if (!skipped) {
  160. let { content, mimeType: type } = pageData;
  161. if (options.compressContent) {
  162. content = new Blob([new Uint8Array(content)], { type });
  163. }
  164. if (options.saveToGDrive) {
  165. if (!(content instanceof Blob)) {
  166. content = new Blob([content], { type });
  167. }
  168. await downloads.saveToGDrive(message.taskId, downloads.encodeSharpCharacter(pageData.filename), content, options, {
  169. forceWebAuthFlow: options.forceWebAuthFlow
  170. }, {
  171. filenameConflictAction: options.filenameConflictAction
  172. });
  173. } if (options.saveToDropbox) {
  174. if (!(content instanceof Blob)) {
  175. content = new Blob([content], { type });
  176. }
  177. await downloads.saveToDropbox(message.taskId, downloads.encodeSharpCharacter(pageData.filename), content, {
  178. filenameConflictAction: options.filenameConflictAction
  179. });
  180. } else if (options.saveWithWebDAV) {
  181. await downloads.saveWithWebDAV(message.taskId, downloads.encodeSharpCharacter(pageData.filename), content, options.webDAVURL, options.webDAVUser, options.webDAVPassword, {
  182. filenameConflictAction: options.filenameConflictAction
  183. });
  184. } else if (options.saveToGitHub) {
  185. await (await downloads.saveToGitHub(message.taskId, downloads.encodeSharpCharacter(pageData.filename), content, options.githubToken, options.githubUser, options.githubRepository, options.githubBranch, {
  186. filenameConflictAction: options.filenameConflictAction
  187. })).pushPromise;
  188. } else if (options.saveWithCompanion && !options.compressContent) {
  189. await companion.save({
  190. filename: pageData.filename,
  191. content: pageData.content,
  192. filenameConflictAction: options.filenameConflictAction
  193. });
  194. } else if (options.saveToRestFormApi) {
  195. await downloads.saveToRestFormApi(
  196. message.taskId,
  197. content,
  198. options.url,
  199. options.saveToRestFormApiToken,
  200. options.saveToRestFormApiUrl,
  201. options.saveToRestFormApiFileFieldName,
  202. options.saveToRestFormApiUrlFieldName
  203. );
  204. } else {
  205. if (!(content instanceof Blob)) {
  206. content = new Blob([content], { type });
  207. }
  208. pageData.url = URL.createObjectURL(content);
  209. await downloads.downloadPage(pageData, options);
  210. }
  211. if (options.openSavedPage) {
  212. const createTabProperties = { active: true, url: "/src/ui/pages/viewer.html?compressed=true&blobURI=" + URL.createObjectURL(content), windowId: tab.windowId };
  213. const index = tab.index;
  214. try {
  215. await browser.tabs.get(tabId);
  216. createTabProperties.index = index + 1;
  217. } catch (error) {
  218. createTabProperties.index = index;
  219. }
  220. browser.tabs.create(createTabProperties);
  221. }
  222. if (pageData.hash) {
  223. await woleet.anchor(pageData.hash, options.woleetKey);
  224. }
  225. }
  226. }
  227. } finally {
  228. if (message.taskId) {
  229. business.onSaveEnd(message.taskId);
  230. } else if (options.autoClose) {
  231. browser.tabs.remove(replacedTabIds[tabId] || tabId);
  232. delete replacedTabIds[tabId];
  233. }
  234. if (pageData && pageData.url) {
  235. URL.revokeObjectURL(pageData.url);
  236. }
  237. ui.onEnd(tabId, true);
  238. }
  239. }
  240. }
  241. async function fetch(url, options = {}) {
  242. const response = await fetchResource(url, options);
  243. return {
  244. status: response.status,
  245. headers: {
  246. get: name => response.headers.get(name)
  247. },
  248. arrayBuffer: () => response.arrayBuffer
  249. };
  250. }