background.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389
  1. /*
  2. * Copyright 2010 Gildas Lormeau
  3. * contact : gildas.lormeau <at> gmail.com
  4. *
  5. * This file is part of SingleFile.
  6. *
  7. * SingleFile is free software: you can redistribute it and/or modify
  8. * it under the terms of the GNU Lesser General Public License as published by
  9. * the Free Software Foundation, either version 3 of the License, or
  10. * (at your option) any later version.
  11. *
  12. * SingleFile 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
  15. * GNU Lesser General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU Lesser General Public License
  18. * along with SingleFile. If not, see <http://www.gnu.org/licenses/>.
  19. */
  20. var singlefile = {};
  21. (function() {
  22. var tabs = [];
  23. singlefile.getOptions = function() {
  24. return localStorage["options"] ? JSON.parse(localStorage["options"]) : {
  25. removeFrames : false,
  26. removeScripts : true,
  27. removeObjects : true,
  28. removeHidden : false,
  29. removeUnused : false
  30. };
  31. };
  32. singlefile.setOptions = function(options) {
  33. localStorage["options"] = JSON.stringify(options);
  34. };
  35. singlefile.resetOptions = function() {
  36. delete localStorage["options"];
  37. };
  38. function getWinProperties() {
  39. var winProperties = {}, property;
  40. for (property in window)
  41. winProperties[property] = true;
  42. return winProperties;
  43. }
  44. function throwAwayHighOrderBytes(str) {
  45. var i, ret = [];
  46. for (i = 0; i < str.length; i++)
  47. ret[i] = String.fromCharCode(str.charCodeAt(i) & 0xff);
  48. return ret.join("");
  49. }
  50. function sendXHR(tabId, url, responseHandler, errorHandler, charset) {
  51. var xhr;
  52. if (!tabs[tabId].responses[url]) {
  53. if (!tabs[tabId].callbacks[url]) {
  54. tabs[tabId].callbacks[url] = [];
  55. xhr = new XMLHttpRequest();
  56. xhr.onreadystatechange = function() {
  57. if (xhr.readyState == 4) {
  58. tabs[tabId].callbacks[url].forEach(function(callback) {
  59. callback.responseHandler(xhr.status, xhr.getResponseHeader("Content-Type"), xhr.responseText);
  60. });
  61. tabs[tabId].responses[url] = {
  62. status : xhr.status,
  63. header : xhr.getResponseHeader("Content-Type"),
  64. text : xhr.responseText
  65. };
  66. }
  67. };
  68. xhr.onerror = function() {
  69. tabs[tabId].callbacks[url].forEach(function(callback) {
  70. callback.errorHandler();
  71. });
  72. };
  73. xhr.open("GET", url, true);
  74. if (charset)
  75. xhr.overrideMimeType('text/plain; charset=' + charset);
  76. try {
  77. xhr.send(null);
  78. } catch (e) {
  79. xhr.onerror();
  80. }
  81. }
  82. tabs[tabId].callbacks[url].push( {
  83. responseHandler : responseHandler,
  84. errorHandler : errorHandler
  85. });
  86. } else
  87. responseHandler(tabs[tabId].responses[url].status, tabs[tabId].responses[url].header, tabs[tabId].responses[url].text);
  88. }
  89. function setData(tabId, data, callback) {
  90. if (data.url.indexOf('http:') == 0 || data.url.indexOf('https:') == 0)
  91. sendXHR(tabId, data.url, function(status, contentType, responseText) {
  92. if (status < 400) {
  93. data.mediaType = contentType ? contentType.split(";")[0] : null;
  94. data.mediaTypeParam = data.base64 ? "base64" : (contentType ? contentType.split(";")[1] : null);
  95. data.content = data.base64 ? btoa(throwAwayHighOrderBytes(responseText)) : data.encodedText ? encodeURI(responseText) : responseText;
  96. }
  97. callback(data);
  98. }, function() {
  99. callback(data);
  100. }, data.characterSet);
  101. else
  102. callback(data);
  103. }
  104. function startDone(tabId) {
  105. var msg, options = singlefile.getOptions();
  106. tabs[tabId].processing = true;
  107. msg = {
  108. getStylesheets : true,
  109. options : singlefile.getOptions()
  110. };
  111. tabs[tabId].ports.forEach(function(portData) {
  112. portData.port.postMessage(msg);
  113. });
  114. }
  115. function getPortData(tabId, port) {
  116. var portData;
  117. tabs[tabId].ports.forEach(function(aPortData) {
  118. if (!portData && aPortData.port == port)
  119. portData = aPortData;
  120. });
  121. return portData;
  122. }
  123. function buildTree(tabId) {
  124. var tabData = tabs[tabId];
  125. function findParent(port) {
  126. var parentPort, parts, parentWinID;
  127. if (port.winID) {
  128. parts = port.winID.split('.');
  129. parts.pop();
  130. parentWinID = parts.join('.');
  131. tabData.ports.forEach(function(portData) {
  132. if (portData.winID == parentWinID)
  133. parentPort = portData;
  134. });
  135. }
  136. return parentPort;
  137. }
  138. function build(portData) {
  139. portData.parent = findParent(portData);
  140. if (portData.parent) {
  141. portData.parent.children = portData.parent.children || [];
  142. portData.parent.children.push(portData);
  143. }
  144. }
  145. function walk(portData, level) {
  146. if (portData.children)
  147. portData.children.forEach(function(pData) {
  148. walk(pData, level + 1);
  149. });
  150. if (!tabData.levels[level])
  151. tabData.levels[level] = [];
  152. tabData.levels[level].push(portData);
  153. }
  154. tabData.ports.forEach(function(portData) {
  155. if (!portData.frameCount)
  156. build(portData, 0);
  157. });
  158. tabData.levels = [];
  159. walk(tabData.top, 0);
  160. tabData.levelIndex = tabData.levels.length - 1;
  161. }
  162. function processFrames(tabId) {
  163. function postGetDocData(pData) {
  164. var data = {};
  165. if (pData.children)
  166. pData.children.forEach(function(pChildData) {
  167. var index = pChildData.winID.split('.').pop();
  168. data[index] = pChildData.data;
  169. });
  170. pData.port.postMessage( {
  171. getDocData : true,
  172. data : data
  173. });
  174. }
  175. tabs[tabId].processedFrameCount = 0;
  176. tabs[tabId].processedFrameMax = tabs[tabId].levels[tabs[tabId].levelIndex].length;
  177. tabs[tabId].levels[tabs[tabId].levelIndex].forEach(postGetDocData);
  178. }
  179. function done(tabId) {
  180. tabs[tabId].processing = false;
  181. tabs[tabId].processed = true;
  182. tabs[tabId].top.port.postMessage( {
  183. done : true,
  184. winProperties : getWinProperties()
  185. });
  186. }
  187. function refreshBadge(tabId) {
  188. var processedResources = 0, totalResources = 0;
  189. tabs[tabId].ports.forEach(function(portData) {
  190. processedResources += portData.processedResources;
  191. totalResources += portData.totalResources;
  192. });
  193. chrome.browserAction.setIcon( {
  194. tabId : tabId,
  195. path : '../resources/icon_48_wait' + Math.floor((processedResources / totalResources) * 9) + '.png'
  196. });
  197. chrome.browserAction.setTitle( {
  198. tabId : tabId,
  199. title : "Progress: " + Math.min(100, Math.floor((processedResources / totalResources) * 100)) + "%"
  200. });
  201. }
  202. chrome.browserAction.onClicked.addListener(function(tab) {
  203. function executeScripts(scripts, callback, index) {
  204. if (!index)
  205. index = 0;
  206. if (index < scripts.length) {
  207. chrome.tabs.executeScript(tab.id, {
  208. file : scripts[index].file,
  209. code : scripts[index].code,
  210. allFrames : true
  211. }, function() {
  212. executeScripts(scripts, callback, index + 1);
  213. });
  214. } else if (callback)
  215. callback();
  216. }
  217. if (!tabs[tab.id] || (!tabs[tab.id].processing && !tabs[tab.id].processed)) {
  218. executeScripts( [ {
  219. code : "var singlefile = {};"
  220. }, {
  221. file : "scripts/filters.js"
  222. }, {
  223. file : "scripts/core.js"
  224. }, {
  225. file : "scripts/ui.js"
  226. }, {
  227. file : "scripts/main.js"
  228. } ], function() {
  229. if (!tabs[tab.id])
  230. return;
  231. chrome.browserAction.setBadgeBackgroundColor( {
  232. color : [ 200, 200, 200, 192 ]
  233. });
  234. chrome.browserAction.setIcon( {
  235. tabId : tab.id,
  236. path : '../resources/icon_48_wait0.png'
  237. });
  238. chrome.browserAction.setTitle( {
  239. tabId : tab.id,
  240. title : "Progress: 0%"
  241. });
  242. tabs[tab.id].top.port.postMessage( {
  243. start : true
  244. });
  245. });
  246. }
  247. });
  248. chrome.extension.onConnect.addListener(function(port) {
  249. var tabId = port.sender.tab.id, tabData;
  250. port.onDisconnect.addListener(function() {
  251. tabData.ports = tabData.ports.filter(function(portData) {
  252. return portData.port != port;
  253. });
  254. tabData.processedDocMax--;
  255. if (tabData.processedDocCount && tabData.processedDocMax == tabData.processedDocCount) {
  256. tabData.processedDocCount = 0;
  257. buildTree(tabId);
  258. processFrames(tabId);
  259. }
  260. if (!tabData.ports.length)
  261. tabs[tabId] = null;
  262. });
  263. port.onMessage.addListener(function(msg) {
  264. var portData;
  265. if (msg.init) {
  266. tabs[tabId] = tabs[tabId] || {
  267. id : tabId,
  268. ports : [],
  269. processedDocCount : 0,
  270. processedDocMax : 0,
  271. processedPortCount : 0,
  272. processedFrameCount : 0,
  273. processedFrameMax : 0,
  274. processing : false,
  275. processed : false,
  276. callbacks : {},
  277. responses : {}
  278. };
  279. tabData = tabs[tabId];
  280. portData = {
  281. port : port,
  282. url : msg.url,
  283. totalResources : 0,
  284. processedResources : 0
  285. };
  286. if (msg.topWindow)
  287. tabData.top = portData;
  288. if (!singlefile.getOptions().removeFrames || msg.topWindow) {
  289. tabData.ports.push(portData);
  290. tabData.processedDocMax++;
  291. }
  292. } else
  293. portData = getPortData(tabId, port);
  294. if (msg.startDone)
  295. startDone(tabId);
  296. if (msg.setStylesheets || msg.setData || msg.setDynamicData)
  297. setData(tabId, msg.data, function(data) {
  298. msg.data = data;
  299. port.postMessage(msg);
  300. });
  301. if (msg.setTotalResources) {
  302. portData.totalResources = msg.totalResources;
  303. tabData.processedPortCount++;
  304. if (tabData.processedPortCount == tabData.ports.length) {
  305. tabData.ports.forEach(function(pData) {
  306. pData.port.postMessage( {
  307. getData : true
  308. });
  309. });
  310. }
  311. }
  312. if (msg.incProcessedResources) {
  313. portData.processedResources++;
  314. refreshBadge(tabId);
  315. }
  316. if (msg.setDataDone) {
  317. portData.frameCount = msg.frameCount;
  318. portData.winID = msg.winID;
  319. if (singlefile.getOptions().removeFrames)
  320. done(tabId);
  321. else {
  322. tabData.processedDocCount++;
  323. if (tabData.processedDocMax == tabData.processedDocCount) {
  324. tabData.processedDocCount = 0;
  325. buildTree(tabId);
  326. processFrames(tabId);
  327. }
  328. }
  329. }
  330. if (msg.setDocData) {
  331. portData.data = "data:" + msg.mimeType + ";charset=utf-8," + encodeURI(msg.data);
  332. if (portData.parent) {
  333. tabData.processedFrameCount++;
  334. if (tabData.processedFrameMax == tabData.processedFrameCount) {
  335. tabData.levelIndex--;
  336. processFrames(tabId);
  337. }
  338. } else
  339. done(tabId);
  340. }
  341. if (msg.done) {
  342. chrome.browserAction.setIcon( {
  343. tabId : tabId,
  344. path : '../resources/icon_48.png'
  345. });
  346. chrome.browserAction.setBadgeBackgroundColor( {
  347. color : [ 10, 200, 10, 192 ]
  348. });
  349. chrome.browserAction.setBadgeText( {
  350. tabId : tabId,
  351. text : "OK"
  352. });
  353. chrome.browserAction.setTitle( {
  354. tabId : tabId,
  355. title : "Save the page with Ctrl-S"
  356. });
  357. }
  358. });
  359. });
  360. })();