Просмотр исходного кода

version 0.2:
- totally refactored code

Gildas lormeau 15 лет назад
Родитель
Сommit
385a3cae36
62 измененных файлов с 2999 добавлено и 1675 удалено
  1. 18 0
      WebContent/core/manifest.json
  2. 17 0
      WebContent/core/pages/background.html
  3. BIN
      WebContent/core/resources/icon_128.png
  4. BIN
      WebContent/core/resources/icon_16.png
  5. BIN
      WebContent/core/resources/icon_48.png
  6. 265 0
      WebContent/core/scripts/bg/background.js
  7. 303 0
      WebContent/core/scripts/bg/bgcore.js
  8. 93 0
      WebContent/core/scripts/bg/nio.js
  9. 96 0
      WebContent/core/scripts/bg/storage.js
  10. 40 0
      WebContent/core/scripts/bg/wininfo.js
  11. 441 0
      WebContent/core/scripts/common/docprocessor.js
  12. 47 0
      WebContent/core/scripts/common/util.js
  13. 451 0
      WebContent/core/scripts/content/content.js
  14. 214 0
      WebContent/core/scripts/content/wininfo.js
  15. 0 4
      WebContent/pages/background.html
  16. 0 119
      WebContent/pages/help.html
  17. 0 37
      WebContent/pages/options.html
  18. BIN
      WebContent/resources/icon_48_wait0.png
  19. BIN
      WebContent/resources/icon_48_wait1.png
  20. BIN
      WebContent/resources/icon_48_wait2.png
  21. BIN
      WebContent/resources/icon_48_wait3.png
  22. BIN
      WebContent/resources/icon_48_wait4.png
  23. BIN
      WebContent/resources/icon_48_wait5.png
  24. BIN
      WebContent/resources/icon_48_wait6.png
  25. BIN
      WebContent/resources/icon_48_wait7.png
  26. BIN
      WebContent/resources/icon_48_wait8.png
  27. BIN
      WebContent/resources/options_screen.png
  28. 0 451
      WebContent/scripts/background.js
  29. 0 273
      WebContent/scripts/core.js
  30. 0 434
      WebContent/scripts/filters.js
  31. 0 53
      WebContent/scripts/main.js
  32. 0 55
      WebContent/scripts/options.js
  33. 0 60
      WebContent/scripts/ui.js
  34. 22 15
      WebContent/ui/manifest.json
  35. 13 0
      WebContent/ui/pages/background.html
  36. 110 95
      WebContent/ui/pages/help.css
  37. 173 0
      WebContent/ui/pages/help.html
  38. 42 0
      WebContent/ui/pages/missingcore.css
  39. 20 0
      WebContent/ui/pages/missingcore.html
  40. 88 79
      WebContent/ui/pages/options.css
  41. 57 0
      WebContent/ui/pages/options.html
  42. BIN
      WebContent/ui/resources/html5-badge-h-storage.png
  43. 0 0
      WebContent/ui/resources/icon_128.png
  44. 0 0
      WebContent/ui/resources/icon_16.png
  45. 0 0
      WebContent/ui/resources/icon_48.png
  46. BIN
      WebContent/ui/resources/icon_48_passive.png
  47. BIN
      WebContent/ui/resources/icon_48_wait0.png
  48. BIN
      WebContent/ui/resources/icon_48_wait1.png
  49. BIN
      WebContent/ui/resources/icon_48_wait2.png
  50. BIN
      WebContent/ui/resources/icon_48_wait3.png
  51. BIN
      WebContent/ui/resources/icon_48_wait4.png
  52. BIN
      WebContent/ui/resources/icon_48_wait5.png
  53. BIN
      WebContent/ui/resources/icon_48_wait6.png
  54. BIN
      WebContent/ui/resources/icon_48_wait7.png
  55. BIN
      WebContent/ui/resources/icon_48_wait8.png
  56. BIN
      WebContent/ui/resources/icon_48_wait9.png
  57. BIN
      WebContent/ui/resources/options_screen.png
  58. 142 0
      WebContent/ui/scripts/bg/background.js
  59. 52 0
      WebContent/ui/scripts/bg/config.js
  60. 84 0
      WebContent/ui/scripts/bg/options.js
  61. 147 0
      WebContent/ui/scripts/bg/ui.js
  62. 64 0
      WebContent/ui/scripts/content/content.js

+ 18 - 0
WebContent/core/manifest.json

@@ -0,0 +1,18 @@
+{
+	"name": "SingleFile Core",
+	"icons": {
+		"16": "resources/icon_16.png",
+		"48": "resources/icon_48.png",
+		"128": "resources/icon_128.png" },
+	"version": "0.2.18",
+	"description": "Page processor used by SingleFile",
+	"background_page" : "pages/background.html",
+	"content_scripts": [{
+		  "matches": ["http://*/*", "https://*/*", "ftp://*/*", "file:///*"],
+		  "js": ["scripts/content/wininfo.js"],
+		  "run_at" : "document_end",
+		  "all_frames" : true
+	 }],
+	"permissions": [ "tabs", "http://*/*", "https://*/*", "unlimitedStorage" ],
+	"minimum_chrome_version" : "7"
+}

+ 17 - 0
WebContent/core/pages/background.html

@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+	var singlefile = {};
+</script>
+<script type="text/javascript" src="../scripts/bg/wininfo.js"></script>
+<script type="text/javascript" src="../scripts/bg/nio.js"></script>
+<script type="text/javascript" src="../scripts/common/util.js"></script>
+<script type="text/javascript" src="../scripts/common/docprocessor.js"></script>
+<script type="text/javascript" src="../scripts/bg/storage.js"></script>
+<script type="text/javascript" src="../scripts/bg/bgcore.js"></script>
+<script type="text/javascript" src="../scripts/bg/background.js"></script>
+</head>
+<body>
+</body>
+</html>

BIN
WebContent/core/resources/icon_128.png


BIN
WebContent/resources/icon_48_wait9.png → WebContent/core/resources/icon_16.png


BIN
WebContent/core/resources/icon_48.png


+ 265 - 0
WebContent/core/scripts/bg/background.js

@@ -0,0 +1,265 @@
+/*
+ * Copyright 2011 Gildas Lormeau
+ * contact : gildas.lormeau <at> gmail.com
+ * 
+ * This file is part of SingleFile Core.
+ *
+ *   SingleFile Core is free software: you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as published by
+ *   the Free Software Foundation, either version 3 of the License, or
+ *   (at your option) any later version.
+ *
+ *   SingleFile Core is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public License
+ *   along with SingleFile Core.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+(function() {
+
+	var DEFAULT_FILENAME_MAX_LENGTH = 90, DEFAULT_CONFIG = {
+		removeFrames : false,
+		removeScripts : true,
+		removeObjects : true,
+		removeHidden : false,
+		removeUnusedCSSRules : false,
+		displayProcessedPage : false,
+		savePage : false,
+		filenameMaxLength : DEFAULT_FILENAME_MAX_LENGTH,
+		processInBackground : true,
+		getContent : true,
+		getRawDoc : false
+	};
+
+	var tabs = singlefile.tabs = [], processingPagesCount = 0, pageId = 0;
+
+	function executeScripts(tabId, scripts, callback, index) {
+		if (!index)
+			index = 0;
+		if (index < scripts.length)
+			chrome.tabs.executeScript(tabId, {
+				file : scripts[index].file,
+				code : scripts[index].code,
+				allFrames : true
+			}, function() {
+				executeScripts(tabId, scripts, callback, index + 1);
+			});
+		else if (callback)
+			callback();
+	}
+
+	function processInit(tabId, port, message) {
+		var pageData = tabs[tabId][message.pageId];
+		pageData.portsId.push(port.portId_);
+		if (!pageData.getDocData(message.winId))
+			pageData.processDoc(port, message.topWindow, message.winId, message.index, message.content, message.title, message.url, message.baseURI,
+					message.characterSet, message.canvasData, {
+						init : docInit,
+						progress : docProgress,
+						end : docEnd
+					});
+	}
+
+	function setContentResponse(tabId, pageId, docData, content) {
+		var pageData = tabs[tabId][pageId];
+		pageData.endProcess(content);
+		processingPagesCount--;
+		chrome.extension.sendRequest(pageData.senderId, {
+			processEnd : true,
+			tabId : tabId,
+			pageId : pageId,
+			blockingProcess : !pageData.config.processInBackground || pageData.config.displayProcessedPage,
+			processingPagesCount : processingPagesCount,
+			content : pageData.config.getContent ? content : null,
+			url : pageData.url,
+			title : pageData.title
+		});
+		if (!pageData.config.processInBackground || pageData.config.displayProcessedPage) {
+			pageData.processing = false;
+			tabs[tabId].processing = false;
+		}
+		if (pageData.pendingDelete)
+			deletePageData(pageData);
+	}
+
+	function docInit(pageData, docData, maxIndex) {
+		function pageInit() {
+			pageData.timeoutPageInit = null;
+			pageData.processableDocs = pageData.initializedDocs;
+			pageData.initProcess();
+			processingPagesCount++;
+			chrome.extension.sendRequest(pageData.senderId, {
+				processStart : true,
+				tabId : pageData.tabId,
+				pageId : pageData.pageId,
+				blockingProcess : !pageData.config.processInBackground || pageData.config.displayProcessedPage,
+				processingPagesCount : processingPagesCount
+			});
+			if (pageData.config.processInBackground && !pageData.config.displayProcessedPage)
+				tabs[pageData.tabId].processing = false;
+		}
+
+		if (!docData.initialized) {
+			docData.initialized = true;
+			if (pageData.initializedDocs != pageData.processableDocs) {
+				docData.progressMax = maxIndex;
+				pageData.initializedDocs++;
+				if (pageData.timeoutPageInit)
+					clearTimeout(pageData.timeoutPageInit);
+				pageData.timeoutPageInit = setTimeout(pageInit, 5000);
+				if (pageData.initializedDocs == pageData.processableDocs || pageData.processSelection || pageData.config.removeFrames
+						|| pageData.config.getRawDoc) {
+					clearTimeout(pageData.timeoutPageInit);
+					pageInit();
+				}
+			}
+		}
+	}
+
+	function docProgress(pageData, docData, index) {
+		var progressIndex = 0, progressMax = 0;
+		docData.progressIndex = index;
+		tabs.forEach(function(tabData) {
+			if (tabData) {
+				tabData.progressIndex = 0;
+				tabData.progressMax = 0;
+				tabData.forEach(function(pageData) {
+					if (pageData) {
+						pageData.computeProgress();
+						tabData.progressIndex += pageData.progressIndex;
+						tabData.progressMax += pageData.progressMax;
+					}
+				});
+				progressIndex += tabData.progressIndex;
+				progressMax += tabData.progressMax;
+			}
+		});
+		chrome.extension.sendRequest(pageData.senderId, {
+			processProgress : true,
+			tabId : pageData.tabId,
+			pageId : pageData.pageId,
+			pageIndex : pageData.progressIndex,
+			pageMaxIndex : pageData.progressMax,
+			tabIndex : tabs[pageData.tabId].progressIndex,
+			tabMaxIndex : tabs[pageData.tabId].progressMax,
+			index : progressIndex,
+			maxIndex : progressMax
+		});
+	}
+
+	function docEnd(pageData, docData, content) {
+		pageData.setDocContent(docData, content, setContentResponse);
+	}
+
+	function process(tabId, senderId, config, processSelection) {
+		var pageData, configScript = "singlefile.config = " + JSON.stringify(config) + "; singlefile.pageId = " + pageId + ";"
+				+ (processSelection ? "singlefile.processSelection = " + processSelection : "");
+		if (tabs[tabId] && tabs[tabId].processing)
+			return;
+		tabs[tabId] = tabs[tabId] || [];
+		tabs[tabId].processing = true;
+		pageData = new singlefile.PageData(tabId, pageId, senderId, config, processSelection, function() {
+			executeScripts(tabId, [ {
+				code : "var singlefile = {};"
+			}, {
+				file : "scripts/common/util.js"
+			}, {
+				file : "scripts/common/docprocessor.js"
+			}, {
+				code : configScript
+			}, {
+				file : "scripts/content/content.js"
+			} ]);
+		});
+		tabs[tabId][pageId] = pageData;
+		pageId++;
+	}
+
+	function deletePageData(pageData) {
+		tabs[pageData.tabId][pageData.pageId] = null;
+		tabs[pageData.tabId] = tabs[pageData.tabId].filter(function(pageData) {
+			return pageData;
+		});
+		if (!tabs[pageData.tabId].length)
+			tabs[pageData.tabId] = null;
+	}
+
+	chrome.extension.onConnect
+			.addListener(function(port) {
+				var tabId = port.sender.tab.id, portPageId = [];
+
+				function onDisconnect() {
+					var pageData = tabs[tabId][portPageId[port.portId_]];
+					if (!pageData)
+						return;
+					pageData.portsId = pageData.portsId.filter(function(id) {
+						return id != port.portId_;
+					});
+					if (!pageData.portsId.length)
+						if (pageData.processing)
+							pageData.pendingDelete = true;
+						else
+							deletePageData(pageData);
+				}
+
+				function onMessage(message) {
+					var pageData, docData;
+					// if (!message.getResourceContentRequest && !message.docProgress)
+					// console.log("onMessage", message, port.portId_);
+					if (message.winId) {
+						portPageId[port.portId_] = message.pageId;
+						if (message.processInit)
+							processInit(tabId, port, message);
+						else {
+							pageData = tabs[tabId][message.pageId];
+							docData = pageData.getDocData(message.winId);
+							if (message.processDocFragment)
+								pageData.processDocFragment(docData, message.mutationEventId, message.content);
+							if (message.getResourceContentRequest)
+								pageData.getResourceContentRequest(message.url, message.requestId, message.winId, message.characterSet, message.mediaTypeParam,
+										docData);
+							if (message.docInit)
+								docInit(pageData, docData, message.maxIndex);
+							if (message.docProgress)
+								docProgress(pageData, docData, message.index);
+							if (message.docEnd)
+								docEnd(pageData, docData, message.content);
+							if (message.setFrameContentResponse)
+								docData.children[message.index].setFrameContentCallback();
+							if (message.getContentResponse) {
+								docData.content = message.content;
+								docData.getContentCallback();
+							}
+							if (message.setContentResponse)
+								setContentResponse(tabId, message.pageId, docData, message.content);
+						}
+					}
+				}
+
+				if (port.name == "singlefile") {
+					port.onMessage.addListener(onMessage);
+					port.onDisconnect.addListener(onDisconnect);
+				}
+			});
+
+	chrome.extension.onRequestExternal.addListener(function(request, sender, sendResponse) {
+		// console.log("onRequestExternal", request);
+		var property, config = DEFAULT_CONFIG;
+		if (request.config)
+			for (property in request.config)
+				config[property] = request.config[property];
+		if (request.processSelection)
+			process(request.id, sender.id, config, true);
+		else if (request.tabIds)
+			request.tabIds.forEach(function(tabId) {
+				process(tabId, sender.id, config);
+			});
+		else
+			process(request.id, sender.id, config);
+		sendResponse({});
+	});
+
+})();

+ 303 - 0
WebContent/core/scripts/bg/bgcore.js

@@ -0,0 +1,303 @@
+/*
+ * Copyright 2011 Gildas Lormeau
+ * contact : gildas.lormeau <at> gmail.com
+ * 
+ * This file is part of SingleFile Core.
+ *
+ *   SingleFile Core is free software: you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as published by
+ *   the Free Software Foundation, either version 3 of the License, or
+ *   (at your option) any later version.
+ *
+ *   SingleFile Core is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public License
+ *   along with SingleFile Core.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+(function() {
+
+	singlefile.PageData = PageData;
+	singlefile.DocData = DocData;
+
+	function PageData(tabId, pageId, senderId, config, processSelection, callback) {
+		var timeoutError, that = this;
+		this.pageId = pageId;
+		this.docs = [];
+		this.processedDocs = 0;
+		this.initializedDocs = 0;
+		this.processableDocs = 0;
+		this.senderId = senderId;
+		this.config = config;
+		this.processSelection = processSelection;
+		this.processing = true;
+		this.tabId = tabId;
+		this.requestManager = new singlefile.nio.RequestManager();
+		this.progressIndex = 0;
+		this.progressMax = 0;
+		this.title = null;
+		this.url = null;
+		this.top = null;
+		this.timeoutPageInit = null;
+		this.portsId = [];
+		timeoutError = setTimeout(function() {
+			that.processing = false;
+			chrome.extension.sendRequest(that.senderId, {
+				processError : true,
+				tabId : tabId
+			});
+		}, 15000);
+		wininfo.init(tabId, function(processableDocs) {
+			clearTimeout(timeoutError);
+			that.processableDocs = processableDocs;
+			callback();
+		});
+	}
+
+	PageData.prototype = {
+		initProcess : function() {
+			var that = this;
+			this.docs.forEach(function(docData) {
+				if (that.config.processInBackground) {
+					if (docData.processDocCallback)
+						docData.processDocCallback();
+				} else
+					docData.process();
+			});
+		},
+		processDoc : function(port, topWindow, winId, index, content, title, url, baseURI, characterSet, canvasData, callbacks) {
+			var that = this, docData;
+			docData = new DocData(port, winId, index, content, baseURI, characterSet, canvasData);
+			if (topWindow) {
+				this.top = docData;
+				this.title = title || "";
+				this.url = url;
+			}
+			this.docs.push(docData);
+			if (this.config.processInBackground && docData.content) {
+				docData.parseContent();
+				docData.processDocCallback = singlefile.initProcess(docData.doc, docData.doc.documentElement, topWindow, baseURI, characterSet, this.config,
+						canvasData, this.requestManager, function(maxIndex) {
+							callbacks.init(that, docData, maxIndex);
+						}, function(index, maxIndex) {
+							callbacks.progress(that, docData, index);
+						}, function() {
+							callbacks.end(that, docData);
+						});
+			}
+		},
+		processDocFragment : function(docData, mutationEventId, content) {
+			var doc = document.implementation.createHTMLDocument();
+			doc.body.innerHTML = content;
+			docData.processDocCallback = singlefile.initProcess(doc, doc.documentElement, this.top == docData, docData.baseURI, docData.characterSet,
+					this.config, null, this.requestManager, function() {
+						docData.processDocCallback();
+					}, null, function() {
+						docData.setDocFragment(doc.body.innerHTML, mutationEventId);
+					});
+		},
+		setDocContent : function(docData, content, callback) {
+			var that = this;
+
+			function buildPage(docData, setFrameContent, getContent, callback) {
+				function setContent(docData) {
+					var parent = docData.parent;
+					if (parent)
+						setFrameContent(docData, function() {
+							parent.processedChildren++;
+							if (parent.processedChildren == parent.childrenLength)
+								getContent(parent, function() {
+									setContent(parent);
+								});
+						});
+					else if (callback)
+						callback(docData);
+				}
+
+				if (docData.childrenLength)
+					docData.children.forEach(function(data) {
+						buildPage(data, setFrameContent, getContent, callback);
+					});
+				else
+					setContent(docData);
+			}
+
+			function bgPageEnd(pageData, docData, callback) {
+				var content = singlefile.util.getDocContent(docData.doc);
+				if (pageData.config.displayProcessedPage)
+					pageData.top.setContent(content);
+				else
+					callback(pageData.tabId, pageData.pageId, pageData.top, content);
+			}
+
+			if (content)
+				docData.content = content;
+			this.processedDocs++;
+			if (this.processSelection)
+				if (this.config.processInBackground)
+					bgPageEnd(this, docData, callback);
+				else
+					docData.getContent(function() {
+						that.top.setContent(docData.content);
+					});
+			else if (this.processedDocs == this.docs.length) {
+				this.docs.forEach(function(docData) {
+					var parentWinId = docData.winId.match(/((?:\d*\.?)*)\.\d*/);
+					parentWinId = parentWinId ? parentWinId[1] : null;
+					if (parentWinId)
+						that.docs.forEach(function(data) {
+							if (data.winId && data.winId == parentWinId)
+								docData.parent = data;
+						});
+					if (docData.parent)
+						docData.parent.setChild(docData);
+				});
+				if (this.config.processInBackground)
+					buildPage(this.top, function(docData, callback) {
+						docData.parent.docFrames[docData.index].setAttribute("src", "data:text/html;charset=utf-8,"
+								+ encodeURI(singlefile.util.getDocContent(docData.doc)));
+						delete docData.doc;
+						callback();
+					}, function(docData, callback) {
+						callback();
+					}, function(docData) {
+						bgPageEnd(that, docData, callback);
+					});
+				else
+					buildPage(this.top, function(docData, callback) {
+						docData.parent.setFrameContent(docData, callback);
+					}, function(docData, callback) {
+						docData.getContent(callback);
+					}, function(docData) {
+						docData.setContent();
+					});
+			}
+		},
+		computeProgress : function() {
+			var that = this;
+			this.progressIndex = 0;
+			this.progressMax = 0;
+			this.docs.forEach(function(docData) {
+				that.progressIndex += docData.progressIndex || 0;
+				that.progressMax += docData.progressMax || 0;
+			});
+		},
+		endProcess : function(content) {
+			var that = this;
+			if (this.config.savePage)
+				singlefile.storage.addContent(this.title ? this.title.replace(/[\\\/:\*\?\"><|]/gi, "").trim() || "Unnamed page" : "Unnamed page", content
+						.replace(/<meta[^>]*http-equiv\s*=\s*["']?content-type[^>]*>/gi, "").replace(/<meta[^>]*charset\s*=[^>]*>/gi, ""),
+						this.config.filenameMaxLength, function(processed, filename) {
+							chrome.extension.sendRequest(that.senderId, {
+								pageSaved : true,
+								tabId : that.tabId,
+								pageId : that.pageId,
+								processed : processed,
+								filename : filename
+							});
+						});
+		},
+		getResourceContentRequest : function(url, requestId, winId, characterSet, mediaTypeParam, docData) {
+			this.requestManager.send(url, function(content) {
+				docData.getResourceContentResponse(content, requestId);
+			}, characterSet, mediaTypeParam);
+		},
+		getDocData : function(winId) {
+			var found;
+			this.docs.forEach(function(docData) {
+				if (docData.winId == winId)
+					found = docData;
+			});
+			return found;
+		}
+	};
+
+	function DocData(port, winId, index, content, baseURI, characterSet, canvasData) {
+		this.port = port;
+		this.content = content;
+		this.baseURI = baseURI;
+		this.characterSet = characterSet;
+		this.canvasData = canvasData;
+		this.winId = winId;
+		this.index = index;
+		this.children = [];
+		this.doc = null;
+		this.docFrames = null;
+		this.processDocCallback = null;
+		this.getContentCallback = null;
+		this.setFrameContentCallback = null;
+		this.processedChildren = 0;
+		this.childrenLength = 0;
+	}
+
+	DocData.prototype = {
+		parseContent : function() {
+			var doc = document.implementation.createHTMLDocument();
+			doc.open();
+			doc.write(this.content);
+			doc.close();
+			this.doc = doc;
+			this.docFrames = doc.querySelectorAll("iframe, frame");
+			delete this.content;
+		},
+		setChild : function(childDoc) {
+			this.children[childDoc.index] = childDoc;
+			this.childrenLength++;
+		},
+		process : function() {
+			this.port.postMessage({
+				processDoc : true,
+				winId : this.winId
+			});
+		},
+		setDocFragment : function(content, mutationEventId) {
+			this.port.postMessage({
+				setDocFragment : true,
+				content : content,
+				mutationEventId : mutationEventId
+			});
+		},
+		getResourceContentResponse : function(content, requestId) {
+			this.port.postMessage({
+				getResourceContentResponse : true,
+				requestId : requestId,
+				winId : this.winId,
+				content : content
+			});
+		},
+		setContent : function(content) {
+			this.port.postMessage({
+				setContentRequest : true,
+				content : content,
+				winProperties : singlefile.winProperties
+			});
+		},
+		getContent : function(callback) {
+			this.getContentCallback = callback;
+			this.port.postMessage({
+				getContentRequest : true,
+				winId : this.winId
+			});
+		},
+		setFrameContent : function(docData, callback) {
+			docData.setFrameContentCallback = callback;
+			this.port.postMessage({
+				setFrameContentRequest : true,
+				winId : this.winId,
+				index : docData.index,
+				content : docData.content
+			});
+		}
+	};
+
+	(function() {
+		var property, winProperties = {};
+		for (property in window)
+			winProperties[property] = true;
+		singlefile.winProperties = winProperties;
+	})();
+
+})();

+ 93 - 0
WebContent/core/scripts/bg/nio.js

@@ -0,0 +1,93 @@
+/*
+ * Copyright 2011 Gildas Lormeau
+ * contact : gildas.lormeau <at> gmail.com
+ * 
+ * This file is part of SingleFile Core.
+ *
+ *   SingleFile Core is free software: you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as published by
+ *   the Free Software Foundation, either version 3 of the License, or
+ *   (at your option) any later version.
+ *
+ *   SingleFile Core is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public License
+ *   along with SingleFile Core.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+(function() {
+
+	singlefile.nio = {};
+
+	singlefile.nio.RequestManager = function() {
+		var cache = {}, keys = [], pendingResponseHandlers = {};
+
+		function sendResponses(key) {
+			if (pendingResponseHandlers[key]) {
+				pendingResponseHandlers[key].forEach(function(callback) {
+					callback(cache[key]);
+				});
+				delete pendingResponseHandlers[key];
+			}
+		}
+
+		function throwAwayHighOrderBytes(str) {
+			var i, ret = [];
+			for (i = 0; i < str.length; i++)
+				ret[i] = String.fromCharCode(str.charCodeAt(i) & 0xff);
+			return ret.join("");
+		}
+
+		this.reset = function() {
+			cache = {};
+			keys = [];
+		};
+
+		this.send = function(url, responseHandler, characterSet, mediaTypeParam) {
+			var xhr, key = JSON.stringify({
+				url : url,
+				characterSet : characterSet,
+				mediaTypeParam : mediaTypeParam
+			}), resource = cache[key];
+
+			if (resource)
+				setTimeout(function() {
+					responseHandler(resource);
+				}, 1);
+			else if (pendingResponseHandlers[key])
+				pendingResponseHandlers[key].push(responseHandler);
+			else {
+				pendingResponseHandlers[key] = [ responseHandler ];
+				xhr = new XMLHttpRequest();
+				xhr.onreadystatechange = function() {
+					if (xhr.readyState == 4) {
+						cache[key] = {
+							url : url,
+							status : xhr.status,
+							mediaType : xhr.getResponseHeader("Content-Type"),
+							content : mediaTypeParam == "base64" ? btoa(throwAwayHighOrderBytes(xhr.responseText)) : xhr.responseText,
+							mediaTypeParam : mediaTypeParam
+						};
+						keys.push(key);
+						sendResponses(key);
+					}
+				};
+				xhr.onerror = function() {
+					sendResponses(key);
+				};
+				xhr.open("GET", url, true);
+				if (characterSet)
+					xhr.overrideMimeType('text/plain; charset=' + characterSet);
+				try {
+					xhr.send(null);
+				} catch (e) {
+					sendResponses(key);
+				}
+			}
+		};
+	};
+
+})();

+ 96 - 0
WebContent/core/scripts/bg/storage.js

@@ -0,0 +1,96 @@
+/*
+ * Copyright 2011 Gildas Lormeau
+ * contact : gildas.lormeau <at> gmail.com
+ * 
+ * This file is part of SingleFile Core.
+ *
+ *   SingleFile Core is free software: you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as published by
+ *   the Free Software Foundation, either version 3 of the License, or
+ *   (at your option) any later version.
+ *
+ *   SingleFile Core is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public License
+ *   along with SingleFile Core.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+(function() {
+	var STORAGE_SIZE = 1073741824, FILENAME_MAX_LENGTH = 256, BOM, fs;
+
+	singlefile.storage = {};
+
+	singlefile.storage.isEnabled = typeof requestFileSystem != "undefined" && typeof ArrayBuffer != "undefined" && typeof Uint8Array != "undefined";
+
+	function init() {
+		var view;
+		if (!singlefile.storage.isEnabled)
+			return;
+		BOM = new ArrayBuffer(3);
+		view = new Uint8Array(BOM);
+		view.set([ 0xEF, 0xBB, 0xBF ]);
+		requestFileSystem(true, STORAGE_SIZE, function(filesystem) {
+			fs = filesystem;
+			singlefile.storage.isEnabled = true;
+		}, function(e) {
+			singlefile.storage.isEnabled = false;
+			console.log(e);
+		});
+	}
+
+	singlefile.storage.addContent = function(name, content, maxLength, callback, index) {
+		var suffix = (index ? " (" + (index + 1) + ")" : ""), max = maxLength - suffix.length, filename = (name.length > max - 6 ? name.substring(0, max - 6)
+				+ "[...] " : name)
+				+ suffix + ".html";
+		if (fs) {
+			fs.root.getFile(filename, {
+				create : true,
+				exclusive : true
+			}, function(fileEntry) {
+				fileEntry.createWriter(function(fileWriter) {
+					var blobBuilder = new BlobBuilder();
+					blobBuilder.append(BOM);
+					blobBuilder.append(content);
+					fileWriter.onerror = function(e) {
+						callback(false, filename);
+					};
+					fileWriter.onwrite = function(e) {
+						callback(true, filename);
+					};
+					fileWriter.write(blobBuilder.getBlob());
+				}, function(e) {
+					console.log(e);
+					callback(false, filename);
+				});
+			}, function(e) {
+				if (e.code == e.INVALID_MODIFICATION_ERR) {
+					index = index || 0;
+					index++;
+					singlefile.storage.addContent(name, content, maxLength, callback, index);
+				} else {
+					console.log(e);
+					callback(false, filename);
+				}
+			});
+		} else
+			callback(false, filename);
+	};
+
+	singlefile.storage.reset = function() {
+		var rootReader;
+		if (fs) {
+			rootReader = fs.root.createReader("/");
+			rootReader.readEntries(function(entries) {
+				var i;
+				for (i = 0; i < entries.length; i++)
+					entries[i].remove();
+			});
+		}
+	};
+
+	init();
+
+})();

+ 40 - 0
WebContent/core/scripts/bg/wininfo.js

@@ -0,0 +1,40 @@
+/*
+ * Copyright 2011 Gildas Lormeau
+ * contact : gildas.lormeau <at> gmail.com
+ * 
+ * This file is part of SingleFile Core.
+ *
+ *   SingleFile Core is free software: you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as published by
+ *   the Free Software Foundation, either version 3 of the License, or
+ *   (at your option) any later version.
+ *
+ *   SingleFile Core is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public License
+ *   along with SingleFile Core.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+var wininfo = {
+	init : function(tabId, processCallback) {
+		function onConnect(port) {
+			if (port.name == "wininfo" && port.sender.tab.id == tabId)
+				port.onMessage.addListener(function(message) {
+					// console.log("winfo.onMessage", tabId, message);
+					if (message.initResponse)
+						processCallback(message.processableDocs);
+					chrome.extension.onConnect.removeListener(onConnect);
+				});
+		}
+
+		chrome.extension.onConnect.addListener(onConnect);
+		chrome.tabs.sendRequest(tabId, {
+			initRequest : true,
+			winId : "0",
+			index : 0
+		});
+	}
+};

+ 441 - 0
WebContent/core/scripts/common/docprocessor.js

@@ -0,0 +1,441 @@
+/*
+ * Copyright 2011 Gildas Lormeau
+ * contact : gildas.lormeau <at> gmail.com
+ * 
+ * This file is part of SingleFile Core.
+ *
+ *   SingleFile Core is free software: you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as published by
+ *   the Free Software Foundation, either version 3 of the License, or
+ *   (at your option) any later version.
+ *
+ *   SingleFile Core is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public License
+ *   along with SingleFile Core.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+(function() {
+
+	var IMPORT_URL_VALUE_EXP = /(url\s*\(\s*(?:'|")?\s*([^('|"|\))]*)\s*(?:'|")?\s*\))|(@import\s*\(?\s*(?:'|")?\s*([^('|"|\))]*)\s*(?:'|")?\s*(?:\)|;))/i;
+	var URL_VALUE_EXP = /url\s*\(\s*(?:'|")?\s*([^('|"|\))]*)\s*(?:'|")?\s*\)/i;
+	var IMPORT_VALUE_ALT_EXP = /@import\s*\(?\s*(?:'|")?\s*([^('|"|\))]*)\s*(?:'|")?\s*(?:\)|;)/i;
+	var URL_EXP = /url\s*\(([^\)]*)\)/gi;
+	var IMPORT_EXP = /(@import\s*url\s*\([^\)]*\)\s*;?)|(@import\s*('|")?\s*[^\(|;|'|"]*\s*('|")?\s*;)/gi;
+	var IMPORT_ALT_EXP = /@import\s*('|")?\s*[^\(|;|'|"]*\s*('|")?\s*;/gi;
+	var EMPTY_PIXEL_DATA = "data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==";
+
+	function formatURL(link, host) {
+		var i, newlinkparts, hparts, lparts;
+		if (!link)
+			return "";
+
+		lparts = link.split('/');
+		host = host.split("#")[0].split("?")[0];
+		if (/http:|https:|ftp:|data:|javascript:/i.test(lparts[0]))
+			return link.trim();
+		hparts = host.split('/');
+		newlinkparts = [];
+		if (hparts.length > 3)
+			hparts.pop();
+		if (lparts[0] == '') {
+			if (lparts[1] == '')
+				host = hparts[0] + '//' + lparts[2];
+			else
+				host = hparts[0] + '//' + hparts[2];
+			hparts = host.split('/');
+			delete lparts[0];
+			if (lparts[1] == '') {
+				delete lparts[1];
+				delete lparts[2];
+			}
+		}
+		for (i = 0; i < lparts.length; i++) {
+			if (lparts[i] == '..') {
+				if (lparts[i - 1])
+					delete lparts[i - 1];
+				else if (hparts.length > 3)
+					hparts.pop();
+				delete lparts[i];
+			}
+			if (lparts[i] == '.')
+				delete lparts[i];
+		}
+		for (i = 0; i < lparts.length; i++)
+			if (lparts[i])
+				newlinkparts[newlinkparts.length] = lparts[i];
+		return (hparts.join('/') + '/' + newlinkparts.join('/')).trim();
+	}
+
+	function resolveURLs(content, host) {
+		var ret = content.replace(URL_EXP, function(value) {
+			var result = value.match(URL_VALUE_EXP);
+			if (result)
+				if (!(result[1].indexOf("data:") == 0))
+					return value.replace(result[1], formatURL(result[1], host));
+			return value;
+		});
+		return ret.replace(IMPORT_ALT_EXP, function(value) {
+			var result = value.match(IMPORT_VALUE_ALT_EXP);
+			if (result)
+				if (!(result[1].indexOf("data:") == 0))
+					return "@import \"" + formatURL(result[1], host) + "\";";
+			return value;
+		});
+
+	}
+
+	function getDataURI(data, defaultURL, woURL) {
+		if (data.content)
+			return [ woURL ? "" : "url(", "data:", data.mediaType, ";", data.mediaTypeParam, ",", data.content, woURL ? "" : ")" ].join("");
+		else
+			return woURL ? defaultURL : [ "url(", defaultURL, ")" ].join("");
+	}
+
+	function removeComments(content) {
+		var start, end;
+		do {
+			start = content.indexOf("/*");
+			end = content.indexOf("*/", start);
+			if (start != -1 && end != -1)
+				content = [ content.substring(0, start), content.substr(end + 2) ].join("");
+		} while (start != -1 && end != -1);
+		return content;
+	}
+
+	function replaceURLs(content, host, requestManager, callback) {
+		var i, url, result, values = removeComments(content).match(URL_EXP), requestMax = 0, requestIndex = 0;
+
+		function sendRequest(origUrl) {
+			requestMax++;
+			requestManager.send(url, function(data) {
+				requestIndex++;
+				if (content.indexOf(origUrl) != -1) {
+					data.mediaType = data.mediaType ? data.mediaType.split(";")[0] : null;
+					content = content.replace(new RegExp(origUrl.replace(/([{}\(\)\^$&.\*\?\/\+\|\[\\\\]|\]|\-)/g, "\\$1"), "gi"), getDataURI(data,
+							EMPTY_PIXEL_DATA, true));
+				}
+				if (requestIndex == requestMax)
+					callback(content);
+			}, "x-user-defined", "base64");
+		}
+
+		if (values)
+			for (i = 0; i < values.length; i++) {
+				result = values[i].match(URL_VALUE_EXP);
+				if (result && result[1]) {
+					url = formatURL(result[1], host);
+					if (!(url.indexOf("data:") == 0))
+						sendRequest(result[1]);
+				}
+			}
+	}
+
+	// ----------------------------------------------------------------------------------------------
+
+	function processStylesheets(doc, docElement, baseURI, requestManager) {
+		Array.prototype.forEach.call(docElement.querySelectorAll('link[href][rel*="stylesheet"]'), function(node) {
+			var href = node.getAttribute("href"), fullHref = formatURL(href, baseURI);
+			if (!(href.indexOf("data:") == 0)) {
+				requestManager.send(fullHref, function(data) {
+					var i, newNode, commentNode;
+					if (data.status == 404) {
+						node.parentElement.removeChild(node);
+						return;
+					}
+					newNode = doc.createElement("style");
+					for (i = 0; i < node.attributes.length; i++)
+						if (node.attributes[i].value)
+							newNode.setAttribute(node.attributes[i].name, node.attributes[i].value);
+					newNode._baseURI = fullHref;
+					newNode.removeAttribute("href");
+					newNode.textContent = resolveURLs(data.content || "", data.url);
+					if (node.disabled) {
+						commentNode = doc.createComment();
+						commentNode.textContent = newNode.outerHTML.replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/--/g, "&minus;&minus;");
+						node.parentElement.replaceChild(commentNode, node);
+					} else
+						node.parentElement.replaceChild(newNode, node);
+				});
+			}
+		});
+	}
+
+	function processImports(docElement, baseURI, characterSet, requestManager) {
+		var ret = true;
+		Array.prototype.forEach.call(docElement.querySelectorAll("style"), function(styleSheet) {
+			var i, url, result, imports = removeComments(styleSheet.textContent).match(IMPORT_EXP);
+
+			function sendRequest(imp) {
+				requestManager.send(url,
+						function(data) {
+							styleSheet.textContent = styleSheet.textContent.replace(imp, data.status != 404 && data.content ? resolveURLs(data.content,
+									data.url) : "");
+						}, null, characterSet);
+				ret = false;
+			}
+
+			if (imports)
+				for (i = 0; i < imports.length; i++) {
+					result = imports[i].match(IMPORT_URL_VALUE_EXP);
+					if (result && (result[2] || result[4])) {
+						url = formatURL(result[2] || result[4], styleSheet._baseURI || baseURI);
+						if (!(url.indexOf("data:") == 0))
+							sendRequest(imports[i]);
+					}
+				}
+		});
+		return ret;
+	}
+
+	function processStyleAttributes(docElement, baseURI, requestManager) {
+		Array.prototype.forEach.call(docElement.querySelectorAll("*[style]"), function(node) {
+			replaceURLs(node.getAttribute("style"), baseURI, requestManager, function(style) {
+				node.setAttribute("style", style);
+			});
+		});
+	}
+
+	function processBgAttributes(docElement, baseURI, requestManager) {
+		var backgrounds = docElement.querySelectorAll("*[background]");
+		Array.prototype.forEach.call(backgrounds, function(node) {
+			var url, value = node.getAttribute("background");
+			if (value.indexOf(".") != -1) {
+				url = formatURL(value, baseURI);
+				if (!(url.indexOf("data:") == 0))
+					requestManager.send(url, function(data) {
+						node.setAttribute("background", getDataURI(data, EMPTY_PIXEL_DATA, true));
+					}, "x-user-defined", "base64");
+			}
+		});
+	}
+
+	function insertDefaultFavico(doc, docElement, baseURI) {
+		var node, docHead = docElement.querySelector("html > head"), favIcon = docElement
+				.querySelector('link[href][rel="shortcut icon"], link[href][rel="apple-touch-icon"], link[href][rel="icon"]');
+		if (!favIcon && docHead) {
+			node = doc.createElement("link");
+			node.setAttribute("type", "image/x-icon");
+			node.setAttribute("rel", "shortcut icon");
+			node.setAttribute("href", formatURL("/favicon.ico", baseURI));
+			docHead.appendChild(node);
+		}
+	}
+
+	function processImages(docElement, baseURI, requestManager) {
+		var images;
+
+		function process(attributeName) {
+			Array.prototype.forEach.call(images, function(node) {
+				var url = formatURL(node.getAttribute(attributeName), baseURI);
+				if (!(url.indexOf("data:") == 0))
+					requestManager.send(url, function(data) {
+						node.setAttribute(attributeName, getDataURI(data, EMPTY_PIXEL_DATA, true));
+					}, "x-user-defined", "base64");
+			});
+		}
+
+		images = docElement.querySelectorAll('link[href][rel="shortcut icon"], link[href][rel="apple-touch-icon"], link[href][rel="icon"]');
+		process("href");
+		images = docElement.querySelectorAll('img[src], input[src][type="image"]');
+		process("src");
+		images = docElement.querySelectorAll('video[poster]');
+		process("poster");
+
+	}
+
+	function processSVGs(docElement, baseURI, requestManager) {
+		var images = docElement.querySelectorAll('object[type="image/svg+xml"], object[type="image/svg-xml"], embed[src*=".svg"]');
+		Array.prototype.forEach.call(images, function(node) {
+			var data = node.getAttribute("data"), src = node.getAttribute("src"), url = formatURL(data || src, baseURI);
+			if (!(url.indexOf("data:") == 0))
+				requestManager.send(url, function(data) {
+					node.setAttribute(data ? "data" : "src", getDataURI(data, "data:text/xml,<svg></svg>", true));
+				}, null, null);
+		});
+	}
+
+	function processStyles(docElement, baseURI, requestManager) {
+		Array.prototype.forEach.call(docElement.querySelectorAll("style"), function(styleSheet) {
+			replaceURLs(styleSheet.textContent, styleSheet._baseURI || baseURI, requestManager, function(textContent) {
+				styleSheet.textContent = textContent;
+			});
+		});
+	}
+
+	function processScripts(docElement, baseURI, characterSet, requestManager) {
+		Array.prototype.forEach.call(docElement.querySelectorAll("script[src]"), function(node) {
+			var src = node.getAttribute("src");
+			if (!(src.indexOf("data:") == 0))
+				requestManager.send(formatURL(src, baseURI), function(data) {
+					if (data.status != 404) {
+						data.content = data.content.replace(/"([^"]*)<\/\s*script\s*>([^"]*)"/gi, '"$1<"+"/script>$2"');
+						data.content = data.content.replace(/'([^']*)<\/\s*script\s*>([^']*)'/gi, "'$1<'+'/script>$2'");
+						node.textContent = [ "\n", data.content, "\n" ].join("");
+					}
+					node.removeAttribute("src");
+				}, characterSet);
+		});
+	}
+
+	function processCanvas(doc, docElement, canvasData) {
+		var index = 0;
+		Array.prototype.forEach.call(docElement.querySelectorAll("canvas"), function(node) {
+			var i, data = canvasData[index], newNode = doc.createElement("img");
+			if (data) {
+				newNode.setAttribute("src", data);
+				for (i = 0; i < node.attributes.length; i++)
+					if (node.attributes[i].value)
+						newNode.setAttribute(node.attributes[i].name, node.attributes[i].value);
+				if (!newNode.width)
+					newNode.style.pixelWidth = node.clientWidth;
+				if (!newNode.height)
+					newNode.style.pixelHeight = node.clientHeight;
+				node.parentElement.replaceChild(newNode, node);
+			}
+			index++;
+		});
+	}
+
+	function removeScripts(docElement) {
+		var body = docElement.querySelector("html > body");
+		Array.prototype.forEach.call(docElement.querySelectorAll("script"), function(node) {
+			node.parentElement.removeChild(node);
+		});
+		if (body && body.getAttribute("onload"))
+			body.removeAttribute("onload");
+	}
+
+	function removeObjects(docElement) {
+		var objects = docElement.querySelectorAll('applet, object:not([type="image/svg+xml"]):not([type="image/svg-xml"]), embed:not([src*=".svg"])');
+		Array.prototype.forEach.call(objects, function(node) {
+			node.parentElement.removeChild(node);
+		});
+		objects = docElement.querySelectorAll('audio[src], video[src]');
+		Array.prototype.forEach.call(objects, function(node) {
+			node.removeAttribute("src");
+		});
+	}
+
+	function removeBlockquotesCite(docElement) {
+		Array.prototype.forEach.call(docElement.querySelectorAll("blockquote[cite]"), function(node) {
+			node.removeAttribute("cite");
+		});
+	}
+
+	function removeFrames(docElement) {
+		Array.prototype.forEach.call(docElement.querySelectorAll("iframe, frame"), function(node) {
+			node.parentElement.removeChild(node);
+		});
+	}
+
+	function resetFrames(docElement, baseURI) {
+		Array.prototype.forEach.call(docElement.querySelectorAll("iframe, frame"), function(node) {
+			var src = formatURL(node.getAttribute("src"), baseURI);
+			if (src.indexOf("data:") != 0)
+				node.setAttribute("src", "about:blank");
+		});
+	}
+
+	function setAbsoluteLinks(docElement, baseURI) {
+		Array.prototype.forEach.call(docElement.querySelectorAll("a[href]"), function(link) {
+			var fullHref = formatURL(link.getAttribute("href"), baseURI);
+			if (fullHref && (!(fullHref.indexOf(baseURI.split("#")[0]) == 0) || fullHref.indexOf("#") == -1))
+				link.setAttribute("href", fullHref);
+		});
+	}
+
+	// ----------------------------------------------------------------------------------------------
+
+	singlefile.initProcess = function(doc, docElement, addDefaultFavico, baseURI, characterSet, config, canvasData, requestManager, onInit, onProgress, onEnd) {
+		var initManager = new RequestManager(), manager = new RequestManager(onProgress);
+
+		function RequestManager(onProgress) {
+			var that = this, currentCount = 0, requests = [];
+
+			this.requestCount = 0;
+
+			this.send = function(url, responseHandler, characterSet, mediaTypeParam) {
+				this.requestCount++;
+				requests.push({
+					url : url,
+					responseHandler : responseHandler,
+					characterSet : characterSet,
+					mediaTypeParam : mediaTypeParam
+				});
+			};
+
+			this.doSend = function() {
+				requests.forEach(function(request) {
+					requestManager.send(request.url, function(response) {
+						request.responseHandler(response);
+						currentCount++;
+						if (onProgress)
+							onProgress(currentCount, that.requestCount);
+						if (currentCount == that.requestCount) {
+							that.requestCount = 0;
+							currentCount = 0;
+							if (that.onEnd)
+								that.onEnd();
+						}
+					}, request.characterSet, request.mediaTypeParam);
+				});
+				requests = [];
+			};
+		}
+
+		function cbImports() {
+			if (config.removeScripts)
+				removeScripts(docElement);
+			if (config.removeObjects)
+				removeObjects(docElement);
+			if (config.removeFrames || config.getRawDoc)
+				removeFrames(docElement);
+			resetFrames(docElement, baseURI);
+			removeBlockquotesCite(docElement);
+			setAbsoluteLinks(docElement, baseURI);
+			if (addDefaultFavico)
+				insertDefaultFavico(doc, docElement, baseURI);
+			processStyleAttributes(docElement, baseURI, manager);
+			processBgAttributes(docElement, baseURI, manager);
+			processImages(docElement, baseURI, manager);
+			processSVGs(docElement, baseURI, manager);
+			processStyles(docElement, baseURI, manager);
+			processScripts(docElement, baseURI, characterSet, manager);
+			processCanvas(doc, docElement, canvasData);
+			if (onInit)
+				setTimeout(function() {
+					onInit(manager.requestCount);
+				}, 1);
+		}
+
+		function cbStylesheets() {
+			initManager.onEnd = function(noRequests) {
+				if (noRequests)
+					cbImports();
+				else
+					cbStylesheets();
+			};
+			processImports(docElement, baseURI, characterSet, initManager);
+			initManager.doSend();
+			if (initManager.requestCount == 0)
+				cbImports();
+		}
+
+		manager.onEnd = onEnd;
+		processStylesheets(doc, docElement, baseURI, initManager);
+		initManager.onEnd = cbStylesheets;
+		initManager.doSend();
+		if (initManager.requestCount == 0)
+			initManager.onEnd();
+		return function() {
+			manager.doSend();
+			if (manager.requestCount == 0 && manager.onEnd)
+				manager.onEnd();
+		};
+	};
+
+})();

+ 47 - 0
WebContent/core/scripts/common/util.js

@@ -0,0 +1,47 @@
+/*
+ * Copyright 2011 Gildas Lormeau
+ * contact : gildas.lormeau <at> gmail.com
+ * 
+ * This file is part of SingleFile Core.
+ *
+ *   SingleFile Core is free software: you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as published by
+ *   the Free Software Foundation, either version 3 of the License, or
+ *   (at your option) any later version.
+ *
+ *   SingleFile Core is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public License
+ *   along with SingleFile Core.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+(function() {
+
+	singlefile.util = {};
+
+	function getDoctype(doc) {
+		var docType = doc.doctype, docTypeStr;
+		if (docType) {
+			docTypeStr = "<!DOCTYPE " + docType.nodeName;
+			if (docType.publicId) {
+				docTypeStr += " PUBLIC \"" + docType.publicId + "\"";
+				if (docType.systemId)
+					docTypeStr += " \"" + docType.systemId + "\"";
+			} else if (docType.systemId)
+				docTypeStr += " SYSTEM \"" + docType.systemId + "\"";
+			if (docType.internalSubset)
+				docTypeStr += " [" + docType.internalSubset + "]";
+			return docTypeStr + ">\n";
+		}
+		return "";
+	}
+
+	singlefile.util.getDocContent = function(doc, docElement) {
+		docElement = docElement || doc.documentElement;
+		return [ getDoctype(doc), docElement.outerHTML ].join("");
+	};
+
+})();

+ 451 - 0
WebContent/core/scripts/content/content.js

@@ -0,0 +1,451 @@
+/*
+ * Copyright 2011 Gildas Lormeau
+ * contact : gildas.lormeau <at> gmail.com
+ * 
+ * This file is part of SingleFile Core.
+ *
+ *   SingleFile Core is free software: you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as published by
+ *   the Free Software Foundation, either version 3 of the License, or
+ *   (at your option) any later version.
+ *
+ *   SingleFile Core is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public License
+ *   along with SingleFile Core.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+(function() {
+
+	var bgPort, docs = {}, pageId = singlefile.pageId, doc = document, docElement, canvasData = [], config = singlefile.config;
+
+	function RequestManager(pageId, winId) {
+		var requestId = 0, callbacks = [];
+
+		this.send = function(url, responseHandler, characterSet, mediaTypeParam) {
+			callbacks[requestId] = responseHandler;
+			bgPort.postMessage({
+				getResourceContentRequest : true,
+				pageId : pageId,
+				winId : winId,
+				requestId : requestId,
+				url : url,
+				characterSet : characterSet,
+				mediaTypeParam : mediaTypeParam
+			});
+			requestId++;
+		};
+
+		this.onResponse = function(id, content) {
+			callbacks[id](content);
+			callbacks[id] = null;
+		};
+	}
+
+	function removeUnusedCSSRules() {
+		Array.prototype.forEach.call(document.querySelectorAll("style"), function(style) {
+			var cssRules = [];
+
+			function process(rules) {
+				Array.prototype.forEach.call(rules, function(rule) {
+					var selector;
+					if (rule.media) {
+						cssRules.push("@media " + Array.prototype.join.call(rule.media, ",") + " {");
+						process(rule.cssRules, true);
+						cssRules.push("}");
+					} else if (rule.selectorText) {
+						selector = rule.selectorText.replace(/::after|::before|::first-line|::first-letter|:focus|:hover/gi, '').trim();
+						if (selector)
+							try {
+								if (document.querySelector(selector))
+									cssRules.push(rule.cssText);
+							} catch (e) {
+								cssRules.push(rule.cssText);
+							}
+					}
+				});
+			}
+			if (style.sheet) {
+				process(style.sheet.rules);
+				style.innerText = cssRules.join("");
+			}
+		});
+	}
+
+	function removeHiddenElements() {
+		Array.prototype.forEach.call(doc.querySelectorAll("html > body *:not(style):not(script):not(link):not(area)"), function(element) {
+			var style = getComputedStyle(element);
+			if ((style.visibility == "hidden" || style.display == "none" || style.opacity == 0))
+				element.parentElement.removeChild(element);
+		});
+	}
+
+	function getSelectedContent() {
+		var node, wrapper, clonedNode, selection = getSelection(), range = selection.rangeCount ? selection.getRangeAt(0) : null;
+
+		function addStyle(node) {
+			var rules, cssText;
+			Array.prototype.forEach.call(node.children, function(child) {
+				addStyle(child);
+			});
+			rules = getMatchedCSSRules(node, '', false);
+			cssText = "";
+			Array.prototype.forEach.call(rules, function(rule) {
+				cssText += rule.style.cssText;
+			});
+			node.setAttribute("style", cssText);
+		}
+
+		if (range && range.startOffset != range.endOffset) {
+			node = range.commonAncestorContainer;
+			if (node.nodeType != node.ELEMENT_NODE)
+				node = node.parentElement;
+			clonedNode = node.cloneNode(true);
+			addStyle(node);
+			node.parentElement.replaceChild(clonedNode, node);
+		}
+		return node;
+	}
+
+	function getCanvasData(doc) {
+		var canvasData = [];
+		Array.prototype.forEach.call(doc.querySelectorAll("canvas"), function(node) {
+			var data = null;
+			try {
+				data = node.toDataURL("image/png", "");
+			} catch (e) {
+			}
+			canvasData.push(data);
+		});
+		return canvasData;
+	}
+
+	function initProcess(doc, docElement, winId, topWindow, canvasData) {
+		var requestManager = new RequestManager(pageId, winId);
+		docs[winId] = {
+			doc : doc,
+			docElement : docElement,
+			frames : docElement.querySelectorAll("iframe, frame"),
+			requestManager : requestManager,
+			processDoc : singlefile.initProcess(doc, docElement, topWindow, doc.baseURI, doc.characterSet, config, canvasData, requestManager, function(
+					maxIndex) {
+				bgPort.postMessage({
+					docInit : true,
+					pageId : pageId,
+					winId : winId,
+					maxIndex : maxIndex
+				});
+			}, function(index) {
+				bgPort.postMessage({
+					docProgress : true,
+					pageId : pageId,
+					winId : winId,
+					index : index
+				});
+			}, function() {
+				bgPort.postMessage({
+					docEnd : true,
+					pageId : pageId,
+					winId : winId,
+					content : topWindow ? null : singlefile.util.getDocContent(doc, docElement)
+				});
+			})
+		};
+	}
+
+	function sendFgProcessInit(title, url, baseURI, winId, winIndex) {
+		bgPort.postMessage({
+			processInit : true,
+			pageId : pageId,
+			topWindow : winId ? false : window == top,
+			url : url || location.href,
+			title : title || doc.title,
+			baseURI : baseURI || doc.baseURI,
+			winId : winId || wininfo.winId,
+			index : winIndex || wininfo.index
+		});
+	}
+
+	function sendBgProcessInit(content, title, url, baseURI, characterSet, winId, winIndex) {
+		bgPort.postMessage({
+			processInit : true,
+			pageId : pageId,
+			topWindow : winId ? false : window == top,
+			url : url || location.href,
+			title : title || doc.title,
+			content : content,
+			baseURI : baseURI || doc.baseURI,
+			characterSet : characterSet || doc.characterSet,
+			canvasData : canvasData,
+			winId : winId || wininfo.winId,
+			index : winIndex || wininfo.index
+		});
+	}
+
+	// ----------------------------------------------------------------------------------------------
+
+	function init() {
+		var selectedContent = getSelectedContent(), topWindow = window == top;
+
+		function doFgProcessInit() {
+			sendFgProcessInit();
+			if (docElement && (!singlefile.processSelection || selectedContent)) {
+				initProcess(doc, docElement, wininfo.winId, topWindow, canvasData);
+				if (topWindow && !config.removeFrames && !config.getRawDoc)
+					wininfo.frames.forEach(function(frame) {
+						if (frame.sameDomain)
+							wininfo.getContent(frame, function(message) {
+								var frameDoc = document.implementation.createHTMLDocument();
+								frameDoc.open();
+								frameDoc.write(message.content);
+								frameDoc.close();
+								sendFgProcessInit(message.title, message.url, message.baseURI, frame.winId, frame.index);
+								initProcess(frameDoc, frameDoc.documentElement, frame.winId, false, getCanvasData(frameDoc));
+							});
+					});
+			}
+		}
+
+		function bgProcessInit() {
+			var xhr;
+			if (singlefile.processSelection) {
+				if (selectedContent || topWindow)
+					sendBgProcessInit(topWindow ? null : singlefile.util.getDocContent(doc, selectedContent));
+			} else {
+				if (config.getRawDoc && topWindow) {
+					xhr = new XMLHttpRequest();
+					xhr.onreadystatechange = function() {
+						if (xhr.readyState == 4)
+							sendBgProcessInit(xhr.responseText);
+					};
+					xhr.open("GET", doc.location.href, true);
+					xhr.overrideMimeType('text/plain; charset=' + doc.characterSet);
+					xhr.send(null);
+				} else {
+					sendBgProcessInit(singlefile.util.getDocContent(doc));
+					if (topWindow && !config.removeFrames)
+						wininfo.frames.forEach(function(frame) {
+							if (frame.sameDomain)
+								wininfo.getContent(frame, function(message) {
+									sendBgProcessInit(message.content, message.title, message.url, message.baseURI, message.characterSet, frame.winId,
+											frame.index);
+								});
+						});
+				}
+			}
+		}
+
+		function fgProcessInit() {
+			var xhr, tmpDoc;
+			if (singlefile.processSelection) {
+				if (selectedContent || topWindow) {
+					docElement = selectedContent;
+					doFgProcessInit();
+				}
+			} else if (config.getRawDoc && topWindow) {
+				xhr = new XMLHttpRequest();
+				xhr.onreadystatechange = function() {
+					if (xhr.readyState == 4) {
+						tmpDoc = document.implementation.createHTMLDocument();
+						tmpDoc.open();
+						tmpDoc.write(xhr.responseText);
+						tmpDoc.close();
+						docElement = doc.importNode(tmpDoc.documentElement, true);
+						doFgProcessInit();
+					}
+				};
+				xhr.open("GET", doc.location.href, true);
+				xhr.overrideMimeType('text/plain; charset=' + doc.characterSet);
+				xhr.send(null);
+			} else {
+				docElement = doc.documentElement.cloneNode(true);
+				doFgProcessInit();
+			}
+		}
+
+		Array.prototype.forEach.call(doc.querySelectorAll("noscript"), function(node) {
+			node.textContent = "";
+		});
+		canvasData = getCanvasData(doc);
+		if (config.removeHidden)
+			removeHiddenElements();
+		if (topWindow)
+			document.documentElement.insertBefore(document.createComment("\n Archive processed by SingleFile \n url: " + location.href + " \n saved date: "
+					+ new Date() + " \n"), document.documentElement.firstChild);
+		if ((!config.removeFrames && !config.getRawDoc) || topWindow)
+			if (config.processInBackground)
+				bgProcessInit();
+			else
+				fgProcessInit();
+	}
+
+	function setContentRequest(message) {
+		var mutationEventId = 0, winId = wininfo.winId;
+
+		function resetWindowProperties(winPropertiesStr) {
+			var property, winProp, customEvent;
+			try {
+				winProp = eval("(" + winPropertiesStr + ")");
+				customEvent = document.createEvent("CustomEvent");
+				for (property in window)
+					if (!winProp[property])
+						window[property] = null;
+				customEvent.initCustomEvent("WindowPropertiesCleaned", true, true);
+				document.dispatchEvent(customEvent);
+			} catch (e) {
+				console.log(e);
+			}
+		}
+
+		function onDOMSubtreeModified(event) {
+			var id = mutationEventId, element = event.target, processDocFn;
+
+			function onSetDocFragment(message) {
+				if (message.setDocFragment && message.mutationEventId == id) {
+					doc.removeEventListener("DOMSubtreeModified", onDOMSubtreeModified, true);
+					element.innerHTML = message.content;
+					doc.addEventListener("DOMSubtreeModified", onDOMSubtreeModified, true);
+					bgPort.onMessage.removeListener(onSetDocFragment);
+				}
+			}
+
+			if (element.innerHTML) {
+				if (config.processInBackground) {
+					bgPort.postMessage({
+						processDocFragment : true,
+						pageId : pageId,
+						winId : winId,
+						content : element.innerHTML,
+						mutationEventId : id
+					});
+					bgPort.onMessage.addListener(onSetDocFragment);
+					mutationEventId++;
+				} else
+					processDocFn = singlefile.initProcess(doc, element, false, doc.baseURI, doc.characterSet, config, canvasData, docs[winId].requestManager,
+							function(maxIndex) {
+								doc.removeEventListener("DOMSubtreeModified", onDOMSubtreeModified, true);
+								processDocFn();
+								doc.addEventListener("DOMSubtreeModified", onDOMSubtreeModified, true);
+							});
+			}
+			event.preventDefault();
+		}
+
+		function onWindowPropertiesCleaned() {
+			var tmpDoc;
+
+			function replaceDoc() {
+				doc.replaceChild(docElement, doc.documentElement);
+				doc.addEventListener("DOMSubtreeModified", onDOMSubtreeModified, true);
+			}
+
+			doc.removeEventListener('WindowPropertiesCleaned', onWindowPropertiesCleaned, true);
+			if (config.processInBackground || singlefile.processSelection || (!config.processInBackground && !config.removeScripts))
+				if (location.pathname.indexOf(".txt") + 4 != location.pathname.length) {
+					doc.open();
+					doc.write(message.content);
+					doc.addEventListener("DOMSubtreeModified", onDOMSubtreeModified, true);
+					doc.close();
+				} else {
+					tmpDoc = document.implementation.createHTMLDocument();
+					tmpDoc.open();
+					tmpDoc.write(message.content);
+					tmpDoc.close();
+					docElement = doc.importNode(tmpDoc.documentElement, true);
+					replaceDoc();
+				}
+			else
+				replaceDoc();
+			if (config.removeUnusedCSSRules)
+				removeUnusedCSSRules();
+			setContentResponse();
+		}
+
+		function sendSetContentResponse(content) {
+			bgPort.postMessage({
+				setContentResponse : true,
+				winId : "0",
+				pageId : pageId,
+				content : config.savePage || config.getContent ? content : null
+			});
+		}
+
+		function setContentResponse() {
+			if (singlefile.processSelection)
+				sendSetContentResponse(message.content);
+			else {
+				if (config.processInBackground)
+					sendSetContentResponse(singlefile.util.getDocContent(doc, doc.documentElement));
+				else
+					sendSetContentResponse(config.removeUnusedCSSRules ? singlefile.util.getDocContent(doc, doc.documentElement) : singlefile.util
+							.getDocContent(doc, docElement));
+			}
+		}
+
+		if (config.displayProcessedPage) {
+			window.location.href = "javascript:(" + resetWindowProperties.toString() + ")('" + JSON.stringify(message.winProperties) + "')";
+			doc.addEventListener('WindowPropertiesCleaned', onWindowPropertiesCleaned, true);
+		} else
+			setContentResponse();
+	}
+
+	function getResourceContentResponse(message) {
+		docs[message.winId].requestManager.onResponse(message.requestId, message.content);
+	}
+
+	function setFrameContentRequest(message) {
+		docs[message.winId].frames[message.index].setAttribute("src", "data:text/html;charset=utf-8," + encodeURI(message.content));
+		bgPort.postMessage({
+			setFrameContentResponse : true,
+			pageId : pageId,
+			winId : message.winId,
+			index : message.index
+		});
+	}
+
+	function getContentRequest(message) {
+		if (docs[message.winId].doc)
+			bgPort.postMessage({
+				getContentResponse : true,
+				winId : message.winId,
+				pageId : pageId,
+				content : singlefile.util.getDocContent(docs[message.winId].doc, docs[message.winId].docElement)
+			});
+		else
+			bgPort.postMessage({
+				getContentResponse : true,
+				pageId : pageId,
+				winId : message.winId,
+				content : singlefile.util.getDocContent(doc, docElement)
+			});
+	}
+
+	function processDoc(message) {
+		if (docs[message.winId])
+			docs[message.winId].processDoc();
+	}
+
+	bgPort = chrome.extension.connect({
+		name : "singlefile"
+	});
+	bgPort.onMessage.addListener(function(message) {
+		// if (!message.getResourceContentResponse)
+		// console.log(message);
+		if (message.getResourceContentResponse)
+			getResourceContentResponse(message);
+		if (message.setFrameContentRequest)
+			setFrameContentRequest(message);
+		if (message.getContentRequest)
+			getContentRequest(message);
+		if (message.setContentRequest)
+			setContentRequest(message);
+		if (message.processDoc)
+			processDoc(message);
+	});
+	if (doc.documentElement instanceof HTMLHtmlElement)
+		init();
+
+})();

+ 214 - 0
WebContent/core/scripts/content/wininfo.js

@@ -0,0 +1,214 @@
+/*
+ * Copyright 2011 Gildas Lormeau
+ * contact : gildas.lormeau <at> gmail.com
+ * 
+ * This file is part of SingleFile Core.
+ *
+ *   SingleFile Core is free software: you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as published by
+ *   the Free Software Foundation, either version 3 of the License, or
+ *   (at your option) any later version.
+ *
+ *   SingleFile Core is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public License
+ *   along with SingleFile Core.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+var wininfo = {};
+
+(function() {
+
+	var EXT_ID = "wininfo";
+
+	var contentRequestCallbacks, executeSetFramesWinIdString = executeSetFramesWinId.toString(), processLength, processIndex, timeoutProcess;
+
+	function addListener(onMessage) {
+		function windowMessageListener(event) {
+			var data = event.data;
+			if (data.indexOf(EXT_ID + '::') == 0)
+				onMessage(JSON.parse(data.substr(EXT_ID.length + 2)));
+		}
+		this.addEventListener("message", windowMessageListener, false);
+	}
+
+	function executeSetFramesWinId(extensionId, selector, index, winId) {
+		function execute(extensionId, elements, index, winId, win) {
+			var i, framesInfo = [], stringify = JSON.stringify || JSON.encode, parse = JSON.parse || JSON.decode;
+
+			function getDoctype(doc) {
+				var docType = doc.doctype, docTypeStr;
+				if (docType) {
+					docTypeStr = "<!DOCTYPE " + docType.nodeName;
+					if (docType.publicId) {
+						docTypeStr += " PUBLIC \"" + docType.publicId + "\"";
+						if (docType.systemId)
+							docTypeStr += " \"" + docType.systemId + "\"";
+					} else if (docType.systemId)
+						docTypeStr += " SYSTEM \"" + docType.systemId + "\"";
+					if (docType.internalSubset)
+						docTypeStr += " [" + docType.internalSubset + "]";
+					return docTypeStr + ">\n";
+				}
+				return "";
+			}
+
+			function addListener(onMessage) {
+				function windowMessageListener(event) {
+					var data = event.data;
+					if (data.indexOf(extensionId + '::') == 0)
+						onMessage(parse(data.substr(extensionId.length + 2)));
+				}
+				top.addEventListener("message", windowMessageListener, false);
+			}
+
+			for (i = 0; i < elements.length; i++) {
+				framesInfo.push({
+					sameDomain : elements[i].contentDocument != null,
+					src : elements[i].src,
+					winId : winId + "." + i,
+					index : i
+				});
+			}
+			if (win != top)
+				win.postMessage(extensionId + "::" + stringify({
+					initResponse : true,
+					winId : winId,
+					index : index
+				}), "*");
+			top.postMessage(extensionId + "::" + stringify({
+				initResponse : true,
+				frames : framesInfo,
+				winId : winId,
+				index : index
+			}), "*");
+			for (i = 0; i < elements.length; i++)
+				(function(index) {
+					var frameElement = elements[i], frameWinId = winId + "." + index, frameDoc = frameElement.contentDocument;
+
+					function onMessage(message) {
+						if (message.getContentRequest) {
+							var customEvent, doctype;
+							if (message.winId == frameWinId) {
+								doctype = getDoctype(frameDoc);
+								top.postMessage(extensionId + "::" + stringify({
+									getContentResponse : true,
+									contentRequestId : message.contentRequestId,
+									winId : frameWinId,
+									content : doctype + frameDoc.documentElement.outerHTML,
+									title : frameDoc.title,
+									baseURI : frameDoc.baseURI,
+									url : frameDoc.location.href,
+									characterSet : "UTF-8"
+								}), "*");
+							}
+						}
+					}
+
+					if (frameDoc && top.addEventListener) {
+						execute(extensionId, frameDoc.querySelectorAll(selector), index, frameWinId, frameElement.contentWindow);
+						addListener(onMessage);
+					} else {
+						frameElement.contentWindow.postMessage(extensionId + "::" + stringify({
+							initRequest : true,
+							winId : frameWinId,
+							index : index
+						}), "*");
+					}
+				})(i);
+		}
+
+		execute(extensionId, document.querySelectorAll(selector), index, winId, window);
+	}
+
+	function getContent(frame, callback) {
+		if (frame.sameDomain) {
+			top.postMessage(EXT_ID + "::" + JSON.stringify({
+				getContentRequest : true,
+				winId : frame.winId,
+				contentRequestId : contentRequestCallbacks.length
+			}), "*");
+			contentRequestCallbacks.push(callback);
+		} else
+			callback();
+	}
+
+	function getContentResponse(message) {
+		var id = message.contentRequestId;
+		delete message.contentRequestId;
+		delete message.getContentResponse;
+		contentRequestCallbacks[id](message);
+		delete contentRequestCallbacks[id];
+	}
+
+	function initRequest(message) {
+		wininfo.winId = message.winId;
+		wininfo.index = message.index;
+		location.href = "javascript:(" + executeSetFramesWinIdString + ")('" + EXT_ID + "','iframe, frame'," + wininfo.index + ",'" + wininfo.winId + "')";
+	}
+
+	function initResponse(message) {
+		function process() {
+			bgPort = chrome.extension.connect({
+				name : "wininfo"
+			});
+			wininfo.frames = wininfo.frames.filter(function(frame) {
+				return frame.winId;
+			});
+			bgPort.postMessage({
+				initResponse : true,
+				processableDocs : wininfo.frames.length + 1
+			});
+		}
+
+		if (window == top) {
+			message.frames = message.frames instanceof Array ? message.frames : JSON.parse(message.frames);
+			if (message.winId != "0")
+				processIndex++;
+			wininfo.frames = wininfo.frames.concat(message.frames);
+			processLength += message.frames.length;
+			if (timeoutProcess)
+				clearTimeout(timeoutProcess);
+			if (processIndex == processLength)
+				process();
+			else
+				timeoutProcess = setTimeout(function() {
+					process();
+				}, 200);
+		} else {
+			wininfo.winId = message.winId;
+			wininfo.index = message.index;
+		}
+	}
+
+	function onRequest(request) {
+		// console.log("onRequest", request);
+		if (request.initRequest && this == top && document.documentElement instanceof HTMLHtmlElement) {
+			contentRequestCallbacks = [];
+			processLength = 0;
+			processIndex = 0;
+			timeoutProcess = null;
+			wininfo.frames = [];
+			initRequest(request);
+		}
+	}
+
+	function onMessage(message) {
+		// console.log("wininfo", "onMessage", message, window.location.href);
+		if (message.initRequest)
+			initRequest(message);
+		if (message.initResponse)
+			initResponse(message);
+		if (message.getContentResponse)
+			getContentResponse(message);
+	}
+
+	if (window == top)
+		wininfo.getContent = getContent;
+	chrome.extension.onRequest.addListener(onRequest);
+	addListener(onMessage);
+
+})();

+ 0 - 4
WebContent/pages/background.html

@@ -1,4 +0,0 @@
-<!DOCTYPE html>
-<html>
-<script type="text/javascript" src="../scripts/background.js"></script>
-</html>

+ 0 - 119
WebContent/pages/help.html

@@ -1,119 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
-<title>SingleFile help</title>
-<link rel="stylesheet" href="help.css">
-</head>
-<body>
-<div>
-<div id="titleBorder">
-<h2><img src="../resources/icon_48.png" id="titleIcon"><span id="title">SingleFile help</span></h2>
-<h4>SingleFile helps you to archive a complete page into a single HTML file.</h4>
-</div>
-<span id="index"><a href="#instructions">Instructions</a> - <a href="#demo">Demo</a> - <a href="#options">Options description</a> - <a href="#notes">Technical notes</a> - <a
-	href="#knownIssues">Known issues</a> - <a href="#unknownIssues">Unknown issues</a></span>
-<hr>
-<ol>
-	<li><a name="instructions">Instructions</a>
-	<ul>
-		<li>wait until the page is fully loaded : you may need to scroll down the entire page and hover dynamic document elements (e.g. "rollover" images) to be sure all elements
-		are displayed</li>
-		<li>click on the SingleFile icon <img src="../resources/icon_48.png" id="icon"> in the chrome toobar</li>
-		<li>wait until the the shadow disappears then hit Ctrl-S or select "Save as" in the wrench menu and save the page</li>
-		<li>all images, style sheets and (almost all) frame contents are embedded into the ".htm" saved file</li>
-	</ul>
-	</li>
-
-	<li><a name="demo">Demo : SingleFile advantages over default file save</a>
-	<div><object width="480" height="385">
-		<param name="movie" value="http://www.youtube.com/v/D99LfOF3qis&hl=fr_FR&fs=1&">
-		<param name="allowFullScreen" value="true">
-		<param name="allowscriptaccess" value="always">
-		<embed src="http://www.youtube.com/v/D99LfOF3qis&hl=fr_FR&fs=1&" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="480" height="385"></object>
-	</div>
-	</li>
-
-	<li><a name="options">Options description</a>
-
-	<p>You can customize the way SingleFile process the page through the options page. Right-click on SingleFile icon <img src="../resources/icon_48.png" id="icon"> in the
-	chrome toobar and select "Options" in the context menu to open the options page.</p>
-	<div class="screenshot"></div>
-	<p>Details :</p>
-	<ul>
-		<li><span class="option">remove frames</span>
-		<p>Remove all frames on the page</p>
-		<p>You can enable this option if the file size is too large when disabled or to remove some ads.</p>
-		<p class="notice">It is recommended to <u>disable</u> this option</p>
-		</li>
-
-		<li><span class="option">remove scripts</span>
-		<p>Remove all javascript scripts</p>
-		<p>Most of the time, you don't need javascript into the saved page. If your want to save the page dynamic behavior, you may need to disable this option. Nevertheless, the
-		dynamic behavior may not work or document can be altered when opening the saved file</p>
-		<p class="notice">It is recommended to <u>enable</u> this option</p>
-		</li>
-
-		<li><span class="option">remove objects</span>
-		<p>Remove all objects</p>
-		<p>Remove all non embeddable elements : flash, java applet ...</p>
-		<p class="notice">It is recommended to <u>enable</u> this option if you want to read the saved page offline</p>
-		</li>
-
-		<li><span class="option">remove hidden elements</span>
-		<p>Remove all hidden elements</p>
-		<p>Remove all the HTML unvisible elements on the page (with <code>visibility = "hidden"</code>&nbsp;or&nbsp;<code>display = "none"</code>&nbsp;or&nbsp;<code>opacity =
-		0</code> CSS property values). This option may alter the document but can considerably reduce the saved file size.</p>
-		<p class="notice">It is recommended to <u>disable</u> this option unless the saved file size is too large</p>
-		</li>
-
-		<li><span class="option">remove unused CSS rules</span>
-		<p>Remove all unused CSS rules</p>
-		<p>Remove all CSS rules that do not match any element. This option may alter the document but can considerably reduce the saved file size. If you enable this option, it may
-		introduce some incompatibilities issues in the saved page when opening it into another browser (i.e. not based on Webkit).</p>
-		<p class="notice">It is recommended to <u>disable</u> this option unless the saved file size is too large or you do not want to open the saved page into another browser</p>
-		</li>
-
-		<li><span class="option">Reset to default options</span>
-		<p>Reset all the options to default state</p>
-		</li>
-	</ul>
-	<p>Note : All options are automatically saved</p>
-	</li>
-
-
-	<li><a name="notes">Technical notes</a>
-	<ul>
-		<li>it is technically not possible to add a "Save" button into chrome extensions or change "Save as" dialog box behavior into pure javascript chrome extensions</li>
-		<li>all images are converted into <a href="http://en.wikipedia.org/wiki/Base64">base64</a></li>
-		<li>frame document contents are encoded with <a href="http://en.wikipedia.org/wiki/Utf_8">utf-8 charset</a></li>
-		<li>encoded contents are injected in the document using <a href="http://en.wikipedia.org/wiki/Data_URI_scheme" target="_blank">data URI scheme</a></li>
-		<li>data URI scheme is supported by the following web browsers: Chrome, Firefox, Opera, Safari, Konqueror and Internet Explorer 8 (limited support : data URIs must be
-		smaller than 32 KB)</li>
-		<li>SVG images are supported (SVG document is converted into utf-8 but is not processed)</li>
-	</ul>
-	</li>
-
-	<li><a name="knownIssues">Known issues</a>
-	<ul>
-		<li>frames or SVG images without src attribute or with empty src attribute value are not embedded</li>
-		<li>SingleFile does not always work when javascript is blocked by chrome (see <a href="http://www.google.com/support/chrome/bin/answer.py?hl=en&answer=114662">"content
-		settings/Javascript" tab in chrome options</a>)</li>
-	</ul>
-	</li>
-
-	<li><a name="unknownIssues">Unknown issues</a>
-	<p>If you find an unknown issue (i.e. frozen process, extra saved files, blank or altered document, tab crash...) :</p>
-	<ul>
-		<li>test with only <span class="option">remove frames</span>, <span class="option">remove scripts</span> and <span class="option">remove objects</span> enabled in options
-		page</li>
-		<li>disable all other extensions to see if there is a conflict</li>
-		<li>if there is a conflict, try to determine against which extension(s)</li>
-		<li>report the issue <a href="https://chrome.google.com/extensions/detail/mpiodijhokgodhhofbcjdecpffjipkle" target="_blank">here</a> with a short description, URL(s), chrome
-		version, OS version</li>
-	</ul>
-	</li>
-</ol>
-</div>
-</body>
-</html>

+ 0 - 37
WebContent/pages/options.html

@@ -1,37 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
-<title>SingleFile options</title>
-<script type="text/javascript" src="../scripts/options.js"></script>
-<link rel="stylesheet" href="options.css">
-</head>
-<body onload="load()">
-<div>
-<h2><img alt="icon" id="icon" src="../resources/icon_48.png"/>SingleFile</h2>
-<hr>
-<h4>Options :</h4>
-<div id="popupContent">	
-	<div>
-		<label for="removeFramesInput">remove frames</label> <input type="checkbox" id="removeFramesInput">
-	</div>
-	<div>
-		<label for="removeScriptsInput">remove scripts</label> <input type="checkbox" id="removeScriptsInput">
-	</div>
-	<div>
-		<label for="removeObjectsInput">remove objects</label> <input type="checkbox" id="removeObjectsInput">
-	</div>
-	<div>
-		<label for="removeHiddenInput">remove hidden elements</label> <input type="checkbox" id="removeHiddenInput">
-	</div>
-	<div>
-		<label for="removeUnusedInput">remove unused CSS rules</label> <input type="checkbox" id="removeUnusedInput">
-	</div>	
-	<div>	
-		<a href="help.html" target="SingleFileHelp">help</a><button id="resetButton">Reset to default options</button>
-	</div>	
-</div>
-</div>
-<button id="reloadButton">Reload extension</button>
-</body>
-</html>

BIN
WebContent/resources/icon_48_wait0.png


BIN
WebContent/resources/icon_48_wait1.png


BIN
WebContent/resources/icon_48_wait2.png


BIN
WebContent/resources/icon_48_wait3.png


BIN
WebContent/resources/icon_48_wait4.png


BIN
WebContent/resources/icon_48_wait5.png


BIN
WebContent/resources/icon_48_wait6.png


BIN
WebContent/resources/icon_48_wait7.png


BIN
WebContent/resources/icon_48_wait8.png


BIN
WebContent/resources/options_screen.png


+ 0 - 451
WebContent/scripts/background.js

@@ -1,451 +0,0 @@
-/*
- * Copyright 2010 Gildas Lormeau
- * contact : gildas.lormeau <at> gmail.com
- * 
- * This file is part of SingleFile.
- *
- *   SingleFile is free software: you can redistribute it and/or modify
- *   it under the terms of the GNU Lesser General Public License as published by
- *   the Free Software Foundation, either version 3 of the License, or
- *   (at your option) any later version.
- *
- *   SingleFile is distributed in the hope that it will be useful,
- *   but WITHOUT ANY WARRANTY; without even the implied warranty of
- *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *   GNU Lesser General Public License for more details.
- *
- *   You should have received a copy of the GNU Lesser General Public License
- *   along with SingleFile.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-var singlefile = {};
-
-(function() {
-
-	var dev = false;
-
-	var tabs = [], SCRAPBOOK_EXT_ID = dev ? "imfajgkkpglkdjkjejkefllgajgmhmfp" : "ihkkeoeinpbomhnpkmmkpggkaefincbn";
-
-	function detectScrapbook(callback) {
-		var img = new Image();
-		img.src = "chrome-extension://" + SCRAPBOOK_EXT_ID + "/resources/icon_16.png";
-		img.onload = function() {
-			callback(true);
-		};
-		img.onerror = function() {
-			callback(false);
-		};
-	}
-
-	singlefile.getOptions = function() {
-		return localStorage["options"] ? JSON.parse(localStorage["options"]) : {
-			removeFrames : false,
-			removeScripts : true,
-			removeObjects : true,
-			removeHidden : false,
-			removeUnused : false
-		};
-	};
-
-	singlefile.setOptions = function(options) {
-		localStorage["options"] = JSON.stringify(options);
-	};
-
-	singlefile.resetOptions = function() {
-		delete localStorage["options"];
-	};
-
-	function getWinProperties() {
-		var winProperties = {}, property;
-		for (property in window)
-			winProperties[property] = true;
-		return winProperties;
-	}
-
-	function throwAwayHighOrderBytes(str) {
-		var i, ret = [];
-		for (i = 0; i < str.length; i++)
-			ret[i] = String.fromCharCode(str.charCodeAt(i) & 0xff);
-		return ret.join("");
-	}
-
-	function sendXHR(tabId, url, responseHandler, errorHandler, charset) {
-		var xhr;
-		if (!tabs[tabId].responses[url]) {
-			if (!tabs[tabId].callbacks[url]) {
-				tabs[tabId].callbacks[url] = [];
-				xhr = new XMLHttpRequest();
-				xhr.onreadystatechange = function() {
-					if (xhr.readyState == 4) {
-						tabs[tabId].callbacks[url].forEach(function(callback) {
-							callback.responseHandler(xhr.status, xhr.getResponseHeader("Content-Type"), xhr.responseText);
-						});
-						tabs[tabId].responses[url] = {
-							status : xhr.status,
-							header : xhr.getResponseHeader("Content-Type"),
-							text : xhr.responseText
-						};
-					}
-				};
-				xhr.onerror = function() {
-					tabs[tabId].callbacks[url].forEach(function(callback) {
-						callback.errorHandler();
-					});
-				};
-				xhr.open("GET", url, true);
-				if (charset)
-					xhr.overrideMimeType('text/plain; charset=' + charset);
-				try {
-					xhr.send(null);
-				} catch (e) {
-					xhr.onerror();
-				}
-			}
-			tabs[tabId].callbacks[url].push({
-				responseHandler : responseHandler,
-				errorHandler : errorHandler
-			});
-		} else
-			responseHandler(tabs[tabId].responses[url].status, tabs[tabId].responses[url].header, tabs[tabId].responses[url].text);
-	}
-
-	function setData(tabId, data, callback) {
-		if (data.url.indexOf('http:') == 0 || data.url.indexOf('https:') == 0)
-			sendXHR(tabId, data.url, function(status, contentType, responseText) {
-				if (status < 400) {
-					data.mediaType = contentType ? contentType.split(";")[0] : null;
-					data.mediaTypeParam = data.base64 ? "base64" : (contentType ? contentType.split(";")[1] : null);
-					data.content = data.base64 ? btoa(throwAwayHighOrderBytes(responseText)) : data.encodedText ? encodeURI(responseText) : responseText;
-				}
-				callback(data);
-			}, function() {
-				callback(data);
-			}, data.characterSet);
-		else
-			callback(data);
-	}
-
-	function startDone(tabId) {
-		var msg, options = singlefile.getOptions();
-		msg = {
-			getStylesheets : true,
-			options : singlefile.getOptions()
-		};
-		tabs[tabId].ports.forEach(function(portData) {
-			portData.port.postMessage(msg);
-		});
-	}
-
-	function getPortData(tabId, port) {
-		var portData;
-		tabs[tabId].ports.forEach(function(aPortData) {
-			if (!portData && aPortData.port == port)
-				portData = aPortData;
-		});
-		return portData;
-	}
-
-	function buildTree(tabId) {
-		var tabData = tabs[tabId];
-
-		function findParent(port) {
-			var parentPort, parts, parentWinID;
-			if (port.winID) {
-				parts = port.winID.split('.');
-				parts.pop();
-				parentWinID = parts.join('.');
-				tabData.ports.forEach(function(portData) {
-					if (portData.winID == parentWinID)
-						parentPort = portData;
-				});
-			}
-			return parentPort;
-		}
-
-		function walk(portData, level) {
-			if (portData.children)
-				portData.children.forEach(function(pData) {
-					walk(pData, level + 1);
-				});
-			if (!tabData.levels[level])
-				tabData.levels[level] = [];
-			tabData.levels[level].push(portData);
-		}
-
-		tabData.ports.forEach(function(portData) {
-			portData.parent = findParent(portData);
-			if (portData.parent) {
-				portData.parent.children = portData.parent.children || [];
-				portData.parent.children.push(portData);
-			}
-		});
-		tabData.levels = [];
-		walk(tabData.top, 0);
-		tabData.levelIndex = tabData.levels.length - 1;
-	}
-
-	function processFrames(tabId) {
-		function postGetDocData(pData) {
-			var data = {};
-			if (pData.children)
-				pData.children.forEach(function(pChildData) {
-					var index = pChildData.winID.split('.').pop();
-					data[index] = pChildData.data;
-				});
-			pData.port.postMessage({
-				getDocData : true,
-				data : data
-			});
-		}
-
-		tabs[tabId].processedFrameCount = 0;
-		tabs[tabId].processedFrameMax = tabs[tabId].levels[tabs[tabId].levelIndex].length;
-		tabs[tabId].levels[tabs[tabId].levelIndex].forEach(postGetDocData);
-	}
-
-	function done(tabId) {
-		tabs[tabId].processing = false;
-		tabs[tabId].processed = true;
-		tabs[tabId].top.port.postMessage({
-			done : true,
-			winProperties : getWinProperties()
-		});
-	}
-
-	function refreshBadge(tabId) {
-		var processedResources = 0, totalResources = 0;
-		tabs[tabId].ports.forEach(function(portData) {
-			processedResources += portData.processedResources;
-			totalResources += portData.totalResources;
-		});
-		chrome.browserAction.setIcon({
-			tabId : tabId,
-			path : '../resources/icon_48_wait' + Math.floor((processedResources / totalResources) * 9) + '.png'
-		});
-		chrome.browserAction.setTitle({
-			tabId : tabId,
-			title : "Progress: " + Math.min(100, Math.floor((processedResources / totalResources) * 100)) + "%"
-		});
-	}
-
-	function processTab(tabId, scrapbooking) {
-		function executeScripts(scripts, callback, index) {
-			if (!index)
-				index = 0;
-			if (index < scripts.length) {
-				chrome.tabs.executeScript(tabId, {
-					file : scripts[index].file,
-					code : scripts[index].code,
-					allFrames : true
-				}, function() {
-					executeScripts(scripts, callback, index + 1);
-				});
-			} else if (callback)
-				callback();
-		}
-
-		if (!tabs[tabId] || (!tabs[tabId].processing && !tabs[tabId].processed)) {
-			executeScripts([ {
-				code : "var singlefile = {};"
-			}, {
-				file : "scripts/filters.js"
-			}, {
-				file : "scripts/core.js"
-			}, {
-				file : "scripts/ui.js"
-			}, {
-				file : "scripts/main.js"
-			} ], function() {
-				if (!tabs[tabId])
-					return;
-				tabs[tabId].processing = true;
-				chrome.browserAction.setBadgeBackgroundColor({
-					color : [ 200, 200, 200, 192 ]
-				});
-				chrome.browserAction.setIcon({
-					tabId : tabId,
-					path : '../resources/icon_48_wait0.png'
-				});
-				chrome.browserAction.setTitle({
-					tabId : tabId,
-					title : "Progress: 0%"
-				});
-				detectScrapbook(function(sendContent) {
-					tabs[tabId].top.port.postMessage({
-						start : true,
-						sendContent : sendContent,
-						scrapbooking : scrapbooking
-					});
-				});
-			});
-		}
-	}
-
-	chrome.browserAction.onClicked.addListener(function(tab) {
-		processTab(tab.id);
-	});
-
-	chrome.extension.onConnect.addListener(function(port) {
-		var tabId = port.sender.tab.id, tabData;
-
-		port.onDisconnect.addListener(function() {
-			tabData.ports = tabData.ports.filter(function(portData) {
-				return portData.port != port;
-			});
-
-			tabData.processedDocMax--;
-			if (tabData.processedDocCount && tabData.processedDocMax == tabData.processedDocCount) {
-				tabData.processedDocCount = 0;
-				buildTree(tabId);
-				processFrames(tabId);
-			}
-
-			if (!tabData.ports.length)
-				tabs[tabId] = null;
-		});
-
-		port.onMessage.addListener(function(msg) {
-			var portData;
-			if (msg.init) {
-				tabs[tabId] = tabs[tabId] || {
-					id : tabId,
-					ports : [],
-					processedDocCount : 0,
-					processedDocMax : 0,
-					processedPortCount : 0,
-					processedFrameCount : 0,
-					processedFrameMax : 0,
-					processed : false,
-					callbacks : {},
-					responses : {}
-				};
-				tabData = tabs[tabId];
-				portData = {
-					port : port,
-					url : msg.url,
-					totalResources : 0,
-					processedResources : 0
-				};
-				if (msg.topWindow) {
-					tabData.top = portData;
-					tabData.title = msg.title;
-				}
-				if (!singlefile.getOptions().removeFrames || msg.topWindow) {
-					tabData.ports.push(portData);
-					tabData.processedDocMax++;
-				}
-			} else
-				portData = getPortData(tabId, port);
-			if (msg.startDone)
-				startDone(tabId);
-			if (msg.setStylesheets || msg.setData || msg.setDynamicData)
-				setData(tabId, msg.data, function(data) {
-					msg.data = data;
-					port.postMessage(msg);
-				});
-			if (msg.setTotalResources) {
-				portData.totalResources = msg.totalResources;
-				tabData.processedPortCount++;
-				if (tabData.processedPortCount == tabData.ports.length) {
-					tabData.ports.forEach(function(pData) {
-						pData.port.postMessage({
-							getData : true
-						});
-					});
-				}
-			}
-			if (msg.incProcessedResources) {
-				portData.processedResources++;
-				refreshBadge(tabId);
-			}
-			if (msg.setDataDone) {
-				portData.frameCount = msg.frameCount;
-				portData.winID = msg.winID;
-				if (singlefile.getOptions().removeFrames) {
-					done(tabId);
-					detectScrapbook(function(sendContent) {
-						if (sendContent)
-							chrome.extension.sendRequest(SCRAPBOOK_EXT_ID, {
-								tabId : tabId,
-								title : tabData.title,
-								content : msg.data,
-								favicoData : msg.favicoData,
-								url : tabData.top.url
-							});
-					});
-				} else {
-					tabData.processedDocCount++;
-					if (tabData.processedDocMax == tabData.processedDocCount) {
-						tabData.processedDocCount = 0;
-						buildTree(tabId);
-						processFrames(tabId);
-					}
-				}
-			}
-			if (msg.setDocData) {
-				portData.data = "data:" + msg.mimeType + ";charset=utf-8," + encodeURI(msg.data);
-				if (portData.parent) {
-					tabData.processedFrameCount++;
-					if (tabData.processedFrameMax == tabData.processedFrameCount) {
-						tabData.levelIndex--;
-						processFrames(tabId);
-					}
-				} else {
-					done(tabId);
-					detectScrapbook(function(sendContent) {
-						if (sendContent)
-							chrome.extension.sendRequest(SCRAPBOOK_EXT_ID, {
-								tabId : tabId,
-								title : tabData.title,
-								content : msg.data,
-								favicoData : msg.favicoData,
-								url : tabData.top.url
-							});
-					});
-				}
-			}
-			if (msg.done) {
-				chrome.browserAction.setIcon({
-					tabId : tabId,
-					path : '../resources/icon_48.png'
-				});
-				chrome.browserAction.setBadgeBackgroundColor({
-					color : [ 10, 200, 10, 192 ]
-				});
-				chrome.browserAction.setBadgeText({
-					tabId : tabId,
-					text : "OK"
-				});
-				chrome.browserAction.setTitle({
-					tabId : tabId,
-					title : "Save the page with Ctrl-S"
-				});
-			}
-		});
-	});
-
-	chrome.extension.onRequestExternal.addListener(function(request, sender, sendResponse) {
-		var tabId = request.id;
-		if (sender.id != SCRAPBOOK_EXT_ID)
-			return;
-		chrome.browserAction.setBadgeText({
-			tabId : tabId,
-			text : ""
-		});
-		if (tabs[tabId] && tabs[tabId].processing) {
-			sendResponse({
-				processing : true
-			});
-			return;
-		}
-		if (tabs[tabId] && tabs[tabId].processed) {
-			sendResponse({
-				processed : true
-			});
-			return;
-		}
-		processTab(tabId, true);
-		sendResponse({});
-	});
-
-})();

+ 0 - 273
WebContent/scripts/core.js

@@ -1,273 +0,0 @@
-/*
- * Copyright 2010 Gildas Lormeau
- * contact : gildas.lormeau <at> gmail.com
- * 
- * This file is part of SingleFile.
- *
- *   SingleFile is free software: you can redistribute it and/or modify
- *   it under the terms of the GNU Lesser General Public License as published by
- *   the Free Software Foundation, either version 3 of the License, or
- *   (at your option) any later version.
- *
- *   SingleFile is distributed in the hope that it will be useful,
- *   but WITHOUT ANY WARRANTY; without even the implied warranty of
- *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *   GNU Lesser General Public License for more details.
- *
- *   You should have received a copy of the GNU Lesser General Public License
- *   along with SingleFile.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-(function(holder) {
-	var targetDoc, doc, options, winID, processedFrames = 0, initPageCallback, timeoutCallback, bgPort, winPort, requests = {}, requestCount = 0, responseCount = 0, sendContent, scrapbooking;
-
-	function storeRequest(url, callback, base64, encodedText, characterSet) {
-		if (!requests[url]) {
-			requests[url] = {
-				base64 : base64,
-				encodedText : encodedText,
-				characterSet : characterSet,
-				callbacks : []
-			};
-			requestCount++;
-		}
-		requests[url].callbacks.push(callback);
-	}
-
-	function sendRequests(methodName) {
-		var url, data, msg;
-		for (url in requests) {
-			data = requests[url];
-			msg = {
-				data : {
-					url : url,
-					base64 : data.base64,
-					encodedText : data.encodedText,
-					characterSet : data.base64 ? "x-user-defined" : data.characterSet
-				}
-			};
-			msg[methodName] = true;
-			bgPort.postMessage(msg);
-		}
-	}
-
-	function getDocument() {
-		var clone, mask;
-		if (options.removeScripts || scrapbooking) {
-			clone = targetDoc.documentElement.cloneNode(true);
-			mask = clone.querySelector("#__SingleFile_mask__");
-			if (mask)
-				mask.parentElement.removeChild(mask);
-			return clone;
-		}
-		return targetDoc.documentElement;
-	}
-
-	function setDocument() {
-		if (options.removeScripts && !scrapbooking)
-			targetDoc.replaceChild(doc, targetDoc.documentElement);
-	}
-
-	function initProcess(opt) {
-		options = opt;
-		if (options.removeScripts || scrapbooking)
-			holder.filters.canvas.replace();
-		if (options.removeHidden)
-			holder.filters.element.removeHidden();
-		doc = getDocument();
-		holder.filters.element.clean(doc);
-		if (options.removeFrames)
-			holder.filters.frame.remove(doc);
-		if (options.removeScripts || scrapbooking)
-			holder.filters.script.remove(doc);
-		if (options.removeObjects)
-			holder.filters.object.remove(doc);
-		holder.filters.a.setAbsolute(doc);
-	}
-
-	function getStylesheets() {
-		holder.filters.document.getStylesheets(doc, storeRequest);
-		sendRequests("setStylesheets");
-		if (requestCount == 0) {
-			holder.filters.document.get(doc, storeRequest, window == top);
-			bgPort.postMessage({
-				setTotalResources : true,
-				totalResources : requestCount
-			});
-		}
-	}
-
-	function getData() {
-		sendRequests("setData");
-		if (requestCount == 0)
-			bgPort.postMessage({
-				setDataDone : true,
-				data : window == top && options.removeFrames && sendContent ? holder.filters.document.getDoctype() + doc.outerHTML : null,
-				favicoData : window == top && options.removeFrames && sendContent ? holder.filters.image.getFavicoData(doc) : null,
-				frameCount : holder.filters.frame.count(doc),
-				winID : winID
-			});
-	}
-
-	function setData(data, callback, isStylesheet) {
-		var callbacks = requests[data.url].callbacks;
-		responseCount++;
-		callbacks.forEach(function(cb) {
-			cb(data);
-		});
-		if (!isStylesheet)
-			bgPort.postMessage({
-				incProcessedResources : true
-			});
-		if (responseCount == requestCount) {
-			responseCount = 0;
-			requestCount = 0;
-			requests = {};
-			if (callback)
-				callback();
-		}
-	}
-
-	function getDocData(data) {
-		if (options.removeUnused) {
-			setDocument();
-			holder.filters.style.removeUnused();
-			doc = getDocument();
-		}
-		holder.filters.frame.set(doc, data);
-		bgPort.postMessage({
-			setDocData : true,
-			data : window != top || sendContent ? holder.filters.document.getDoctype() + doc.outerHTML : null,
-			favicoData : window == top && sendContent ? holder.filters.image.getFavicoData(doc) : null,
-			mimeType : "text/html"
-		});
-	}
-
-	function done(winProperties) {
-		var callbackId;
-		setDocument();
-		targetDoc.addEventListener("DOMNodeInsertedIntoDocument", function(event) {
-			if (!callbackId)
-				if (event.target.querySelectorAll)
-					holder.filters.document.get(event.target, storeRequest, false);
-			if (callbackId)
-				clearTimeout(callbackId);
-			callbackId = setTimeout(function() {
-				if (requestCount)
-					sendRequests("setDynamicData");
-				callbackId = null;
-			}, 20);
-		}, true);
-		bgPort.postMessage({
-			done : true
-		});
-		if (options.removeScripts && !scrapbooking) {
-			function resetWindowProperties(winPropertiesStr) {
-				var property, winProp = JSON.parse(winPropertiesStr);
-				for (property in window)
-					if (!winProp[property])
-						window[property] = null;
-			}
-			window.location.href = "javascript:(" + resetWindowProperties.toString() + ")('" + JSON.stringify(winProperties) + "')";
-		}
-	}
-
-	function start(msg) {
-		sendContent = msg.sendContent;
-		scrapbooking = msg.scrapbooking;
-		document.documentElement.insertBefore(document.createComment("\n Archive processed by SingleFile \n url: " + document.location.href + " \n saved date: " + new Date() + " \n"),
-				document.documentElement.firstChild);
-		timeoutCallback = setTimeout(initPageCallback, 1000);
-		setWinId("0");
-	}
-
-	function setWinId(id) {
-		winID = id;
-		holder.filters.frame.clean();
-		if (holder.filters.frame.count())
-			winPort.postMessageToFrames('iframe[src], frame[src]', function(index, params) {
-				return {
-					setID : true,
-					winID : params.winID + "." + index
-				};
-			}, {
-				winID : id
-			});
-		else
-			setWinIdDone();
-	}
-
-	function setWinIdDone() {
-		processedFrames++;
-		if (processedFrames == holder.filters.frame.count()) {
-			if (top != window)
-				winPort.postMessageToParent({
-					done : true
-				});
-			else {
-				clearTimeout(timeoutCallback);
-				initPageCallback();
-			}
-		}
-	}
-
-	holder.core = {
-		init : function(srcDoc, backgroundPort, windowPort) {
-			windowPort.addListener(function(message) {
-				if (message.setID)
-					setWinId(message.winID);
-				else if (message.done)
-					setWinIdDone();
-			});
-			bgPort = backgroundPort;
-			winPort = windowPort;
-			initPageCallback = function() {
-				bgPort.postMessage({
-					startDone : true
-				});
-			};
-			bgPort.addListener(function(message) {
-				if (message.start)
-					start(message);
-				else if (message.getStylesheets) {
-					initProcess(message.options);
-					getStylesheets();
-				} else if (message.setStylesheets)
-					setData(message.data, getStylesheets, true);
-				else if (message.getData)
-					getData();
-				else if (message.setData)
-					setData(message.data, function() {
-						bgPort.postMessage({
-							setDataDone : true,
-							data : window == top && sendContent ? holder.filters.document.getDoctype() + doc.outerHTML : null,
-							favicoData : window == top && options.removeFrames && sendContent ? holder.filters.image.getFavicoData(doc) : null,
-							frameCount : holder.filters.frame.count(doc),
-							winID : winID
-						});
-					});
-				else if (message.setDynamicData)
-					setData(message.data, null, true);
-				else if (message.getDocData)
-					getDocData(message.data);
-				else if (message.done)
-					done(message.winProperties);
-			});
-			targetDoc = srcDoc;
-			doc = targetDoc.documentElement;
-			if (doc instanceof HTMLHtmlElement)
-				bgPort.postMessage({
-					init : true,
-					topWindow : window == top,
-					url : location.href,
-					title : document.title
-				});
-		}
-	};
-
-	holder.init = function(currDoc, backgroundPort, windowPort) {
-		holder.filters.init(currDoc);
-		holder.ui.init(backgroundPort);
-		holder.core.init(currDoc, backgroundPort, windowPort);
-	};
-})(singlefile);

+ 0 - 434
WebContent/scripts/filters.js

@@ -1,434 +0,0 @@
-/*
- * Copyright 2010 Gildas Lormeau
- * contact : gildas.lormeau <at> gmail.com
- * 
- * This file is part of SingleFile.
- *
- *   SingleFile is free software: you can redistribute it and/or modify
- *   it under the terms of the GNU Lesser General Public License as published by
- *   the Free Software Foundation, either version 3 of the License, or
- *   (at your option) any later version.
- *
- *   SingleFile is distributed in the hope that it will be useful,
- *   but WITHOUT ANY WARRANTY; without even the implied warranty of
- *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *   GNU Lesser General Public License for more details.
- *
- *   You should have received a copy of the GNU Lesser General Public License
- *   along with SingleFile.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-(function(holder) {
-	var IMPORT_URL_VALUE_EXP = /(url\s*\(\s*(?:'|")?\s*([^('|"|\))]*)\s*(?:'|")?\s*\))|(@import\s*\(?\s*(?:'|")?\s*([^('|"|\))]*)\s*(?:'|")?\s*(?:\)|;))/i;
-
-	var URL_VALUE_EXP = /url\s*\(\s*(?:'|")?\s*([^('|"|\))]*)\s*(?:'|")?\s*\)/i;
-	var IMPORT_VALUE_ALT_EXP = /@import\s*\(?\s*(?:'|")?\s*([^('|"|\))]*)\s*(?:'|")?\s*(?:\)|;)/i;
-
-	var URL_EXP = /url\s*\(([^\)]*)\)/gi;
-
-	var IMPORT_EXP = /(@import\s*url\s*\([^\)]*\)\s*;?)|(@import\s*('|")?\s*[^\(|;|'|"]*\s*('|")?\s*;)/gi;
-	var IMPORT_ALT_EXP = /@import\s*('|")?\s*[^\(|;|'|"]*\s*('|")?\s*;/gi;
-
-	var EMPTY_PIXEL_DATA = "data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==";
-
-	var targetDoc;
-
-	function trim(s) {
-		return s.replace(/^\s*([\S\s]*?)\s*$/, '$1');
-	}
-
-	function formatURL(link, host) {
-		var i, newlinkparts, hparts, lparts = link.split('/');
-
-		host = host.split("#")[0].split("?")[0];
-		if (/http:|https:|ftp:|data:|javascript:/i.test(lparts[0]))
-			return trim(link);
-		hparts = host.split('/');
-		newlinkparts = [];
-		if (hparts.length > 3)
-			hparts.pop();
-		if (lparts[0] == '') {
-			if (lparts[1] == '')
-				host = hparts[0] + '//' + lparts[2];
-			else
-				host = hparts[0] + '//' + hparts[2];
-			hparts = host.split('/');
-			delete lparts[0];
-			if (lparts[1] == '') {
-				delete lparts[1];
-				delete lparts[2];
-			}
-		}
-		for (i = 0; i < lparts.length; i++) {
-			if (lparts[i] == '..') {
-				if (lparts[i - 1])
-					delete lparts[i - 1];
-				else if (hparts.length > 3)
-					hparts.pop();
-				delete lparts[i];
-			}
-			if (lparts[i] == '.')
-				delete lparts[i];
-		}
-		for (i = 0; i < lparts.length; i++)
-			if (lparts[i])
-				newlinkparts[newlinkparts.length] = lparts[i];
-		return trim(hparts.join('/') + '/' + newlinkparts.join('/'));
-	}
-
-	function resolveURLs(content, host) {
-		var ret = content.replace(URL_EXP, function(value) {
-			var result = value.match(URL_VALUE_EXP);
-			if (result)
-				if (result[1].indexOf("data:") != 0)
-					return value.replace(result[1], formatURL(result[1], host));
-			return value;
-		});
-		return ret.replace(IMPORT_ALT_EXP, function(value) {
-			var result = value.match(IMPORT_VALUE_ALT_EXP);
-			if (result)
-				if (result[1].indexOf("data:") != 0)
-					return "@import \"" + formatURL(result[1], host) + "\";";
-			return value;
-		});
-
-	}
-
-	function getDataURI(data, defaultURL, woURL) {
-		if (data.content)
-			return (woURL ? "" : "url(") + "data:" + data.mediaType + ";" + data.mediaTypeParam + "," + data.content + (woURL ? "" : ")");
-		else
-			return woURL ? defaultURL : ("url(" + defaultURL + ")");
-	}
-
-	function removeCssComments(content) {
-		var start, end;
-		do {
-			start = content.indexOf("/*");
-			end = content.indexOf("*/", start);
-			if (start != -1 && end != -1)
-				content = content.substring(0, start) + content.substr(end + 2);
-		} while (start != -1 && end != -1);
-		return content;
-	}
-
-	function replaceCssURLs(getStyle, setStyle, host, callback) {
-		var i, url, result, values = removeCssComments(getStyle()).match(URL_EXP);
-		if (values)
-			for (i = 0; i < values.length; i++) {
-				result = values[i].match(URL_VALUE_EXP);
-				if (result && result[1]) {
-					url = formatURL(result[1], host);
-					if (url.indexOf("data:") != 0)
-						(function(origUrl) {
-							callback(url, function(data) {
-								if (getStyle().indexOf(origUrl) != -1)
-									setStyle(getStyle().replace(new RegExp(origUrl.replace(/([{}\(\)\^$&.\*\?\/\+\|\[\\\\]|\]|\-)/g, "\\$1"), "gi"),
-											getDataURI(data, EMPTY_PIXEL_DATA, true)));
-							}, true);
-						})(result[1]);
-				}
-			}
-	}
-
-	holder.filters = {
-		init : function(doc) {
-			targetDoc = doc;
-		},
-		document : {
-			getStylesheets : function(doc, sendRequest) {
-				holder.filters.link.get(doc, sendRequest);
-				holder.filters.style.getImport(doc, sendRequest);
-			},
-			get : function(doc, sendRequest, topWindow) {
-				holder.filters.styleAttr.get(doc, sendRequest);
-				holder.filters.bgAttr.get(doc, sendRequest);
-				holder.filters.image.get(doc, sendRequest);
-				if (topWindow)
-					holder.filters.image.getFavico(doc, sendRequest);
-				holder.filters.svg.get(doc, sendRequest);
-				holder.filters.script.get(doc, sendRequest);
-				holder.filters.style.getURL(doc, sendRequest);
-			},
-			getDoctype : function() {
-				var docType = targetDoc.doctype, docTypeStr;
-				if (docType) {
-					docTypeStr = "<!DOCTYPE " + docType.nodeName;
-					if (docType.publicId) {
-						docTypeStr += " PUBLIC \"" + docType.publicId + "\"";
-						if (docType.systemId)
-							docTypeStr += " \"" + docType.systemId + "\"";
-					} else if (docType.systemId)
-						docTypeStr += " SYSTEM \"" + docType.systemId + "\"";
-					if (docType.internalSubset)
-						docTypeStr += " [" + docType.internalSubset + "]";
-					return docTypeStr + ">\n";
-				}
-				return "";
-			}
-		},
-		element : {
-			clean : function(doc) {
-				Array.prototype.forEach.call(doc.querySelectorAll("blockquote[cite]"), function(element) {
-					element.removeAttribute("cite");
-				});
-			},
-			removeHidden : function() {
-				if (targetDoc.body)
-					Array.prototype.forEach.call(targetDoc.body.querySelectorAll("*:not(style):not(script):not(link):not(area)"), function(element) {
-						var style = getComputedStyle(element);
-						if ((style.visibility == "hidden" || style.display == "none" || style.opacity == 0) && (element.id != "__SingleFile_mask__"))
-							element.parentElement.removeChild(element);
-					});
-			}
-		},
-		a : {
-			setAbsolute : function(doc) {
-				var baseURI = document.baseURI.split("#")[0];
-				Array.prototype.forEach.call(doc.querySelectorAll("a[href]"), function(link) {
-					if (link.href && (link.href.indexOf(baseURI) != 0 || link.href.indexOf("#") == -1))
-						link.href = link.href;
-				});
-			}
-		},
-		frame : {
-			clean : function() {
-				Array.prototype.forEach.call(targetDoc.querySelectorAll("iframe[src], frame[src]"), function(frame) {
-					if (!frame.src)
-						frame.removeAttribute("src");
-				});
-			},
-			count : function() {
-				return targetDoc.querySelectorAll("iframe[src], frame[src]").length;
-			},
-			remove : function(doc) {
-				Array.prototype.forEach.call(doc.querySelectorAll("iframe[src], frame[src]"), function(frame) {
-					frame.src = "about:blank";
-				});
-			},
-			set : function(doc, urlsArray) {
-				Array.prototype.forEach.call(doc.querySelectorAll("iframe[src], frame[src]"), function(frame, index) {
-					frame.src = urlsArray[index] || "about:blank";
-				});
-			}
-		},
-		object : {
-			remove : function(doc) {
-				var i, nodes = doc.querySelectorAll('applet, object:not([type="image/svg+xml"]):not([type="image/svg-xml"]), embed:not([src*=".svg"])');
-				for (i = 0; i < nodes.length; i++)
-					nodes[i].parentElement.removeChild(nodes[i]);
-			}
-		},
-		styleAttr : {
-			get : function(doc, sendRequest) {
-				var STYLE_ATTR_SELECTOR = "*[style]";
-				Array.prototype.forEach.call(doc.querySelectorAll(STYLE_ATTR_SELECTOR), function(node) {
-					replaceCssURLs(function() {
-						return node.getAttribute("style");
-					}, function(value) {
-						node.setAttribute("style", value);
-					}, targetDoc.baseURI, sendRequest);
-				});
-			}
-		},
-		bgAttr : {
-			get : function(doc, sendRequest) {
-				var BG_SELECTOR = 'body[background],table[background],thead[background],tbody[background],tr[background],th[background],td[background]';
-				Array.prototype.forEach.call(doc.querySelectorAll(BG_SELECTOR), function(node) {
-					var url, value = node.getAttribute("background");
-					if (value.indexOf(".") != -1) {
-						url = formatURL(value, targetDoc.baseURI);
-						if (url.indexOf("data:") != 0)
-							sendRequest(url, function(data) {
-								node.setAttribute("background", getDataURI(data, EMPTY_PIXEL_DATA, true));
-							}, true);
-					}
-				});
-			}
-		},
-		image : {
-			get : function(doc, sendRequest) {
-				var IMG_SELECTOR = 'link[href][rel="shortcut icon"], link[href][rel="apple-touch-icon"], link[href][rel="icon"], img[src], input[src][type="image"], video[poster]';
-				Array.prototype.forEach.call(doc.querySelectorAll(IMG_SELECTOR), function(node) {
-					var url = formatURL(node.href || node.src || node.poster, targetDoc.baseURI);
-					if (url.indexOf("data:") != 0)
-						sendRequest(url, function(data) {
-							node.setAttribute(node.href ? "href" : node.src ? "src" : "poster", getDataURI(data, EMPTY_PIXEL_DATA, true));
-						}, true);
-				});
-			},
-			getFavico : function(doc, sendRequest) {
-				var node, docHead = doc.querySelector("html > head"), foundLink = false, IMG_SELECTOR = 'link[href][rel="shortcut icon"], link[href][rel="apple-touch-icon"], link[href][rel="icon"]';
-				Array.prototype.forEach.call(doc.querySelectorAll(IMG_SELECTOR), function(n) {
-					var url = formatURL(n.href, targetDoc.baseURI);
-					if (!foundLink && url.indexOf("data:") != 0)
-						foundLink = true;
-				});
-				if (!foundLink && docHead) {
-					node = targetDoc.createElement("link");
-					node.type = "image/x-icon";
-					node.rel = "shortcut icon";
-					node.href = "/favicon.ico";
-					docHead.appendChild(node);
-					sendRequest(node.href, function(data) {
-						node.setAttribute(node.href ? "href" : "src", getDataURI(data, EMPTY_PIXEL_DATA, true));
-					}, true);
-				}
-			},
-			getFavicoData : function(doc) {
-				var favico, favicosByRel = {}, favicos = doc.querySelectorAll('link[href][rel="shortcut icon"], link[href][rel="icon"], link[href][rel="apple-touch-icon"]');
-				Array.prototype.forEach.call(favicos, function(favico) {
-					favicosByRel[favico.rel.toLowerCase()] = favico;
-				});
-				favico = favicosByRel["shortcut icon"] || favicosByRel["icon"] || favicosByRel["apple-touch-icon"];
-				if (favico && favico.href != EMPTY_PIXEL_DATA)
-					return favico.href;
-				return null;
-			}
-		},
-		canvas : {
-			replace : function() {
-				Array.prototype.forEach.call(targetDoc.querySelectorAll("canvas"), function(node) {
-					var i, data, newNode = targetDoc.createElement("img");
-					try {
-						data = node.toDataURL("image/png", "");
-					} catch (e) {
-					}
-					if (data) {
-						newNode.setAttribute("src", data);
-						for (i = 0; i < node.attributes.length; i++)
-							if (node.attributes[i].value)
-								newNode.setAttribute(node.attributes[i].name, node.attributes[i].value);
-						if (!newNode.width)
-							newNode.style.pixelWidth = node.clientWidth;
-						if (!newNode.height)
-							newNode.style.pixelHeight = node.clientHeight;
-						node.parentElement.replaceChild(newNode, node);
-					}
-				});
-			}
-		},
-		svg : {
-			get : function(doc, sendRequest) {
-				var SVG_SELECTOR = 'object[type="image/svg+xml"], object[type="image/svg-xml"], embed[src*=".svg"]';
-				Array.prototype.forEach.call(doc.querySelectorAll(SVG_SELECTOR), function(node) {
-					var url = formatURL(node.data || node.src, targetDoc.baseURI);
-					if (url.indexOf("data:") != 0)
-						sendRequest(url, function(data) {
-							node.setAttribute(node.data ? "data" : "src", getDataURI(data, "data:text/xml,<svg></svg>", true));
-						}, false, true);
-				});
-			}
-		},
-		link : {
-			get : function(doc, sendRequest) {
-				var LINK_SELECTOR = 'link[href][rel*="stylesheet"]';
-				Array.prototype.forEach.call(doc.querySelectorAll(LINK_SELECTOR), function(node) {
-					if (node.href.indexOf("data:") != 0)
-						sendRequest(node.href, function(data) {
-							var i, newNode, commentNode;
-							if (data.mediaType == "text/html") {
-								node.parentElement.removeChild(node);
-								return;
-							}
-							newNode = targetDoc.createElement("style");
-							for (i = 0; i < node.attributes.length; i++)
-								if (node.attributes[i].value)
-									newNode.setAttribute(node.attributes[i].name, node.attributes[i].value);
-							newNode._href = node.href;
-							newNode.removeAttribute("href");
-							newNode.textContent = resolveURLs(data.content || "", data.url) + "\n";							
-							if (node.disabled) {
-								commentNode = doc.createComment();
-								commentNode.textContent = newNode.outerHTML.replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/--/g,"&minus;&minus;");
-								node.parentElement.replaceChild(commentNode, node);
-							} 
-							else
-								node.parentElement.replaceChild(newNode, node);
-						});
-				});
-			}
-		},
-		script : {
-			get : function(doc, sendRequest) {
-				var SCRIPT_SELECTOR = 'script[src]';
-				Array.prototype.forEach.call(doc.querySelectorAll(SCRIPT_SELECTOR), function(node) {
-					if (node.src.indexOf("data:") != 0)
-						sendRequest(node.src, function(data) {
-							data.content = data.content.replace(/"([^"]*)<\/\s*script\s*>([^"]*)"/gi, '"$1<"+"/script>$2"');
-							data.content = data.content.replace(/'([^']*)<\/\s*script\s*>([^']*)'/gi, "'$1<'+'/script>$2'");
-							node.textContent = [ "\n", data.content, "\n" ].join("");
-							node.removeAttribute("src");
-						}, false, false, targetDoc.characterSet);
-				});
-			},
-			remove : function(doc) {
-				var i, nodes = doc.querySelectorAll('script'), body = doc.querySelector("html > body");
-				for (i = 0; i < nodes.length; i++)
-					nodes[i].parentElement.removeChild(nodes[i]);
-				if (body && body.getAttribute("onload"))
-					body.removeAttribute("onload");
-			}
-		},
-		style : {
-			getURL : function(doc, sendRequest) {
-				Array.prototype.forEach.call(doc.querySelectorAll("style"), function(styleSheet) {
-					replaceCssURLs(function() {
-						return styleSheet.textContent;
-					}, function(value) {
-						styleSheet.textContent = value;
-					}, styleSheet._href || targetDoc.baseURI, sendRequest);
-				});
-			},
-			getImport : function(doc, sendRequest) {
-				Array.prototype.forEach.call(doc.querySelectorAll("style"), function(styleSheet) {
-					var i, url, result, imports = removeCssComments(styleSheet.textContent).match(IMPORT_EXP);
-					if (imports)
-						for (i = 0; i < imports.length; i++) {
-							result = imports[i].match(IMPORT_URL_VALUE_EXP);
-							if (result && (result[2] || result[4])) {
-								url = formatURL(result[2] || result[4], styleSheet._href || targetDoc.baseURI);
-								if (url.indexOf("data:") != 0)
-									(function(imp) {
-										sendRequest(url, function(data) {
-											styleSheet.textContent = styleSheet.textContent.replace(imp, data.content ? resolveURLs(data.content, data.url)
-													: "");
-										}, false, false, targetDoc.characterSet);
-									})(imports[i]);
-							}
-						}
-				});
-			},
-			removeUnused : function() {
-				Array.prototype.forEach.call(targetDoc.querySelectorAll("style"), function(style) {
-					var cssRules = [];
-
-					function process(rules) {
-						var selector;
-						Array.prototype.forEach.call(rules, function(rule) {
-							if (rule instanceof CSSMediaRule) {
-								cssRules.push("@media " + Array.prototype.join.call(rule.media, ",") + " {");
-								process(rule.cssRules, true);
-								cssRules.push("}");
-							} else if (rule.selectorText) {
-								selector = trim(rule.selectorText.replace(/::after|::before|::first-line|::first-letter|:focus|:hover/gi, ''));
-								if (selector) {
-									try {
-										if (targetDoc.querySelector(selector))
-											cssRules.push(rule.cssText);
-									} catch (e) {
-										cssRules.push(rule.cssText);
-									}
-								}
-							}
-						});
-					}
-					if (style.sheet) {
-						process(style.sheet.rules);
-						style.innerText = cssRules.join("");
-					}
-				});
-			}
-		}
-	};
-})(singlefile);

+ 0 - 53
WebContent/scripts/main.js

@@ -1,53 +0,0 @@
-/*
- * Copyright 2010 Gildas Lormeau
- * contact : gildas.lormeau <at> gmail.com
- * 
- * This file is part of SingleFile.
- *
- *   SingleFile is free software: you can redistribute it and/or modify
- *   it under the terms of the GNU Lesser General Public License as published by
- *   the Free Software Foundation, either version 3 of the License, or
- *   (at your option) any later version.
- *
- *   SingleFile is distributed in the hope that it will be useful,
- *   but WITHOUT ANY WARRANTY; without even the implied warranty of
- *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *   GNU Lesser General Public License for more details.
- *
- *   You should have received a copy of the GNU Lesser General Public License
- *   along with SingleFile.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-(function(holder) {
-	var port = chrome.extension.connect(), extId = "__SingleFile__";
-
-	holder.init(document, {
-		addListener : function(onMessage) {
-			port.onMessage.addListener(onMessage);
-		},
-		postMessage : function(message) {
-			port.postMessage(message);
-		}
-	}, {
-		addListener : function(onMessage) {
-			window.addEventListener("message", function(event) {
-				var data = event.data;
-				if (data.indexOf(extId + '::') == 0)
-					onMessage(JSON.parse(data.substr(extId.length + 2)));
-			}, false);
-		},
-		postMessageToFrames : function(frameSelector, messageFn, params) {
-			var execute = function(id, selector, fnStr, fnParamsStr) {
-				var i, frameElements = document.querySelectorAll(selector), fn = eval("(" + fnStr + ")");
-				for (i = 0; i < frameElements.length; i++)
-					frameElements[i].contentWindow.postMessage(id + "::" + JSON.stringify(fn(i, JSON.parse(fnParamsStr))), "*");
-			};
-			location.href = "javascript:(" + execute.toString() + ")('" + extId + "','" + frameSelector + "','" + messageFn.toString() + "','"
-					+ JSON.stringify(params) + "')";
-		},
-		postMessageToParent : function(message) {
-			var msg = extId + "::" + JSON.stringify(message);
-			location.href = "javascript:parent.postMessage('" + msg + "', '*')";
-		}
-	});
-})(singlefile);

+ 0 - 55
WebContent/scripts/options.js

@@ -1,55 +0,0 @@
-/*
- * Copyright 2010 Gildas Lormeau
- * contact : gildas.lormeau <at> gmail.com
- * 
- * This file is part of SingleFile.
- *
- *   SingleFile is free software: you can redistribute it and/or modify
- *   it under the terms of the GNU Lesser General Public License as published by
- *   the Free Software Foundation, either version 3 of the License, or
- *   (at your option) any later version.
- *
- *   SingleFile is distributed in the hope that it will be useful,
- *   but WITHOUT ANY WARRANTY; without even the implied warranty of
- *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *   GNU Lesser General Public License for more details.
- *
- *   You should have received a copy of the GNU Lesser General Public License
- *   along with SingleFile.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-function load() {
-	var bgPage = chrome.extension.getBackgroundPage(), options = bgPage.singlefile.getOptions(), removeScriptsInput, removeFramesInput, removeObjectsInput, removeHiddenInput, removeUnusedInput;
-	removeFramesInput = document.getElementById("removeFramesInput");
-	removeScriptsInput = document.getElementById("removeScriptsInput");
-	removeObjectsInput = document.getElementById("removeObjectsInput");
-	removeHiddenInput = document.getElementById("removeHiddenInput");
-	removeUnusedInput = document.getElementById("removeUnusedInput");
-	document.getElementById("popupContent").onchange = function() {
-		setTimeout(function() {
-			bgPage.singlefile.setOptions({
-				removeFrames : removeFramesInput.checked,
-				removeScripts : removeScriptsInput.checked,
-				removeObjects : removeObjectsInput.checked,
-				removeHidden : removeHiddenInput.checked,
-				removeUnused : removeUnusedInput.checked
-			});
-		}, 500);
-	};
-	removeFramesInput.checked = options.removeFrames;
-	removeScriptsInput.checked = options.removeScripts;
-	removeObjectsInput.checked = options.removeObjects;
-	removeHiddenInput.checked = options.removeHidden;
-	removeUnusedInput.checked = options.removeUnused;
-	removeScriptsInput.addEventListener("click", function() {
-		removeHiddenInput.checked = false;
-		removeUnusedInput.checked = false;
-	});
-	document.getElementById("resetButton").addEventListener("click", function() {
-		bgPage.singlefile.resetOptions();
-		load();
-	});
-	document.getElementById("reloadButton").addEventListener("click", function() {
-		bgPage.history.go(0);
-	});
-}

+ 0 - 60
WebContent/scripts/ui.js

@@ -1,60 +0,0 @@
-/*
- * Copyright 2010 Gildas Lormeau
- * contact : gildas.lormeau <at> gmail.com
- * 
- * This file is part of SingleFile.
- *
- *   SingleFile is free software: you can redistribute it and/or modify
- *   it under the terms of the GNU Lesser General Public License as published by
- *   the Free Software Foundation, either version 3 of the License, or
- *   (at your option) any later version.
- *
- *   SingleFile is distributed in the hope that it will be useful,
- *   but WITHOUT ANY WARRANTY; without even the implied warranty of
- *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *   GNU Lesser General Public License for more details.
- *
- *   You should have received a copy of the GNU Lesser General Public License
- *   along with SingleFile.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-(function(holder) {
-	function showProcessing() {
-		var div = document.createElement("div");
-		div.id = "__SingleFile_mask__";
-		div.style.position = "fixed";
-		div.style.top = "0px";
-		div.style.left = "0px";
-		div.style.height = "100%";
-		div.style.width = "100%";
-		div.style.backgroundColor = "black";
-		div.style.zIndex = 2147483647;
-		div.style.opacity = 0;
-		div.style["-webkit-transition"] = "opacity 250ms";
-		document.body.appendChild(div);
-		div.offsetWidth;
-		div.style.opacity = .3;
-	}
-
-	function hideProcessing() {
-		var div = document.getElementById("__SingleFile_mask__");
-		if (div)
-			document.body.removeChild(div);
-	}
-
-	holder.ui = {
-		init : function(bgPort) {
-			if (window == top)
-				bgPort.addListener(function(message) {
-					if (!message.scrapbooking) {
-						if (message.start) {
-							showProcessing();
-						} else if (message.done)
-							setTimeout(function() {
-								hideProcessing();
-							}, 250);
-					}
-				});
-		}
-	};
-})(singlefile);

+ 22 - 15
WebContent/manifest.json → WebContent/ui/manifest.json

@@ -1,16 +1,23 @@
-{
-	"name": "SingleFile",
-	"icons": {
-		"16": "resources/icon_16.png",
-		"48": "resources/icon_48.png",
-		"128": "resources/icon_128.png" },
-	"version": "0.1.9",
-	"description": "SingleFile helps you to archive a complete page into a single HTML file",
-	"background_page" : "pages/background.html",
-	"options_page": "pages/options.html",
-	"browser_action": {
-	    "default_icon": "resources/icon_48.png", 
-	    "default_title": "Process this page with SingleFile"
-	},
-	"permissions": [ "tabs", "http://*/*", "https://*/*" ]
+{
+	"name": "SingleFile",
+	"icons": {
+		"16": "resources/icon_16.png",
+		"48": "resources/icon_48.png",
+		"128": "resources/icon_128.png" },
+	"version": "0.2.18",
+	"description": "SingleFile helps you to archive a complete page into a single HTML file",
+	"background_page" : "pages/background.html",
+	"options_page": "pages/options.html",
+	"browser_action": {
+	    "default_icon": "resources/icon_48.png", 
+	    "default_title": "Process this page with SingleFile"
+	},
+	"content_scripts" : [ {
+		"matches" : [ "http://*/*", "https://*/*" ],
+		"js" : [ "scripts/content/content.js" ],
+		"run_at" : "document_start",
+		"all_frames" : true
+	} ],
+	"permissions": [ "tabs", "notifications", "contextMenus" ],
+	"minimum_chrome_version" : "7"
 }

+ 13 - 0
WebContent/ui/pages/background.html

@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+	var singlefile = {};
+</script>
+<script type="text/javascript" src="../scripts/bg/ui.js"></script>
+<script type="text/javascript" src="../scripts/bg/config.js"></script>
+<script type="text/javascript" src="../scripts/bg/background.js"></script>
+</head>
+<body>
+</body>
+</html>

+ 110 - 95
WebContent/pages/help.css → WebContent/ui/pages/help.css

@@ -1,96 +1,111 @@
-body {
-	background-color: #eee;
-}
-
-body>div {
-	background-color: #fff;
-	font-family: sans-serif;
-	max-width: 800px;
-	margin-left: auto;
-	margin-right: auto;
-	border-width: 1px;
-	border-style: solid;
-	border-radius: 8px;
-	padding-left: 10px;
-	padding-right: 10px;
-	padding-bottom: 20px;
-	text-align: justify;
-	-webkit-box-shadow: #888 2px 2px 2px;
-}
-
-div>object {
-	margin-top: 30px;
-	margin-left: auto;
-	margin-right: auto;
-	display: block;
-	margin-left: auto;
-}
-
-.option {
-	font-size: 16px;
-	font-style: normal;
-	font-family: sans-serif;
-	color: graytext;
-}
-
-.notice {
-	font-style: italic;
-	font-family: serif;
-	font-size: 1.1em;
-}
-
-ol {
-	-webkit-padding-start: 30px;
-	margin: 0px;
-	padding-right: 10px;
-}
-
-ol>li {
-	padding-top: 2em;	
-}
-
-a[name] {
-	font-weight: bold;
-}
-
-ol>li>ul>li {
-	padding-top: .5em;
-}
-
-#icon {
-	height: 1em;
-}
-
-#titleIcon {
-	width: 1.2em;
-	height: 1.2em;
-}
-
-.screenshot {
-	background-image: url(../resources/options_screen.png);
-	background-repeat: no-repeat;
-	background-position: center center;
-	height: 341px;
-}
-
-#title {
-	padding-left: .3em;
-	vertical-align: top;
-}
-
-#titleBorder {
-	margin-top: 10px;
-	padding-left: 10px;
-	padding-bottom: 10px;
-	margin-bottom: 20px;
-}
-
-#index {
-	padding-left: 10px;
-	font-size: .7em;
-}
-
-h2,h4 {
-	padding-left: .3em;
-	margin-bottom: 0px;
+body {
+	background-color: #eee;
+}
+
+body>div {
+	position: relative;
+	background-color: #fff;
+	font-family: sans-serif;
+	max-width: 800px;
+	margin-left: auto;
+	margin-right: auto;
+	border-width: 1px;
+	border-style: solid;
+	border-radius: 8px;
+	padding-left: 10px;
+	padding-right: 10px;
+	padding-bottom: 20px;
+	text-align: justify;
+	-webkit-box-shadow: #888 2px 2px 2px;
+}
+
+div>object {
+	margin-top: 30px;
+	margin-left: auto;
+	margin-right: auto;
+	display: block;
+	margin-left: auto;
+}
+
+.option {
+	font-size: 16px;
+	font-style: normal;
+	font-family: sans-serif;
+	color: graytext;
+}
+
+.notice {
+	font-style: italic;
+	font-family: serif;
+	font-size: 1.1em;
+}
+
+ol {
+	-webkit-padding-start: 30px;
+	margin: 0px;
+	padding-right: 10px;
+}
+
+ol>li {
+	padding-top: 2em;	
+}
+
+a[id] {
+	font-weight: bold;
+}
+
+ol>li>ul>li {
+	padding-top: .5em;
+}
+
+#icon {
+	height: 1em;
+}
+
+#titleIcon {
+	width: 1.2em;
+	height: 1.2em;
+}
+
+.screenshot {
+	background-image: url(../resources/options_screen.png);
+	background-repeat: no-repeat;
+	background-position: center center;
+	height: 560px;
+}
+
+#title {
+	padding-left: .3em;
+	vertical-align: top;
+}
+
+#titleBorder {
+	margin-top: 10px;
+	padding-left: 10px;
+	padding-bottom: 10px;
+	margin-bottom: 20px;
+}
+
+#index {
+	padding-left: 10px;
+	font-size: .7em;
+}
+
+h2,h4 {
+	padding-left: .3em;
+	margin-bottom: 0px;
+}
+
+li {
+	line-height: 1.5em;
+}
+
+#logo-html5 {
+	position: absolute;
+	right: 0px;
+	bottom: 20px;
+}
+
+.availability {
+	font-size: 11pt;
 }

+ 173 - 0
WebContent/ui/pages/help.html

@@ -0,0 +1,173 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<title>SingleFile help</title>
+<link rel="stylesheet" href="help.css">
+</head>
+<body>
+<div>
+<div id="titleBorder">
+<h2><img src="../resources/icon_48.png" id="titleIcon"><span id="title">SingleFile help</span></h2>
+<h4>SingleFile helps you to archive a complete page into a single HTML file.</h4>
+</div>
+<span id="index"><a href="#instructions">Instructions</a> - <a href="#demo">Demo</a> - <a href="#options">Options description</a> - <a href="#folder">Saved archives
+folder</a> - <a href="#notes">Technical notes</a> - <a href="#knownIssues">Known issues</a> - <a href="#unknownIssues">Unknown issues</a></span>
+<hr>
+<ol>
+	<li><a id="instructions">Instructions</a>
+	<ul>
+		<li>wait until the page is fully loaded : you may need to scroll down the entire page and hover dynamic document elements (e.g. "rollover" images) to be sure all elements
+		are displayed</li>
+		<li>click on the SingleFile icon <img src="../resources/icon_48.png" id="icon"> in the Chrome toolbar or press Ctrl-Shift-S shortcut or use context menu to launch page
+		processing</li>
+		<li>wait until the shadow disappears then hit Ctrl-S or select "Save as" in the wrench menu and save the page</li>
+		<li>all images, style sheets and frame contents are embedded into the ".htm" saved file</li>
+	</ul>
+	</li>
+
+	<li><a id="demo">Demo : SingleFile advantages over default file save</a>
+	<div><object width="480" height="385">
+		<param name="movie" value="http://www.youtube.com/v/D99LfOF3qis&hl=fr_FR&fs=1&">
+		<param name="allowFullScreen" value="true">
+		<param name="allowscriptaccess" value="always">
+		<embed src="http://www.youtube.com/v/D99LfOF3qis&hl=fr_FR&fs=1&" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="480" height="385"></object>
+	</div>
+	<li><a id="options">Options description</a>
+
+	<p>You can customize the way SingleFile process the page through the options page. Right-click on SingleFile icon <img src="../resources/icon_48.png" id="icon"> in the
+	Chrome toolbar and select "Options" in the context menu to open the options page.</p>
+	<div class="screenshot"></div>
+	<p>Details :</p>
+	<ul>
+		<li><span class="option">remove frames</span>
+		<p>Remove all frames on the page</p>
+		<p>You can enable this option if the file size is too large when disabled or to remove some ads.</p>
+		<p class="notice">It is recommended to <u>disable</u> this option</p>
+		</li>
+
+		<li><span class="option">remove scripts</span>
+		<p>Remove all javascript scripts</p>
+		<p>Most of the time, you don't need javascript into the saved page. If your want to save the page dynamic behavior, you may need to disable this option. Nevertheless, the
+		dynamic behavior may not work or document can be altered when opening the saved file.<br>
+		It is recommended to enable <span class="option">process raw document</span> if you disable this option.</p>
+		<p class="notice">It is recommended to <u>enable</u> this option</p>
+		</li>
+
+		<li><span class="option">remove objects</span>
+		<p>Remove all objects</p>
+		<p>Remove all non embeddable elements : flash, java applet ...</p>
+		<p class="notice">It is recommended to <u>enable</u> this option if you want to read the saved page offline</p>
+		</li>
+
+		<li><span class="option">remove hidden elements</span>
+		<p>Remove all hidden elements</p>
+		<p>Remove all the HTML unvisible elements on the page (with <code>visibility = "hidden"</code>&nbsp;or&nbsp;<code>display = "none"</code>&nbsp;or&nbsp;<code>opacity =
+		0</code> CSS property values). This option may alter the document but can considerably reduce the saved file size.</p>
+		<p class="notice">It is recommended to <u>disable</u> this option unless the saved file size is too large</p>
+		</li>
+
+		<li><span class="option">remove unused CSS rules</span>
+		<p>Remove all CSS rules that do not match any element</p>
+		<p>This option may alter the document but can considerably reduce the saved file size. If you enable this option, it may introduce some incompatibilities issues in the saved
+		page when opening it into another browser (i.e. not based on Webkit).</p>
+		<p class="notice">It is recommended to <u>disable</u> this option unless the saved file size is too large or you do not want to open the saved page into another browser</p>
+		</li>
+
+		<li><span class="option">scrapbook the page</span>
+		<p>Send the archived page to <a href="https://chrome.google.com/webstore/detail/ihkkeoeinpbomhnpkmmkpggkaefincbn">Scrapbook for SingleFile</a></p>
+		<p>This option allows to use SingleFile action icon as a shortcut to send the viewed page to <a
+			href="https://chrome.google.com/webstore/detail/ihkkeoeinpbomhnpkmmkpggkaefincbn">Scrapbook for SingleFile</a> extension if installed.</p>
+		<p class="notice">It is recommended to <u>disable</u> this option unless you want to use SingleFile icon to scrapbook pages</p>
+		</li>
+
+		<li><span class="option">process in background</span>
+		<p>Process the page in background</p>
+		<p>Processing in background means it won't be blocking (*). If you disable this option, a shadow will prevent you to use the page during the SingleFile processing. Even
+		though processing may be faster.</p>
+		<p class="notice">It is recommended to <u>disable</u> this option unless <span class="option">display processed page</span> option is disabled</p>
+		</li>
+
+		<li><span class="option">save processed page (*)</span>
+		<p>Save the page on filesystem</p>
+		<p>When enabled this option allows to save automatically archives on filesystem. See <a href="#folder">Saved archives folder</a> for more details.</p>
+		<p class="notice">It is recommended to <u>disable</u> this option unless you want files to be automatically saved</p>
+		</li>
+
+		<li><span class="option">display processed page (*)</span>
+		<p>Display the processed page in the tab</p>
+		<p>This option must be enabled if you want to use Chrome "Save as" dialog box to save the processed page. To improve processing speed, you should disable <span class="option">process
+		in background</span> when this option is enabled.</p>
+		<p class="notice">It is recommended to <u>enable</u> this option</p>
+		</li>
+
+		<li><span class="option">max filename length (*)</span>
+		<p>Maximum filename length</p>
+		<p>Since filesystem restricts filename length and the extension cannot guess this value, you can set it to the maximum allowed by your Operating System.</p>
+		<p class="notice">It is recommended to set this option to <u>90</u> if you are using Windows</p>
+		</li>
+
+		<li><span class="option">process raw document</span>
+		<p>Process the raw downloaded document</p>
+		<p>This option allows SingleFile to process the downloaded document instead of the displayed document. The main difference is that JavaScript scripts won't be parsed and
+		executed. Frames won't be embedded too.</p>
+		<p class="notice">It is recommended to <u>disable</u> this option unless <span class="option">remove scripts</span> is disabled</p>
+		</li>
+
+		<li><span class="option">Reset to default options</span>
+		<p>Reset all the options to default state</p>
+		</li>
+	</ul>
+	</li>
+
+	<li><a id="folder">Saved archives folder (*)</a>
+	<p>When <span class="option">save processed page</span> is enabled, pages will be automatically saved but Chrome will not allow to choose the root folder where archives are
+	stored. Neither it will allow to make links pointing to saved files folder. So you have to find "manually" the this folder.</p>
+	<p>Files are saved in "FileSystem" folder located into Chrome user data folder:</p>
+	<ul style="text-align: left">
+		<li>Windows default path: <br>
+		<span class="option">%LOCALAPPDATA%\Google\Chrome\User&nbsp;Data\Default\FileSystem\chrome-extension_jemlklgaibiijojffihnhieihhagocma_0\Persistent\</span></li>
+		<li>Linux default path: <br>
+		<span class="option">~/.config/google-chrome/Default/FileSystem/chrome-extension_jemlklgaibiijojffihnhieihhagocma_0/Persistent/</span></li>
+		<li>Mac OS X default path: <br>
+		<span class="option">~/Library/Application&nbsp;Support/Google/Chrome/Default/FileSystem/chrome-extension_jemlklgaibiijojffihnhieihhagocma_0/Persistent/</span></li>
+	</ul>
+	<p>You can create symbolic links to help you to access easily to this folder and use sync services like dropbox. Here are the way of doing it on <a
+		href="http://technet.microsoft.com/en-us/library/cc753194(WS.10).aspx">windows (mklink /d)</a> and <a href="http://www.unixtutorial.org/commands/ln/">linux or Mac OS X (ln
+	-s)</a>.</p>
+	<p>You can also change Chrome user data folder with <a href="http://www.chromium.org/user-experience/user-data-directory">--user-data-dir switch</a>.
+	</li>
+
+	<li><a id="notes">Technical notes</a>
+	<ul>
+		<li>all images are converted into <a href="http://en.wikipedia.org/wiki/Base64">base64</a></li>
+		<li>frame document contents and automatically saved pages are encoded with <a href="http://en.wikipedia.org/wiki/Utf_8">utf-8 charset</a></li>
+		<li>encoded contents are injected in the document using <a href="http://en.wikipedia.org/wiki/Data_URI_scheme">data URI scheme</a></li>
+		<li>data URI scheme is supported by the following web browsers: Chrome, Firefox, Opera, Safari, Konqueror and Internet Explorer 8 (limited support : data URIs must be
+		smaller than 32 KB, embedded frames are not supported)</li>
+		<li>SVG images are supported (SVG document is converted into utf-8 but is not processed)</li>
+	</ul>
+	</li>
+
+	<li><a id="knownIssues">Known issues</a>
+	<ul>
+		<li>SVG images without src attribute or with empty src attribute are not embedded</li>		
+	</ul>
+	</li>
+
+	<li><a id="unknownIssues">Unknown issues</a>
+	<p>If you find an unknown issue (i.e. frozen process, extra saved files, blank or altered document, tab crash...):</p>
+	<ul>
+		<li>reset options</li>
+		<li>disable all other extensions to see if there is a conflict</li>
+		<li>if there is a conflict, try to determine against which extension(s)</li>
+	</ul>
+	<p>Report the issue <a href="https://chrome.google.com/webstore/detail/mpiodijhokgodhhofbcjdecpffjipkle">here</a> with a short description, URL(s), Chrome version, OS version</p>
+	</li>
+</ol>
+<br>
+<p class="availability">*: feature only available on Chrome 9+</p>
+<a href="http://www.w3.org/html/logo/"> <img src="../resources/html5-badge-h-storage.png" alt="HTML5 offline & storage logo" id="logo-html5"
+	title="This extension is powered by HTML5 Offline & Storage"> </a></div>
+</body>
+</html>

+ 42 - 0
WebContent/ui/pages/missingcore.css

@@ -0,0 +1,42 @@
+body {
+	background-color: #eee;
+}
+
+body>div {
+	background-color: #fff;
+	font-family: sans-serif;
+	max-width: 530px;
+	margin-left: auto;
+	margin-right: auto;
+	border-width: 1px;
+	border-style: solid;
+	border-radius: 8px;
+	padding-left: 10px;
+	padding-right: 10px;
+	padding-bottom: 10px;
+	text-align: justify;
+	-webkit-box-shadow: #888 2px 2px 2px;
+}
+
+#titleIcon {
+	width: 1.2em;
+	height: 1.2em;
+}
+
+#title {
+	padding-left: .3em;
+	vertical-align: top;
+}
+
+h2,h4 {
+	padding-left: .3em;
+	margin-bottom: 0px;
+}
+
+.warning {
+	color: rgb(104,0,0);
+}
+
+.warning a {
+	color: rgb(176,0,0) ;
+}

+ 20 - 0
WebContent/ui/pages/missingcore.html

@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<title>SingleFile Info</title>
+<link rel="stylesheet" href="missingcore.css">
+</head>
+<body>
+<div>
+<div id="titleBorder">
+	<h2><img src="../resources/icon_48.png" id="titleIcon"><span id="title">SingleFile info</span></h2>	
+</div>
+	<hr>
+	<h4>SingleFile 0.2 has been successfully installed</h4>	
+	<p class="warning">
+		Please download and install <b><a href="https://chrome.google.com/webstore/detail/jemlklgaibiijojffihnhieihhagocma">SingleFile Core extension</a></b> to complete the installation
+	</p>
+</div>
+</body>
+</html>

+ 88 - 79
WebContent/pages/options.css → WebContent/ui/pages/options.css

@@ -1,80 +1,89 @@
-body {
-	background-color: #eee;
-}
-
-body>div {
-	margin-left: auto;
-	margin-right: auto;
-	width: 240px;
-	background-color: #fff;
-	font-family: sans-serif;
-	margin-bottom: 10px;
-	border-width: 1px;
-	border-style: solid;
-	border-radius: 8px;
-	padding-left: 10px;
-	padding-right: 10px;
-	padding-bottom: 5px;
-	text-align: justify;
-	-webkit-box-shadow: #888 2px 2px 2px;
-}
-
-#icon {
-	width: 1em;
-	padding-right: .3em;
-}
-
-button {
-	background: -webkit-gradient(linear, left bottom, left top, color-stop(0.12, rgb(201
-		, 201, 201) ), color-stop(0.79, rgb(247, 247, 247) ) );
-	border-color: rgb(191, 191, 191);
-	border-style: solid;
-	height: 25px;
-	border-radius: 4px;
-}
-
-button:active {
-	border-color: rgb(237, 237, 237);
-}
-
-#popupContent {
-	height: auto;
-	padding-right: 5px;
-}
-
-#popupContent>div {
-	margin-bottom: 15px;
-}
-
-input[type="checkbox"],button,select {
-	float: right;
-	margin: 1px 0px;
-}
-
-button {
-	padding-left: 20px;
-	padding-right: 20px;
-}
-
-h2 {
-	margin-bottom: 14px;
-	margin-top: 14px;
-}
-
-h3 {
-	margin-bottom: 10px;
-	margin-top: 10px;
-}
-
-a {
-	display: inline-block;
-	padding-top: 5px;
-}
-
-#reloadButton {
-	background: lightGrey;
-	bottom: 0px;
-	margin: 5px;
-	position: absolute;
-	right: 0px;
+body {
+	background-color: #eee;
+}
+
+body>div {
+	margin-left: auto;
+	margin-right: auto;
+	width: 300px;
+	background-color: #fff;
+	font-family: sans-serif;
+	margin-bottom: 10px;
+	border-width: 1px;
+	border-style: solid;
+	border-radius: 8px;
+	padding-left: 10px;
+	padding-right: 10px;
+	padding-bottom: 5px;
+	text-align: justify;
+	-webkit-box-shadow: #888 2px 2px 2px;
+}
+
+#icon {
+	width: 1em;
+	padding-right: .3em;
+}
+
+button {
+	background: -webkit-gradient(linear, left bottom, left top, color-stop(0.12, rgb(201
+		, 201, 201) ), color-stop(0.79, rgb(247, 247, 247) ) );
+	border-color: rgb(191, 191, 191);
+	border-style: solid;
+	height: 25px;
+	border-radius: 4px;
+}
+
+button:active {
+	border-color: rgb(237, 237, 237);
+}
+
+#popupContent {
+	height: auto;
+	padding-right: 5px;
+}
+
+#popupContent .option {
+	margin-left: 5px;
+	margin-bottom: 12px;
+}
+
+input[type="checkbox"],button,input[type="text"] {
+	float: right;
+	margin: 1px 0px;
+}
+
+input[type="checkbox"] {
+	margin-top: 4px;
+}
+
+button {
+	padding-left: 20px;
+	padding-right: 20px;
+}
+
+h2 {
+	margin-bottom: 14px;
+	margin-top: 14px;
+}
+
+h3 {
+	margin-bottom: 10px;
+	margin-top: 10px;
+}
+
+a {
+	display: inline-block;
+	padding-top: 5px;
+}
+
+#reloadButton {
+	background: lightGrey;
+	bottom: 0px;
+	margin: 5px;
+	position: absolute;
+	right: 0px;
+}
+
+#filenameMaxLengthInput {
+	width: 20px;
 }

+ 57 - 0
WebContent/ui/pages/options.html

@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<title>SingleFile options</title>
+<link rel="stylesheet" href="options.css">
+<script type="text/javascript" src="../scripts/bg/options.js"></script>
+</head>
+<body>
+<div>
+<h2><img alt="SingleFile icon" id="icon" src="../resources/icon_48.png"/>SingleFile</h2>
+<hr>
+<div id="popupContent">	
+	<h4>Basic options:</h4>
+	<div class="option">
+		<label for="removeFramesInput">remove frames</label> <input type="checkbox" id="removeFramesInput">
+	</div>
+	<div class="option">
+		<label for="removeObjectsInput">remove objects</label> <input type="checkbox" id="removeObjectsInput">
+	</div>
+	<div class="option">
+		<label for="removeScriptsInput">remove scripts</label> <input type="checkbox" id="removeScriptsInput">
+	</div>
+	<div class="option">
+		<label for="removeHiddenInput">remove hidden elements</label> <input type="checkbox" id="removeHiddenInput">
+	</div>
+	<div class="option">
+		<label for="removeUnusedCSSRulesInput">remove unused CSS rules</label> <input type="checkbox" id="removeUnusedCSSRulesInput">
+	</div>
+	<div class="option">
+		<label for="getContentInput">scrapbook the page</label> <input type="checkbox" id="getContentInput">
+	</div>	
+	<h4>Advanced options:</h4>
+	<div class="option">
+		<label for="processInBackgroundInput">process in background</label> <input type="checkbox" id="processInBackgroundInput">
+	</div>
+	<div id="storageOptions">
+		<div class="option">
+			<label for="savePageInput">save processed page</label> <input type="checkbox" id="savePageInput">
+		</div>
+		<div class="option">
+			<label for="displayProcessedPageInput">display processed page</label> <input type="checkbox" id="displayProcessedPageInput">
+		</div>
+		<div class="option">
+			<label for="filenameMaxLengthInput">max filename length</label> <input type="text" id="filenameMaxLengthInput">
+		</div>		
+	</div>
+	<div class="option">
+		<label for="getRawDocInput">process raw document</label> <input type="checkbox" id="getRawDocInput">
+	</div>
+	<div class="option">	
+		<a href="help.html" target="SingleSileHelpPage">help</a><button id="resetButton">Reset to default options</button>
+	</div>	
+</div>
+</div>
+</body>
+</html>

BIN
WebContent/ui/resources/html5-badge-h-storage.png


+ 0 - 0
WebContent/resources/icon_128.png → WebContent/ui/resources/icon_128.png


+ 0 - 0
WebContent/resources/icon_16.png → WebContent/ui/resources/icon_16.png


+ 0 - 0
WebContent/resources/icon_48.png → WebContent/ui/resources/icon_48.png


BIN
WebContent/ui/resources/icon_48_passive.png


BIN
WebContent/ui/resources/icon_48_wait0.png


BIN
WebContent/ui/resources/icon_48_wait1.png


BIN
WebContent/ui/resources/icon_48_wait2.png


BIN
WebContent/ui/resources/icon_48_wait3.png


BIN
WebContent/ui/resources/icon_48_wait4.png


BIN
WebContent/ui/resources/icon_48_wait5.png


BIN
WebContent/ui/resources/icon_48_wait6.png


BIN
WebContent/ui/resources/icon_48_wait7.png


BIN
WebContent/ui/resources/icon_48_wait8.png


BIN
WebContent/ui/resources/icon_48_wait9.png


BIN
WebContent/ui/resources/options_screen.png


+ 142 - 0
WebContent/ui/scripts/bg/background.js

@@ -0,0 +1,142 @@
+/*
+ * Copyright 2011 Gildas Lormeau
+ * contact : gildas.lormeau <at> gmail.com
+ * 
+ * This file is part of SingleFile.
+ *
+ *   SingleFile is free software: you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as published by
+ *   the Free Software Foundation, either version 3 of the License, or
+ *   (at your option) any later version.
+ *
+ *   SingleFile is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public License
+ *   along with SingleFile.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+(function() {
+
+	var dev = true;
+
+	var extensionDetected = [];
+
+	function detectExtension(extensionId, callback) {
+		var img;
+		if (extensionDetected[extensionId])
+			callback(true);
+		else {
+			img = new Image();
+			img.src = "chrome-extension://" + extensionId + "/resources/icon_16.png";
+			img.onload = function() {
+				extensionDetected[extensionId] = true;
+				callback(true);
+			};
+			img.onerror = function() {
+				extensionDetected[extensionId] = false;
+				callback(false);
+			};
+		}
+	}
+
+	function processable(url) {
+		return !url.indexOf("https://chrome.google.com") == 0 && (url.indexOf("http://") == 0 || url.indexOf("https://") == 0);
+	}
+
+	function process(tabId, url, processSelection) {
+		var SINGLE_FILE_CORE_EXT_ID = dev ? "jnonclilicbambcohnkjlpicicmmkceh" : "jemlklgaibiijojffihnhieihhagocma";
+		detectExtension(SINGLE_FILE_CORE_EXT_ID, function(detected) {
+			if (detected) {
+				if (processable(url)) {
+					singlefile.ui.notifyProcessInit(tabId);
+					chrome.extension.sendRequest(SINGLE_FILE_CORE_EXT_ID, {
+						processSelection : processSelection,
+						id : tabId,
+						config : singlefile.config.get()
+					});
+				}
+			} else
+				chrome.tabs.create({
+					url : "pages/missingcore.html"
+				});
+		});
+	}
+
+	function notifyProcessable(tabId, url, reset) {
+		singlefile.ui.notifyProcessable(tabId, processable(url), reset);
+	}
+
+	function notifyScrapbook(request) {
+		var SCRAPBOOK_EXT_ID = dev ? "imfajgkkpglkdjkjejkefllgajgmhmfp" : "ihkkeoeinpbomhnpkmmkpggkaefincbn";
+		if (request.content)
+			detectExtension(SCRAPBOOK_EXT_ID, function(detected) {
+				if (detected)
+					chrome.extension.sendRequest(SCRAPBOOK_EXT_ID, request);
+			});
+	}
+
+	chrome.tabs.onSelectionChanged.addListener(function() {
+		chrome.tabs.getSelected(null, function(tab) {
+			notifyProcessable(tab.id, tab.url);
+		});
+	});
+	chrome.tabs.onCreated.addListener(function(tab) {
+		notifyProcessable(tab.id, tab.url);
+	});
+	chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
+		notifyProcessable(tab.id, tab.url, true);
+	});
+
+	chrome.tabs.onRemoved.addListener(function(tabId) {
+		singlefile.ui.notifyTabRemoved(tabId);
+	});
+	chrome.extension.onRequestExternal.addListener(function(request, sender, sendResponse) {
+		if (request.processStart) {
+			singlefile.ui.notifyProcessStart(request.tabId, request.processingPagesCount);
+			if (request.blockingProcess)
+				chrome.tabs.sendRequest(request.tabId, {
+					processStart : true
+				});
+			notifyScrapbook(request);
+		}
+		if (request.processProgress) {
+			singlefile.ui.notifyProcessProgress(request.index, request.maxIndex);
+			notifyScrapbook(request);
+		}
+		if (request.pageSaved)
+			singlefile.ui.notifySavedPage(request.processed, request.filename);
+		if (request.processEnd) {
+			if (request.blockingProcess)
+				chrome.tabs.sendRequest(request.tabId, {
+					processEnd : true
+				});
+			singlefile.ui.notifyProcessEnd(request.tabId, request.processingPagesCount);
+			notifyScrapbook(request);
+		}
+		if (request.processError)
+			singlefile.ui.notifyProcessError(request.tabId);
+	});
+	chrome.extension.onRequest.addListener(function(request, sender) {
+		process(sender.tab.id, sender.tab.url);
+	});
+	chrome.browserAction.onClicked.addListener(function(tab) {
+		process(tab.id, tab.url);
+	});
+	chrome.contextMenus.create({
+		title : "Process page with SingleFile",
+		onclick : function(info, tab) {
+			process(tab.id, tab.url);
+		}
+	});
+	chrome.contextMenus.create({
+		contexts : [ "selection" ],
+		title : "Process selection with SingleFile",
+		onclick : function(info, tab) {
+			process(tab.id, tab.url, true);
+		}
+	});
+
+})();

+ 52 - 0
WebContent/ui/scripts/bg/config.js

@@ -0,0 +1,52 @@
+/*
+ * Copyright 2011 Gildas Lormeau
+ * contact : gildas.lormeau <at> gmail.com
+ * 
+ * This file is part of SingleFile.
+ *
+ *   SingleFile is free software: you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as published by
+ *   the Free Software Foundation, either version 3 of the License, or
+ *   (at your option) any later version.
+ *
+ *   SingleFile is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public License
+ *   along with SingleFile.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+(function() {
+
+	singlefile.config = {};
+
+	singlefile.config.set = function(config) {
+		localStorage.config = JSON.stringify(config);
+	};
+
+	singlefile.config.get = function() {
+		return localStorage.config ? JSON.parse(localStorage.config) : {
+			removeFrames : false,
+			removeScripts : true,
+			removeObjects : true,
+			removeHidden : false,
+			removeUnusedCSSRules : false,
+			processInBackground : false,
+			displayProcessedPage : true,
+			savePage : false,
+			filenameMaxLength : 90,
+			getContent : false,
+			getRawDoc : false
+		};
+	};
+
+	singlefile.config.reset = function() {
+		delete localStorage.config;
+	};
+
+	// migration 0.1 -> 0.2
+	delete localStorage.options;
+
+})();

+ 84 - 0
WebContent/ui/scripts/bg/options.js

@@ -0,0 +1,84 @@
+/*
+ * Copyright 2011 Gildas Lormeau
+ * contact : gildas.lormeau <at> gmail.com
+ * 
+ * This file is part of SingleFile.
+ *
+ *   SingleFile is free software: you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as published by
+ *   the Free Software Foundation, either version 3 of the License, or
+ *   (at your option) any later version.
+ *
+ *   SingleFile is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public License
+ *   along with SingleFile.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+function load() {
+	var removeScriptsInput, removeFramesInput, removeObjectsInput, removeHiddenInput, removeUnusedCSSRulesInput, processInBackgroundInput, getRawDocInput, displayProcessedPageInput, savePageInput, getContentInput;
+	var bgPage = chrome.extension.getBackgroundPage(), config = bgPage.singlefile.config.get(), filenameMaxLengthInput, storageIsEnabled = typeof requestFileSystem != "undefined"
+			&& typeof ArrayBuffer != "undefined";
+
+	function refresh() {
+		savePageInput.disabled = !storageIsEnabled;
+		if (savePageInput.disabled)
+			savePageInput.checked = false;
+		displayProcessedPageInput.disabled = !savePageInput.checked || removeUnusedCSSRulesInput.checked;
+		if (displayProcessedPageInput.disabled) {
+			document.querySelector("label[for=displayProcessedPageInput]").style.opacity = ".5";
+			displayProcessedPageInput.checked = true;
+		} else
+			document.querySelector("label[for=displayProcessedPageInput]").style.opacity = "1";
+	}
+
+	removeFramesInput = document.getElementById("removeFramesInput");
+	removeScriptsInput = document.getElementById("removeScriptsInput");
+	removeObjectsInput = document.getElementById("removeObjectsInput");
+	removeHiddenInput = document.getElementById("removeHiddenInput");
+	removeUnusedCSSRulesInput = document.getElementById("removeUnusedCSSRulesInput");
+	processInBackgroundInput = document.getElementById("processInBackgroundInput");
+	getRawDocInput = document.getElementById("getRawDocInput");
+	displayProcessedPageInput = document.getElementById("displayProcessedPageInput");
+	savePageInput = document.getElementById("savePageInput");
+	filenameMaxLengthInput = document.getElementById("filenameMaxLengthInput");
+	getContentInput = document.getElementById("getContentInput");
+	document.getElementById("popupContent").onchange = function() {
+		refresh();
+		bgPage.singlefile.config.set({
+			removeFrames : removeFramesInput.checked,
+			removeScripts : removeScriptsInput.checked,
+			removeObjects : removeObjectsInput.checked,
+			removeHidden : removeHiddenInput.checked,
+			removeUnusedCSSRules : removeUnusedCSSRulesInput.checked,
+			processInBackground : processInBackgroundInput.checked,
+			getRawDoc : getRawDocInput.checked,
+			displayProcessedPage : displayProcessedPageInput.checked,
+			savePage : savePageInput.checked,
+			filenameMaxLength : parseInt(filenameMaxLengthInput.value, 10),
+			getContent : getContentInput.checked
+		});
+	};
+	removeFramesInput.checked = config.removeFrames;
+	removeScriptsInput.checked = config.removeScripts;
+	removeObjectsInput.checked = config.removeObjects;
+	removeHiddenInput.checked = config.removeHidden;
+	removeUnusedCSSRulesInput.checked = config.removeUnusedCSSRules;
+	processInBackgroundInput.checked = config.processInBackground;
+	getRawDocInput.checked = config.getRawDoc;
+	displayProcessedPageInput.checked = config.displayProcessedPage;
+	savePageInput.checked = config.savePage;
+	filenameMaxLengthInput.value = config.filenameMaxLength;
+	getContentInput.checked = config.getContent;
+	refresh();
+	document.getElementById("resetButton").addEventListener("click", function() {
+		bgPage.singlefile.config.reset();
+		load();
+	});
+	document.getElementById("storageOptions").style.display = storageIsEnabled ? "" : "none";
+}
+
+addEventListener("load", load);

+ 147 - 0
WebContent/ui/scripts/bg/ui.js

@@ -0,0 +1,147 @@
+/*
+ * Copyright 2011 Gildas Lormeau
+ * contact : gildas.lormeau <at> gmail.com
+ * 
+ * This file is part of SingleFile.
+ *
+ *   SingleFile is free software: you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as published by
+ *   the Free Software Foundation, either version 3 of the License, or
+ *   (at your option) any later version.
+ *
+ *   SingleFile is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public License
+ *   along with SingleFile.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+(function() {
+
+	singlefile.ui = {};
+
+	var DEFAULT_ICON_PATH = "../resources/icon_48.png";
+	var DEFAULT_PASSIVE_ICON_PATH = "../resources/icon_48_passive.png";
+	var DEFAULT_BADGE_CONFIG = {
+		text : "",
+		bgColor : [ 64, 64, 64, 255 ],
+		title : "Process this page with SingleFile",
+		iconPath : DEFAULT_ICON_PATH
+	};
+
+	var currentBarProgress = -1, currentProgress = -1, tabsData = {}, badgeConfig = JSON.parse(JSON.stringify(DEFAULT_BADGE_CONFIG));
+
+	function refreshBadge(tabId) {
+		function refreshTabBadge(tabId) {
+			chrome.browserAction.setBadgeText({
+				tabId : tabId,
+				text : tabsData[tabId] ? tabsData[tabId].text || badgeConfig.text : badgeConfig.text
+			});
+			chrome.browserAction.setBadgeBackgroundColor({
+				tabId : tabId,
+				color : tabsData[tabId] ? tabsData[tabId].bgColor || badgeConfig.bgColor : badgeConfig.bgColor
+			});
+			chrome.browserAction.setTitle({
+				tabId : tabId,
+				title : tabsData[tabId] ? tabsData[tabId].title || badgeConfig.title : badgeConfig.title
+			});
+			chrome.browserAction.setIcon({
+				tabId : tabId,
+				path : tabsData[tabId] ? tabsData[tabId].iconPath || badgeConfig.iconPath : badgeConfig.iconPath
+			});
+		}
+
+		if (tabId)
+			refreshTabBadge(tabId);
+		else
+			chrome.tabs.getAllInWindow(null, function(tabs) {
+				tabs.forEach(function(tab) {
+					refreshTabBadge(tab.id);
+				});
+			});
+	}
+
+	singlefile.ui.notifySavedPage = function(processed, filename) {
+		var notificationArchiving = webkitNotifications.createNotification(DEFAULT_ICON_PATH, "SingleFile", processed ? (filename + " is saved") : ("Error: "
+				+ filename + " cannot be saved"));
+		notificationArchiving.show();
+		if (processed)
+			setTimeout(function() {
+				notificationArchiving.cancel();
+			}, 3000);
+	};
+
+	singlefile.ui.notifyProcessInit = function(tabId) {
+		var tabData = {
+			id : tabId,
+			text : "...",
+			bgColor : [ 2, 147, 20, 255 ],
+			title : "Initialize process...",
+			iconPath : DEFAULT_PASSIVE_ICON_PATH
+		};
+		tabsData[tabId] = tabData;
+		refreshBadge(tabId);
+	};
+
+	singlefile.ui.notifyProcessStart = function(tabId, processingPagesCount) {
+		delete tabsData[tabId].text;
+		delete tabsData[tabId].title;
+		delete tabsData[tabId].iconPath;
+		tabsData[tabId].bgColor = [ 4, 229, 36, 255 ];
+		badgeConfig.text = "" + processingPagesCount;
+		refreshBadge();
+	};
+
+	singlefile.ui.notifyProcessError = function(tabId) {
+		tabsData[tabId].bgColor = [ 229, 4, 12, 255 ];
+		tabsData[tabId].text = "ERR";
+		refreshBadge(tabId);
+	};
+
+	singlefile.ui.notifyProcessEnd = function(tabId, processingPagesCount) {
+		tabsData[tabId].text = "OK";
+		badgeConfig.text = "" + (processingPagesCount || "");
+		if (!processingPagesCount) {
+			currentBarProgress = -1;
+			currentProgress = -1;
+			delete tabsData[tabId].title;
+			badgeConfig = JSON.parse(JSON.stringify(DEFAULT_BADGE_CONFIG));
+		}
+		refreshBadge();
+	};
+
+	singlefile.ui.notifyProcessProgress = function(index, maxIndex) {
+		var barProgress, progress;
+		if (maxIndex) {
+			progress = Math.min(100, Math.floor((index / maxIndex) * 100));
+			if (currentProgress != progress) {
+				currentProgress = progress;
+				badgeConfig.title = "progress: " + Math.min(100, Math.floor((index / maxIndex) * 100)) + "%";
+				barProgress = Math.floor((index / maxIndex) * 9);
+				if (currentBarProgress != barProgress) {
+					currentBarProgress = barProgress;
+					badgeConfig.iconPath = "../resources/icon_48_wait" + barProgress + ".png";
+				}
+				refreshBadge();
+			}
+		}
+	};
+
+	singlefile.ui.notifyTabRemoved = function(tabId) {
+		delete tabsData[tabId];
+	};
+
+	singlefile.ui.notifyProcessable = function(tabId, processable, reset) {
+		if (!processable) {
+			tabsData[tabId] = {
+				iconPath : DEFAULT_PASSIVE_ICON_PATH,
+				title : "SingleFile cannot process this page"
+			};
+			refreshBadge(tabId);
+		} else if (reset)
+			delete tabsData[tabId];
+	};
+
+})();

+ 64 - 0
WebContent/ui/scripts/content/content.js

@@ -0,0 +1,64 @@
+/*
+ * Copyright 2011 Gildas Lormeau
+ * contact : gildas.lormeau <at> gmail.com
+ * 
+ * This file is part of SingleFile.
+ *
+ *   SingleFile is free software: you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as published by
+ *   the Free Software Foundation, either version 3 of the License, or
+ *   (at your option) any later version.
+ *
+ *   SingleFile is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public License
+ *   along with SingleFile.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+(function() {
+
+	var MASK_ID = "__SingleFile_mask__", topWindow = window == top;
+
+	function processStart() {
+		var div = document.getElementById(MASK_ID);
+		if (!div) {
+			div = document.createElement("div");
+			div.id = "__SingleFile_mask__";
+			div.style.position = "fixed";
+			div.style.top = "0px";
+			div.style.left = "0px";
+			div.style.height = "100%";
+			div.style.width = "100%";
+			div.style.backgroundColor = "black";
+			div.style.zIndex = 2147483647;
+			div.style.opacity = 0;
+			div.style["-webkit-transition"] = "opacity 250ms";
+			document.body.appendChild(div);
+			div.offsetWidth;
+			div.style.opacity = .3;
+		}
+	}
+
+	function processEnd() {
+		var div = document.getElementById(MASK_ID);
+		if (div)
+			document.body.removeChild(div);
+	}
+
+	window.addEventListener("keyup", function(event) {
+		if (event.ctrlKey && event.shiftKey && event.keyCode == 83)
+			chrome.extension.sendRequest({});
+	}, true);
+
+	if (topWindow)
+		chrome.extension.onRequest.addListener(function(request, sender, sendResponse) {
+			if (request.processStart)
+				processStart();
+			if (request.processEnd)
+				processEnd();
+		});
+
+})();