1
0

single-file-extension-core.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304
  1. (function (global, factory) {
  2. typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
  3. typeof define === 'function' && define.amd ? define(['exports'], factory) :
  4. (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.extension = {}));
  5. })(this, (function (exports) { 'use strict';
  6. /*
  7. * Copyright 2010-2020 Gildas Lormeau
  8. * contact : gildas.lormeau <at> gmail.com
  9. *
  10. * This file is part of SingleFile.
  11. *
  12. * The code in this file is free software: you can redistribute it and/or
  13. * modify it under the terms of the GNU Affero General Public License
  14. * (GNU AGPL) as published by the Free Software Foundation, either version 3
  15. * of the License, or (at your option) any later version.
  16. *
  17. * The code in this file is distributed in the hope that it will be useful,
  18. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  19. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
  20. * General Public License for more details.
  21. *
  22. * As additional permission under GNU AGPL version 3 section 7, you may
  23. * distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU
  24. * AGPL normally required by section 4, provided you include this license
  25. * notice and a URL through which recipients can access the Corresponding
  26. * Source.
  27. */
  28. /* global browser, fetch, TextDecoder */
  29. let contentScript, frameScript;
  30. const contentScriptFiles = [
  31. "lib/web-stream.js",
  32. "lib/chrome-browser-polyfill.js",
  33. "lib/single-file.js"
  34. ];
  35. const frameScriptFiles = [
  36. "lib/chrome-browser-polyfill.js",
  37. "lib/single-file-frames.js"
  38. ];
  39. const basePath = "../../../";
  40. async function inject(tabId, options) {
  41. await initScripts(options);
  42. let scriptsInjected;
  43. if (!options.removeFrames) {
  44. try {
  45. await browser.tabs.executeScript(tabId, { code: frameScript, allFrames: true, matchAboutBlank: true, runAt: "document_start" });
  46. } catch (error) {
  47. // ignored
  48. }
  49. }
  50. try {
  51. await browser.tabs.executeScript(tabId, { code: contentScript, allFrames: false, runAt: "document_idle" });
  52. scriptsInjected = true;
  53. } catch (error) {
  54. // ignored
  55. }
  56. if (scriptsInjected) {
  57. if (options.frameId) {
  58. await browser.tabs.executeScript(tabId, { code: "document.documentElement.dataset.requestedFrameId = true", frameId: options.frameId, matchAboutBlank: true, runAt: "document_start" });
  59. }
  60. }
  61. return scriptsInjected;
  62. }
  63. async function initScripts(options) {
  64. const extensionScriptFiles = options.extensionScriptFiles || [];
  65. if (!contentScript && !frameScript) {
  66. [contentScript, frameScript] = await Promise.all([
  67. getScript(contentScriptFiles.concat(extensionScriptFiles)),
  68. getScript(frameScriptFiles)
  69. ]);
  70. }
  71. }
  72. async function getScript(scriptFiles) {
  73. const scriptsPromises = scriptFiles.map(async scriptFile => {
  74. if (typeof scriptFile == "function") {
  75. return "(" + scriptFile.toString() + ")();";
  76. } else {
  77. const scriptResource = await fetch(browser.runtime.getURL(basePath + scriptFile));
  78. return new TextDecoder().decode(await scriptResource.arrayBuffer());
  79. }
  80. });
  81. let content = "";
  82. for (const scriptPromise of scriptsPromises) {
  83. content += await scriptPromise;
  84. }
  85. return content;
  86. }
  87. /*
  88. * Copyright 2010-2020 Gildas Lormeau
  89. * contact : gildas.lormeau <at> gmail.com
  90. *
  91. * This file is part of SingleFile.
  92. *
  93. * The code in this file is free software: you can redistribute it and/or
  94. * modify it under the terms of the GNU Affero General Public License
  95. * (GNU AGPL) as published by the Free Software Foundation, either version 3
  96. * of the License, or (at your option) any later version.
  97. *
  98. * The code in this file is distributed in the hope that it will be useful,
  99. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  100. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
  101. * General Public License for more details.
  102. *
  103. * As additional permission under GNU AGPL version 3 section 7, you may
  104. * distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU
  105. * AGPL normally required by section 4, provided you include this license
  106. * notice and a URL through which recipients can access the Corresponding
  107. * Source.
  108. */
  109. /* global browser, window, document, CustomEvent, setTimeout, clearTimeout */
  110. const FETCH_REQUEST_EVENT = "single-file-request-fetch";
  111. const FETCH_ACK_EVENT = "single-file-ack-fetch";
  112. const FETCH_RESPONSE_EVENT = "single-file-response-fetch";
  113. const ERR_HOST_FETCH = "Host fetch error (SingleFile)";
  114. const HOST_FETCH_MAX_DELAY = 2500;
  115. const USE_HOST_FETCH = Boolean(window.wrappedJSObject);
  116. const fetch$1 = (url, options) => window.fetch(url, options);
  117. let requestId = 0, pendingResponses = new Map();
  118. browser.runtime.onMessage.addListener(message => {
  119. if (message.method == "singlefile.fetchFrame" && window.frameId && window.frameId == message.frameId) {
  120. return onFetchFrame(message);
  121. }
  122. if (message.method == "singlefile.fetchResponse") {
  123. return onFetchResponse(message);
  124. }
  125. });
  126. async function onFetchFrame(message) {
  127. try {
  128. const response = await fetch$1(message.url, { cache: "force-cache", headers: message.headers });
  129. return {
  130. status: response.status,
  131. headers: [...response.headers],
  132. array: Array.from(new Uint8Array(await response.arrayBuffer()))
  133. };
  134. } catch (error) {
  135. return {
  136. error: error && error.toString()
  137. };
  138. }
  139. }
  140. async function onFetchResponse(message) {
  141. const pendingResponse = pendingResponses.get(message.requestId);
  142. if (pendingResponse) {
  143. if (message.error) {
  144. pendingResponse.reject(new Error(message.error));
  145. pendingResponses.delete(message.requestId);
  146. } else {
  147. if (message.truncated) {
  148. if (pendingResponse.array) {
  149. pendingResponse.array = pendingResponse.array.concat(message.array);
  150. } else {
  151. pendingResponse.array = message.array;
  152. pendingResponses.set(message.requestId, pendingResponse);
  153. }
  154. if (message.finished) {
  155. message.array = pendingResponse.array;
  156. }
  157. }
  158. if (!message.truncated || message.finished) {
  159. pendingResponse.resolve({
  160. status: message.status,
  161. headers: { get: headerName => message.headers && message.headers[headerName] },
  162. arrayBuffer: async () => new Uint8Array(message.array).buffer
  163. });
  164. pendingResponses.delete(message.requestId);
  165. }
  166. }
  167. }
  168. return {};
  169. }
  170. async function hostFetch(url, options) {
  171. const result = new Promise((resolve, reject) => {
  172. document.dispatchEvent(new CustomEvent(FETCH_REQUEST_EVENT, { detail: JSON.stringify({ url, options }) }));
  173. document.addEventListener(FETCH_ACK_EVENT, onAckFetch, false);
  174. document.addEventListener(FETCH_RESPONSE_EVENT, onResponseFetch, false);
  175. const timeout = setTimeout(() => {
  176. removeListeners();
  177. reject(new Error(ERR_HOST_FETCH));
  178. }, HOST_FETCH_MAX_DELAY);
  179. function onResponseFetch(event) {
  180. if (event.detail) {
  181. if (event.detail.url == url) {
  182. removeListeners();
  183. if (event.detail.response) {
  184. resolve({
  185. status: event.detail.status,
  186. headers: new Map(event.detail.headers),
  187. arrayBuffer: async () => event.detail.response
  188. });
  189. } else {
  190. reject(event.detail.error);
  191. }
  192. }
  193. } else {
  194. reject();
  195. }
  196. }
  197. function onAckFetch() {
  198. clearTimeout(timeout);
  199. }
  200. function removeListeners() {
  201. document.removeEventListener(FETCH_RESPONSE_EVENT, onResponseFetch, false);
  202. document.removeEventListener(FETCH_ACK_EVENT, onAckFetch, false);
  203. }
  204. });
  205. try {
  206. return await result;
  207. } catch (error) {
  208. if (error && error.message == ERR_HOST_FETCH) {
  209. return fetch$1(url, options);
  210. } else {
  211. throw error;
  212. }
  213. }
  214. }
  215. async function fetchResource(url, options = {}) {
  216. try {
  217. const fetchOptions = { cache: "force-cache", headers: options.headers };
  218. return await (options.referrer && USE_HOST_FETCH ? hostFetch(url, fetchOptions) : fetch$1(url, fetchOptions));
  219. }
  220. catch (error) {
  221. requestId++;
  222. const promise = new Promise((resolve, reject) => pendingResponses.set(requestId, { resolve, reject }));
  223. await sendMessage({ method: "singlefile.fetch", url, requestId, referrer: options.referrer, headers: options.headers });
  224. return promise;
  225. }
  226. }
  227. async function frameFetch(url, options) {
  228. const response = await sendMessage({ method: "singlefile.fetchFrame", url, frameId: options.frameId, referrer: options.referrer, headers: options.headers });
  229. return {
  230. status: response.status,
  231. headers: new Map(response.headers),
  232. arrayBuffer: async () => new Uint8Array(response.array).buffer
  233. };
  234. }
  235. async function sendMessage(message) {
  236. const response = await browser.runtime.sendMessage(message);
  237. if (!response || response.error) {
  238. throw new Error(response && response.error && response.error.toString());
  239. } else {
  240. return response;
  241. }
  242. }
  243. /*
  244. * Copyright 2010-2020 Gildas Lormeau
  245. * contact : gildas.lormeau <at> gmail.com
  246. *
  247. * This file is part of SingleFile.
  248. *
  249. * The code in this file is free software: you can redistribute it and/or
  250. * modify it under the terms of the GNU Affero General Public License
  251. * (GNU AGPL) as published by the Free Software Foundation, either version 3
  252. * of the License, or (at your option) any later version.
  253. *
  254. * The code in this file is distributed in the hope that it will be useful,
  255. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  256. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
  257. * General Public License for more details.
  258. *
  259. * As additional permission under GNU AGPL version 3 section 7, you may
  260. * distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU
  261. * AGPL normally required by section 4, provided you include this license
  262. * notice and a URL through which recipients can access the Corresponding
  263. * Source.
  264. */
  265. function injectScript(tabId, options) {
  266. return inject(tabId, options);
  267. }
  268. function getPageData(options, doc, win, initOptions = { fetch: fetchResource, frameFetch }) {
  269. return globalThis.singlefile.getPageData(options, initOptions, doc, win);
  270. }
  271. exports.getPageData = getPageData;
  272. exports.injectScript = injectScript;
  273. Object.defineProperty(exports, '__esModule', { value: true });
  274. }));