1
0

single-file-background.js 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  1. (function () {
  2. 'use strict';
  3. /*
  4. * Copyright 2010-2020 Gildas Lormeau
  5. * contact : gildas.lormeau <at> gmail.com
  6. *
  7. * This file is part of SingleFile.
  8. *
  9. * The code in this file is free software: you can redistribute it and/or
  10. * modify it under the terms of the GNU Affero General Public License
  11. * (GNU AGPL) as published by the Free Software Foundation, either version 3
  12. * of the License, or (at your option) any later version.
  13. *
  14. * The code in this file is distributed in the hope that it will be useful,
  15. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
  17. * General Public License for more details.
  18. *
  19. * As additional permission under GNU AGPL version 3 section 7, you may
  20. * distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU
  21. * AGPL normally required by section 4, provided you include this license
  22. * notice and a URL through which recipients can access the Corresponding
  23. * Source.
  24. */
  25. /* global browser, XMLHttpRequest */
  26. const referrers = new Map();
  27. const REQUEST_ID_HEADER_NAME = "x-single-file-request-id";
  28. const MAX_CONTENT_SIZE = 8 * (1024 * 1024);
  29. browser.runtime.onMessage.addListener((message, sender) => {
  30. if (message.method && message.method.startsWith("singlefile.fetch")) {
  31. return new Promise(resolve => {
  32. onRequest(message, sender)
  33. .then(resolve)
  34. .catch(error => resolve({ error: error && error.toString() }));
  35. });
  36. }
  37. });
  38. async function onRequest(message, sender) {
  39. if (message.method == "singlefile.fetch") {
  40. try {
  41. const response = await fetchResource(message.url, { referrer: message.referrer, headers: message.headers });
  42. return sendResponse(sender.tab.id, message.requestId, response);
  43. } catch (error) {
  44. return sendResponse(sender.tab.id, message.requestId, { error: error.message, array: [] });
  45. }
  46. } else if (message.method == "singlefile.fetchFrame") {
  47. return browser.tabs.sendMessage(sender.tab.id, message);
  48. }
  49. }
  50. async function sendResponse(tabId, requestId, response) {
  51. for (let blockIndex = 0; blockIndex * MAX_CONTENT_SIZE <= response.array.length; blockIndex++) {
  52. const message = {
  53. method: "singlefile.fetchResponse",
  54. requestId,
  55. headers: response.headers,
  56. status: response.status,
  57. error: response.error
  58. };
  59. message.truncated = response.array.length > MAX_CONTENT_SIZE;
  60. if (message.truncated) {
  61. message.finished = (blockIndex + 1) * MAX_CONTENT_SIZE > response.array.length;
  62. message.array = response.array.slice(blockIndex * MAX_CONTENT_SIZE, (blockIndex + 1) * MAX_CONTENT_SIZE);
  63. } else {
  64. message.array = response.array;
  65. }
  66. await browser.tabs.sendMessage(tabId, message);
  67. }
  68. return {};
  69. }
  70. function fetchResource(url, options = {}, includeRequestId) {
  71. return new Promise((resolve, reject) => {
  72. const xhrRequest = new XMLHttpRequest();
  73. xhrRequest.withCredentials = true;
  74. xhrRequest.responseType = "arraybuffer";
  75. xhrRequest.onerror = event => reject(new Error(event.detail));
  76. xhrRequest.onreadystatechange = () => {
  77. if (xhrRequest.readyState == XMLHttpRequest.DONE) {
  78. if (xhrRequest.status || xhrRequest.response.byteLength) {
  79. if ((xhrRequest.status == 401 || xhrRequest.status == 403 || xhrRequest.status == 404) && !includeRequestId) {
  80. fetchResource(url, options, true)
  81. .then(resolve)
  82. .catch(reject);
  83. } else {
  84. resolve({
  85. arrayBuffer: xhrRequest.response,
  86. array: Array.from(new Uint8Array(xhrRequest.response)),
  87. headers: { "content-type": xhrRequest.getResponseHeader("Content-Type") },
  88. status: xhrRequest.status
  89. });
  90. }
  91. } else {
  92. reject(new Error("Empty response"));
  93. }
  94. }
  95. };
  96. xhrRequest.open("GET", url, true);
  97. if (options.headers) {
  98. for (const entry of Object.entries(options.headers)) {
  99. xhrRequest.setRequestHeader(entry[0], entry[1]);
  100. }
  101. }
  102. if (includeRequestId) {
  103. const randomId = String(Math.random()).substring(2);
  104. setReferrer(randomId, options.referrer);
  105. xhrRequest.setRequestHeader(REQUEST_ID_HEADER_NAME, randomId);
  106. }
  107. xhrRequest.send();
  108. });
  109. }
  110. function setReferrer(requestId, referrer) {
  111. referrers.set(requestId, referrer);
  112. }
  113. /*
  114. * Copyright 2010-2020 Gildas Lormeau
  115. * contact : gildas.lormeau <at> gmail.com
  116. *
  117. * This file is part of SingleFile.
  118. *
  119. * The code in this file is free software: you can redistribute it and/or
  120. * modify it under the terms of the GNU Affero General Public License
  121. * (GNU AGPL) as published by the Free Software Foundation, either version 3
  122. * of the License, or (at your option) any later version.
  123. *
  124. * The code in this file is distributed in the hope that it will be useful,
  125. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  126. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
  127. * General Public License for more details.
  128. *
  129. * As additional permission under GNU AGPL version 3 section 7, you may
  130. * distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU
  131. * AGPL normally required by section 4, provided you include this license
  132. * notice and a URL through which recipients can access the Corresponding
  133. * Source.
  134. */
  135. /* global browser */
  136. browser.runtime.onMessage.addListener((message, sender) => {
  137. if (message.method == "singlefile.frameTree.initResponse" || message.method == "singlefile.frameTree.ackInitRequest") {
  138. browser.tabs.sendMessage(sender.tab.id, message, { frameId: 0 });
  139. return Promise.resolve({});
  140. }
  141. });
  142. /*
  143. * Copyright 2010-2020 Gildas Lormeau
  144. * contact : gildas.lormeau <at> gmail.com
  145. *
  146. * This file is part of SingleFile.
  147. *
  148. * The code in this file is free software: you can redistribute it and/or
  149. * modify it under the terms of the GNU Affero General Public License
  150. * (GNU AGPL) as published by the Free Software Foundation, either version 3
  151. * of the License, or (at your option) any later version.
  152. *
  153. * The code in this file is distributed in the hope that it will be useful,
  154. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  155. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
  156. * General Public License for more details.
  157. *
  158. * As additional permission under GNU AGPL version 3 section 7, you may
  159. * distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU
  160. * AGPL normally required by section 4, provided you include this license
  161. * notice and a URL through which recipients can access the Corresponding
  162. * Source.
  163. */
  164. /* global browser, setTimeout, clearTimeout */
  165. const timeouts = new Map();
  166. browser.runtime.onMessage.addListener((message, sender) => {
  167. if (message.method == "singlefile.lazyTimeout.setTimeout") {
  168. let tabTimeouts = timeouts.get(sender.tab.id);
  169. let frameTimeouts;
  170. if (tabTimeouts) {
  171. frameTimeouts = tabTimeouts.get(sender.frameId);
  172. if (frameTimeouts) {
  173. const previousTimeoutId = frameTimeouts.get(message.type);
  174. if (previousTimeoutId) {
  175. clearTimeout(previousTimeoutId);
  176. }
  177. } else {
  178. frameTimeouts = new Map();
  179. }
  180. }
  181. const timeoutId = setTimeout(async () => {
  182. try {
  183. const tabTimeouts = timeouts.get(sender.tab.id);
  184. const frameTimeouts = tabTimeouts.get(sender.frameId);
  185. if (tabTimeouts && frameTimeouts) {
  186. deleteTimeout(frameTimeouts, message.type);
  187. }
  188. await browser.tabs.sendMessage(sender.tab.id, { method: "singlefile.lazyTimeout.onTimeout", type: message.type });
  189. } catch (error) {
  190. // ignored
  191. }
  192. }, message.delay);
  193. if (!tabTimeouts) {
  194. tabTimeouts = new Map();
  195. frameTimeouts = new Map();
  196. tabTimeouts.set(sender.frameId, frameTimeouts);
  197. timeouts.set(sender.tab.id, tabTimeouts);
  198. }
  199. frameTimeouts.set(message.type, timeoutId);
  200. return Promise.resolve({});
  201. }
  202. if (message.method == "singlefile.lazyTimeout.clearTimeout") {
  203. let tabTimeouts = timeouts.get(sender.tab.id);
  204. if (tabTimeouts) {
  205. const frameTimeouts = tabTimeouts.get(sender.frameId);
  206. if (frameTimeouts) {
  207. const timeoutId = frameTimeouts.get(message.type);
  208. if (timeoutId) {
  209. clearTimeout(timeoutId);
  210. }
  211. deleteTimeout(frameTimeouts, message.type);
  212. }
  213. }
  214. return Promise.resolve({});
  215. }
  216. });
  217. browser.tabs.onRemoved.addListener(tabId => timeouts.delete(tabId));
  218. function deleteTimeout(framesTimeouts, type) {
  219. framesTimeouts.delete(type);
  220. }
  221. })();