autosave.js 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  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 singlefile, URL, Blob, XMLHttpRequest, woleet */
  24. singlefile.extension.core.bg.autosave = (() => {
  25. return {
  26. onMessage,
  27. onMessageExternal,
  28. onInit,
  29. isEnabled,
  30. refreshTabs
  31. };
  32. async function onMessage(message, sender) {
  33. const ui = singlefile.extension.ui.bg.main;
  34. if (message.method.endsWith(".init")) {
  35. const [options, autoSaveEnabled] = await Promise.all([singlefile.extension.core.bg.config.getOptions(sender.tab.url, true), isEnabled(sender.tab)]);
  36. return { options, autoSaveEnabled };
  37. }
  38. if (message.method.endsWith(".save")) {
  39. const tabId = sender.tab.id;
  40. const options = await singlefile.extension.core.bg.config.getOptions(sender.tab.url, true);
  41. if (options) {
  42. if (options.autoClose) {
  43. singlefile.extension.core.bg.tabs.remove(tabId);
  44. }
  45. ui.onStart(tabId, 1, true);
  46. await saveContent(message, sender.tab);
  47. ui.onEnd(tabId, true);
  48. }
  49. return {};
  50. }
  51. }
  52. async function onMessageExternal(message, currentTab) {
  53. if (message.method == "enableAutoSave") {
  54. const tabsData = await singlefile.extension.core.bg.tabsData.get(currentTab.id);
  55. tabsData[currentTab.id].autoSave = message.enabled;
  56. await singlefile.extension.core.bg.tabsData.set(tabsData);
  57. singlefile.extension.ui.bg.main.refreshTab(currentTab);
  58. }
  59. if (message.method == "isAutoSaveEnabled") {
  60. return isEnabled(currentTab);
  61. }
  62. }
  63. async function onInit(tab) {
  64. const [options, autoSaveEnabled] = await Promise.all([singlefile.extension.core.bg.config.getOptions(tab.url, true), isEnabled(tab)]);
  65. if (options && ((options.autoSaveLoad || options.autoSaveLoadOrUnload) && autoSaveEnabled)) {
  66. singlefile.extension.core.bg.business.saveTabs([tab], { autoSave: true });
  67. }
  68. }
  69. async function isEnabled(tab) {
  70. const config = singlefile.extension.core.bg.config;
  71. if (tab) {
  72. const [tabsData, rule] = await Promise.all([singlefile.extension.core.bg.tabsData.get(), config.getRule(tab.url)]);
  73. return Boolean(tabsData.autoSaveAll ||
  74. (tabsData.autoSaveUnpinned && !tab.pinned) ||
  75. (tabsData[tab.id] && tabsData[tab.id].autoSave)) &&
  76. (!rule || rule.autoSaveProfile != config.DISABLED_PROFILE_NAME);
  77. }
  78. }
  79. async function refreshTabs() {
  80. const tabs = singlefile.extension.core.bg.tabs;
  81. const allTabs = (await singlefile.extension.core.bg.tabs.get({}));
  82. return Promise.all(allTabs.map(async tab => {
  83. const [options, autoSaveEnabled] = await Promise.all([singlefile.extension.core.bg.config.getOptions(tab.url, true), isEnabled(tab)]);
  84. try {
  85. await tabs.sendMessage(tab.id, { method: "content.init", autoSaveEnabled, options });
  86. } catch (error) {
  87. // ignored
  88. }
  89. }));
  90. }
  91. async function saveContent(message, tab) {
  92. const options = await singlefile.extension.core.bg.config.getOptions(tab.url, true);
  93. const tabId = tab.id;
  94. options.content = message.content;
  95. options.url = message.url;
  96. options.frames = message.frames;
  97. options.canvases = message.canvases;
  98. options.fonts = message.fonts;
  99. options.stylesheets = message.stylesheets;
  100. options.images = message.images;
  101. options.posters = message.posters;
  102. options.usedFonts = message.usedFonts;
  103. options.shadowRoots = message.shadowRoots;
  104. options.imports = message.imports;
  105. options.referrer = message.referrer;
  106. options.updatedResources = message.updatedResources;
  107. options.backgroundTab = true;
  108. options.autoSave = true;
  109. options.incognito = tab.incognito;
  110. options.tabId = tabId;
  111. options.tabIndex = tab.index;
  112. let pageData;
  113. try {
  114. if (options.autoSaveExternalSave) {
  115. await singlefile.extension.core.bg.companion.save(options);
  116. } else {
  117. pageData = await singlefile.extension.getPageData(options, null, null, { fetch });
  118. if (options.includeInfobar) {
  119. await singlefile.common.ui.content.infobar.includeScript(pageData);
  120. }
  121. const blob = new Blob([pageData.content], { type: "text/html" });
  122. if (options.saveToGDrive) {
  123. await singlefile.extension.core.bg.downloads.uploadPage(message.taskId, pageData.filename, blob, options, {});
  124. } else {
  125. pageData.url = URL.createObjectURL(blob);
  126. await singlefile.extension.core.bg.downloads.downloadPage(pageData, options);
  127. }
  128. if (pageData.hash) {
  129. await woleet.anchor(pageData.hash);
  130. }
  131. }
  132. } finally {
  133. singlefile.extension.core.bg.business.onSaveEnd(message.taskId);
  134. if (pageData && pageData.url) {
  135. URL.revokeObjectURL(pageData.url);
  136. }
  137. }
  138. }
  139. function fetch(url) {
  140. return new Promise((resolve, reject) => {
  141. const xhrRequest = new XMLHttpRequest();
  142. xhrRequest.withCredentials = true;
  143. xhrRequest.responseType = "arraybuffer";
  144. xhrRequest.onerror = event => reject(new Error(event.detail));
  145. xhrRequest.onreadystatechange = () => {
  146. if (xhrRequest.readyState == XMLHttpRequest.DONE) {
  147. resolve({
  148. status: xhrRequest.status,
  149. headers: {
  150. get: name => xhrRequest.getResponseHeader(name)
  151. },
  152. arrayBuffer: async () => xhrRequest.response
  153. });
  154. }
  155. };
  156. xhrRequest.open("GET", url, true);
  157. xhrRequest.send();
  158. });
  159. }
  160. })();