diff options
-rw-r--r-- | bugzillaBugTriage.js | 322 | ||||
-rw-r--r-- | jquery-rpc-drupal-fix.js | 174 | ||||
-rw-r--r-- | jquery.rpc.js | 236 | ||||
-rw-r--r-- | offline-submit/collect-inputs.js | 45 | ||||
-rw-r--r-- | offline-submit/form-serialization.js | 129 | ||||
-rw-r--r-- | offline-submit/http-requests.txt | 28 | ||||
-rw-r--r-- | offline-submit/john-resig-offline-app-stub.js | 43 | ||||
-rw-r--r-- | offline-submit/jquery.malsup.com.htm | 583 | ||||
-rw-r--r-- | offline-submit/lazarus.js | 4418 | ||||
-rw-r--r-- | offline-submit/post-data.txt | 79 | ||||
-rw-r--r-- | serialized.txt | 2 | ||||
-rw-r--r-- | skip-process-bug.js | 1 |
12 files changed, 5561 insertions, 499 deletions
diff --git a/bugzillaBugTriage.js b/bugzillaBugTriage.js index 87eb78a..517df99 100644 --- a/bugzillaBugTriage.js +++ b/bugzillaBugTriage.js @@ -24,7 +24,7 @@ var TriagedDistro = 13; var NumberOfFrames = 7; var XMLRPCurl = "https://bugzilla.redhat.com/xmlrpc.cgi"; var bugURL = "https://bugzilla.redhat.com/show_bug.cgi?id="; -var myConfig = jetpack.storage.simple; +var myStorage = jetpack.storage.simple; var badMIMEArray = ["application/octet-stream","text/x-log","undefined"]; //============================================================== @@ -41,7 +41,7 @@ jetpack.future.import("storage.settings"); // jetpack.settings.open(); //} -var jsonDataURL = myConfig.JSONURL ? myConfig.JSONURL : +var jsonDataURL = myStorage.JSONURL ? myStorage.JSONURL : "http://mcepl.fedorapeople.org/scripts/BugZappers_data.json"; var PCIIDsURL = "http://mcepl.fedorapeople.org/scripts/drm_pciids.json"; var abrtQueryURL = "https://bugzilla.redhat.com/buglist.cgi?"+ @@ -85,6 +85,7 @@ var suspiciousComponents = []; var signatureFedoraString = ""; // TODO we should have an array SpecialFlags instead of multiple Boolean variables var queryButtonAvailable = false; +var upstreamButtonAvailable = false; var chipIDsGroupings = []; var AddrArray = []; var PCI_ID_Array = []; @@ -146,6 +147,25 @@ loadJSON(PCIIDsURL, PCI_ID_Array = response; }); +//======== load external library =============================== + +var XMLRPCMessage = {}; +var req = new XMLHttpRequest(); +req.open("GET","http://mcepl.fedorapeople.org/scripts/xmlrpc.js",true); +req.onreadystatechange = function (aEvt) { + if (req.readyState == 4) { + if (req.status == 200) { + var thisDoc = jetpack.tabs.focused.contentDocument; + var script = thisDoc.createElement("script"); + script.setAttribute("type","text/javascript"); + script.innerHTML = req.responseText; + thisDoc.getElementsByTagName("head")[0].appendChild(script); + XMLRPCMessage = jetpack.tabs.focused.contentWindow.wrappedJSObject.XMLRPCMessage; + } + } +}; +req.send(""); + //============================================================== /** @@ -459,6 +479,152 @@ BzPage.prototype.getOptionValue = function (id) { return this.dok.getElementById(id).value; }; +/* Offline supporting functions */ +/** + * + * @todo FIXME this probably makes a closure and a memory leak + * name='changeform' + * investigate https://developer.mozilla.org/en/How_to_Turn_Off_Form_Autocompletion + * + * <form method="post" action="process_bug.cgi" autocomplete="off"> + + Reading http://www.w3.org/TR/1999/REC-html401-19991224/interact/forms.html#h-17.13 + random notes: + - 17.13.3 provides all steps necessary + - enctype != application/x-www-form-urlencoded => SHOULD fails (no further questions needed) + - http://www.w3.org/MarkUp/html-spec/html-spec_8.html#SEC8.2.1. is nice explanation + (albeit quite dated) + - on multiple values http://www.w3.org/TR/1999/REC-html401-19991224/interact/forms.html#h-17.6.1 + - příliš jednoduché http://www.innovation.ch/java/HTTPClient/emulating_forms.html + - + */ +BzPage.prototype.serializeForm = function (form) { + var serialForm = { + dataOut: "", + name: form.name, + method: form.method, + acceptCharset: form.acceptCharset, + action: form.action, // TODO shouldn't we get a non-relative URL? + enctype: form.enctype, + cookie: this.dok.cookie, + autocomplete: form.getAttribute("autocomplete"), + bugNo: this.bugNo + }; + + function genURIElement(sName,sValue) { + return encodeURIComponent(sName)+"="+ + encodeURIComponent(sValue); + } + + /** + * @param o control to be serialized + * @return String with the serialized control + */ + function serializeControl(element) { + var val = element.value; + console.log("val.toSource() = " + val.toSource()); + /* + on HTMLSelectElement we have an attribute 'type' of type DOMString, readonly + The type of this form control. This is the string "select-multiple" + when the multiple attribute is true and the string "select-one" when + false. + */ + if ((val == null) || (val == undefined)|| (val == "")) { + return ; + } else if (val instanceof Array) { + return val.map(function (x) { + return genURIElement(element.name,x.value); + }).join("&"); + } else if (val instanceof String) { + return genURIElement(element.name,val); + } else { // assume HTMLCollection + return Array.map(val,function (x) { + return genURIElement(element.name,x.value); + }).join("&"); + } + } + + serialForm.dataOut = Array.filter(form.elements,function (el) { + return !el.disabled && el.name && + // FIXME shouldn't I just add && el.value here? + (el.checked || /select|textarea/i.test(el.nodeName) || + /text|hidden|password|search/i.test(el.type)); + }).map(serializeControl).join("&"); + return serialForm; +}; + +BzPage.prototype.submitCallback = function(evt) { + console.log("Submit Callback!"); + if (jetpack.__parent__.navigator.onLine) { + var serForm = this.serializeForm(jetpack.tabs.focused. + contentWindow.document.forms.namedItem("changeform")); + console.log("serForm:\n"+serForm.toSource()); + } else { + var serForm = this.serializeForm(jetpack.tabs.focused. + contentWindow.document.forms.namedItem("changeform")); + myStorage.forms[this.bugNo] = serForm; + evt.stopPropagation(); + evt.preventDefault(); + } +}; + +/** + * + * + * Yes, this is correct, this is NOT method of bzPage! + */ +function onlineCallback() { + function deserializeAndSend(formData) { + // FIXME notImplemented + // is it enough to just + // run XMLHttpRequest? Probably yes, this is just a form + // and this is just a HTTP request + // it is probably better to get already processed + // application/x-www-form-urlencoded + // see http://htmlhelp.com/reference/html40/forms/form.html for details + // and also https://developer.mozilla.org/en/AJAX/Getting_Started + // what's? + // https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference\ + // /Global_Functions/encodeURI & co. + // this seems to be also interesting + // https://developer.mozilla.org/en/Code_snippets/Post_data_to_window + console.error("Sending bugs not implemented yet!"); + return ""; // FIXME check other HTTP headers to be set + + var bugID = formData.bugNo; + var req = new XMLHttpRequest(); + req.open("POST",formData.action,true); + // FIXME co očekávám za odpověď? req.overrideMimeType("text/xml"); + // * Accept-Encoding + // * Accept-Language + // * Accept (MIME types) + req.setRequestHeader("Connection","keep-alive"); + req.setRequestHeader("Keep-Alive",300); + req.setRequestHeader("Content-Type",formData.enctype); + req.setRequestHeader("Referer",bugURL+bugID); + req.setRequestHeader("Accept-Charset",formData.acceptCharset); + req.setRequestHeader("Cookie",formData.cookie) + req.onreadystatechange = function (aEvt) { + if (req.readyState == 4) { + if (req.status == 200) { + console.log("Sent form for bug " + bugID); + delete myStorage.forms[bugID]; + } else { + console.error("Sending form for bug " + bugID + + "failed!"); + } + } + }; + req.send(formData.data); + } + + if (myStorage.forms.length > 0) { + myStorage.forms.forEach(function (x) { + deserializeAndSend(x); + }); + } +} + /* Bugzilla functions.*/ /** @@ -776,7 +942,7 @@ BzPage.prototype.addNewButton = function (originalLocation,newId,newLabel, newButton.value = newLabel; newButton.addEventListener("click",function (evt) { that.generalPurposeCureForAllDisease(commStr,nState, secPar); - },false); + }, false); if (after) { originalLocation.parentNode.insertBefore(newButton, @@ -834,7 +1000,7 @@ BzPage.prototype.fillInChipMagic = function () { interestingArray = ChipsetRE.exec(interestingLineArr[0]); interestingLine = interestingArray[2]. replace(/[\s"]+/g," ").trim(); - var whiteboardInput = this.dok. + var whiteboardInput = that.dok. getElementById("status_whiteboard"); that.addNewButton(whiteboardInput,"chipmagic","Fill In", "","CHIPMAGIC", @@ -912,13 +1078,24 @@ BzPage.prototype.sendBugUpstream = function() { var url = filterByRegexp(upstreamBugzillasArray, this.getOptionValue("component")); - var text = this.dok.getElementById("short_desc_nonedit_display"). - textContent.trim() + "\n\n"; - text += this.collectComments(); - jetpack.clipboard.set(text); var ret = jetpack.tabs.open(url); - console.log("opening new tab returned"); - console.log(ret); + var that = this; + console.log("Setting up load listener"); + jetpack.tabs.onReady(function() { + var otherDoc = ret.contentDocument; + console.log("otherDoc"); + console.log(otherDoc); + var otherElems = otherDoc.forms.namedItem("Create").elements; + otherElems.namedItem("short_desc").value = + that.dok.getElementById("short_desc_nonedit_display"). + textContent.trim(); + otherElems.namedItem("comment").value = that.collectComments(); + ret.focused(); + + console.log("opening new tab returned"); + console.log(ret); + + }); }; /** @@ -943,9 +1120,6 @@ BzPage.prototype.parseAttachmentLine = function (inElem) { // getting name of the attachment var attName = inElem.getElementsByTagName("b")[0].textContent.trim(); - // getting id - // $("a:contains('Details')", inElem); - // FIXME isn't there more simple way to replace above? querySelector? var aHrefsArr = inElem.getElementsByTagName("a"); var aHref = Array.filter(aHrefsArr,function (x) { return x.textContent.trim() == "Details"; @@ -1091,21 +1265,18 @@ BzPage.prototype.getWholeURL = function(selectValue,bugID) { */ BzPage.prototype.callBack = function(data,textStatus) { if (--this.reqCounter <= 0) { - setTimeout(document.location.reload,1000); + setTimeout(this.dok.location.reload,1000); } }; /** - * Create XML-RPC message for updateAttachMimeType procedure with given parameters. - * Yes, I know this is very ugly, but in the current state of jetpack it is not possible - * to import external jQuery modules, so I cannot use jquery.rpc as much as I would like to. + * The worker function -- call XMLRPC to fix MIME type of the + * particular attachment * - * @param login string with login - * @param password string with password - * @param attachID Number with the attachment ID# - * @param mimeType stri ng with MIME type, optional and defaults to text/plain - * @param email Boolean whether we should send email or not - * @return string with the XML-RPC message + * @param id integer with the attachment id to be fixed + * @param type string with the new MIME type, optional defaults to "text/plain" + * @param email Boolean whether email should be sent to appropriate person; option, + defaults to false updateAttachMimeType($data_ref, $username, $password) @@ -1119,56 +1290,24 @@ Update the attachment mime type of an attachment. The first argument is a data h "nomail" => 0, # OPTIONAL Flag that is either 1 or 0 if you want email to be sent or not for this change }; + */ -BzPage.prototype.createXMLRPCMessage = function(login,password,attachId,mimeType,email) { - if (mimeType === undefined) { - mimeType = "text/plain"; +BzPage.prototype.fixAttachById = function(id,type,email) { + if (type === undefined) { + type = "text/plain"; } if (email === undefined) { email = false; } - var emailStr = email ? "0" : "1"; - - var msg = <methodCall> - <methodName>bugzilla.updateAttachMimeType</methodName> - <params> - <param> - <value><struct> - <member> - <name>attach_id</name> - <value><i4>{attachId}</i4></value> - </member> - <member> - <name>mime_type</name> - <value><string>{mimeType}</string></value> - </member> - <member> - <name>nomail</name> - <value><boolean>{emailStr}</boolean></value> - </member> - </struct></value> - </param> - <param> - <value><string>{login}</string></value> - </param> - <param> - <value><string>{password}</string></value> - </param> - </params> - </methodCall>; - return msg.toXMLString(); -}; -/** - * The worker function -- call XMLRPC to fix MIME type of the - * particular attachment - * - * @param id integer with the attachment id to be fixed - * @param type string with the new MIME type, e.g. "text/plain" - */ -BzPage.prototype.fixAttachById = function(id,type) { - var msg = this.createXMLRPCMessage(this.login,this.password,id,type); + var msg = new XMLRPCMessage("bugzilla.updateAttachMimeType"); + msg.addParameter({'attach_id':id, 'mime_type':type, 'nomail':!email}); + msg.addParameter(this.login); + msg.addParameter(this.password); + console.log("testing XMLRPCMessage:\n" + msg.xml()); + var req = new XMLHttpRequest(); + var that = this; req.open("POST",XMLRPCurl,true); req.overrideMimeType("text/xml"); req.setRequestHeader("Content-type","text/xml"); @@ -1182,17 +1321,16 @@ BzPage.prototype.fixAttachById = function(id,type) { } } }; - req.send(msg); + req.send(msg.xml()); this.reqCounter++; }; +// FIXME possibly eliminate this function altogether and +// make it inline? BzPage.prototype.fixAllAttachments = function(list) { - var tmpElem = {}; - - for(var i=0;i<list.length;i++) { - tmpElem = list[i]; - this.fixAttachById(tmpElem[1]); - } + Array.forEach(list,function (x) { + this.fixAttachById(x[1]); + }, this); }; /** @@ -1209,7 +1347,7 @@ BzPage.prototype.createFixAllButton = function (list) { elem.innerHTML = "<b>F</b>ix all"; elem.addEventListener("click", function() { that.fixAllAttachments(list); - },false); + }, false); return elem; }; @@ -1469,12 +1607,12 @@ BzPage.prototype.buildButtons = function (above,below) { if (queryButtonAvailable) { // Add query search button this.addNewButton(brElementPlacer,"newqueryintab", - "Query for string","","QUERYSEL","",false); + "Query for string","","QUERYSEL","", false); } // Button for upstreaming if (upstreamButtonAvailable) { this.addNewButton(brElementPlacer,"sendupstream", - "Send upstream","","SENDUPSTREAM","",false); + "Send upstream","","SENDUPSTREAM","", false); } // var brElement2BMoved = this.dok.querySelector("#comment_status_commit br:last-of-type"); @@ -1508,18 +1646,16 @@ function BzPage(doc) { var that = this; this.originalButton = this.dok.getElementById("commit"); - console.log(this.dok.querySelector("#header ul.links li:last-of-type")); var loginArr = this.dok. querySelector("#header ul.links li:last-of-type"). textContent.split("\n"); this.login = loginArr[loginArr.length-1].trim(); - this.password = ""; // the following is quite awful brutal hack console.log("on __parent__ navigator.online = " + jetpack.__parent__.navigator.onLine); - if (myConfig.BZpassword) { - this.password = myConfig.BZpassword; + if (myStorage.BZpassword) { + this.password = myStorage.BZpassword; } else { var prompts = Components.classes["@mozilla.org/embedcomp/prompt-service;1"] .getService(Components.interfaces.nsIPromptService); @@ -1531,7 +1667,7 @@ function BzPage(doc) { // set if OK was pressed. The checkbox is not displayed. if (result) { this.password = password.value; - myConfig.BZpassword = this.password; + myStorage.BZpassword = this.password; jetpack.storage.simple.sync(); } } @@ -1583,7 +1719,8 @@ function BzPage(doc) { // Dig out backtrace this.btSnippet = ""; - var bugTitle = this.dok.getElementById("short_desc_nonedit_display").textContent; + var bugTitle = this.dok.getElementById("short_desc_nonedit_display"). + textContent; if (AbrtRE.test(bugTitle)) { var notedLabel = this.dok.querySelector("label[for='newcc']"); @@ -1601,7 +1738,8 @@ function BzPage(doc) { abrtButton.textContent = "Abrt bugs"; mainTitle.appendChild(abrtButton); var develWhiteboardElem = this.dok.getElementById("cf_devel_whiteboard"); - var develWhiteboard = develWhiteboardElem ? develWhiteboardElem.value : "btparsed"; + var develWhiteboard = develWhiteboardElem ? develWhiteboardElem. + value : "btparsed"; if (!(this.hasKeyword("Triaged") || (/btparsed/.test(develWhiteboard)))) { var btAttachments = this.attachments.filter(function (att,idx,arr) { @@ -1632,7 +1770,7 @@ function BzPage(doc) { function () { that.addTextToTextBox("comment", signatureFedoraString); } - ,false); + , false); } this.setBranding(); @@ -1643,7 +1781,7 @@ function BzPage(doc) { var additionalButtons = this.dok.getElementById("bugzilla-body"). getElementsByClassName("related_actions")[0]; var customJSONURLUI = this.dok.createElement("li"); - customJSONURLUI.innerHTML = " - <a href='#' id='customJSONbutton'>" + + customJSONURLUI.innerHTML = "\u00A0-\u00A0<a href='#' id='customJSONbutton'>" + "BugZap config</a>"; additionalButtons.appendChild(customJSONURLUI); this.dok.getElementById("customJSONbutton"). @@ -1651,11 +1789,11 @@ function BzPage(doc) { var newURL = jetpack.tabs.focused.contentWindow. prompt("URL for your JSON customization file"); if (newURL) { - myConfig.JSONURL = newURL; + myStorage.JSONURL = newURL; jetpack.storage.simple.sync(); jetpack.tabs.focused.contentWindow.location.reload(); } - },false); + }, false); // set default assignee on change of the component this.dok.getElementById("component").addEventListener("change", @@ -1663,7 +1801,13 @@ function BzPage(doc) { that.component = that.getOptionValue("component"); that.changeOwner(filterByRegexp(defAssigneeList, that.component). toLowerCase()); - },false); + }, false); + + // offline-capable submit + this.dok.forms.namedItem("changeform"). + addEventListener('submit', function (evt) { + that.submitCallback.call(that, evt)}, + false); } var callback = function (doc) { @@ -1673,5 +1817,5 @@ var callback = function (doc) { var options = {}; options.matches = [ "https://bugzilla.redhat.com/show_bug.cgi" - ]; +]; jetpack.pageMods.add(callback, options); diff --git a/jquery-rpc-drupal-fix.js b/jquery-rpc-drupal-fix.js deleted file mode 100644 index 1d29d95..0000000 --- a/jquery-rpc-drupal-fix.js +++ /dev/null @@ -1,174 +0,0 @@ -window.jQuery = window.jQuery || {}; -window.jQuery.fn = window.jQuery.fn || {}; - -jQuery.fn.rpc = function(url, dataType, onLoadCallback, version) { - version = version || "1.0"; - dataType = dataType || "json"; - if(dataType != "json" && dataType != "xml") { - new Error("IllegalArgument: Unsupported data type"); - } - var _self = this; - var serializeToXml = function(data) { - switch (typeof data) { - case 'boolean': - return '<boolean>'+ ((data) ? '1' : '0') +'</boolean>'; - case 'number': - var parsed = parseInt(data); - if(parsed == data) { - return '<int>'+ data +'</int>'; - } - return '<double>'+ data +'</double>'; - case 'string': - return '<string>'+ data +'</string>'; - case 'object': - if(data instanceof Date) { - return '<dateTime.iso8601>'+ data.getFullYear() + data.getMonth() + data.getDate() +'T'+ data.getHours() +':'+ data.getMinutes() +':'+ data.getSeconds() +'</dateTime.iso8601>'; - } else if(data instanceof Array) { - var ret = '<array><data>'+"\n"; - for (var i=0; i < data.length; i++) { - ret += ' <value>'+ serializeToXml(data[i]) +"</value>\n"; - } - ret += '</data></array>'; - return ret; - } else { - var ret = '<struct>'+"\n"; - jQuery.each(data, function(key, value) { - ret += " <member><name>"+ key +"</name><value>"; - ret += serializeToXml(value) +"</value></member>\n"; - }); - ret += '</struct>'; - return ret; - } - } - } - var xmlRpc = function(method, params) { - var ret = '<?xml version="'+version+'"?><methodCall><methodName>'+method+'</methodName><params>'; - for(var i=0; i<params.length; i++) { - ret += "<param><value>"+serializeToXml(params[i])+"</value></param>"; - } - ret += "</params></methodCall>"; - return ret; - } - var parseXmlValue = function(node) { - childs = jQuery(node).children(); - for(var i=0; i < childs.length; i++) { - switch(childs[i].tagName) { - case 'boolean': - return (jQuery(childs[i]).text() == 1); - case 'int': - return parseInt(jQuery(childs[i]).text()); - case 'double': - return parseFloat(jQuery(childs[i]).text()); - case "string": - return jQuery(childs[i]).text(); - case "array": - var ret = []; - jQuery("> data > value", childs[i]).each( - function() { - ret.push(parseXmlValue(this)); - } - ); - return ret; - case "struct": - var ret = {}; - jQuery("> member", childs[i]).each( - function() { - ret[jQuery( "> name", this).text()] = parseXmlValue(jQuery("value", this)); - } - ); - return ret; - case "dateTime.iso8601": - /* TODO: fill me :( */ - return NULL; - } - } - } - var parseXmlResponse = function(data) { - var ret = {}; - ret.version = version; - jQuery("methodResponse params param > value", data).each( - function(index) { - ret.result = parseXmlValue(this); - } - ); - jQuery("methodResponse fault > value", data).each( - function(index) { - ret.error = parseXmlValue(this); - } - ); - return ret; - } - var rpc_contents = { - 'xml':'text/xml' - ,'json':'application/json' - }; - var _rpc = function(method, callback) { - var params = []; - for (var i=2; i<arguments.length; i++) { - params.push(arguments[i]); - } - console.log(params); - var data; - if(dataType == 'json') { - data = {"version":version, "method":method, "params":params}; - } else { - data = xmlRpc(method, params); - } - console.log(data); - jQuery.ajax({ - "url": url, - "dataType": dataType, - "type": 'POST', - "data": data, - "success": function(inp) { - var json = inp; - if(dataType == "xml") { - json = parseXmlResponse(inp); - } - console.log("json response:", json); - callback(json); - }, - "processData": false, - "contentType": rpc_contents[dataType] - }); - }; - _rpc("system.listMethods", - function(json) { - console.log(json); - /* get the functions */ - if(!json.result) { - return; - } - var proc = null; - for(var i = 0; i<json.result.length; i++) { - proc = json.result[i]; - var obj = _self; - var objStack = proc.split(/\./); - for(var j = 0; j < (objStack.length - 1); j++){ - obj[objStack[j]] = obj[objStack[j]] || {}; - obj = obj[objStack[j]]; - } - /* add the new procedure */ - obj[objStack[j]] = ( - function(method, obj) { - var _outer = {"method":method,"rpc":_rpc}; - return function(callback) { - var params = []; - params.push(_outer.method); - params.push(callback); - for (var i=1; i<arguments.length; i++) { - params.push(arguments[i]); - } - _rpc.apply(_self, params); - } - } - )(proc, _rpc); - } - console.log('Load was performed.'); - if(onLoadCallback) { - onLoadCallback(_self); - } - } - ); -}; - diff --git a/jquery.rpc.js b/jquery.rpc.js deleted file mode 100644 index 892eb48..0000000 --- a/jquery.rpc.js +++ /dev/null @@ -1,236 +0,0 @@ -window.jQuery = window.jQuery || {}; - -jQuery.rpc = function(url, dataType, onLoadCallback, version, methods) { - return new (function(url, dataType, onLoadCallback, version) { - version = version || "1.0"; - dataType = dataType || "json"; - if(dataType != "json" && dataType != "xml") { - new Error("IllegalArgument: Unsupported data type"); - } - - var _self = this; - - function pad2(f) { - if(f<=9) { - return "0"+f; - } else { - return ""+f; - } - } - - var serializeToXml = function(data) { - switch (typeof data) { - case 'boolean': - return '<boolean>'+ ((data) ? '1' : '0') +'</boolean>'; - case 'number': - var parsed = parseInt(data); - if(parsed == data) { - return '<int>'+ data +'</int>'; - } - return '<double>'+ data +'</double>'; - case 'string': - return '<string>'+ data +'</string>'; - case 'object': - if(data instanceof Date) { - return '<dateTime.iso8601>'+ data.getFullYear() + pad2(data.getMonth()) + pad2(data.getDate()) +'T'+ pad2(data.getHours()) +':'+ pad2(data.getMinutes()) +':'+ pad2(data.getSeconds()) +'</dateTime.iso8601>'; - } else if(data instanceof Array) { - var ret = '<array><data>'+"\n"; - for (var i=0; i < data.length; i++) { - ret += ' <value>'+ serializeToXml(data[i]) +"</value>\n"; - } - ret += '</data></array>'; - return ret; - } else { - var ret = '<struct>'+"\n"; - jQuery.each(data, function(key, value) { - ret += " <member><name>"+ key +"</name><value>"; - ret += serializeToXml(value) +"</value></member>\n"; - }); - ret += '</struct>'; - return ret; - } - } - } - var xmlRpc = function(method, params) { - var ret = '<?xml version="'+version+'"?><methodCall><methodName>'+method+'</methodName><params>'; - for(var i=0; i<params.length; i++) { - ret += "<param><value>"+serializeToXml(params[i])+"</value></param>"; - } - ret += "</params></methodCall>"; - return ret; - } - var parseXmlValue = function(node) { - var jnode = jQuery(node); - childs = jnode.children(); - - // String not enclosed in a <string> tag - if(childs.length==0) { - var s=""; - for(var j=0; j<jnode[0].childNodes.length;j++) { - s+=new String(jnode[0].childNodes[j].nodeValue); - } - return s; - } - - for(var i=0; i < childs.length; i++) { - switch(childs[i].tagName) { - case 'boolean': - return (jQuery(childs[i]).text() == 1); - case 'int': - case 'i4': - return parseInt(jQuery(childs[i]).text()); - case 'double': - return parseFloat(jQuery(childs[i]).text()); - case "string": - return jQuery(childs[i]).text(); - case "array": - var ret = []; - jQuery("> data > value", childs[i]).each( - function() { - ret.push(parseXmlValue(this)); - } - ); - return ret; - case "struct": - var ret = {}; - jQuery("> member", childs[i]).each( - function() { - ret[jQuery( "> name", this).text()] = parseXmlValue(jQuery("value", this)); - } - ); - return ret; - case "dateTime.iso8601": - /* TODO: fill me :( */ - return 0; - } - } - } - var parseXmlResponse = function(data) { - var ret = {}; - ret.version = version; - jQuery("methodResponse params param > value", data).each( - function(index) { - ret.result = parseXmlValue(this); - } - ); - jQuery("methodResponse fault > value", data).each( - function(index) { - ret.error = parseXmlValue(this); - } - ); - return ret; - } - var rpc_contents = { - 'xml':'text/xml' - ,'json':'application/json' - }; - var _rpc = function(method) { - var params = []; - var async; // whether we're async or not - var callback; - var nargs=arguments.length; - - if(nargs > 1 && typeof(arguments[nargs-1])=="function") { - callback = arguments[nargs-1]; - nargs=nargs-1; - async=true; - } else { - async=false; - } - - for (var i=1; i<nargs; i++) { - params.push(arguments[i]); - } - - var data; - if(dataType == 'json') { - data = {"version":version, "method":method, "params":params}; - } else { - data = xmlRpc(method, params); - } - - if(async) { - jQuery.ajax({ - url: url, - dataType: dataType, - type: "POST", - data: data, - success: function(inp) { - var json = inp; - if(dataType == "xml") { - json = parseXmlResponse(inp); - } - // console.log("json response:", json); - callback(json); - }, - processData: false, - contentType: rpc_contents[dataType]}); - } else { - var result = jQuery.ajax({ - url: url, - dataType: dataType, - type: "POST", - data: data, - async: false, - contentType: rpc_contents[dataType]}); - if(dataType=="json") { - return eval("("+result.responseText+")"); - } else { - var myresult=parseXmlResponse(result.responseXML); - return myresult; - } - } - - }; - function populate(methods) { - // console.log(json); - /* get the functions */ - var proc = null; - for(var i = 0; i<methods.length; i++) { - proc = methods[i]; - var obj = _self; - var objStack = proc.split(/\./); - for(var j = 0; j < (objStack.length - 1); j++){ - obj[objStack[j]] = obj[objStack[j]] || {}; - obj = obj[objStack[j]]; - } - /* add the new procedure */ - obj[objStack[j]] = ( - function(method, obj) { - var _outer = {"method":method,"rpc":_rpc}; - return function() { - var params = []; - params.push(_outer.method); - for (var i=0; i<arguments.length; i++) { - params.push(arguments[i]); - } - return _rpc.apply(_self, params); - } - } - )(proc, _rpc); - } - // console.log('Load was performed.'); - } - - function asyncCallback(json) { - populate(json.result); - if(onLoadCallback) { - onLoadCallback(_self); - } - } - - if(!methods) { - _rpc("system.listMethods", asyncCallback); - } else { - populate(methods); - if(onLoadCallback) { - // not sure this works - the intention is to call the callback as soon as this - // javascript function returns control back to the browser, to emulate the behaviour - // that the system.listMethods approach - setTimeout(function(){onLoadCallback(_self);},0); - } - } - - })(url, dataType, onLoadCallback, version); -}; - diff --git a/offline-submit/collect-inputs.js b/offline-submit/collect-inputs.js new file mode 100644 index 0000000..d6a2b47 --- /dev/null +++ b/offline-submit/collect-inputs.js @@ -0,0 +1,45 @@ +bzPage.prototype.collectInputs = function (form) { + +// form.elements array of all controls ... no, forEach though +// INPUT, SELECT, TEXTAREA, and BUTTON +// each interesting control of them have NAME and VALUE +// You can access a particular element by using either an index or the element name or id. +// +// https://developer.mozilla.org/en/DOM/form +// form.elements.querySelectorAll("*[name]") + + var x=this.dok.getElementById("myForm"); +for (var i=0;i<x.length;i++) +{ +document.write(x.elements[i].value + ", " + x.elements[i].tagName + ", " + x.elements[i].name); +document.write("<br />"); +} + +} + +// INTERESTING: http://www.quirksmode.org/js/forms.html + +// Example onsubmit handler + +function checkscript() { + if (some value is/is not something) { + // something is wrong + alert('alert user of problem'); + return false; + } + else if (another value is/is not something) { + // something else is wrong + alert('alert user of problem'); + return false; + } + + // If the script makes it to here, everything is OK, + // so you can submit the form + + return true; +} + +// with XPCNativeWrappers you cannot use document.formname.inputname.value +var form = document.forms.namedItem("gs"); +var input = form.elements.namedItem("q"); +var q = input.value; diff --git a/offline-submit/form-serialization.js b/offline-submit/form-serialization.js new file mode 100644 index 0000000..d7c4d07 --- /dev/null +++ b/offline-submit/form-serialization.js @@ -0,0 +1,129 @@ +/* ------------------------------------------------------------------ */ + +/** + * @param f : Form|HTMLFormElement + * @return string + */ +function serializeForm(f) +{ + var gotSubmit = false; + + /** + * @param o + */ + function serializeControl(o) + { + /* HTML 4.01: Controls that are disabled cannot be successful. */ + if (!o.disabled) + { + /* + * If a form contains more than one submit button, + * only the activated submit button is successful. + * (here: the first one) + */ + var isSubmit = /(^|\s)(submit|image)(\s|$)/i.test(o.type); + if (!gotSubmit || !isSubmit) + { + if (isSubmit) gotSubmit = true; + + /* + * For menus, the control name is provided by a SELECT element + * and values are provided by OPTION elements. Only selected + * options may be successful. When no options are selected, + * the control is not successful and neither the name nor any + * values are submitted to the server when the form is submitted. + */ + var m = /(^|\s)(select(-one)?|undefined)(\s|$)/i.exec(o.type); + if (m) + { + /* select-one */ + if (m[3]) + { + if (o.selectedIndex > -1) + { + items.add(o.name, o.options[o.selectedIndex].value); + } + } + + /* select */ + else if (m[2]) + { + for (var i = 0, opts = o.options, len = opts && opts.length; + i < len; i++) + { + var opt = opts[i]; + if (opt.selected) + { + items.add(o.name, opt.value); + } + } + } + } + + /* + * All "on" checkboxes may be successful. + * For radio buttons that share the same value of the + * name attribute, only the "on" radio button may be successful. + */ + else if (!/(^|\s)file|reset(\s|$)/i.test(o.type) + && !(/(^|\s)object(\s|$)/i.test(o.tagName) && o.declare) + && !/(^|\s)(checkbox|radio)(\s|$)/i.test(o.type) + || o.checked) + { + items.add(o.name, o.value); + } + } + } + } + + var es = getFeature(f, "elements"); + if (es) + { + var items = []; + + items.add = function(sName, sValue) { + var s = esc(sName) + "=" + esc(sValue); + this.push(s); + }; + + if (!isMethod(items, "push")) + { + items.push = function() { + for (var i = 0, len = arguments.length; i < len; i++) + { + items[items.length] = arguments[i]; + } + }; + } + + for (var i = 0, len = es.length; i < len; i++) + { + var e = es[i]; + + /* + * Elements with the same name create a NodeList object, + * however options of select objects are also indexable in Gecko. + */ + if (typeof e[0] != "undefined" && typeof e.options == "undefined") + { + for (var j = 0, len2 = e.length; j < len2; j++) + { + serializeControl(e[j]); + } + } + else + { + serializeControl(e); + } + } + + return items.join("&"); + } + + return ""; +} + +/* ------------------------------------------------------------------ */ + +// esc(), getFeature() and isMethod() are user-defined, of course, but you +// probably get the idea.. diff --git a/offline-submit/http-requests.txt b/offline-submit/http-requests.txt new file mode 100644 index 0000000..7800182 --- /dev/null +++ b/offline-submit/http-requests.txt @@ -0,0 +1,28 @@ +00:03:23.973 4.726 2246 4222 POST 200 text/html https://bugzilla.redhat.com/process_bug.cgi +00:03:28.208 0.149 577 (2635) GET (Cache) text/css https://bugzilla.redhat.com/skins/standard/global.css +00:03:28.215 0.169 583 (1100) GET (Cache) text/css https://bugzilla.redhat.com/skins/standard/yui/calendar.css +00:03:28.221 0.173 579 (442) GET (Cache) text/css https://bugzilla.redhat.com/skins/standard/show_bug.css +00:03:28.237 0.192 583 (1728) GET (Cache) text/css https://bugzilla.redhat.com/skins/contrib/RedHat/global.css +00:03:28.243 0.197 589 (103) GET (Cache) text/css https://bugzilla.redhat.com/skins/contrib/RedHat/yui/calendar.css +00:03:28.250 0.209 585 (100) GET (Cache) text/css https://bugzilla.redhat.com/skins/contrib/RedHat/show_bug.css +00:03:28.257 0.227 575 (100) GET (Cache) text/css https://bugzilla.redhat.com/skins/custom/global.css +00:03:28.264 0.236 581 (103) GET (Cache) text/css https://bugzilla.redhat.com/skins/custom/yui/calendar.css +00:03:28.273 0.234 577 (100) GET (Cache) text/css https://bugzilla.redhat.com/skins/custom/show_bug.css +00:03:28.280 0.293 550 (493) GET (Cache) application/x-javascript https://bugzilla.redhat.com/js/cookies.js +00:03:28.508 0.220 581 (1568) GET (Cache) text/css https://bugzilla.redhat.com/skins/contrib/Dusk/global.css +00:03:28.513 1.086 587 477 GET 404 text/html https://bugzilla.redhat.com/skins/contrib/Dusk/yui/calendar.css +00:03:28.520 1.091 583 473 GET 404 text/html https://bugzilla.redhat.com/skins/contrib/Dusk/show_bug.css +00:03:28.574 0.222 547 (2033) GET (Cache) application/x-javascript https://bugzilla.redhat.com/js/util.js +00:03:28.582 0.234 548 (3746) GET (Cache) application/x-javascript https://bugzilla.redhat.com/js/field.js +00:03:28.589 0.252 562 (9752) GET (Cache) application/x-javascript https://bugzilla.redhat.com/js/yui/yahoo-dom-event.js +00:03:28.598 0.272 555 (12147) GET (Cache) application/x-javascript https://bugzilla.redhat.com/js/yui/calendar.js +00:03:28.606 0.283 554 (4985) GET (Cache) application/x-javascript https://bugzilla.redhat.com/js/productform.js +00:03:28.633 0.276 550 (5467) GET (Cache) application/x-javascript https://bugzilla.redhat.com/js/rpc-min.js +00:03:28.641 0.284 551 (1047) GET (Cache) application/x-javascript https://bugzilla.redhat.com/js/show_bug.js +00:03:29.557 0.077 427 (14643) GET (Cache) text/plain http://mcepl.fedorapeople.org/scripts/BugZappers_data.json +00:03:29.564 0.071 422 (11352) GET (Cache) text/plain http://mcepl.fedorapeople.org/scripts/drm_pciids.json +00:03:32.916 0.168 566 11698 GET 200 application/vnd.google.safebrowsing-chunk http://safebrowsing-cache.google.com/safebrowsing/rd/goog-malware-shavar_s_25761-25920.25761-25796.25797-25920: +00:03:34.383 0.275 561 20882 GET 200 application/vnd.google.safebrowsing-chunk http://safebrowsing-cache.google.com/safebrowsing/rd/goog-malware-shavar_a_16771-16780.16771,16773-16780.: +00:03:34.691 0.080 566 7154 GET 200 application/vnd.google.safebrowsing-chunk http://safebrowsing-cache.google.com/safebrowsing/rd/goog-malware-shavar_a_16781-16800.16781-16791.16792-16800: +00:03:34.810 0.084 564 3875 GET 200 application/vnd.google.safebrowsing-chunk http://safebrowsing-cache.google.com/safebrowsing/rd/goog-phish-shavar_s_52481-52640.52481-52528.52529-52640: +00:03:35.195 0.111 564 15346 GET 200 application/vnd.google.safebrowsing-chunk http://safebrowsing-cache.google.com/safebrowsing/rd/goog-phish-shavar_a_78641-78720.78641-78718.78719-78720: diff --git a/offline-submit/john-resig-offline-app-stub.js b/offline-submit/john-resig-offline-app-stub.js new file mode 100644 index 0000000..b9d6160 --- /dev/null +++ b/offline-submit/john-resig-offline-app-stub.js @@ -0,0 +1,43 @@ +// stub for the offline application in pseudocode +// from http://ejohn.org/blog/offline-events/ + +function saveData( item ){ + if ( navigator.onLine ) { + saveToServer( item ); + } else { + toSave.push( item ); + } +} + +function loadData( item ){ + if ( navigator.onLine ) { + return loadFromServer( item ); + } else { + var result = loadFromQueue( item ); + if ( !result ) { + displayError(); + toLoad.push( item ); + } + return result; + } +} + +setInterval(function(){ + if ( navigator.onLine ) { + var item = predictNextItemToBeLoaded(); + loadData( item ); + } +}, 5000); + +window.ononline = function(){ + toSave.forEach( saveData ); + toLoad.forEach( loadData ); +}; + +window.onload = function(){ + document.getElementById("myform").onsubmit = function(){ + saveData( this ); + return false; + }; +}; + diff --git a/offline-submit/jquery.malsup.com.htm b/offline-submit/jquery.malsup.com.htm new file mode 100644 index 0000000..b0008d7 --- /dev/null +++ b/offline-submit/jquery.malsup.com.htm @@ -0,0 +1,583 @@ +<!DOCTYPE html>
+<html lang="en">
+<head>
+<title>JavaScript Form Serialization Comparison</title>
+<link rel="stylesheet" type="text/css" media="screen" href="../../jq.css" />
+<style type="text/css">
+h1, #intro, #testForm td.hint { font-family: 'trebuchet ms', verdana, arial; }
+h1 { padding-left: 0 }
+#intro { background: #ffc; padding: 10px }
+#banner { text-align: left; }
+code.inline { background-color: #dfe0ff; color: #000 }
+ul { padding-left: 20px; margin: 10px 10px 10px 0 }
+#disclaimer { font-family: arial, sans-serif; font-size: small; font-style: italic; color: #555; margin-bottom: 5px}
+span.name { color: #c22 }
+span.dis { color: #f11 }
+#results { margin: 15px 0 }
+#results div { font-family: monospace; color: #888; }
+#results td { padding: 5px; }
+#testForm { border: 1px solid #888; background-color: #eee; padding: 0px }
+#testForm tr { background-color: #eee; }
+#testForm td { font-family: monospaced; padding: 2px 10px; }
+#testForm td.hint { color: #880; background: #ffc url(infoIcon.png) 5px 50% no-repeat; padding: 5px 5px 5px 30px }
+#toolbar { padding: 5px; border: 1px solid #aaa; border-width: 1px 0; background-color: #eee }
+#main { padding-top: 0; }
+legend, .wrong, span.dis { font-weight: bold }
+.wrong { color: #f00; }
+ul li { list-style-type: square }
+ol li { list-style-type: decimal }
+blockquote { padding: 10px; border: 1px dashed #aaa; line-height: 1.5em; font-family: sans-serif; background-color: #ffe }
+#footer { margin-top: 15px; padding-top: 10px; border-top: 1px solid #ddd; color: #888; }
+#main { padding: 15px }
+</style>
+<script type="text/javascript" src="../../jquery-1.3.2.js"></script>
+<script type="text/javascript" src="../jquery.form.js"></script>
+<script type="text/javascript" src="mochi.base.js"></script>
+<script type="text/javascript" src="mochi.iter.js"></script>
+<script type="text/javascript" src="mochi.dom.js"></script>
+
+<script type="text/javascript" src="dojo.js"></script>
+<script type="text/javascript" src="dojo.xhr.js"></script>
+
+<script type="text/javascript" src="prototype-1.6.0.3.js"></script>
+<!--<script type="text/javascript" src="mootools-1.2.1.js"></script>-->
+<script type="text/javascript" src="yahoo.js"></script>
+<script type="text/javascript" src="yui-event.js"></script>
+<script type="text/javascript" src="yui-connection.js"></script>
+
+<script type="text/javascript" src="firebug.js"></script>
+<script type="text/javascript" src="../../chili-1.7.pack.js"></script>
+<script type="text/javascript"><!--
+
+//
+// note that all usage of $ in this file is using Prototype's $ fn
+//
+var q, iter;
+
+// initialize test harness
+jQuery(init);
+
+function init() {
+ // profiling in FF only
+ if (!/Firefox/.test(navigator.userAgent)) {
+ $('timer').checked = true;
+ $('profiler').disabled = true;
+ console.open();
+ }
+
+ // bind the Test buttons to the 'runTest' function
+ jQuery('input.tester').click(function() { runTest(this); });
+
+ // let's see browser's native form serialization too!
+ var match = window.location.href.match(/\?([^#]+)/);
+ if (!match) return;
+ $('nativeOutput').innerHTML = match[1].escapeHTML();
+}
+
+function runTest(e) {
+ $('S5').selectedIndex = -1; // for the S5 test - force selectedIndex to -1
+ iter = $('iterations').value;
+ var name = e.id;
+ var n = '('+iter+') ' + name;
+ var useTimer = jQuery('#toolbar :radio').fieldValue() == 1;
+
+ // start clock
+ useTimer ? console.time(n) : console.profile(n);
+
+ try {
+ // run the test
+ window[name+'Test']();
+ } catch(e) {
+ q = 'ERROR: ' + e.message;
+ }
+
+ // stop clock
+ useTimer ? console.timeEnd(n) : console.profileEnd(n);
+
+ // highlight inaccurate results:
+ // - there should not be any "NO" values, this includes the reset input, submit inputs and disabled inputs
+ // - S5 should not be submitted (the selected index is -1)
+ // - S3 should not be blank
+ // - the query string should not have a trailing ampersand (dojo)
+ // - the "My Name" input should have been converted to My+Name, not My%20Name
+ q = q.replace(/(reset=NO|S5=|&$|sub(\d)=NO|D(\d)=NO)/g, '<span class="wrong">$1</span>');
+ q = q.replace(/S3=&/, '<span class="wrong">S3=</span>&');
+ q = q.replace(/My(%20)Name/,'<span class="wrong">$1</span>');
+ $(name+'Output').innerHTML = q;
+}
+
+function jQueryNativeTest() {
+ var form = jQuery('#testForm');
+ for (var i=0; i < iter; i++)
+ q = form.serialize();
+}
+
+function jQueryFormTest() {
+ var form = jQuery('#testForm');
+ for (var i=0; i < iter; i++)
+ q = form.formSerialize();
+}
+
+function dojoTest() {
+ var form = dojo.byId('testForm');
+ for (var i=0; i < iter; i++)
+ q = dojo.formToQuery(form);
+}
+
+function PrototypeTest() {
+ var form = $('testForm');
+ for (var i=0; i < iter; i++)
+ q = Form.serialize(form);
+}
+
+/*
+function mooTest() {
+ var form = $('testForm');
+ for (var i=0; i < iter; i++)
+ q = form.toQueryString();
+}
+*/
+
+function YUITest() {
+ var form = $('testForm');
+ for (var i=0; i < iter; i++)
+ q = YAHOO.util.Connect.setForm(form);
+}
+
+function MochiKitTest() {
+ var form = $('testForm');
+ for (var i=0; i < iter; i++)
+ q = MochiKit.Base.queryString(form);
+}
+// -->
+</script>
+
+</head>
+<body>
+<h1 id="banner">JavaScript Form Serialization Comparison: jQuery • dojo • Prototype • YUI • MochiKit</h1>
+<div id="intro">
+ This page tests two things about JavaScript form serialization: <strong>quality</strong> and <strong>speed</strong>.
+ <ul>
+
+ <li>Quality is dertermined by analyzing the serialized output according to the <a href="http://www.w3.org/TR/html401/interact/forms.html">W3C Recommendation</a>.</li>
+ <li>Speed is measured using <a href="http://www.getfirebug.com/">Firebug</a> to time (or profile in FireFox) numerous iterations of the serialization process.<sup>[1]</sup></li>
+ </ul>
+ <div id="disclaimer">If you believe the tests or comments on this page are inaccurate
+ or unfair - please let me know. It is not my intent to misrepresent any of these libraries.
+ Feedback can be sent to: forms at malsup dot com.
+ </div>
+ <div>»» Special thanks to Marius Feraru for adding support for native browser output and the EOL test!</div>
+</div>
+<div id="toolbar">
+ <input type="radio" name="time" value="1" checked="checked" id="timer" />Timer <input type="radio" name="time" value="0" id="profiler" />Profiler
+ <span style="margin-left: 15px">Iterations:</span>
+ <select id="iterations" name="iterations" class="tester">
+ <option value="1" selected="selected">1</option>
+ <option value="25">25</option>
+ <option value="50">50</option>
+
+ <option value="100">100</option>
+ <option value="200">200</option>
+ </select>
+ <p />
+ <input id="jQueryNative" type="button" class="tester" value="jQuery 1.3.2 core" />
+ <input id="jQueryForm" type="button" class="tester" value="jQuery 1.3.2 with Form Plugin" />
+ <input id="dojo" type="button" class="tester" value="dojo 1.2.3" />
+ <input id="YUI" type="button" class="tester" value="YUI 2.6.0" />
+ <input id="MochiKit" type="button" class="tester" value="MochiKit 1.4.2" />
+ <input id="Prototype" type="button" class="tester" value="Prototype 1.6.0.3" />
+ <!-- <input id="moo" type="button" class="tester" value="Test moo" /> -->
+</div>
+<div id="main">
+ <form id="results" action="#">
+ <fieldset><legend>Output <span style="color:#666"><em>(errors in red)</em></span></legend>
+ <table>
+ <tr><td>NATIVE</td><td><div id="nativeOutput" class="output"><a href="#" onclick="$('S5').selectedIndex = -1; $('testForm').submit(); return false">check your browser's *native* form serialization</a></div></td></tr>
+ <tr><td>jQuery</td><td><div id="jQueryNativeOutput" class="output"></div></td></tr>
+ <tr><td>jQuery Form</td><td><div id="jQueryFormOutput" class="output"></div></td></tr>
+ <tr><td>dojo</td><td><div id="dojoOutput" class="output"></div></td></tr>
+ <tr><td>YUI</td><td><div id="YUIOutput" class="output"></div></td></tr>
+ <tr><td>MochiKit</td><td><div id="MochiKitOutput" class="output"></div></td></tr>
+ <tr><td>Prototype</td><td><div id="PrototypeOutput" class="output"></div></td></tr>
+ <!--<tr><td>moo</td><td><div id="mooOutput" class="output"></div></td></tr> -->
+
+ </table>
+ </fieldset>
+ </form>
+
+ <p>
+ The following form contains each of the possible elements that can be submitted (with the exception of <code class="inline"><input type="file"></code>).
+ Which elements, and how they should be submitted, is specified here:
+ <a href="http://www.w3.org/TR/html401/interact/forms.html#h-17.13">http://www.w3.org/TR/html401/interact/forms.html#h-17.13</a>
+ </p>
+
+ <form id="testForm" action="#" method="get"><div>
+
+ <table>
+ <tr><td class="hint" colspan="2">Most enabled input elements and textareas are handled correctly by each of the libraries.</td></tr>
+ <tr><td><code class="html"><textarea name="T3" rows="2" cols="15">?<br />Z</textarea></code></td><td><textarea name="T3" rows="2" cols="15">?Z</textarea></td></tr>
+ <tr><td><code class="html"><input type="hidden" name="H1" value="x" /></code></td><td><input type="hidden" name="H1" value="x" /></td></tr>
+ <tr><td><code class="html"><input type="hidden" name="H2" /></code></td><td><input type="hidden" name="H2" /></td></tr>
+
+ <tr><td><code class="html"><input type="password" name="PWD" /></code></td><td><input name="PWD" type="password" value="" /></td></tr>
+ <tr><td><code class="html"><input type="text" name="T1" /></code></td><td><input name="T1" type="text" /></td></tr>
+ <tr><td><code class="html"><input type="text" name="T2" value="YES" readonly="readonly" /></code></td><td><input name="T2" type="text" value="YES" readonly="readonly" /></td></tr>
+ <tr><td><code class="html"><input type="checkbox" name="C1" value="1" /></code></td><td><input type="checkbox" name="C1" value="1" /></td></tr>
+ <tr><td><code class="html"><input type="checkbox" name="C2" /></code></td><td><input type="checkbox" name="C2" /></td></tr>
+ <tr><td><code class="html"><input type="radio" name="R1" value="1" /></code></td><td><input type="radio" name="R1" value="1" /></td></tr>
+
+ <tr><td><code class="html"><input type="radio" name="R1" value="2" /></code></td><td><input type="radio" name="R1" value="2" /></td></tr>
+ <tr><td><code class="html"><input type="text" name="My Name" value="me" /></code></td><td><input type="text" name="My Name" value="me" /></td></tr>
+
+ <tr><td class="hint" colspan="2">The reset input should never be serialized. Prototype and MochiKit get this wrong.</td></tr>
+ <tr><td><code class="html"><input type="reset" name="reset" value="NO" /></code></td><td><input type="reset" name="reset" value="NO" /></td></tr>
+
+ <tr><td class="hint" colspan="2">Normal <strong>Single</strong> select elements are handled correctly by each of the libraries.</td></tr>
+
+ <tr><td><code class="html"><select name="S1"></code> <em>(100 options)</em></td><td>
+ <select name="S1">
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+
+ </select>
+ </td></tr>
+ <tr><td class="hint" colspan="2">Normal <strong>multiple</strong> select elements are handled correctly by each of the libraries.</td></tr>
+ <tr><td><code class="html"><select name="S2" multiple="multiple" size="3"></code> <em>(100 options)</em></td><td>
+ <select name="S2" multiple="multiple" size="3">
+ <option value="abc">ABC</option>
+
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ <option value="abc">ABC</option>
+ </select></td></tr>
+
+ <tr><td class="hint" colspan="2">The following select element has a selected option with no value attribute. The option text value should be submitted.
+ <code class="inline">S3=YES</code> is correct. <code class="inline">S3=</code> is incorrect. dojo gets this wrong in IE.</td></tr>
+
+ <tr><td><code class="html"><select name="S3"><option selected="selected">YES</option></select></code></td><td><select name="S3">
+ <option selected="selected">YES</option>
+ </select></td></tr>
+ <tr><td class="hint" colspan="2">The following select element has a selected option with a value attribute set to "". <code class="inline">S4=</code> is correct. <code class="inline">S4=NO</code> is incorrect.
+ </td></tr>
+
+ <tr><td><code class="html"><select name="S4"><option value="" selected="selected">NO</option></select></code></td><td><select name="S4">
+ <option value="" selected="selected">NO</option>
+ </select></td></tr>
+ <tr><td class="hint" colspan="2">The following select element has its <code class="inline">selectedIndex</code> property set to <code class="inline">-1</code> before serialization.
+ It should not be submitted. dojo and MochiKit get this wrong.</td></tr>
+
+ <tr><td><code class="html"><select name="S5"><option value="NO">NO</option></select></code></td>
+ <td><select id="S5" name="S5"><option value="NO">NO</option></select></td></tr>
+ <tr><td class="hint" colspan="2">Submit elements should only appear in the query string when they are the element used to submit the form. Only jQuery and dojo get this right.
+ YUI always includes the first submit element. Prototype sends all the input submit elements (but not button submit elements).
+ MochiKit sends them all.</td></tr>
+ <tr><td><code class="html"><input type="submit" name="sub1" value="NO" /></code></td><td><input type="submit" name="sub1" value="NO" /></td></tr>
+ <tr><td><code class="html"><input type="submit" name="sub2" value="NO" /></code></td><td><input type="submit" name="sub2" value="NO" /></td></tr>
+
+ <tr><td><code class="html"><input type="image" name="sub3" src="submit.gif" value="NO" /></code></td><td><input type="image" name="sub3" value="NO" src="submit.gif" /></td></tr>
+ <tr><td><code class="html"><button type="submit" name="sub4" value="NO">NO</button></code></td><td><button name="sub4" type="submit" value="NO">NO</button></td></tr>
+
+ <tr><td class="hint" colspan="2">Disabled elements should never be submitted. MochiKit and mootools get this wrong.</td></tr>
+ <tr><td><code class="html"><input type="text" name="D1" value="NO" <span class="dis">disabled="disabled"</span> /></code></td><td><input name="D1" type="text" value="NO" disabled="disabled" /></td></tr>
+
+ <tr><td><code class="html"><input type="checkbox" name="D2" value="NO" checked="checked" <span class="dis">disabled="disabled"</span> /></code></td><td><input type="checkbox" checked="checked" disabled="disabled" name="D2" value="NO" /></td></tr>
+ <tr><td><code class="html"><input type="radio" name="D3" value="NO" checked="checked" <span class="dis">disabled="disabled"</span> /></code></td><td><input type="radio" name="D3" value="NO" checked="checked" disabled="disabled" /></td></tr>
+ <tr><td><code class="html"><select name="D4"><option selected="selected" value="NO" <span class="dis">disabled="disabled"</span>>NO</option></code></td><td><select name="D4" disabled="disabled">
+
+ <option selected="selected" value="NO">NO</option>
+ </select></td></tr>
+ <tr><td class="hint" colspan="2">Inputs with a type attribute of something other than<br />
+ PASSWORD | CHECKBOX | RADIO | SUBMIT | RESET | FILE | HIDDEN | IMAGE | BUTTON<br />
+ should be treated as TEXT inputs.
+ </td></tr>
+ <tr><td><code class="html"><input type="bogus" name="TYPE-TEST" value="YES" /></code></td><td><input type="bogus" name="TYPE-TEST" value="YES" /></td></tr>
+ <tr><td><code class="html"><input type="search" name="SEARCH" value="YES" /></code></td><td><input type="search" name="SEARCH" value="YES" /></td></tr>
+ </table>
+ </div></form>
+ <p />
+ <h1>Notes</h1>
+ This test page does not expose serialization problems that occur during an actual form submit. These include:
+ <p />
+
+ <ul>
+ <li>YUI, Prototype and MochiKit do not support input elements of <code class="inline">type="image"</code>.</li>
+ <li>dojo does not capture the click coordinates of input elements of <code class="inline">type="image"</code>. However, it does correctly recognize it as the submitting element.</li>
+ <li>dojo does not properly support semantic serialization when the form contains an input of <code class="inline">type="image"</code>. When such an element is used to submit
+ the form, it will always appear last in the query string regardless of its semantic position. (This is likely a very
+ low-percentage use case.)</li>
+
+ <li>mootools is not compatible with Prototype and is not included on this test page. A test page that includes mootools can be found <a href="moo.html">here</a>.
+ In summary, mootools does not support semantic serialization and it incorrectly submits disabled fields.
+ </li>
+ </ul>
+ <p />
+ <h1>Encoding</h1>
+ Each of the libraries tested use JavaScript's <code class="inline">encodeURIComponent</code><sup>[2]</sup> function to encode form data.
+ Technically, this does not conform to the HTML spec <a href="http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4.1">which
+ states</a> the following
+ about the <code class="inline">application/x-www-form-urlencoded</code> content type (emphasis added):
+ <blockquote>
+
+ This is the default content type. Forms submitted with this content type must be encoded as follows:<br />
+ <ol>
+ <li>Control names and values are escaped. <strong>Space characters are replaced by `+'</strong>, and then reserved
+ characters are escaped as described in <a href="http://www.w3.org/TR/html401/references.html#ref-RFC1738">[RFC1738]</a>,
+ section 2.2: Non-alphanumeric characters are
+ replaced by `%HH', a percent sign and two hexadecimal digits representing the ASCII code of the character.
+ <strong>Line breaks are represented as "CR LF" pairs (i.e., `%0D%0A')</strong>.</li>
+ <li>The control names/values are listed in the order they appear in the document. The name is separated
+ from the value by `=' and name/value pairs are separated from each other by '&'.</li>
+
+ </ol>
+ </blockquote>
+ While this does not seem to cause any problems, it is interesting and noteworthy.
+ <p />
+ <h1>Links</h1>
+ <ul>
+ <li><a href="http://jquery.com">jQuery</a></li>
+ <li><a href="http://www.dojotoolkit.com/">dojo</a></li>
+
+ <li><a href="http://developer.yahoo.com/yui/">YUI</a></li>
+ <li><a href="http://mochikit.com/">MochiKit</a></li>
+ <li><a href="http://prototypejs.org/">Prototype</a></li>
+ <li><a href="http://mootools.net/">mootools</a></li>
+ </ul>
+ <div id="footer">
+ [1] Timing numerous iterations of the form serialization process is a contrived test case.
+ This is not something that would ever be done with a real form (I hope). And this is why there
+ is an option of 1 in the iterations combobox - so you can run only a single pass if you like.
+ Running numerous iterations simply makes it easier to compare the relative performance
+ of the libraries tested.
+ <p />
+
+ [2] dojo optionally will encode form data in ascii format (using dojo.string.encodeAscii).
+ </div>
+</div>
+</body>
+</html>
diff --git a/offline-submit/lazarus.js b/offline-submit/lazarus.js new file mode 100644 index 0000000..9db59b0 --- /dev/null +++ b/offline-submit/lazarus.js @@ -0,0 +1,4418 @@ +
+//declare namespace
+this.Lazarus = this.Lazarus || {}
+
+Lazarus.STATE_UNINITALIZED = 0;
+Lazarus.STATE_DISABLED = 1;
+Lazarus.STATE_PASSWORD_REQUIRED = 2;
+Lazarus.STATE_ENABLED = 3;
+Lazarus.STATE_DISABLED_FOR_DOMAIN = 4;
+Lazarus.STATE_PRIVATE_BROWSING = 5;
+Lazarus.STATE_GENERATING_KEYS = 6;
+
+
+Lazarus.LOGIN_HOSTNAME = 'chrome://lazarus';
+Lazarus.LOGIN_REALM = 'Private Key Password';
+Lazarus.LOGIN_USERNAME = 'lazarus-private-key';
+
+Lazarus.IFRAME_NAME = '<content-editable-iframes>';
+Lazarus.MIN_TEXT_NEEDED_TO_SHOW_NOTIFICATION = 1024; //characters
+
+//flag to indicate if this browser is ready yet.
+Lazarus.initalized = false;
+
+//timers
+Lazarus.cleanupSavedFormsTimer = 0;
+Lazarus.autoSaveFormTimer = 0;
+
+//pointer to last autosave form.
+Lazarus.currAutoSaveForm = null;
+
+//pointer to the last editor (txetbox or iframe) that a user put input into.
+Lazarus.currentEditor = null;
+
+//flag to say if context menu is currently being shown
+Lazarus.isContextMenuShowing = false;
+
+/* known input types
+case "text":
+case "textarea":
+case "file":
+case "radio":
+case "checkbox":
+case "select":
+case "password":
+case "hidden":
+case "submit":
+case "reset":
+case "button":
+case "image":
+*/
+
+
+
+//array of editor infos that the user has typed into this session
+Lazarus.editorInfos = [];
+
+
+/**
+* window has loaded
+*/
+Lazarus.init = function(){
+
+ Lazarus.Global = Components.utils.import("resource://lazarus/global.js").Global;
+ Lazarus.Crypto = Components.utils.import("resource://lazarus/crypto.js").Crypto;
+
+ Lazarus.initalized = true;
+ Lazarus.initDevEnviroment();
+
+ //set loading icon
+ Lazarus.refreshIcon();
+ Lazarus.repositionNotification();
+
+ //Update UI elements
+ Lazarus.$("lazarus-statusbaricon-tooltip-title").setAttribute("value", Lazarus.getString("lazarus.statusbarpanel.image.tooltip", Lazarus.getVersionStr() +" ["+ Lazarus.build +"]"));
+ Lazarus.refreshMenuIcons();
+ Lazarus.Pref.addObserver('extensions.lazarus.showContextMenuIcons', Lazarus.refreshMenuIcons);
+
+ if (Lazarus.getPref('extensions.lazarus.showDonateNotification')){
+ Lazarus.checkForDonateThanks();
+ }
+
+
+ //check crypto component (breaks a lot with various Linux builds)
+ if (Lazarus.checkCrypto()){
+ //open the database
+ if (Lazarus.initDB()){
+ if (Lazarus.initEncryptionKeys()){
+ if (Lazarus.loadPublicKey()){
+ Lazarus.enable();
+ }
+
+ Lazarus.refreshIcon();
+
+ Lazarus.saveAutoSaveText();
+
+ //remove expired forms
+ Lazarus.cleanupSavedForms();
+
+ //hmmm, interesting. Cleaning the database makes little difference to the database size (33MB -> 32MB),
+ // unless a lot of deletions have been made
+ //
+ if (Lazarus.getPref('extensions.lazarus.cleanDatabaseAtStartup')){
+ Lazarus.cleanDB();
+ }
+ }
+ //else we are generating new encryption keys, do nothing
+ }
+ else {
+ //errors *should* have been thrown in the error console.
+ //inform the user something went wrong :(
+ Lazarus.error("Failed to load database");
+ Lazarus.disable();
+ }
+ }
+ else {
+ Lazarus.error("Failed to load crypto component");
+ Lazarus.disable();
+ }
+ Lazarus.refreshIcon();
+}
+
+/**
+* turn off the donate notifications if this user has donated.
+*/
+Lazarus.checkForDonateThanks = function(){
+
+ var onload = function(uri){
+ if (uri && uri.spec && uri.spec.indexOf('//lazarus.interclue.com/donate-thanks.html') > -1){
+ Lazarus.setPref('extensions.lazarus.showDonateNotification', false);
+ Lazarus.Event.remove("location-change", onload);
+ }
+ }
+ Lazarus.Event.add("location-change", onload);
+}
+
+/**
+* check to make sure the crypto component has loaded correctly
+*/
+Lazarus.checkCrypto = function(){
+ try {
+ var crypto = Components.classes["@labs.mozilla.com/Weave/Crypto;1"].createInstance(Components.interfaces.IWeaveCrypto);
+ return true;
+ }
+ catch(e){
+ return false;
+ }
+}
+
+/**
+* reposition the notification bar so that it's always the last element
+*/
+Lazarus.repositionNotification = function(){
+ //#124: Zotero compatiilty problem
+ var notif = document.getElementById('lazarus-notification');
+ notif.parentNode.appendChild(notif);
+}
+
+
+
+/**
+* enables Lazarus for this browser
+*/
+Lazarus.enable = function(){
+
+ Lazarus.saveAutoSavedForms();
+
+ //add preference observers
+ Lazarus.Pref.addObserver("extensions.lazarus.expireSavedForms", Lazarus.startCleanupTimer);
+ Lazarus.Pref.addObserver("extensions.lazarus.expireSavedFormsInterval", Lazarus.startCleanupTimer);
+ Lazarus.Pref.addObserver("extensions.lazarus.showInStatusbar", Lazarus.refreshIcon);
+
+ //we need to capture any onsubmit event from forms within a webpage
+ gBrowser.addEventListener("submit", Lazarus.onFormSubmit, false);
+ gBrowser.addEventListener("submit", Lazarus.saveLastSubmittedForm, false);
+ gBrowser.addEventListener("DOMContentLoaded", Lazarus.autofillEvent, true);
+ gBrowser.addEventListener("DOMContentLoaded", Lazarus.initRecoverForm, true);
+ gBrowser.addEventListener("reset", Lazarus.onFormReset, false);
+ gBrowser.addEventListener("change", Lazarus.onFormChange, false);
+ //we also need to save forms if people are typing into them.
+ gBrowser.addEventListener("keyup", Lazarus.onKeyUp, false);
+
+ //kjd: removing lazarus icon for now
+ //gBrowser.addEventListener("keydown", Lazarus.onKeyDown, false);
+
+
+ //clear the saved forms if user wants to when "clear private data" is hit.
+ Lazarus.$("Tools:Sanitize").addEventListener("command", Lazarus.fireClearPrivateDataIfNoPrompt, false);
+
+ //add handlers to the context menu
+ Lazarus.$("contentAreaContextMenu").addEventListener("popupshowing", Lazarus.onContextMenuShowing, false);
+ Lazarus.$("contentAreaContextMenu").addEventListener("popuphidden", Lazarus.onContextMenuHide, false);
+
+ //need events when a user changes the current document
+ Lazarus.Event.add("location-change", Lazarus.onLocationChange);
+
+ Lazarus.Event.add("extension-uninstall", Lazarus.onUninstall);
+ Lazarus.Event.add("extension-uninstall-request", Lazarus.onUninstallRequest);
+ Lazarus.Event.add("application-startup", Lazarus.onStartUp);
+ Lazarus.Event.add("application-startup", Lazarus.removeOldForms);
+ Lazarus.Event.add("application-shutdown", Lazarus.fireClearPrivateDataOnShutdown);
+ Lazarus.Event.add("application-shutdown", Lazarus.onShutdown);
+ Lazarus.Event.add("clear-private-data", Lazarus.onClearPrivateData);
+
+ Lazarus.startCleanupTimer();
+
+ //show the statusbar image (if user wants it)
+ Lazarus.refreshIcon();
+}
+
+
+Lazarus.onKeyDown = function(evt){
+ var ele = evt.target;
+
+ //ignore if we have already attached an onblur handler to this element
+ if (!ele.lazarusIconAdded){
+ switch(Lazarus.getElementType(ele)){
+ case "text":
+ case "password":
+ case "textarea":
+ //are we saving this element?
+ var form = Lazarus.findFormFromElement(ele);
+ var editor = Lazarus.findEditorFromElement(ele);
+ if ((form && Lazarus.shouldSaveForm(form)) || (editor && Lazarus.shouldSaveEditorInfo(form))){
+ //highlight element
+ Lazarus.addBackgroundIcon(ele);
+ }
+ else {
+ //ignore furthur keypresses on this element?
+ }
+
+ default:
+ //ignore this element
+ }
+ }
+}
+
+
+Lazarus.addBackgroundIcon = function(ele){
+
+ if (!ele.lazarusIconAdded){
+ ele.lazarusIconAdded = true;
+
+ var doc = ele.ownerDocument;
+ if (!doc.lazarusIcon){
+ var div = doc.createElement('div');
+ div.style.width = "11px";
+ div.style.height = "14px";
+ div.style.position = "absolute";
+ div.style.background = "url("+ Lazarus.icon +") no-repeat center center";
+ div.title = Lazarus.getString('Lazarus.savingText');
+ doc.body.appendChild(div);
+ doc.lazarusIcon = div;
+ }
+ //now position the image over the end of the textbox
+ var rect = ele.getBoundingClientRect();
+ doc.lazarusIcon.style.top = rect.top +"px";
+ doc.lazarusIcon.style.left = (rect.right - parseInt(doc.lazarusIcon.style.width)) +"px";
+ doc.lazarusIcon.style.display = "block";
+
+ var onBlur = function(){
+ //hide the icon
+ doc.lazarusIcon.style.display = "none";
+ ele.lazarusIconAdded = false;
+ }
+
+ //when adding the icon, we should hide it on blur
+ ele.addEventListener("blur", onBlur, false);
+ }
+}
+
+
+/**
+* restart the browser
+*/
+Lazarus.restart = function(){
+ var nsIAppStartup = Components.interfaces.nsIAppStartup;
+ Components.classes["@mozilla.org/toolkit/app-startup;1"].getService(nsIAppStartup).quit(nsIAppStartup.eRestart | nsIAppStartup.eAttemptQuit);
+}
+
+/**
+* turns Lazarus off for this browser
+*/
+Lazarus.disable = function(){
+
+ //add preference observers
+ Lazarus.Pref.addObserver("extensions.lazarus.expireSavedForms", Lazarus.startCleanupTimer);
+ Lazarus.Pref.addObserver("extensions.lazarus.expireSavedFormsInterval", Lazarus.startCleanupTimer);
+ Lazarus.Pref.addObserver("extensions.lazarus.showInStatusbar", Lazarus.refreshIcon);
+
+ //we need to capture any onsubmit event from forms within a webpage
+ gBrowser.removeEventListener("submit", Lazarus.onFormSubmit, false);
+ gBrowser.removeEventListener("submit", Lazarus.saveLastSubmittedForm, false);
+ gBrowser.removeEventListener("DOMContentLoaded", Lazarus.autofillEvent, true);
+ gBrowser.removeEventListener("DOMContentLoaded", Lazarus.initRecoverForm, true);
+ gBrowser.removeEventListener("reset", Lazarus.onFormReset, false);
+ gBrowser.removeEventListener("change", Lazarus.onFormChange, false);
+ //we also need to save forms if people are typing into them.
+ gBrowser.removeEventListener("keyup", Lazarus.onKeyUp, false);
+
+ //need events when a user changes the current document
+ Lazarus.Event.remove("location-change", Lazarus.onLocationChange);
+
+ //clear the saved forms if user wants to when "clear private data" is hit.
+ Lazarus.$("Tools:Sanitize").removeEventListener("command", Lazarus.fireClearPrivateDataIfNoPrompt, false);
+
+ Lazarus.stopCleanupTimer();
+
+ //and close the database
+ Lazarus.db.close();
+
+ //update the statusbar image
+ Lazarus.refreshIcon();
+}
+
+/**
+* return TRUE if we can encrypt a string (ie the Crypto component is working, and the public key exists)
+*/
+Lazarus.canEncrypt = function(){
+ return Lazarus.Crypto.publicKey ? true : false;
+}
+
+Lazarus.canDecrypt = function(){
+ return Lazarus.Crypto.privateKey ? true : false;
+}
+
+
+
+/**
+* initalizes the lazarus recover-form page
+*/
+Lazarus.initRecoverForm = function(evt){
+ var doc = evt.originalTarget;
+ if (Lazarus.isDocRecoveryForm(doc)){
+ doc.title = Lazarus.getString("recoverform.title");
+
+ //Lazarus.$('heading', doc).innerHTML = Lazarus.getString("recoverform.title");
+ Lazarus.$('description', doc).innerHTML = Lazarus.getString("recoverform.description");
+ Lazarus.$('notes', doc).innerHTML= Lazarus.getString("recoverform.notes");
+
+ Lazarus.$('form-url-label', doc).innerHTML= Lazarus.getString("recoverform.form.url");
+ Lazarus.$('form-action-label', doc).innerHTML= Lazarus.getString("recoverform.form.action");
+ Lazarus.$('notes', doc).innerHTML= Lazarus.getString("recoverform.notes");
+
+ var m = doc.URL.match(/[\?&]id=(\d+)/)
+ var id = m ? parseInt(m[1]) : -1;
+ if (id > -1){
+ var row = Lazarus.db.getRow("SELECT * FROM forms WHERE id = ?1", id);
+ if (row){
+ if (Lazarus.canDecrypt()){
+ var formInfo = Lazarus.JSON.decode(Lazarus.decrypt(row["forminfo"]));
+
+ Lazarus.$('form-url', doc).innerHTML = formInfo.origURL ? Lazarus.generateLinkFromURL(formInfo.origURL) : '';
+ Lazarus.$('form-action', doc).innerHTML = formInfo.action ? Lazarus.generateLinkFromURL(formInfo.action) : '';
+
+ var form = Lazarus.buildForm(formInfo, doc);
+ form.setAttribute("lazarus-form-id", row["formid"]);
+ Lazarus.$('form-box', doc).appendChild(form);
+ Lazarus.restoreForm(form, formInfo);
+ }
+ else {
+ //need to explain to user why we cannot fill in the form
+ Lazarus.showNotificationBox("password-required");
+ }
+ }
+ else {
+ Lazarus.$('form-box', doc).innerHTML = Lazarus.getString("error.form.not.found");
+ Lazarus.$('form-box', doc).className = "warning";
+ }
+ }
+ else {
+ Lazarus.$('form-box', doc).innerHTML = Lazarus.getString("error.form.not.found");
+ Lazarus.$('form-box', doc).className = "warning";
+ }
+ }
+}
+
+/**
+* return TRUE if the given document is the lazarus Recover Form page.
+*/
+Lazarus.isDocRecoveryForm = function(doc){
+ return (doc && doc.URL && doc.URL.indexOf("chrome://lazarus/content/recover-form.html") == 0);
+}
+
+/**
+* generate an HTML link given a url.
+* if url is too long, then truncate url to maxChars
+*/
+Lazarus.generateLinkFromURL = function(url, maxChars){
+
+ maxChars = maxChars || 50;
+
+ if (/^(file|http|https):/.test(url)){
+ var text = url;
+ if (text.length > maxChars){
+ text = text.substring(0, maxChars -3) +"...";
+ }
+
+ return '<a href="'+ Lazarus.htmlEncode(url) +'" title="'+ Lazarus.htmlEncode(url) +'">'+ Lazarus.htmlEncode(text) +'</a>';
+ }
+ else {
+ return Lazarus.htmlEncode(url);
+ }
+}
+
+/**
+* encode a string for display in an html page
+*/
+Lazarus.htmlEncode = function(str){
+ str = str.replace(/&/g, "&");
+ str = str.replace(/</g, "<");
+ str = str.replace(/>/g, ">");
+ str = str.replace(/"/g, """);
+ return str;
+}
+
+/**
+* builds an HTML form from a formInfo object
+*/
+Lazarus.buildForm = function(formInfo, doc){
+ var form = doc.createElement("form");
+ form.setAttribute("method", formInfo.method || "get");
+ form.setAttribute("enctype", formInfo.enctype || "");
+ //support for AJAX textareas
+ form.isTextarea = formInfo.isTextarea;
+
+ for(var name in formInfo.fields){
+ for (var i=0; i<formInfo.fields[name].length; i++){
+ var fieldInfo = formInfo.fields[name][i];
+ var ele = null;
+ var eleLabel = '';
+ switch(fieldInfo.type){
+ case "radio":
+ case "checkbox":
+ if (formInfo.version && formInfo.version >= 1 && fieldInfo.value && typeof fieldInfo.value.valueAttr != "undefined"){
+ ele = doc.createElement("input");
+ ele.setAttribute("type", fieldInfo.type);
+ ele.setAttribute("value", fieldInfo.value.valueAttr);
+ eleLabel = name +"["+ fieldInfo.value.valueAttr +"]";
+ }
+ break;
+
+ case "password":
+ case "hidden":
+ case "file":
+ case "text":
+ ele = doc.createElement("input");
+ ele.setAttribute("type", fieldInfo.type);
+ break;
+
+ case "textarea":
+ ele = doc.createElement("textarea");
+ break;
+
+ case "select":
+ if (formInfo.version && formInfo.version >= 1 && Lazarus.isArray(fieldInfo.value) && fieldInfo.value.length){
+ ele = doc.createElement("select");
+
+ for (var i=0; i<fieldInfo.value.length; i++){
+ var opt = doc.createElement("option");
+ opt.setAttribute("value", fieldInfo.value[i]);
+ opt.appendChild(doc.createTextNode(fieldInfo.value[i]));
+ ele.appendChild(opt);
+ }
+ if (fieldInfo.value.length > 1){
+ ele.setAttribute("size", fieldInfo.value.length);
+ ele.setAttribute("multiple", "true");
+ }
+ }
+ break;
+
+ //no saved
+ case "submit":
+ case "reset":
+ case "button":
+ case "image":
+ break;
+
+ case "iframe":
+ //ignore iframes for now?
+ break;
+
+ default:
+ Lazarus.error("Unknown element type ["+ fieldInfo.type +"]");
+ }
+
+ if (ele){
+ if (fieldInfo.type == "hidden"){
+ form.appendChild(ele);
+ }
+ else {
+ var box = doc.createElement("div");
+ box.setAttribute("class", "form-field-box");
+
+ var label = doc.createElement("label");
+ var text = doc.createTextNode(eleLabel || name);
+ label.appendChild(text);
+ box.appendChild(label);
+
+ ele.setAttribute("name", fieldInfo.name);
+ ele.setAttribute("class", "form-field "+ fieldInfo.type);
+ box.appendChild(ele);
+ form.appendChild(box);
+ }
+ }
+ }
+ }
+
+ return form;
+}
+
+/**
+* save the last submitted form id for use in auto restore template
+*/
+Lazarus.saveLastSubmittedForm = function(evt){
+ var form = Lazarus.findFormFromElement(evt.target);
+ if (form){
+ Lazarus.lastSubmittedFormId = Lazarus.getFormId(form);
+ }
+}
+
+/**
+* call autofill if this is a valid html document.
+*/
+Lazarus.autofillEvent = function(evt){
+ if (evt.originalTarget instanceof HTMLDocument){
+ Lazarus.autofillDoc(evt.originalTarget);
+ }
+}
+
+
+/**
+* convert a string of HTML into human readable text
+*/
+Lazarus.htmlToText = function(html){
+
+ //replace headings (</h1>) and paragraph ends with 2 line breaks
+ var text = html.replace(/\s*<\/((h\d)|p)\s*>\s*/ig," \n\n");
+
+ //replace divs blocks with single line breaks
+ text = text.replace(/\s*<(\/div)\b[^>]*>\s*/ig,"\n");
+
+ //replace list items with line breaks and dots
+ text = text.replace(/\s*<(li)\b[^>]*>\s*/ig,"\n * ");
+
+ //replace line breaks
+ text = text.replace(/<(br)\b[^>]*>/ig,"\n");
+
+ //strip all other tags
+ text = text.replace(/<(\/|\w)[^>]*>/g,' ');
+
+ //convert html spaces into normal spaces
+ text = text.replace(/ /g, ' ');
+
+ //compress whitespace
+ text = text.replace(/[ \t\f\v]+/g, ' ');
+
+ //never have more than 2 line breaks in a row.
+ text = text.replace(/\n\s*?\n(\s*?\n)*/g, "\n\n");
+
+ //and finally trim.
+ text = text.replace(/^\s+/, '').replace(/\s+$/, '');
+
+ return text;
+}
+
+
+/**
+* return TRUE if forms on the given document should be saved
+*/
+Lazarus.isValidDoc = function(doc){
+ return (doc && doc.URL && /^(file|http|https):/.test(doc.URL));
+}
+
+/**
+* autofills forms found in the given document with there respective templates if they exist
+*/
+Lazarus.autofillDoc = function(doc){
+
+ if (Lazarus.isValidDoc(doc) && doc.forms && doc.forms.length){
+ var rsAutoFillTemplates = Lazarus.db.rs("SELECT id, formid FROM forms WHERE autofill = 1 AND savetype = "+ Lazarus.FORM_TYPE_TEMPLATE +" ORDER BY created DESC");
+ if (rsAutoFillTemplates.length > 0){
+ var templates = {};
+ //convert the template id's to a hash table
+ //TODO: there should only ever be one autofill template for a given form
+ //raise a warning if this is not the case
+ for (var i=0; i<rsAutoFillTemplates.length; i++){
+ templates[rsAutoFillTemplates[i]["formid"]] = rsAutoFillTemplates[i]["id"];
+ }
+
+ for (var i=0; i<doc.forms.length; i++){
+ var form = doc.forms[i];
+ var formId = Lazarus.getFormId(form);
+ //only autofill if the form is empty, and the form hasn't just been submitted.
+ if (formId != Lazarus.lastSubmittedFormId && templates[formId] && Lazarus.isFormEmpty(form)){
+ //if the user has a password and is not yet logged in, then we cannot autofill the form
+ if (Lazarus.canDecrypt()){
+ var encryptedFormInfo = Lazarus.db.getStr("SELECT forminfo FROM forms WHERE id = ?1", templates[formId]);
+ var formInfo = Lazarus.JSON.decode(Lazarus.decrypt(encryptedFormInfo));
+ Lazarus.restoreForm(form, formInfo);
+ }
+ else {
+ //need to explain to user why we cannot autofill the template.
+ Lazarus.showNotificationBox("password-required-autofill");
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ //if this is the currently visible tab, then clear the lastSubmittedFormId
+ if (content.document && doc === content.document){
+ Lazarus.lastSubmittedFormId = null;
+ }
+}
+
+/**
+* return TRUE if the given form contains no user entered text.
+*/
+Lazarus.isFormEmpty = function(form){
+ for (var i=0; i<form.elements.length; i++){
+ var ele = form.elements[i];
+ switch (Lazarus.getElementType(ele)){
+ case "text":
+ case "textarea":
+ case "file":
+ case "password":
+ var text = Lazarus.getElementValue(ele);
+ if (Lazarus.trim(text)){
+ return false;
+ }
+
+ //ignore other input types
+ default:
+ }
+ }
+ return true;
+}
+
+/**
+* initalise any developer specific functionality
+*/
+Lazarus.initDevEnviroment = function(){
+
+ if (Lazarus.getExtPref("openErrorConsoleAtStartup", false)){
+ //open the javascript console
+ toJavaScriptConsole();
+ }
+
+ if (Lazarus.getExtPref("debugMode") >= 4){
+ Lazarus.$('lazarus-statusbar-menuitem-test').hidden = false;
+ }
+}
+
+/**
+* handle text within the address bar changing
+*/
+Lazarus.onLocationChange = function(evt){
+ //only close the notification if current page was not the result of submitting a form.
+ if (!Lazarus.lastSubmittedFormId){
+ Lazarus.closeNotificationBox(true);
+ }
+ Lazarus.refreshIcon();
+}
+
+/**
+* fire the "clear private data" event
+*/
+Lazarus.fireClearPrivateDataIfNoPrompt = function(){
+ if (!Lazarus.getPref("privacy.sanitize.promptOnSanitize", true)){
+ Lazarus.Event.fire("clear-private-data");
+ }
+}
+
+
+/**
+* return the current state of lazarus
+*/
+Lazarus.getState = function(){
+ if (!Lazarus.initalized){
+ return Lazarus.STATE_UNINITALIZED;
+ }
+ else if (Lazarus.Crypto.generatingKeys || Lazarus.cleaningDatabase){
+ return Lazarus.STATE_GENERATING_KEYS;
+ }
+ else if (!Lazarus.canEncrypt()){
+ return Lazarus.STATE_DISABLED;
+ }
+ else if (Lazarus.isDisabledByPrivateBrowsing()){
+ return Lazarus.STATE_PRIVATE_BROWSING;
+ }
+ else if (Lazarus.isPageDisabled()){
+ return Lazarus.STATE_DISABLED_FOR_DOMAIN;
+ }
+ else if (!Lazarus.canDecrypt()){
+ return Lazarus.STATE_PASSWORD_REQUIRED;
+ }
+ //all good?
+ else {
+ return Lazarus.STATE_ENABLED;
+ }
+}
+
+/**
+* fix for multiline xul:description elements
+*/
+Lazarus.setDescriptionText = function(ele, text){
+ for (var i=ele.childNodes.length-1; i>=0; i--){
+ ele.removeChild(ele.childNodes[i]);
+ }
+ ele.appendChild(ele.ownerDocument.createTextNode(text));
+}
+
+/**
+* updates the statusbar icon
+*/
+Lazarus.refreshIcon = function(){
+
+ Lazarus.$("lazarus-statusbarpanel").hidden = !Lazarus.getExtPref("showInStatusbar");
+
+ var iconURL = "";
+ var tooltipId = "";
+
+ switch (Lazarus.getState()){
+ case Lazarus.STATE_ENABLED:
+ iconURL = "chrome://lazarus/skin/lazarus.png";
+ tooltipId = "lazarus-statusbaricon-tooltip-enabled"
+ break;
+
+ case Lazarus.STATE_GENERATING_KEYS:
+ iconURL = 'chrome://lazarus/skin/lazarus-loading.gif';
+ tooltipId = 'lazarus-statusbaricon-tooltip-generatingkeys';
+ break;
+
+ case Lazarus.STATE_PASSWORD_REQUIRED:
+ iconURL = "chrome://lazarus/skin/lazarus-login.png";
+ tooltipId = "lazarus-statusbaricon-tooltip-passwordrequired";
+ break;
+
+ case Lazarus.STATE_DISABLED_FOR_DOMAIN:
+ iconURL = "chrome://lazarus/skin/lazarus-disabled.png";
+ tooltipId = "lazarus-statusbaricon-tooltip-disabledfordomain";
+ break;
+
+ case Lazarus.STATE_PRIVATE_BROWSING:
+ iconURL = "chrome://lazarus/skin/lazarus-disabled.png";
+ tooltipId = "lazarus-statusbaricon-tooltip-private-browsing";
+ break;
+
+ case Lazarus.STATE_UNINITALIZED:
+ case Lazarus.STATE_DISABLED:
+ default:
+ iconURL = "chrome://lazarus/skin/lazarus-disabled.png";
+ tooltipId = "lazarus-statusbaricon-tooltip-disabled";
+ }
+
+ //this appears to screw over firefox, missing images and such, if called during startup?
+ //NOTE: firfox must be completely closed for this effect, using "restart" will NOT recreate it.
+ //Lazarus.setDescriptionText(Lazarus.$("lazarus-statusbaricon-tooltip-description"), tooltip);
+
+ Lazarus.$("lazarus-statusbarpanel-image").setAttribute("src", iconURL);
+ Lazarus.$("lazarus-statusbarpanel-image").setAttribute("tooltip", tooltipId);
+}
+
+/**
+* displays the lazarus welcome message
+*/
+Lazarus.showWelcome = function(){
+ //dont show the welcome message immediately, need to wait a sec so the
+ //browser is open, and we can center the dialog relative to it.
+ //~ setTimeout(function(){
+ //~ Lazarus.openOptionsDialog("welcome-pane");
+ //~ }, 100);
+
+ //hmmm, session recovery is happening after this event,
+ //which is replacing our tab with the recovered sessions tabs
+ setTimeout(function(){
+ Lazarus.openLazarusWebsite("oninstall.html?ver="+ Lazarus.getVersionStr());
+ }, 3000);
+}
+
+/**
+* opens the lazarus onupdate page for this version of lazarus
+*/
+Lazarus.showUpdatePage = function(){
+ //hmmm, session recovery is happening after this event,
+ //which is replacing our tab with the recovered sessions tabs
+ setTimeout(function(){
+ Lazarus.openLazarusWebsite("onupdate.html?ver="+ Lazarus.getVersionStr());
+ }, 3000);
+}
+
+
+/**
+* handle when the first browser window is opened.
+*/
+Lazarus.onStartUp = function(){
+
+}
+
+/**
+* onUninstall
+*/
+Lazarus.onUninstall = function(ext){
+
+ if (ext.id == Lazarus.guid){
+
+ //var msg = '';
+ if (Lazarus.getExtPref("uninstall.removeSavedForms", false)){
+ //remove database
+ //unable to disconnect the database, so the best we can do is empty it.
+ Lazarus.emptyDB();
+
+ }
+ if (Lazarus.getExtPref("uninstall.removeUserSettings", false)){
+ //cleanup prefs
+ Lazarus.killPref("extensions.lazarus");
+ Lazarus.Pref.savePrefFile();
+ }
+ }
+}
+
+/**
+*
+*/
+Lazarus.onUninstallRequest = function(ext){
+ if (ext.id == Lazarus.guid){
+ //ask the user if we should remove their preferences/saved forms
+ //we need this dialog to appear from the addons dialog (if it exists)
+ //~ var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"].getService(Components.interfaces.nsIWindowMediator);
+ //~ var win = wm.getMostRecentWindow("Extension:Manager");
+
+ //~ //if theres no extension manager window (eg all-in-one-sidebar)
+ //~ //use this window to open the dialog
+ //~ if (!win){
+ //~ win = window;
+ //~ }
+ //disabling dialog, uninstall process is stuffed up
+ //win.openDialog("chrome://lazarus/content/uninstall.xul", "LazarusUninstallOptions", "chrome,dialog,modal,resizable=yes,titlebar=yes");
+ }
+}
+
+
+/**
+* return TRUE if the given editor still exists in the browser
+*/
+Lazarus.editorExists = function(info){
+ try {
+ //KLUDGE:
+ //if a page is refreshed then the page remains, even if you then navigate away from it.
+ //it appears to still exist, even though the user cannot see it.
+ if (Lazarus.isIframe(info.editor) && (!info.editor.contentWindow || !info.editor.contentWindow.document)){
+ return false;
+ }
+ else {
+ return (info.editor.ownerDocument.defaultView && (info.url == info.editor.ownerDocument.defaultView.top.location.href));
+ }
+ }
+ catch(e){
+ return false;
+ }
+}
+
+
+Lazarus.saveEditorInfo = function(info, saveType){
+
+
+ if (Lazarus.shouldSaveEditorInfo(info)){
+
+ var textHash = Lazarus.md5(info.text);
+
+ if (saveType == Lazarus.FORM_TYPE_AUTOSAVE){
+ var record = Lazarus.db.getRow("SELECT id, text_hash FROM textdata WHERE domain_hash = ?1 AND savetype = ?2 LIMIT 1", info.domainHash, Lazarus.FORM_TYPE_AUTOSAVE);
+ //if form hasn't changed
+ if (record && record.text_hash == textHash){
+ //do nothing...
+ }
+ //if it has changed, or doesn't exist yet, save the changes
+ else {
+ Lazarus.debug("Saving textdata - autosave", info);
+ if (record){
+ Lazarus.db.exe("DELETE FROM textdata WHERE id = ?1", record.id);
+ //and get rid of any fulltext index as well
+ Lazarus.db.exe("DELETE FROM textdata_fulltext WHERE docid = ?1", record.id);
+ }
+ var encText = Lazarus.encrypt(info.text);
+ var encSummary = Lazarus.encrypt(info.summary);
+
+ var id = Lazarus.db.insert("INSERT INTO textdata (text_encrypted, summary_encrypted, created, domain_hash, url_encrypted, text_hash, text_length, savetype) \
+ VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8)",
+ encText, encSummary, info.created, info.domainHash, info.urlEncrypted, textHash, info.text.length, Lazarus.FORM_TYPE_AUTOSAVE);
+
+ //and update the full text index,
+ if (!Lazarus.getPref('extensions.lazarus.disableSearch')){
+ //we're going to add domain info into this as well
+ var hashedText = Lazarus.hashText(info.text);
+ hashedText += ' '+ Lazarus.hashText(info.domain.replace(/\./g, ' '));
+ Lazarus.db.exe("INSERT INTO textdata_fulltext (docid, hashed_text) VALUES (?1, ?2)", id, hashedText);
+ }
+ }
+ }
+ else {
+
+ //if the info object already exists, with exactly the same text, then just update the timestamp
+ var id = Lazarus.db.getInt("SELECT id FROM textdata WHERE domain_hash = ?1 AND text_hash = ?2 LIMIT 1", info.domainHash, textHash);
+ if (id){
+ Lazarus.debug("Updating textdata - perm", info);
+ Lazarus.db.exe("UPDATE textdata SET created = ?1, savetype = ?2 WHERE id = ?3", info.created, Lazarus.FORM_TYPE_NORMAL, id);
+ }
+ //otherwise, insert a new info object
+ else {
+ Lazarus.debug("Saving textdata - perm", info);
+ var encText = Lazarus.encrypt(info.text);
+ var encSummary = Lazarus.encrypt(info.summary);
+
+ var id = Lazarus.db.insert("INSERT INTO textdata (text_encrypted, summary_encrypted, created, domain_hash, url_encrypted, text_hash, text_length, savetype) \
+ VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8)",
+ encText, encSummary, info.created, info.domainHash, info.urlEncrypted, textHash, info.text.length, Lazarus.FORM_TYPE_NORMAL);
+
+ if (!Lazarus.getPref('extensions.lazarus.disableSearch')){
+ var hashedText = Lazarus.hashText(info.text);
+ hashedText += ' '+ Lazarus.hashText(info.domain.replace(/\./g, ' '));
+ Lazarus.db.exe("INSERT INTO textdata_fulltext (docid, hashed_text) VALUES (?1, ?2)", id, hashedText);
+ }
+ }
+ }
+ }
+}
+
+
+/**
+* genereate a random seed for use in the hashing function
+*/
+Lazarus.generateRandomHashSeed = function(){
+ var rnd = Math.random().toString() +':'+ Lazarus.timestamp(true).toString();
+ return Lazarus.FNV1a(rnd);
+}
+
+
+/**
+* hashes individual words in a bunch of text
+*/
+Lazarus.hashText = function(text){
+
+ //we'll need to grab the seed from the database.
+ var seed = Lazarus.db.getStr("SELECT value FROM settings WHERE name = 'hash-seed'");
+
+ if (!seed){
+ seed = Lazarus.generateRandomHashSeed();
+ Lazarus.db.exe("INSERT INTO settings (name, value) VALUES ('hash-seed', ?1)", seed);
+ //and delete the full text index, because none of the others will work any more
+ Lazarus.db.exe("DELETE FROM textdata_fulltext");
+ }
+
+ //var p = new Profiler("hashedText: "+ text.length);
+ var map = {};
+ //p.mark("setup");
+
+ //remove all non-text characters (strip HTML?)
+ text = Lazarus.trim(text.toLowerCase().replace(/[^\s\w\-_]+/g, ' '));
+ //p.mark("remove non-text: "+ text.length);
+
+ var words = text.split(/\s+/g);
+ //p.mark("split into words : "+ words.length);
+ //we should also remove useless words (like "the", "and", "as" etc...)
+ var len = words.length;
+ var hashedWords = [];
+ for (var i=0; i<len; i++){
+ var word = words[i];
+ if (!map[word]){
+ map[word] = Lazarus.FNV1a(word, seed);
+ }
+ hashedWords.push(map[word]);
+ }
+ //p.mark("hash words");
+
+ //hashed text comes out at 70,000 characters for a 56,000 character start.
+ //acceptable.
+ var hashedText = hashedWords.join(" ");
+ //debug(p.stop("join text: "+ hashedText.length));
+ return hashedText;
+}
+
+
+/**
+* hash text within a MATCH query whilst keeping SQLite MATCH keywords/characters
+*/
+Lazarus.hashQuery = function(query){
+ //phrase searches ('"broccoli cheese"')
+ //Excluding terms ('onions -celery')
+ //OR queries ('onions OR cheese')
+ //Prefix search ('ch*') cannot be used due to hashing of the text strings.
+ return query.replace(/\w+/g, function(m){
+ if (m.toLowerCase() == "or"){
+ return "OR";
+ }
+ else {
+ return Lazarus.hashText(m);
+ }
+ });
+}
+
+Lazarus.saveAutoSaveText = function(){
+ Lazarus.db.exe("UPDATE textdata SET savetype = ?1 WHERE savetype = ?2", Lazarus.FORM_TYPE_NORMAL, Lazarus.FORM_TYPE_AUTOSAVE);
+}
+
+Lazarus.autoSaveEditors = function(){
+
+ //NOTE: traversing backwards through the list so we can remove items without it affecting
+ //the rest of the list
+ var removed = [];
+
+ for (var i=Lazarus.editorInfos.length-1; i>=0; i--){
+ var info = Lazarus.editorInfos[i];
+ //the page may have been submitted, or closed without submitting
+ //if the editor is suddenly empty, we should assume it has been submitted (possibly via AJAX)
+ if (!Lazarus.editorExists(info) || Lazarus.getEditorInfo(info.editor).isEmpty){
+ removed.push(info);
+ Lazarus.editorInfos.splice(i, 1);
+ }
+ }
+
+ //and save any that have been removed
+ for (var i=0; i<removed.length; i++){
+ Lazarus.saveEditorInfo(removed[i]);
+ }
+
+ //we also want to update the current textbox (if any)
+
+ //all others should be saved as "temporary saves"
+ for (var i=0; i<Lazarus.editorInfos.length; i++){
+ if (Lazarus.editorInfos[i].editor === Lazarus.currentEditor){
+ Lazarus.saveEditorInfo(Lazarus.editorInfos[i], Lazarus.FORM_TYPE_AUTOSAVE);
+ break;
+ }
+ }
+
+ if (Lazarus.editorInfos.length == 0){
+ Lazarus.stopEditorAutoSaveTimer();
+ }
+}
+
+
+/**
+* return the previous editosInfo
+*/
+Lazarus.getPreviousEditorInfo = function(editor){
+ for (var i=0; i<Lazarus.editorInfos.length; i++){
+ if (Lazarus.editorInfos[i].editor === editor){
+ return Lazarus.editorInfos[i];
+ }
+ }
+ return null;
+}
+
+
+/**
+* remove an editor info object from our list
+*/
+Lazarus.removeEditorInfo = function(info){
+ for (var i=0; i<Lazarus.editorInfos.length; i++){
+ if (Lazarus.editorInfos[i].editor === info.editor){
+ Lazarus.editorInfos.splice(i, 1);
+ return true;
+ }
+ }
+ return false;
+}
+
+
+/**
+* update editor info
+*/
+Lazarus.updateEditorInfo = function(info){
+ for (var i=0; i<Lazarus.editorInfos.length; i++){
+ if (Lazarus.editorInfos[i].editor === info.editor){
+ Lazarus.editorInfos[i] = info;
+ return true;
+ }
+ }
+ return false;
+}
+
+
+
+/**
+* save forms if people are typing into them
+*/
+Lazarus.onKeyUp = function(evt){
+ //dont save form for every keypress (CPU hog)
+ //we'll restart a timer whenever a key is pressed,
+ //and save when the timer fires.
+ var form = Lazarus.findFormFromElement(evt.target);
+
+ if (Lazarus.isPageDisabled()){
+ return;
+ }
+
+ //if the event happened within a form, set a timer to auto save the form
+ if (form && form.ownerDocument instanceof HTMLDocument){
+ Lazarus.restartAutoSaveTimer(form);
+ }
+
+ //we'll do the same for textarea's and contentEditable iframes
+ var editor = Lazarus.findEditorFromElement(evt.target);
+
+ if (editor){
+
+ Lazarus.currentEditor = editor;
+
+ var info = Lazarus.getEditorInfo(editor);
+ var prevInfo = Lazarus.getPreviousEditorInfo(editor);
+
+ if (prevInfo){
+ //if the textbox is suddenly empty, the form may have been submitted or reset (or all contents removed (ctrl+a + del))
+ if (info.isEmpty){
+ Lazarus.saveEditorInfo(prevInfo);
+ //then remove the editorInfo from the list
+ Lazarus.removeEditorInfo(prevInfo);
+ }
+ //otherwise update the previous editorInfo, and wait for the timer to save it.
+ else {
+ Lazarus.updateEditorInfo(info);
+ }
+ }
+ //this is the first time the user has typed into this textbox
+ //save it, if it's got some text in it
+ else if (!info.isEmpty){
+ Lazarus.editorInfos.push(info);
+ Lazarus.restartEditorAutoSaveTimer();
+ }
+ else {
+ //editor is empty, dont keep it
+ }
+
+ Lazarus.restartEditorAutoSaveTimer();
+ }
+}
+
+
+/**
+* return an editor info object
+*/
+Lazarus.getEditorInfo = function(editor){
+ var info = {}
+ info.editor = editor;
+ info.text = Lazarus.extractText(editor);
+ info.isEmpty = Lazarus.isEditorEmpty(editor);
+ info.summary = Lazarus.generateSummary(info.text);
+ info.created = Lazarus.timestamp();
+ info.domain = Lazarus.getDomainFromElement(editor);
+ info.domainHash = Lazarus.md5(info.domain);
+ info.basedomain = Lazarus.getBaseDomain(info.domain);
+ info.url = editor.ownerDocument.defaultView.top.location.href;
+ info.urlEncrypted = Lazarus.encrypt(info.url);
+ return info;
+}
+
+
+/**
+* return TRUE if the editor doesn't contain any text
+*/
+Lazarus.isEditorEmpty = function(editor){
+ var text = Lazarus.extractText(editor);
+ var type = editor.nodeName.toLowerCase();
+ if (type == "textarea"){
+ return text.match(/^\s*$/) ? true : false;
+ }
+ else if (type == "iframe"){
+ return text.replace(/(<(\/|\w)[^>]*>)|( )/g, '').match(/^\s*$/) ? true : false;
+ }
+ else {
+ throw Error("Unknown editor type: "+ type);
+ }
+}
+
+/**
+* return the text/html from an editable iframe or textarea
+*/
+Lazarus.extractText = function(ele){
+ var text = '';
+ if (Lazarus.isTextarea(ele)){
+ text = (typeof ele.value == "string") ? ele.value : '';
+ }
+ else if (Lazarus.isIframe(ele)){
+ text = (typeof ele.contentWindow.document.body.innerHTML) ? ele.contentWindow.document.body.innerHTML : '';
+ }
+ return Lazarus.trim(text);
+}
+
+
+/**
+* generate a safe summary for display within a XUL:menuitem or XUL:tooltip
+*/
+Lazarus.generateSummary = function(text){
+ //strip html
+ text = Lazarus.htmlToText(text);
+ return (text.length > 255) ? (text.substr(0, 252) +"...") : text;
+}
+
+Lazarus.restartEditorAutoSaveTimer = function(){
+ Lazarus.stopEditorAutoSaveTimer();
+ Lazarus.editorAutoSaveFormTimer = setInterval(Lazarus.autoSaveEditors, Lazarus.getExtPref("autoSaveInterval", 2000));
+}
+Lazarus.stopEditorAutoSaveTimer = function(){
+ if (Lazarus.editorAutoSaveFormTimer){
+ clearInterval(Lazarus.editorAutoSaveFormTimer);
+ }
+}
+
+
+/**
+* start the autosave timer
+*/
+Lazarus.restartAutoSaveTimer = function(form){
+ Lazarus.stopAutoSaveTimer();
+ //we'll save the form here, so we can retrieve it when the timer fires
+ Lazarus.currAutoSaveForm = form;
+ Lazarus.autoSaveFormTimer = setTimeout(Lazarus.autoSaveForm, Lazarus.getExtPref("autoSaveInterval", 2000));
+}
+
+/**
+* stops the autosave timer.
+*/
+Lazarus.stopAutoSaveTimer = function(){
+ if (Lazarus.autoSaveFormTimer){
+ clearTimeout(Lazarus.autoSaveFormTimer);
+ }
+}
+
+/**
+* run cleanup for this window
+*/
+Lazarus.cleanup = function(){
+ gBrowser.removeEventListener("keyup", Lazarus.onKeyUp, false);
+ gBrowser.removeEventListener("submit", Lazarus.onFormSubmit, false);
+ gBrowser.removeEventListener("reset", Lazarus.onFormReset, false);
+ gBrowser.removeEventListener("change", Lazarus.onFormChange, false);
+ Lazarus.$("Tools:Sanitize").removeEventListener("command", Lazarus.fireCLearPrivateDataIfNoDialog, false);
+ Lazarus.stopCleanupTimer();
+}
+
+/**
+* handle last browser window shutting down
+*/
+Lazarus.onShutdown = function(){
+
+}
+
+/**
+*
+*/
+Lazarus.onContextMenuHide = function(){
+ Lazarus.isContextMenuShowing = false;
+}
+
+
+/**
+* show or hide the menu item depending on what item caused the context menu to appear.
+*/
+Lazarus.onContextMenuShowing = function(evt){
+
+ //we need to set a flag to prevent new autosaves whilst the context menu is shown
+ Lazarus.isContextMenuShowing = true;
+
+ //this event is fired whenever a submenu is shown, as well as when the main context menu it shown
+ //bugfix: only re-calculate the popup menu when it's first opened.
+ //trying to alter the initial menu when a submenu is opened can cause the browser to hang.
+
+ //hmmm this is causing a noticable hang when the form contains a lot (30k) of info
+ //as a workaround, we will not calculate any submenu until the submenu is opened.
+ var evtTargetId = evt.target.id;
+
+ //quick check
+ if (evtTargetId != "contentAreaContextMenu" && evtTargetId != "lazarus-restoreform-submenu-menupopup" && evtTargetId != "lazarus-restoretext-submenu-menupopup"){
+ return;
+ }
+
+
+ //did the user click on a form?
+ var form = Lazarus.findFormFromElement(gContextMenu.target);
+ var editor = Lazarus.findEditorFromElement(gContextMenu.target);
+
+ if (evtTargetId == "contentAreaContextMenu"){
+
+ //assume we should not show any menuitems
+ var showMainMenu = false;
+ var showSubMenu = false;
+ var showLogin = false;
+ var showSaveForm = false;
+ var showPageDisabled = false;
+ var showPrivateBrowsing = false;
+
+ var showRestoreText = false;
+ var showRestoreTextDisabled = false;
+
+
+ if (!form && !editor){
+ //dont show anything
+ }
+ else if (Lazarus.isDisabledByPrivateBrowsing()){
+ showPrivateBrowsing = true;
+ }
+ else if (Lazarus.isPageDisabled(form.ownerDocument.URL)){
+ showPageDisabled = true;
+ }
+ else if (!Lazarus.canDecrypt()){
+ showLogin = true;
+ }
+ else {
+ if (form){
+
+ showSaveForm = true;
+ var savedForms = Lazarus.getFormInfo(form, "id, formid, created, savetype, forminfohash, formtext, formname");
+
+ //just show the one menu item, but dont show it if the form is identical to this one.
+ if (savedForms.length == 1){
+ showMainMenu = true;
+ var info = Lazarus.formInfo(form);
+
+ var infoHash = Lazarus.generateHash(info.fields);
+
+ if (infoHash == savedForms[0]["forminfohash"]){
+ Lazarus.$('lazarus-restoreform-contextmenuitem').setAttribute('src', "chrome://lazarus/skin/lazarus-disable.png");
+ Lazarus.$('lazarus-restoreform-contextmenuitem').setAttribute('disabled', "true");
+ Lazarus.$('lazarus-restoreform-contextmenuitem').setAttribute('tooltiptext', Lazarus.getString("form.is.equal"));
+ }
+ else {
+ var savedForm = savedForms[0];
+ Lazarus.$('lazarus-restoreform-contextmenuitem').setAttribute('src', "chrome://lazarus/skin/lazarus.png");
+ Lazarus.$('lazarus-restoreform-contextmenuitem').setAttribute('lazarus-forms-id', savedForm["id"]);
+ Lazarus.$('lazarus-restoreform-contextmenuitem').setAttribute('tooltiptext', Lazarus.generateSavedFormTooltip(savedForm));
+ Lazarus.$('lazarus-restoreform-contextmenuitem').setAttribute('disabled', "");
+ }
+ }
+ //show submenu
+ else if (savedForms.length > 1){
+ showSubMenu = true;
+ }
+ }
+
+ if (editor){
+ showRestoreTextDisabled = true;
+ //do we have any text saved for this domain/basedomain?
+ var domain = Lazarus.getDomainFromElement(editor);
+ if (domain){
+ var domainHash = Lazarus.md5(domain);
+ if (Lazarus.db.getInt("SELECT count(id) FROM textdata WHERE domain_hash = ?1 LIMIT 1", domainHash)){
+ showRestoreText = true;
+ showRestoreTextDisabled = false;
+ }
+ }
+ }
+ }
+
+ //only show our menu item if over a form.
+ Lazarus.$('lazarus-restoretextdisabled-contextmenuitem').hidden = !showRestoreTextDisabled;
+ Lazarus.$('lazarus-restoretext-submenu').hidden = !showRestoreText;
+ Lazarus.$('lazarus-restoreform-contextmenuitem').hidden = !showMainMenu;
+ Lazarus.$('lazarus-restoreform-submenu').hidden = !showSubMenu;
+ Lazarus.$('lazarus-enterpassword-contextmenuitem').hidden = !showLogin;
+ Lazarus.$('lazarus-domaindisabled-contextmenuitem').hidden = !showPageDisabled;
+ Lazarus.$('lazarus-privatebrowsing-contextmenuitem').hidden = !showPrivateBrowsing;
+ Lazarus.$('lazarus-saveform-contextmenuitem').hidden = !(showSaveForm && Lazarus.getExtPref("includeExperimental", false));
+ }
+ else if (evtTargetId == "lazarus-restoreform-submenu-menupopup" && form){
+ Lazarus.buildSubMenu(form);
+ }
+ else if (evtTargetId == "lazarus-restoretext-submenu-menupopup" && editor){
+ Lazarus.buildRestoreTextSubMenu(editor);
+ }
+}
+
+
+
+Lazarus.getDomainFromElement = function(ele){
+ try {
+ return ele.ownerDocument.defaultView.top.location.host;
+ }
+ catch(e){
+ return null;
+ }
+}
+
+/**
+* builds the list of items that can be restored for this editor element
+*/
+Lazarus.buildRestoreTextSubMenu = function(editor){
+ //
+ var menu = Lazarus.$("lazarus-restoretext-submenu-menupopup");
+ //remove all the current submenu items
+ while(menu.lastChild){
+ menu.removeChild(menu.lastChild);
+ }
+
+ var domainHash = Lazarus.md5(Lazarus.getDomainFromElement(editor));
+
+ //and build the new ones
+ var items = Lazarus.db.rs("SELECT id, text_hash, summary_encrypted, created FROM textdata WHERE domain_hash = ?1 ORDER BY created DESC LIMIT ?2", domainHash, Lazarus.getPref('extensions.lazarus.maxTextItemsInSubmenu', 20));
+
+ var text = Lazarus.extractText(editor);
+ var textHash = Lazarus.md5(text);
+
+ for (var i=0; i<items.length; i++){
+ var item = items[i];
+ var menuitem = document.createElement("menuitem");
+
+ if (Lazarus.getExtPref("showFormTextInSubMenu") && Lazarus.canDecrypt()){
+ var summary = Lazarus.decrypt(item["summary_encrypted"]);
+ menuitem.setAttribute("label", summary);
+ menuitem.setAttribute("tooltiptext", summary);
+ }
+ else {
+ var time = Lazarus.timestamp();
+ menuitem.setAttribute("label", Lazarus.getTimeString(time - item["created"]));
+ //show the tooltip if possible
+ if (Lazarus.canDecrypt()){
+ menuitem.setAttribute("tooltiptext", Lazarus.decrypt(item["summary_encrypted"]));
+ }
+ }
+
+ menuitem.setAttribute("lazarus-restoretext-id", item["id"]);
+ menuitem.setAttribute("oncommand", "Lazarus.onRestoreTextMenuItem(this)");
+
+ //disable the menu item if the form is identical
+ if (item["text_hash"] == textHash){
+ menuitem.setAttribute('tooltiptext', Lazarus.getString("text.is.equal"));
+ menuitem.setAttribute('disabled', "true");
+ }
+ menu.appendChild(menuitem);
+ }
+}
+
+/**
+*
+*/
+Lazarus.getFormRestoredNotificationText = function(charsRestored, msgId){
+ //we're trying out a bunch of text at the moment
+ var numChars = Lazarus.formatNumber(charsRestored);
+ var numForms = Lazarus.formatNumber(Lazarus.db.getInt("SELECT COUNT(*) FROM forms"));
+ var numText = Lazarus.formatNumber(Lazarus.db.getInt("SELECT COUNT(*) FROM textdata"));
+
+ var msgs = [
+ 'Lazarus has just saved you from having to retype '+ numChars +' characters. If you feel this has helped you, then please consider donating to this project so we can make Lazarus even better.',
+ 'Lazarus restored '+ numChars +' characters.\nDatabase contains '+ numForms +' Forms and '+ numText +' Text-blocks.',
+ 'Lazarus restored '+ numChars +' characters.\nDatabase contains '+ numForms +' Saved Forms and '+ numText +' Textareas.',
+ 'Lazarus resurrected '+ numChars +' characters.\nLazarus is securely storing '+ numForms +' Forms and '+ numText +' Text-blocks'
+ ];
+
+ return msgs[msgId];
+}
+
+
+/**
+*
+*/
+Lazarus.onRestoreTextMenuItem = function(menuitem){
+ if (!Lazarus.canDecrypt()){
+ Lazarus.debug("Unable to restore form, password required");
+ Lazarus.showNotificationBox("password-required");
+ return;
+ }
+
+ var editor = Lazarus.findEditorFromElement(gContextMenu.target);
+
+ if (editor){
+ var id = parseInt(menuitem.getAttribute("lazarus-restoretext-id"));
+
+ Lazarus.debug("Attempting to restore text: "+ id);
+
+ var enc_text = Lazarus.db.getStr("SELECT text_encrypted FROM textdata WHERE id = ?1", id);
+ if (enc_text){
+ var text = Lazarus.decrypt(enc_text);
+ if (text){
+ Lazarus.setElementValue(editor, text);
+ Lazarus.incPref("extensions.lazarus.restoreFormCount");
+ if ((text.length > Lazarus.MIN_TEXT_NEEDED_TO_SHOW_NOTIFICATION) && Lazarus.getPref("extensions.lazarus.showDonateNotification") && Lazarus.getPref("extensions.lazarus.restoreFormCount", 0) > 3){
+ var msgId = Math.floor(Math.random() * 4);
+ Lazarus.showNotificationBox("form-restored", Lazarus.getFormRestoredNotificationText(text.length, msgId), "msgid-"+ msgId);
+ }
+ }
+ else {
+ alert(Lazarus.getString("error.form.db.corrupt"));
+ }
+ }
+ else {
+ Lazarus.error("Unable to find textdata: "+ id);
+ alert(Lazarus.getString("error.form.not.found"));
+ }
+ }
+ else {
+ //should never get here
+ alert(Lazarus.getString("error.form.object.not.found"));
+ }
+}
+
+
+/**
+* generates an md5 hash of a javascript object
+*/
+Lazarus.generateHash = function(obj){
+ return Lazarus.md5(Lazarus.JSON.encode(obj));
+}
+
+/**
+* return the current unix timestamp
+*/
+Lazarus.timestamp = function(asFloat){
+ var s = new Date().getTime() / 1000;
+ return asFloat ? s : Math.floor(s);
+}
+
+/**
+* return text usable by a menuitem (XUL Label)
+*/
+Lazarus.getMenuItemText = function(text){
+
+ //strip any html found in the text
+ text = Lazarus.trim(Lazarus.cleanText(text));
+
+ //labels only handle a single line of text
+ text = text.split(/\n/, 2)[0];
+ //and we dont want it to be too long
+ if (text.length > 48){
+ text = text.substr(0, 48) +"...";
+ }
+ return Lazarus.trim(text);
+}
+
+
+/**
+* ask the user for their Lazarus password
+*/
+Lazarus.showEnterPasswordDialog = function(){
+
+ var prompts = Components.classes["@mozilla.org/embedcomp/prompt-service;1"].getService(Components.interfaces.nsIPromptService);
+
+ //keep asking until they hit cancel
+ while(true){
+ var password = {value: ""};
+ var check = {value: false};
+ //if a user has a master password set, then allow the lazarus password to be saved in the SSD
+ var checkText = Lazarus.isMasterPasswordSet() ? Lazarus.getString('password.dialog.checkbox.label') : null;
+ //
+ if (prompts.promptPassword(null, Lazarus.getString("password.dialog.title"), Lazarus.getString("password.dialog.label"), password, checkText, check)){
+ if (Lazarus.loadPrivateKey(password.value)){
+ if (check.value){
+ Lazarus.savePassword(password.value);
+ }
+ return true;
+ }
+ }
+ else {
+ break;
+ }
+ }
+
+ return false;
+}
+
+
+Lazarus.savePassword = function(password){
+ //remove the existing password first.
+ Lazarus.removePassword();
+ var nsLoginInfo = new Components.Constructor("@mozilla.org/login-manager/loginInfo;1", Components.interfaces.nsILoginInfo, "init");
+ var loginInfo = new nsLoginInfo(Lazarus.LOGIN_HOSTNAME, null, Lazarus.LOGIN_REALM, Lazarus.LOGIN_USERNAME, password, '', '');
+ var nsLoginManager = Components.classes["@mozilla.org/login-manager;1"].getService(Components.interfaces.nsILoginManager);
+ nsLoginManager.addLogin(loginInfo);
+}
+
+
+Lazarus.removePassword = function(){
+
+ // Get Login Manager
+ var nsLoginManager = Components.classes["@mozilla.org/login-manager;1"].getService(Components.interfaces.nsILoginManager);
+ // Find users for this extension
+ var logins = nsLoginManager.findLogins({}, Lazarus.LOGIN_HOSTNAME, null, Lazarus.LOGIN_REALM);
+ for (var i = 0; i < logins.length; i++) {
+ if (logins[i].username == Lazarus.LOGIN_USERNAME){
+ nsLoginManager.removeLogin(logins[i]);
+ }
+ }
+}
+
+/**
+* load the password from the loginManager
+*/
+Lazarus.loadPassword = function(){
+
+ if (Lazarus.isMasterPasswordSet() && !Lazarus.isMasterPasswordRequired()){
+ // Get Login Manager
+ var nsLoginManager = Components.classes["@mozilla.org/login-manager;1"].getService(Components.interfaces.nsILoginManager);
+
+ // Find users for the given parameters
+ var logins = nsLoginManager.findLogins({}, Lazarus.LOGIN_HOSTNAME, null, Lazarus.LOGIN_REALM);
+
+ // Find user from returned array of nsILoginInfo objects
+ for (var i = 0; i < logins.length; i++) {
+ if (logins[i].username == Lazarus.LOGIN_USERNAME){
+ return logins[i].password;
+ }
+ }
+ }
+ //we default to an empty password
+ return '';
+}
+
+Lazarus.logout = function(){
+ Lazarus.unloadPrivateKey();
+ Lazarus.refreshIcon();
+}
+
+/**
+* generate a label for saved form menuitem
+*/
+Lazarus.generateSavedFormLabel = function(savedForm, time){
+ var label = '';
+
+ //template should ALWAYS use their template name as a label
+ if (savedForm["savetype"] == Lazarus.FORM_TYPE_TEMPLATE){
+ label = savedForm["formname"];
+ }
+ //use summary
+ else if (Lazarus.getExtPref("showFormTextInSubMenu") && Lazarus.canDecrypt()){
+ label = Lazarus.getMenuItemText(Lazarus.decrypt(savedForm["formtext"])) || ("["+ Lazarus.getString("untitled") +"]");
+ }
+ //use timestamps if we are unable to decrypt the forms
+ else {
+ time = time || Lazarus.timestamp();
+ label = Lazarus.getTimeString(time - savedForm["created"]);
+ }
+
+ //mark autosaved forms so users can easily tell the difference.
+ switch(savedForm["savetype"]){
+ case Lazarus.FORM_TYPE_AUTOSAVE:
+ case Lazarus.FORM_TYPE_STALE_AUTOSAVE:
+ label += " "+ Lazarus.getString("menuitem.label.append.autosave");
+ break;
+
+ case Lazarus.FORM_TYPE_TEMPLATE:
+ label += " "+ Lazarus.getString("menuitem.label.append.template");
+ break;
+
+ default:
+ label += " "+ Lazarus.getString("menuitem.label.append.normal");
+ break;
+ }
+
+ return label;
+}
+
+/**
+* generate a tooltip for saved form menuitem
+*/
+Lazarus.generateSavedFormTooltip = function(savedForm, /**/time){
+
+ time = time || Lazarus.timestamp();
+
+ var tooltip = '';
+
+ //show time sting as tooltip
+ if (Lazarus.getExtPref("showFormTextInSubMenu")){
+
+ tooltip = Lazarus.getTimeString(time - savedForm["created"]);
+ }
+ //show date and time of save
+ else {
+ var date = new Date(savedForm["created"] * 1000);
+ tooltip = Lazarus.formatDate(date);
+ }
+
+ //mark autosaved forms so users can easily tell the difference.
+ switch(savedForm["savetype"]){
+ case Lazarus.FORM_TYPE_AUTOSAVE:
+ case Lazarus.FORM_TYPE_STALE_AUTOSAVE:
+ tooltip += " "+ Lazarus.getString("menuitem.tooltip.append.autosave");
+ break;
+
+ case Lazarus.FORM_TYPE_TEMPLATE:
+ tooltip += " "+ Lazarus.getString("menuitem.tooltip.append.template");
+ break;
+
+ default:
+ tooltip += " "+ Lazarus.getString("menuitem.tooltip.append.normal");
+ break;
+ //do nothing
+ }
+
+ return tooltip;
+}
+
+/**
+* builds the list of available restore point into the submenu
+*/
+Lazarus.buildSubMenu = function(form){
+
+
+ var menu = Lazarus.$("lazarus-restoreform-submenu-menupopup");
+ //remove all the current submenu items
+ for (var i=menu.childNodes.length-1; i>=0; i--){
+ var node = menu.childNodes[i];
+ if (node.getAttribute("lazarus-dynamic-submenu")){
+ menu.removeChild(node);
+ }
+ }
+
+ var time = Lazarus.timestamp();
+ var separator = Lazarus.$("lazarus-submenu-separator");
+ //now build the new ones
+ var lastSaveType = -1;
+ var savedForms = Lazarus.getFormInfo(form, "id, formid, created, savetype, forminfohash, formtext, formname, forminfo");
+ var info = Lazarus.formInfo(form);
+ var infoHash = Lazarus.generateHash(info.fields);
+
+ for (var i=0; i<savedForms.length; i++){
+ var savedForm = savedForms[i];
+ var menuitem = document.createElement("menuitem");
+
+ //generate text for the label
+ //menuitems can only handle a single line of text, and we don't want it to be too long
+ var label = Lazarus.generateSavedFormLabel(savedForm, time);
+ var tooltip = Lazarus.generateSavedFormTooltip(savedForm, time);
+
+ menuitem.setAttribute("label", label);
+ //menuitem.setAttribute('tooltiptext', tooltip);
+ menuitem.setAttribute('oncommand', "Lazarus.onRestoreFormMenuItem(this)");
+ menuitem.setAttribute('lazarus-forms-id', savedForm["id"]);
+ menuitem.setAttribute('lazarus-dynamic-submenu', "true");
+
+ //disable the menu item if the form is identical
+ if (savedForm["forminfohash"] == infoHash){
+ menuitem.setAttribute('tooltiptext', Lazarus.getString("form.is.equal"));
+ menuitem.setAttribute('disabled', "true");
+ }
+ else {
+ menuitem.setAttribute('tooltiptext', tooltip);
+ menuitem.setAttribute('disabled', "");
+ }
+
+ //add separators between templates, normal saves, and autosaves
+ if (lastSaveType != -1 && lastSaveType !== savedForm["savetype"] && savedForm["savetype"] != Lazarus.FORM_TYPE_STALE_AUTOSAVE){
+ var newSeparator = document.createElement("menuseparator");
+ newSeparator.setAttribute('lazarus-dynamic-submenu', "true");
+ menu.insertBefore(newSeparator, separator);
+ }
+ lastSaveType = savedForm["savetype"];
+ menu.insertBefore(menuitem, separator);
+ }
+}
+
+
+/**
+* return the period (in seconds) that forms should be saved for
+*/
+Lazarus.getExpiryTime = function(){
+
+ if (Lazarus.getExtPref("expireSavedForms")){
+ return (Lazarus.getExtPref("expireSavedFormsInterval") * Lazarus.getExtPref("expireSavedFormsUnit") * 60);
+ }
+ else {
+ return 0;
+ }
+}
+
+/**
+* retrieves a list of details about a form from the database.
+*/
+Lazarus.getFormInfo = function(form, fields){
+ var formId = Lazarus.getFormId(form);
+
+ var expires = Lazarus.getExpiryTime();
+ var cutoffTime = (expires > 0) ? (Lazarus.timestamp() - expires) : 0;
+
+ fields = fields || "*";
+ //we need to get ALL the templates for this form first
+ var forms = [];
+ forms = forms.concat(Lazarus.db.rs("SELECT "+ fields +" FROM forms WHERE formid = ?1 AND created >= ?2 AND savetype = "+ Lazarus.FORM_TYPE_TEMPLATE +" ORDER BY savetype DESC, created DESC", formId, cutoffTime));
+ //and then the autosaves
+ var maxAutoSaves = Lazarus.getExtPref("maxAutosavesPerForm", 3);
+ forms = forms.concat(Lazarus.db.rs("SELECT "+ fields +" FROM forms WHERE formid = ?1 AND created >= ?2 AND savetype IN ("+ Lazarus.FORM_TYPE_AUTOSAVE +","+ Lazarus.FORM_TYPE_STALE_AUTOSAVE +") ORDER BY created DESC LIMIT ?3", formId, cutoffTime, maxAutoSaves));
+ //and then add normal saved forms
+ var maxForms = Lazarus.getExtPref("maxSavesPerForm", 10);
+ forms = forms.concat(Lazarus.db.rs("SELECT "+ fields +" FROM forms WHERE formid = ?1 AND created >= ?2 AND savetype = "+ Lazarus.FORM_TYPE_NORMAL +" ORDER BY created DESC LIMIT ?3", formId, cutoffTime, maxForms));
+ return forms;
+}
+
+
+/**
+* return a fieldInfo object filled with information about the given field
+*/
+Lazarus.fieldInfo = function(ele){
+
+ var getPassword = Lazarus.getExtPref("savePasswordFields");
+ var getHidden = Lazarus.getExtPref("saveHiddenFields");
+
+ var info = {};
+ info.name = ele.getAttribute("name");
+ info.type = Lazarus.getElementType(ele);
+ info.value = Lazarus.getElementValue(ele);
+ if (info.type == "password" && !getPassword){
+ info.value = null;
+ }
+ if (info.type == "hidden" && !getHidden){
+ info.value = null;
+ }
+
+ switch (info.type){
+ case "text":
+ case "textarea":
+ case "file":
+ case "password":
+ case "iframe":
+ if (info.value && Lazarus.trim(info.value)){
+ info.text = Lazarus.trim(info.value);
+ }
+ break;
+
+ default:
+ }
+
+ return info;
+}
+
+/**
+* clean HTML tags and entities from an html string
+*/
+Lazarus.cleanText = function(text){
+ return text.replace(/<[^>]*>/g, ' ').replace(/&\w+;?/g, ' ').replace(/ +/g, ' ');
+}
+
+/**
+* return a formInfo object filled with details about a form
+*/
+Lazarus.formInfo = function(form){
+
+ var info = {};
+ info.version = Lazarus.FORM_INFO_VERSION;
+ info.formid = Lazarus.getFormId(form);
+ info.action = Lazarus.getUrlPage(form.action || form.ownerDocument.URL);
+ info.origURL = form.ownerDocument.URL;
+ info.domain = Lazarus.getDomainFromElement(form);
+ info.method = (form.method && form.method.toLowerCase()) == "post" ? "post" : "get";
+ info.enctype = form.enctype ? form.enctype.toLowerCase() : '';
+ info.fields = {};
+ info.formtext = [];
+ info.textLen = 0;
+ for (var i=0; i<form.elements.length; i++){
+ var ele = form.elements[i];
+ var name = ele.getAttribute("name");
+ if (name){
+ switch (Lazarus.getElementType(ele)){
+ case "text":
+ case "textarea":
+ case "file":
+ case "radio":
+ case "checkbox":
+ case "select":
+ case "password":
+ case "hidden":
+ var fieldInfo = Lazarus.fieldInfo(ele);
+ info.fields[name] = info.fields[name] || [];
+ info.fields[name].push(fieldInfo);
+
+ if (fieldInfo.text){
+ info.formtext.push(fieldInfo.text);
+ info.textLen += fieldInfo.text.length;
+ }
+ break;
+
+ default:
+ //ignore all other types
+ }
+ }
+ //support for ajax textareas
+ else if (form.isTextarea && Lazarus.getElementType(ele) == "textarea"){
+ var fieldInfo = Lazarus.fieldInfo(ele);
+ info.isTextarea = true;
+ info.fields["textarea"] = [fieldInfo];
+ if (fieldInfo.text){
+ info.formtext.push(fieldInfo.text);
+ info.textLen += fieldInfo.text.length;
+ }
+ }
+ //support for ajax textareas
+ else if (form.isIframe && Lazarus.getElementType(ele) == "iframe"){
+ var fieldInfo = Lazarus.fieldInfo(ele);
+ info.isIframe = true;
+ info.fields["iframe"] = [fieldInfo];
+ if (fieldInfo.text){
+ info.formtext.push(fieldInfo.text);
+ info.textLen += fieldInfo.text.length;
+ }
+ }
+ }
+ //we need to add any WYSIWYG iframes into the form as well
+ var iframes = Lazarus.getEditableIframes(form);
+ if (iframes.length > 0){
+ info.fields[Lazarus.IFRAME_NAME] = [];
+ info.isIframe = true;
for (var i=0; i<iframes.length; i++){
+ var name = Lazarus.IFRAME_NAME;
+ var fieldInfo = Lazarus.fieldInfo(iframes[i]);
+
+ info.fields[name] = info.fields[name] || [];
+ info.fields[name].push(fieldInfo);
+
+ if (fieldInfo.text){
+ info.formtext.push(fieldInfo.text);
+ info.textLen += fieldInfo.text.length;
+ }
+ }
+ }
+
+ info.formtext = Lazarus.trim(info.formtext.join("\n\n"));
+
+ return info;
+}
+
+
+/**
+* return an array of editable iframes found within the given node
+*/
+Lazarus.getEditableIframes = function(ele){
+ var editableIframes = [];
+ if (ele && ele.getElementsByTagName){
+ var iframes = ele.getElementsByTagName('iframe');
+
+ for (var i=0; i<iframes.length; i++){
+ if (Lazarus.isEditableDoc(iframes[i].contentWindow.document)){
+ editableIframes.push(iframes[i]);
+ }
+ //better get iframes within the iframes as well
+ if (iframes[i].contentWindow.document.body){
+ editableIframes = editableIframes.concat(Lazarus.getEditableIframes(iframes[i].contentWindow.document.body));
+ }
+ }
+ }
+ return editableIframes;
+}
+
+/**
+* return TRUE if the given document is in edit mode
+*/
+Lazarus.isEditableDoc = function(doc){
+ return (doc && (doc.designMode == "on" || (doc.body && doc.body.contentEditable === "true")));
+}
+
+/**
+* return the base domain (wikipedia.org) given a full domain (en.wikipedia.org)
+*/
+Lazarus.getBaseDomain = function(domain){
+
+ //if domain is an ip address return that
+ var regexIp = /\d+\.\d+\.\d+\.\d+$/
+ var m = domain.match(regexIp);
+ if (m){return m[0]}
+
+ //known top level domains (TLDs) including country specific ones
+
+ //http://en.wikipedia.org/wiki/Country_code_top-level_domain
+ //all domains *should* either end in a 2 letter tld (google.co.nz, google.com.au) or none (google.com) for american sites
+ //that should be preceeded by the generic tld
+ //and then the basedomain
+ var regex = /[^\.]+\.[^\.]+(\.\w{2})?$/;
+ var m = domain.match(regex);
+ //if we cant figure it out then return the whole domain
+ return m ? m[0] : domain;
+}
+
+
+
+/**
+* restores a form to the given state
+*/
+Lazarus.restoreForm = function(form, formInfo){
+
+ //attempt to restore the saved fields for this form
+ var restorePassword = Lazarus.getExtPref("savePasswordFields");
+ var restoreHidden = Lazarus.getExtPref("saveHiddenFields");
+
+ //try and recover as many fields as possible for this form.
+ var iRestored = 0;
+ var iNamedElements = 0;
+ var formFields = {};
+ for (var i=0; i<form.elements.length; i++){
+ var ele = form.elements[i];
+ var name = ele.getAttribute("name");
+ var eleType = Lazarus.getElementType(ele);
+ var eleValue = Lazarus.getElementValue(ele);
+
+ if (name){
+ switch (eleType){
+ case "text":
+ case "textarea":
+ case "file":
+ case "radio":
+ case "checkbox":
+ case "select":
+ case "password":
+ case "hidden":
+ //try to restore this element
+ iNamedElements++;
+ //do we have an match for this element in our saved form
+ if (formInfo.fields[name]){
+ for (var j=0; j<formInfo.fields[name].length; j++){
+ var info = formInfo.fields[name][j];
+ if (!info.restored && info.type == eleType){
+ switch (eleType){
+ case "text":
+ case "textarea":
+ case "file":
+ case "select":
+ Lazarus.setElementValue(ele, info.value);
+ iRestored++;
+ break;
+
+ case "radio":
+ case "checkbox":
+ if (info.value && (eleValue.valueAttr == info.value.valueAttr)){
+ Lazarus.setElementValue(ele, info.value.checked);
+ iRestored++;
+ }
+ break;
+
+ case "password":
+ if (restorePassword){
+ Lazarus.setElementValue(ele, info.value);
+ }
+ iRestored++;
+ break;
+
+ case "hidden":
+ if (restoreHidden){
+ Lazarus.setElementValue(ele, info.value);
+ }
+ iRestored++;
+ break;
+
+ default:
+ //no need to restore
+ iRestored++;
+ }
+ }
+ }
+ }
+ default:
+ //ignore other types of elements
+ }
+ }
+ else if (form.isTextarea && Lazarus.isTextarea(ele)){
+ var info = formInfo.fields["textarea"][0];
+ Lazarus.setElementValue(ele, info.value);
+ iRestored++;
+ iNamedElements++;
+ }
+ else if (form.isIframe && ele.tagName == "iframe"){
+ var info = formInfo.fields["iframe"][0];
+ Lazarus.setElementValue(ele, info.value);
+ iRestored++;
+ iNamedElements++;
+ }
+ }
+ //try and restore any WYSIWYG editors as well
+ var iframes = Lazarus.getEditableIframes(form);
+ if (iframes && formInfo.fields[Lazarus.IFRAME_NAME]){
+ for (var i=0; i<iframes.length; i++){
+ var info = formInfo.fields[Lazarus.IFRAME_NAME][i];
+ if (info){
+ Lazarus.setElementValue(iframes[i], info.value);
+ }
+ }
+ }
+
+
+ //check for errors
+ if (iRestored == 0){
+ alert(Lazarus.getString("error.restore.none"));
+ return false;
+ }
+ //tell them if we couldn't restore some fields
+ else if (iRestored < iNamedElements){
+ //only show the partial restore message if the form is not an old style form
+ if (formInfo.version && formInfo.version >= 1){
+ alert(Lazarus.getString("error.restore.partial"));
+ }
+ return (iRestored / iNamedElements);
+ }
+ else {
+ return true;
+ }
+}
+
+/**
+* handle the onselectmenuitem event
+*/
+Lazarus.onRestoreFormMenuItem = function(menuitem){
+
+ if (!Lazarus.canDecrypt()){
+ Lazarus.debug("Unable to restore form, password required");
+ Lazarus.showNotificationBox("password-required");
+ return;
+ }
+
+ var id = parseInt(menuitem.getAttribute("lazarus-forms-id"));
+
+ Lazarus.debug("Attempting to restore form "+ id);
+ var form = Lazarus.findFormFromElement(gContextMenu.target, "form");
+ if (form){
+ var row = Lazarus.db.getRow("SELECT * FROM forms WHERE id = ?1", id);
+ if (row){
+ var formInfo;
+ try {
+ formInfo = Lazarus.JSON.decode(Lazarus.decrypt(row["forminfo"]));
+ }
+ catch(e){
+ Lazarus.error(e);
+ }
+
+ if (formInfo){
+ //if we fully restore a form, then show the "donate" popup
+ if (Lazarus.restoreForm(form, formInfo) === true){
+ Lazarus.incPref("extensions.lazarus.restoreFormCount");
+ if ((formInfo.textLen > Lazarus.MIN_TEXT_NEEDED_TO_SHOW_NOTIFICATION) && Lazarus.getPref("extensions.lazarus.showDonateNotification") && Lazarus.getPref("extensions.lazarus.restoreFormCount", 0) > 3){
+ var msgId = Math.floor(Math.random() * 4);
+ Lazarus.showNotificationBox("form-restored", Lazarus.getFormRestoredNotificationText(formInfo.textLen, msgId), "msgid-"+ msgId);
+ }
+ }
+ }
+ else {
+ alert(Lazarus.getString("error.form.db.corrupt"));
+ }
+ }
+ else {
+ alert(Lazarus.getString("error.form.not.found"));
+ }
+ }
+ else {
+ alert(Lazarus.getString("error.form.object.not.found"));
+ }
+}
+
+/**
+* return TRUE if form is a form we should save
+*/
+Lazarus.shouldSaveForm = function(form){
+ //only save forms on file/http/https sites
+ var doc = form.ownerDocument;
+ if (doc && doc instanceof HTMLDocument && doc.URL && /^(file|http|https):/.test(doc.URL) && !Lazarus.isPageDisabled(doc.URL) && !Lazarus.isDisabledByPrivateBrowsing()){
+ return (Lazarus.isSearchForm(form)) ? Lazarus.getPref("extensions.lazarus.saveSearchForms") : true;
+ }
+ else {
+ return false;
+ }
+}
+
+/**
+* return TRUE if we should be saving this info
+*/
+Lazarus.shouldSaveEditorInfo = function(info){
+ if (Lazarus.isPageDisabled(info.url)){
+ return false;
+ }
+ else if (Lazarus.isDisabledByPrivateBrowsing()){
+ return false;
+ }
+ else {
+ return true;
+ }
+}
+
+
+
+Lazarus.disabledDomains = null;
+
+Lazarus.isPageDisabled = function(url){
+ if (!url && content.document && content.document.URL){
+ url = content.document.URL;
+ }
+ if (url){
+ var domainId = Lazarus.urlToDomainId(url);
+ if (domainId){
+ var domains = Lazarus.getDisabledDomains();
+ return (typeof domains[domainId] !== "undefined") ? domains[domainId] : false;
+ }
+ }
+ return false;
+}
+
+/**
+* return TRUE if lazarus can save forms from this site
+* return FALSE for non-valid sites (eg about:config, chrome://... etc..)
+*/
+Lazarus.isValidSite = function(url){
+ //default to the current documents url
+ if (!url && content.document && content.document.URL){
+ url = content.document.URL;
+ }
+ return (url && Lazarus.urlToDomainId(url)) ? true : false;
+}
+
+Lazarus.getDisabledDomains = function(){
+ var domainlist = Lazarus.getPref('extensions.lazarus.domainBlacklist', '');
+
+ if (!Lazarus.disabledDomains || Lazarus.disabledDomains['__origList__'] != domainlist){
+ //rebuild the list
+ Lazarus.disabledDomains = {
+ '__origList__': domainlist
+ }
+ var domains = domainlist.split(/\s*,\s*/g);
+ for (var i=0; i<domains.length; i++){
+ Lazarus.disabledDomains[domains[i]] = true;
+ }
+ }
+ return Lazarus.disabledDomains;
+}
+
+Lazarus.saveDisabledDomains = function(domainsTable){
+ //convert the table back into an array and save it.
+ var domains = [];
+ for(var domain in domainsTable){
+ if (domainsTable[domain] && /^\w+:/.test(domain)){
+ domains.push(domain);
+ }
+ }
+
+ return Lazarus.setPref('extensions.lazarus.domainBlacklist', domains.join(','));
+}
+
+
+Lazarus.enableForCurrentDomain = function(){
+ //fetch the current domain, and disable Lazarus
+ if (content.document && content.document.URL){
+ var domainId = Lazarus.urlToDomainId(content.document.URL);
+ //and add to disabled domains list.
+
+ var domains = Lazarus.getDisabledDomains();
+ if (!domains[domainId]){
+ domains[domainId] = true;
+ Lazarus.saveDisabledDomains(domains);
+ Lazarus.debug('Domain disabled ['+ domainId +']');
+ }
+ else {
+ //should already be disabled!
+ Lazarus.warning('Domain is already disabled ['+ domainId +']');
+ }
+
+ //and refresh the statusbar icon
+ Lazarus.refreshIcon();
+ }
+ else {
+ alert(Lazarus.getString("error.no.document"));
+ }
+}
+
+
+/**
+* enables or disabled on the current website
+*/
+Lazarus.toggleCurrentDomain = function(enable){
+ //fetch the current domain, and disable Lazarus
+ if (content.document && content.document.URL){
+ var domains = Lazarus.getDisabledDomains();
+ var domainId = Lazarus.urlToDomainId(content.document.URL);
+
+ if (domainId){
+ //add/remove from disabled domains list
+ domains[domainId] = enable ? false : true;
+ Lazarus.saveDisabledDomains(domains);
+ Lazarus.debug('Domain ['+ domainId +'] enabled = '+ enable);
+ //and refresh the statusbar icon
+ Lazarus.refreshIcon();
+ }
+ else {
+ Lazarus.error("Failed to extract domainId from URL ["+ content.document.URL +"]");
+ }
+ }
+ else {
+ alert(Lazarus.getString("error.no.document"));
+ }
+}
+
+
+Lazarus.onStatusbarMenuShowing = function(popupmenu){
+ var validSite = Lazarus.isValidSite();
+ var siteDisabled = Lazarus.isPageDisabled();
+ var isPrivate = Lazarus.isDisabledByPrivateBrowsing();
+
+ Lazarus.$('lazarus-statusbar-menuitem-toggledomain-separator').hidden = !(validSite && !isPrivate);
+ Lazarus.$('lazarus-statusbar-menuitem-enablefordomain').hidden = !(validSite && siteDisabled && !isPrivate);
+ Lazarus.$('lazarus-statusbar-menuitem-disablefordomain').hidden = !(validSite && !siteDisabled && !isPrivate);
+ Lazarus.$('lazarus-statusbar-menuitem-logout').hidden = !(Lazarus.Crypto.isPasswordEntered);
+}
+
+/**
+* return a website identifier
+* http://google.com is different from https://google.com
+*/
+Lazarus.urlToDomainId = function(url){
+ var uri = Lazarus.urlToURI(url);
+ return (uri && uri.scheme && uri.host && (/^(http|https|file)$/.test(uri.scheme))) ? (uri.scheme +":"+ ((uri.port > -1 && uri.port != 80) ? uri.port : '') +'//'+ Lazarus.getBaseDomain(uri.host) +'/') : '';
+}
+
+
+/**
+* return TRUE if form appears to be a search form
+*/
+Lazarus.isSearchForm = function(form){
+ //search forms are forms that have exactly one textbox, and may contain a number of other non-text fields
+ var MAX_TEXT_FIELDS = 1;
+ var MAX_NON_TEXT_FIELDS = 5;
+
+ var iTextFields = 0;
+ var iNonTextFields = 0;
+
+ for (var i=0; i<form.elements.length; i++){
+ var ele = form.elements[i];
+ switch(Lazarus.getElementType(ele)){
+ case "text":
+ iTextFields++;
+ break;
+
+ case "radio":
+ case "checkbox":
+ case "select":
+ iNonTextFields++;
+ break;
+
+ case "file":
+ case "password":
+ case "textarea":
+ return false;
+
+ case "hidden":
+ case "submit":
+ case "reset":
+ case "button":
+ case "image":
+ default:
+ //ignore all other types
+ }
+ }
+ //NOTE: we MUST have a single text field
+ return (iTextFields == MAX_TEXT_FIELDS && iNonTextFields <= MAX_NON_TEXT_FIELDS);
+}
+
+
+/**
+* check a submit event, and saves form details if the event is from a valid form
+*/
+Lazarus.onFormSubmit = function(evt){
+ var form = Lazarus.findFormFromElement(evt.target);
+ //is the form on an html document?
+ if (form && form.ownerDocument instanceof HTMLDocument){
+ Lazarus.stopAutoSaveTimer();
+ Lazarus.saveForm(form, Lazarus.FORM_TYPE_NORMAL);
+ }
+}
+
+/**
+* return "post" of "get" depending on the given forms method
+*/
+Lazarus.getFormMethod = function(form){
+ var method = form.getAttribute("method");
+ return (method && method.toLowerCase() == "post") ? "post" : "get";
+}
+
+/**
+* autosave a form if we change anything about it.
+*/
+Lazarus.onFormChange = function(evt){
+ var form = Lazarus.findFormFromElement(evt.target);
+
+ if (form && form.ownerDocument instanceof HTMLDocument){
+ //I think we should chuck a timer in here, because we might end up saving a lot.
+ Lazarus.restartAutoSaveTimer(form);
+ }
+}
+
+/**
+* check a reset event, and saves form details if the event is from a valid form
+*/
+Lazarus.onFormReset = function(evt){
+ var form = Lazarus.findFormFromElement(evt.target);
+ //is the form on an html document?
+ if (form && form.ownerDocument instanceof HTMLDocument){
+ Lazarus.stopAutoSaveTimer();
+ Lazarus.saveForm(form, Lazarus.FORM_TYPE_AUTOSAVE);
+ }
+}
+
+/**
+* create a searchable index of hashed words
+*/
+Lazarus.indexFormText = function(id, text, url){
+ //and update the full text index,
+ if (!Lazarus.getPref('extensions.lazarus.disableSearch')){
+ //we're going to add domain info into this as well so a user can search for domain fragments too
+ var hashedText = Lazarus.hashText(text);
+
+ //extract the domain from the url
+ var domain = Lazarus.urlToURI(url).host;
+
+ hashedText += ' '+ Lazarus.hashText(domain.replace(/\./g, ' '));
+
+ Lazarus.db.exe("DELETE FROM forms_fulltext WHERE docid = ?1", id);
+ Lazarus.db.exe("INSERT INTO forms_fulltext (docid, hashed_text) VALUES (?1, ?2)", id, hashedText);
+ }
+}
+
+/**
+* save all the named elements in a form to the permanent store
+*/
+Lazarus.saveForm = function(form, formType, templateName, autofill){
+
+ autofill = autofill ? 1 : 0;
+
+ if (Lazarus.shouldSaveForm(form)){
+
+ templateName = templateName || '';
+
+ var info = Lazarus.formInfo(form);
+
+ //dont save empty forms?
+ //No. what about forms with lots of radio buttons (eg multichoice surveys)
+ var infoJSON = Lazarus.JSON.encode(info)
+ var encryptedInfo = Lazarus.encrypt(infoJSON);
+ var encryptedText = Lazarus.encrypt(Lazarus.getMenuItemText(info.formtext));
+ var encryptedFormURL = Lazarus.encrypt(Lazarus.getFormURL(form));
+
+ var infoHash = Lazarus.generateHash(info.fields);
+
+ //each form type needs to be saved differently
+ switch(formType){
+ case Lazarus.FORM_TYPE_AUTOSAVE:
+ case Lazarus.FORM_TYPE_STALE_AUTOSAVE:
+
+ var savedForm = Lazarus.db.getRow("SELECT id, savetype FROM forms WHERE formid = ?1 AND forminfohash = ?2 AND savetype IN ("+ Lazarus.FORM_TYPE_AUTOSAVE +","+ Lazarus.FORM_TYPE_STALE_AUTOSAVE +") LIMIT 1", info.formid, infoHash);
+
+ //add or update the current autosave
+ if (!savedForm){
+ var lastAutoSaveId = Lazarus.db.getInt("SELECT id FROM forms WHERE formid = ?1 AND savetype = "+ Lazarus.FORM_TYPE_AUTOSAVE +" ORDER BY created DESC LIMIT 1", info.formid);
+ var lastAutoSave = (lastAutoSaveId && Lazarus.lastAutoSaveForm && Lazarus.lastAutoSaveForm["formid"] == info.formid) ? Lazarus.lastAutoSaveForm : null;
+ var newId = 0;
+
+ if (lastAutoSave && Lazarus.shouldCreateNewAutosave(info, lastAutoSave)){
+ //if the saved form is smaller than the last saved form by a substantial amount,
+ //create a new restore point.
+ Lazarus.debug("Creating additional autosave point "+ info.formid);
+ Lazarus.db.exe("INSERT INTO forms (formid, created, forminfo, formtext, forminfohash, formurl, text_length, savetype) \
+ VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, "+ Lazarus.FORM_TYPE_AUTOSAVE +")", info.formid, Lazarus.timestamp(), encryptedInfo, encryptedText, infoHash, encryptedFormURL, info.textLen);
+ newId = Lazarus.db.getInt("SELECT MAX(id) FROM forms");
+ }
+ else if (lastAutoSave){
+ Lazarus.debug("Updating autosave form "+ info.formid);
+ Lazarus.db.exe("UPDATE forms SET created = ?1, forminfo = ?2, formtext = ?3, forminfohash = ?4, formurl = ?5, text_length = ?6 \
+ WHERE id = ?7", Lazarus.timestamp(), encryptedInfo, encryptedText, infoHash, encryptedFormURL, info.textLen, lastAutoSaveId);
+ newId = lastAutoSaveId;
+ }
+ else {
+ Lazarus.debug("Creating new autosave form "+ info.formid);
+ Lazarus.db.exe("INSERT INTO forms (formid, created, forminfo, formtext, forminfohash, formurl, text_length, savetype) \
+ VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, "+ Lazarus.FORM_TYPE_AUTOSAVE +")", info.formid, Lazarus.timestamp(), encryptedInfo, encryptedText, infoHash, encryptedFormURL, info.textLen);
+ newId = Lazarus.db.getInt("SELECT MAX(id) FROM forms");
+ }
+
+ //update the full text search as well
+ Lazarus.indexFormText(newId, info.formtext, info.origURL);
+
+ //and save this forminfo as the last autosave point
+ Lazarus.lastAutoSaveForm = info;
+ }
+ else {
+ Lazarus.debug("No need to autosave form, form already exists: "+ info.formid);
+ }
+
+ break;
+
+ case Lazarus.FORM_TYPE_NORMAL:
+ //delete all autosaves
+ Lazarus.removeForms(Lazarus.db.getColumn("SELECT id FROM forms WHERE formid = ?1 AND savetype IN ("+ Lazarus.FORM_TYPE_AUTOSAVE +","+ Lazarus.FORM_TYPE_STALE_AUTOSAVE +")", info.formid));
+
+ //if this form already exists, delete it
+ Lazarus.removeForms(Lazarus.db.getColumn("SELECT id FROM forms WHERE formid = ?1 AND forminfohash = ?2 AND savetype = "+ Lazarus.FORM_TYPE_NORMAL, info.formid, infoHash));
+
+ //and save this form
+ Lazarus.db.exe("INSERT INTO forms (formid, created, forminfo, formtext, forminfohash, formname, formurl, text_length, savetype, autofill) \
+ VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, 0)",
+ info.formid, Lazarus.timestamp(), encryptedInfo, encryptedText, infoHash, templateName, encryptedFormURL, info.textLen, formType);
+ Lazarus.debug("form saved ["+ info.formid +"]");
+
+ //update the full text search as well
+ var newId = Lazarus.db.getInt("SELECT MAX(id) FROM forms");
+ Lazarus.indexFormText(newId, info.formtext, info.origURL);
+ break;
+
+ case Lazarus.FORM_TYPE_TEMPLATE:
+ //if another template with the same name exists, delete it
+ Lazarus.removeForms(Lazarus.db.getColumn("SELECT id FROM forms WHERE formname = ?1 AND savetype = "+ Lazarus.FORM_TYPE_TEMPLATE, templateName));
+
+ //likewise we are not allowed to have more then one autofill for the same form.
+ if (autofill){
+ Lazarus.db.exe("UPDATE forms SET autofill = 0 WHERE formid = ?1", info.formid);
+ }
+
+ //and save this form
+ Lazarus.db.exe("INSERT INTO forms (formid, created, forminfo, formtext, forminfohash, formname, formurl, text_length, savetype, autofill) \
+ VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10)",
+ info.formid, Lazarus.timestamp(), encryptedInfo, encryptedText, infoHash, templateName, encryptedFormURL, info.textLen, formType, autofill);
+
+ var newId = Lazarus.db.getInt("SELECT MAX(id) FROM forms");
+ Lazarus.indexFormText(newId, info.formtext, info.origURL);
+ Lazarus.debug("template saved ["+ info.formid +"]");
+ break;
+
+ default:
+ Lazarus.error(Error("Unknown form type ["+ row["formtype"] +"]"));
+
+ }
+ Lazarus.startCleanupTimer();
+ }
+ else {
+ Lazarus.debug("Form not saved: invalid form");
+ }
+}
+
+
+/**
+* remove forms from the database
+*/
+Lazarus.removeForms = function(ids){
+ Lazarus.debug("removing forms", ids);
+ if (!ids || (Lazarus.isArray(ids) && ids.length === 0)){
+ return;
+ }
+
+ if (!Lazarus.isArray(ids)){
+ ids = [ids];
+ }
+ //make sure the ids are safe.
+ for(var i=0; i<ids.length; i++){
+ ids[i] = parseInt(ids[i]);
+ }
+ Lazarus.db.exe("DELETE FROM forms WHERE id IN ("+ ids.join(",") +")");
+ Lazarus.db.exe("DELETE FROM forms_fulltext WHERE docid IN ("+ ids.join(",") +")");
+
+ Lazarus.debug(ids.length +" forms removed");
+}
+
+
+/**
+* return the URL of the page this form is currently on, but leave any query info
+*/
+Lazarus.getFormURL = function(form){
+ //we'll strip any anchor tags from the URL
+ return form.ownerDocument.URL.replace(/#.*/, '');
+}
+
+/**
+* return an integer representing how different one form is from another
+* 0 being identical
+* 100 being 100 characters/values are different
+*/
+Lazarus.shouldCreateNewAutosave = function(newForm, oldForm){
+
+ //number of characters to ignore when calculating if the form has changed substantially
+ var TEXT_DIFFERENCE = 32; //
+
+ if (oldForm.formtext.length > (TEXT_DIFFERENCE * 2)){
+ var str = oldForm.formtext.substr(0, oldForm.formtext.length - TEXT_DIFFERENCE);
+ //if new form is a continuation of old form (ie the first (X - TEXT_DIFFERENCE) characters are still the same),
+ //then overwrite current autosave,
+ //otherwise create a new autosave.
+ return (newForm.formtext.indexOf(str) == -1);
+ }
+ //form contains little text, overwrite the current autosave
+ else {
+ return false;
+ }
+}
+
+/**
+* automatically save the form the user is working on.
+*/
+Lazarus.autoSaveForm = function(){
+ //does the form still exist?
+ if (Lazarus.currAutoSaveForm && !Lazarus.isContextMenuShowing){
+ Lazarus.saveForm(Lazarus.currAutoSaveForm, Lazarus.FORM_TYPE_AUTOSAVE);
+ }
+}
+
+/**
+* start the cleanup timer
+*/
+Lazarus.startCleanupTimer = function(){
+
+ Lazarus.stopCleanupTimer();
+
+ if (Lazarus.getExpiryTime()){
+ Lazarus.cleanupSavedFormsTimer = setInterval(Lazarus.cleanupSavedForms, 1000 * 60);
+ }
+}
+
+/**
+* stop the cleanup timer
+*/
+Lazarus.stopCleanupTimer = function(){
+ if (Lazarus.cleanupSavedFormsTimer){
+ clearInterval(Lazarus.cleanupSavedFormsTimer);
+ }
+}
+
+/**
+* remove old forms from the database
+*/
+Lazarus.cleanupSavedForms = function(){
+
+ //dont cleanup any forms if a user might be trying to retore them
+ if (Lazarus.isContextMenuShowing){return}
+
+ var expires = Lazarus.getExpiryTime();
+
+ if (expires > 0){
+ //add a couple of minutes to the expiry time, so we don't accidentally
+ //remove a form whilst we are current restoring it.
+ var cutoffTime = Lazarus.timestamp() - (expires + 120);
+ var ids = Lazarus.db.getColumn("SELECT id FROM forms WHERE created < ?1 AND savetype != "+ Lazarus.FORM_TYPE_TEMPLATE, cutoffTime);
+ Lazarus.removeForms(ids);
+
+ var ids = Lazarus.db.getColumn("SELECT id FROM textdata WHERE created < ?1", cutoffTime);
+ if (ids.length > 0){
+ Lazarus.db.exe("DELETE FROM textdata WHERE id IN ("+ ids.join(",") +")");
+ Lazarus.db.exe("DELETE FROM textdata_fulltext WHERE docid IN ("+ ids.join(",") +")");
+ }
+ }
+ else {
+ Lazarus.stopCleanupTimer();
+ }
+}
+
+/**
+* return an identifier for a url
+*/
+Lazarus.getUrlPage = function(url){
+ var uri = Lazarus.urlToURI(url);
+ if (uri){
+ return uri.scheme +"://"+ uri.hostPort + uri.path.replace(/[\?#].*$/, '');
+ }
+ //for testing on file systems
+ else if ((Lazarus.getExtPref("debugMode") >= 3) && form.action.match(/^file:\/\//i)){
+ return "file://just/testing";
+ }
+ else {
+ return null;
+ }
+}
+
+/**
+* return the user editable fields within a form.
+*/
+Lazarus.getEditableFields = function(form){
+ var fields = [];
+ var saveHidden = Lazarus.getExtPref("saveHiddenFields");
+ var savePassword = Lazarus.getExtPref("savePasswordFields");
+
+ for (var i=0; i<form.elements.length; i++){
+ var ele = form.elements[i];
+ //ignore fields with no name (they dont get submitted to the server)
+ if (ele.getAttribute("name")){
+ switch(Lazarus.getElementType(ele)){
+ case "text":
+ case "textarea":
+ case "file":
+ case "radio":
+ case "checkbox":
+ case "select":
+ fields.push(ele);
+ break;
+
+ case "password":
+ if (savePassword){
+ fields.push(ele);
+ }
+ break;
+
+ case "hidden":
+ if (saveHidden){
+ fields.push(ele);
+ }
+ break;
+
+ //ignore buttons
+ case "submit":
+ case "reset":
+ case "button":
+ case "image":
+ //and unknown elements
+ default:
+ //unknown element type
+ break;
+ }
+ }
+ }
+ return fields;
+}
+
+
+/**
+* return TRUE if element is a textarea
+*/
+Lazarus.isTextarea = function(ele){
+ return (ele && ele.nodeName && ele.nodeName.toLowerCase() == "textarea");
+}
+
+
+/**
+* return TRUE if element is an iframe
+*/
+Lazarus.isIframe = function(ele){
+ return (ele && ele.nodeName && ele.nodeName.toLowerCase() == "iframe");
+}
+
+/**
+* generate a formid for this form
+*/
+Lazarus.getFormId = function(form){
+
+ //if this form is from the Lazarus Form Recovery page, then use the id specified in the form
+ if (Lazarus.isDocRecoveryForm(form.ownerDocument)){
+ return form.getAttribute("lazarus-form-id");
+ }
+
+
+ //forms with no "action" attribute default to sending data to the current page
+ var action = form.action || form.ownerDocument.URL;
+ var uri = Lazarus.urlToURI(action);
+
+ var formId = (uri && uri.host) ? Lazarus.getBaseDomain(uri.host) : '';
+ //debugging (on local file system)
+ if (!formId && (Lazarus.getExtPref("debugMode") >= 3) && action.match(/^file:\/\//i)){
+ formId = "file://just/testing";
+ }
+
+ //and point to the same place
+ if (formId){
+
+ //support for ajax textareas
+ if (form.isTextarea){
+ formId += "<textarea>";
+ }
+ //and iframes not contained within forms
+ else if (form.isIframe){
+ formId += "<iframe>";
+ }
+ //#7: Not saving trac "comment" forms
+ //sometimes forms will add additional hidden fields
+
+ //if the form has a name, formId , or class we'll add that as well.
+ //no not class. If a form has an error the classname might be changed to highlight
+ //the error to the user
+
+ //we will generate an formId from the editable fields name property
+ //only fields with a name property are submitted.
+ else if (form.name || form.id){
+ formId += form.name ? ("@"+ form.name) : "";
+ //KLUDGE: gmails email form changes the id of the form every time you start a new email
+ formId += form.id && (form.ownerDocument.domain.indexOf("google") == -1) ? ("#"+ form.id) : "";
+ }
+ //fall back to using an identifier generated form the elements within the page
+ else {
+ var names = [];
+ for (var i=0; i<form.elements.length; i++){
+ var ele = form.elements[i];
+ var name = ele.getAttribute("name");
+ if (name){
+ switch(Lazarus.getElementType(ele)){
+ case "text":
+ case "textarea":
+ case "file":
+ case "radio":
+ case "checkbox":
+ case "select":
+ names.push(name.toLowerCase());
+ }
+ }
+ }
+
+ //sort and remove duplicate names
+ names.sort();
+ names = Lazarus.arrayUnique(names);
+
+ formId += "["+ names.join(",") +"]";
+ }
+
+ //#48: including the domain within the formId constitutes a privacy risk.
+ formId = Lazarus.md5(formId);
+ return formId;
+ }
+ else {
+ Lazarus.warning("Lazarus: Unable to convert url to uri ["+ action +"]");
+ return '';
+ }
+}
+
+/**
+* return a new array containing members of the given array with no duplicates
+*/
+Lazarus.arrayUnique = function(arr){
+ var newArr = [];
+ for (var i=0; i<arr.length; i++){
+ if (!Lazarus.inArray(arr[i], newArr)){
+ newArr.push(arr[i]);
+ }
+ }
+ return newArr;
+}
+
+/**
+* return a user friendly string stating the elapsed time in seconds, minutes, hours or days
+*/
+Lazarus.getTimeString = function(sec){
+
+ if (sec < 1){sec = 1}
+
+ var units = {
+ "day" : 60 * 60 * 24,
+ "hour" : 60 * 60,
+ "minute": 60,
+ "second": 1
+ }
+
+ for(var unit in units){
+ if (sec >= units[unit]){
+ var numUnits = Math.floor(sec / units[unit]);
+ if (numUnits == 1){
+ return Lazarus.getString("elapsed."+ unit);
+ }
+ else {
+ return Lazarus.getString("elapsed."+ unit +"s", numUnits);
+ }
+ }
+ }
+ //should never get here
+ return Lazarus.getString("elapsed.second");
+}
+
+/**
+* returns an elements value
+*/
+Lazarus.getElementValue = function(ele){
+ switch(Lazarus.getElementType(ele)){
+ //text fields
+ case "text":
+ case "password":
+ case "textarea":
+ case "file":
+ case "hidden":
+ return ele.value;
+
+ case "radio":
+ case "checkbox":
+ return {
+ "valueAttr": ele.value,
+ "checked": ele.checked
+ }
+
+ //buttons
+ case "submit":
+ case "reset":
+ case "button":
+ case "image":
+ return ele.value;
+
+ case "select":
+ //select boxes have the option to allow multiple selections
+ var selected = [];
+ if (ele.options){
+ for (var i=0; i<ele.options.length; i++){
+ if (ele.options[i].selected){
+ selected.push(ele.options[i].value);
+ }
+ }
+ }
+ return selected;
+
+ case "iframe":
+ var doc = ele.contentWindow.document;
+ return (doc && doc.body && doc.body.innerHTML) ? doc.body.innerHTML : '';
+
+ default:
+ //unknown element type
+ return null;
+ }
+}
+
+/**
+* returns an elements value
+*/
+Lazarus.setElementValue = function(ele, value){
+ switch(Lazarus.getElementType(ele)){
+ //text fields
+ case "text":
+ case "password":
+ case "textarea":
+ case "file":
+ case "hidden":
+ ele.value = value;
+ break;
+
+ case "radio":
+ case "checkbox":
+ ele.checked = value;
+ break;
+
+ //buttons
+ case "submit":
+ case "reset":
+ case "button":
+ case "image":
+ ele.value = value;
+ break;
+
+ case "select":
+ //select boxes have the option to allow multiple selections
+ var selected = [];
+ if (ele.options){
+ //bugfix: RT: 101284
+ //inArray is taking too long for large (10,000+) select boxes, so we'll convert the values array into
+ //a hash table instead
+ //hmmm, the problem is not actually in the inArray function
+ //but rather the line
+ //ele.options[i].selected = table[ele.options[i].value] ? true : false;
+ // it appears that a lot goes on behind the scenes when a selectbox option
+ //is set, even if the value doesn't change.
+ var table = Lazarus.arrayToHashTable(value);
+ for (var i=0; i<ele.options.length; i++){
+ var selectOption = table[ele.options[i].value] ? true : false;
+ if (ele.options[i].selected != selectOption){
+ ele.options[i].selected = selectOption;
+ }
+ }
+ }
+ break;
+
+ case "iframe":
+ var doc = ele.contentWindow.document;
+ if (doc && doc.body){
+ doc.body.innerHTML = value;
+ }
+ break;
+
+ default:
+ //unknown element type
+ break;
+ }
+}
+
+
+/**
+* return a string explaining the type of a form element
+* differentiates between different input types.
+*/
+Lazarus.getElementType = function(ele){
+ if (ele.nodeName.toLowerCase() == "input"){
+ return ele.type.toLowerCase();
+ }
+ else {
+ return ele.nodeName.toLowerCase();
+ }
+}
+
+/**
+* return a fake form object
+*/
+Lazarus.createFakeForm = function(ele, type){
+ return {
+ ownerDocument: ele.ownerDocument,
+ elements: [ele],
+ action: Lazarus.urlToDomainId(ele.ownerDocument.URL) || '',
+ isTextarea: (type == "textarea"),
+ isIframe: (type == "iframe"),
+ isFakeForm: true
+ };
+}
+
+Lazarus.getParentIframe = function(ele){
+ var win = ele.ownerDocument.defaultView.frameElement;
+ var win = doc && doc.defaultView;
+ return win.frameElement;
+}
+
+
+/**
+* return the first content editable iframe or textarea from a given element
+*/
+Lazarus.findEditorFromElement = function(ele){
+
+ while(ele){
+ if (Lazarus.isTextarea(ele)){
+ return ele;
+ }
+ else if (Lazarus.isEditableDoc(ele.ownerDocument) && ele.ownerDocument.defaultView.frameElement){
+ return ele.ownerDocument.defaultView.frameElement;
+ }
+ else {
+ ele = ele.parentNode;
+ }
+ }
+}
+
+/**
+* finds a parent form from this element
+*/
+Lazarus.findFormFromElement = function(ele){
+
+ var iframe = false;
+
+ while(ele){
+ if (ele.nodeName && ele.nodeName.toLowerCase() == "form"){
+ return ele;
+ }
+ else if (ele.form && ele.form.nodeName.toLowerCase() == "form"){
+ return ele.form;
+ }
+ //add support for AJAX textareas that are not contained within a form.
+ else if (Lazarus.isTextarea(ele)){
+ //we're going to build a fake form here, that contains the , so the rest of the code can handle
+ return Lazarus.createFakeForm(ele, "textarea");
+ }
+ else if (Lazarus.isEditableDoc(ele.ownerDocument)){
+ iframe = ele.ownerDocument.defaultView.frameElement;
+ //move to parent iframe
+ //Hmmm. Problem exists when the editable iframe is contained within another iframe (eg FCKEditor)
+ //we'll need to iterate up through all the frames. But we also need to check if this frame contains the form element
+ ele = iframe;
+ }
+ else if (ele.parentNode && ele.parentNode.tagName){
+ ele = ele.parentNode;
+ }
+ //an iframe has been detected, but no form was found in this iframe
+ //so move up to the containing frame (if any)
+ else if (iframe){
+ ele = ele.ownerDocument.defaultView.frameElement;
+ }
+ else {
+ return null;
+ }
+ }
+
+ if (iframe){
+ //we have an editable iframe that is NOT contained within a form!
+ //probably some type of AJAX form
+ //we'll need to build a fake form and the iframe in there
+ return Lazarus.createFakeForm(iframe, "iframe");
+ }
+
+ return null;
+}
+/**
+* finds the first node of type nodeName from ele up the dom tree
+*/
+Lazarus.findParent = function(ele, nodeName){
+ nodeName = nodeName.toLowerCase();
+ while(ele){
+ if (ele.nodeName && ele.nodeName.toLowerCase() == nodeName){
+ return ele;
+ }
+ else {
+ ele = ele.parentNode;
+ }
+ }
+ return null;
+}
+
+/**
+*
+*/
+Lazarus.onStatusbarImageClick = function(evt){
+
+ var state = Lazarus.getState()
+
+ if (state == Lazarus.STATE_DISABLED){
+ evt.preventDefault();
+ Lazarus.openGenerateKeysDialog();
+ if (Lazarus.reloadKeys()){
+ Lazarus.enable();
+ }
+ return false;
+ }
+
+ //only on left click
+ if (evt.which == 1){
+ switch (state){
+ case Lazarus.STATE_ENABLED:
+ Lazarus.openStatusbarMenu(evt);
+ return;
+
+ case Lazarus.STATE_PASSWORD_REQUIRED:
+ Lazarus.showEnterPasswordDialog();
+ return
+
+ case Lazarus.STATE_PRIVATE_BROWSING:
+ alert(Lazarus.getString('private.browsing.mode'));
+ return;
+
+ case Lazarus.STATE_GENERATING_KEYS:
+ return;
+
+ case Lazarus.STATE_DISABLED_FOR_DOMAIN:
+ alert(Lazarus.getString('status.disabledfordomain'))
+ return;
+
+ case Lazarus.STATE_UNINITALIZED:
+ default:
+ Lazarus.error("Not initalized");
+ return
+ }
+ }
+}
+
+
+/**
+* show the statusbar menu
+*/
+Lazarus.openStatusbarMenu = function(evt){
+ Lazarus.$('lazarus-statusbar-menupopup').openPopup(Lazarus.$('lazarus-statusbarpanel'), 'before_end', 0, 0, true);
+}
+
+/**
+* open the options dialog
+*/
+Lazarus.openOptionsDialog = function(optionsPane){
+ optionsPane = optionsPane || null;
+
+ var features = "chrome,titlebar,toolbar,centerscreen,modal";
+ features += Lazarus.getPref("browser.preferences.animateFadeIn", false) ? "" : ",resizable";
+
+ window.open("chrome://lazarus/content/options.xul", "LazarusOptions", features, optionsPane);
+}
+
+/**
+* open the about dialog
+*/
+Lazarus.openAboutDialog = function(){
+ window.open("chrome://lazarus/content/about.xul", "LazarusAbout", "chrome,titlebar,toolbar,centerscreen,modal,resizable");
+}
+
+/**
+* open the about dialog
+*/
+Lazarus.openLazarusWebsite = function(page){
+ page = page || '';
+ Lazarus.openURL(Lazarus.website + page, true, true);
+}
+
+/**
+* open a URL in the current browser
+*/
+Lazarus.openURL = function(url, newTab, selectTab){
+ var browser = document.getElementById('content');
+
+ if (newTab){
+ var tab = browser.addTab(url);
+ if (tab && selectTab){
+ browser.selectedTab = tab;
+ }
+ }
+ else {
+ content.location = url;
+ }
+}
+
+
+/**
+* returns a localized string
+*/
+Lazarus.getString = function(strId){
+ try {
+ var strbundle = Lazarus.$("lazarus-strings");
+
+ if (arguments.length == 1){
+ return strbundle.getString(strId);
+ }
+ else {
+ var replacements = [];
+ //NOTE: first argument is the strId
+ for (var i=1; i<arguments.length; i++){
+ replacements.push(arguments[i])
+ }
+ return strbundle.getFormattedString(strId, replacements);
+ }
+ }
+ catch(e){
+ Lazarus.error(e, "failed to getString ["+ strId +"]");
+ return '';
+ }
+}
+
+
+Lazarus.checkDatabase = function(db){
+ //build the database tables
+ try {
+ //forms
+ db.exe('CREATE TABLE IF NOT EXISTS forms (\
+ id INTEGER PRIMARY KEY, formid TEXT, \
+ created INT, \
+ formname TEXT, \
+ forminfo TEXT, \
+ formtext TEXT, \
+ savetype INT, \
+ forminfohash TEXT, \
+ formurl TEXT, \
+ text_length INT, \
+ autofill INT);');
+ db.exe('CREATE INDEX IF NOT EXISTS index_forms_created ON forms (created DESC)');
+ db.exe('CREATE INDEX IF NOT EXISTS index_forms_formid ON forms (formid ASC)');
+
+ //settings
+ db.exe('CREATE TABLE IF NOT EXISTS settings (name TEXT PRIMARY KEY, value TEXT)');
+
+ //texdata
+ db.exe('CREATE TABLE IF NOT EXISTS textdata (\
+ id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, \
+ text_encrypted TEXT, \
+ text_hash TEXT, \
+ text_length INT, \
+ summary_encrypted TEXT, \
+ created INTEGER DEFAULT \'0\' NOT NULL, \
+ domain_hash TEXT, \
+ url_encrypted TEXT, \
+ savetype INTEGER \
+ )');
+
+ Lazarus.debug("DB: Creating indexes");
+
+ db.exe('CREATE INDEX IF NOT EXISTS index_textdata_domain ON textdata (domain_hash ASC)');
+ db.exe('CREATE INDEX IF NOT EXISTS index_textdata_created ON textdata (created DESC)');
+
+ Lazarus.debug("DB: Creating Full Text indexes");
+
+ //textdata full text index
+ if (!db.tableExists('textdata_fulltext')){
+ db.exe('CREATE VIRTUAL TABLE textdata_fulltext USING fts3(hashed_text)');
+ }
+
+ //forms: full text index
+ if (!db.tableExists('forms_fulltext')){
+ db.exe('CREATE VIRTUAL TABLE forms_fulltext USING fts3(hashed_text)');
+ }
+
+ Lazarus.debug("DB: Testing read/write");
+
+ //test read and write
+ var now = (new Date()).getTime();
+ db.exe("DELETE FROM settings WHERE name = 'last-accessed'");
+ db.exe("INSERT INTO settings (name, value) VALUES ('last-accessed', ?1)", now);
+ var lastAccessed = db.getInt("SELECT value FROM settings WHERE name = 'last-accessed'");
+ var success = (lastAccessed === now);
+ Lazarus.debug("DB: read/write success? "+ success);
+ return success;
+ }
+ catch(e){
+ Lazarus.error(e);
+ return false;
+ }
+}
+
+/**
+* initalizes the database
+*/
+Lazarus.initDB = function(){
+ //create / connect to the database
+ //we might have already connected in an update script.
+ if (!Lazarus.Global.db){
+
+ if (Lazarus.getPref("extensions.lazarus.deleteDatabase", false)){
+ Lazarus.killPref("extensions.lazarus.deleteDatabase", false);
+ Lazarus.killDB();
+ }
+
+ if (Lazarus.getPref("extensions.lazarus.restoreDatabase", false)){
+ Lazarus.killPref("extensions.lazarus.restoreDatabase");
+ if (Lazarus.file.exists("%profile%/lazarus-backup.sqlite")){
+ Lazarus.file.move("%profile%/lazarus-backup.sqlite", "%profile%/lazarus.sqlite", true);
+ }
+ else {
+ Lazarus.error("Unable to restore database: backup not found");
+ }
+ }
+
+ //if a backup exists, and the original database is missing, then try and use the backup database
+ if (Lazarus.file.exists("%profile%/lazarus-backup.sqlite") && !Lazarus.file.exists("%profile%/lazarus.sqlite")){
+ Lazarus.file.move("%profile%/lazarus-backup.sqlite", "%profile%/lazarus.sqlite");
+ }
+ else if (Lazarus.getPref("extensions.lazarus.restoreFromBackup", false) && Lazarus.file.exists("%profile%/lazarus-backup.sqlite")){
+ Lazarus.file.move("%profile%/lazarus-backup.sqlite", "%profile%/lazarus.sqlite");
+ }
+
+ var db = new Lazarus.SQLite("%profile%/lazarus.sqlite", false);
+
+ if (!Lazarus.checkDatabase(db)){
+ //save the corrupted database file
+ Lazarus.warning("Database is corrupted, saving corrupt database");
+ var d = Lazarus.formatDate(new Date(), true);
+ Lazarus.file.kill("%profile%/lazarus-"+ d +"-corrupted.sqlite");
+ Lazarus.file.move("%profile%/lazarus.sqlite", "%profile%/lazarus-"+ d +"-corrupted.sqlite");
+ db = null;
+ }
+
+ //use the backup database if it exists.
+ if (!db && Lazarus.file.exists("%profile%/lazarus-backup.sqlite")){
+ Lazarus.warning("Restoring previous backup database");
+ Lazarus.file.move("%profile%/lazarus-backup.sqlite", "%profile%/lazarus.sqlite");
+ db = new Lazarus.SQLite("%profile%/lazarus.sqlite", false);
+ if (!Lazarus.checkDatabase(db)){
+ //delete the broken backup file
+ Lazarus.file.kill("%profile%/lazarus.sqlite");
+ db = null;
+ }
+ }
+
+ if (!db){
+ //delete any existing database, and build a new one from scratch
+ Lazarus.file.kill("%profile%/lazarus.sqlite");
+ db = new Lazarus.SQLite("%profile%/lazarus.sqlite", false);
+ if (!Lazarus.checkDatabase(db)){
+ //oh, so screwed
+ Lazarus.error("Unable to create the Lazarus database");
+ db = null;
+ }
+ }
+
+ if (db){
+ //ok we should now have a valid and checked database
+ //make a backup of it.
+ //remove the old backup (if it exists)
+ if (Lazarus.getPref("extensions.lazarus.backupDatabase")){
+ Lazarus.debug("Backing up database");
+ Lazarus.file.copy("%profile%/lazarus.sqlite", "%profile%/lazarus-backup.sqlite", true);
+ }
+ }
+
+ Lazarus.Global.db = db;
+ }
+
+ Lazarus.db = Lazarus.Global.db;
+ return Lazarus.db ? true : false;
+}
+
+
+Lazarus.cleanDB = function(callback){
+ //takes ages to clean the database
+ Lazarus.debug("Cleaning database");
+ Lazarus.cleaningDatabase = true;
+ Lazarus.refreshIcon();
+ Lazarus.backgroundTask(function(){
+ Lazarus.db.exe("VACUUM");
+ }, function(){
+ Lazarus.cleaningDatabase = false;
+ Lazarus.refreshIcon();
+ if (callback){
+ callback();
+ }
+ });
+}
+
+/**
+* convert autosaved forms to semi-permanent save points
+*/
+Lazarus.saveAutoSavedForms = function(){
+
+ //convert autosaved forms to semi-permanent points
+ var rs = Lazarus.db.rs("SELECT id, formid FROM forms WHERE savetype = "+ Lazarus.FORM_TYPE_AUTOSAVE +" ORDER BY formid, created DESC");
+ var lastFormId = '';
+ var lastFormCnt = 0;
+ var MAX_AUTOSAVES_TO_KEEP = 2;
+ for (var i=0; i<rs.length; i++){
+ var savedForm = rs[i];
+ if (lastFormId != savedForm["formid"]){
+ lastFormId = savedForm["formid"];
+ lastFormCnt = 1;
+ }
+ else {
+ lastFormCnt++;
+ }
+
+ if (lastFormCnt <= MAX_AUTOSAVES_TO_KEEP){
+ Lazarus.db.exe('UPDATE forms SET savetype = '+ Lazarus.FORM_TYPE_STALE_AUTOSAVE +' WHERE id = ?1', savedForm["id"]);
+ }
+ else {
+ //excess saved form, remove it
+ Lazarus.removeForms(savedForm["id"]);
+ }
+ }
+}
+
+/**
+* removes old/excess forms from the database
+*/
+Lazarus.removeOldForms = function(){
+ var rs = Lazarus.db.rs("SELECT count(*) as cnt, formid FROM forms WHERE savetype = "+ Lazarus.FORM_TYPE_NORMAL +" GROUP BY formid");
+ var maxForms = Lazarus.getExtPref("maxSavesPerForm", 10);
+ for (var i=0; i<rs.length; i++){
+ var save = rs[i];
+ if (save["cnt"] > maxForms){
+ Lazarus.debug("Removing excess forms "+ save["formid"]);
+ var ids = Lazarus.db.getColumn("SELECT id FROM forms WHERE formid = ?1 AND savetype = "+ Lazarus.FORM_TYPE_NORMAL +" ORDER BY created DESC LIMIT -1 OFFSET ?2", save["formid"], maxForms);
+ if (ids.length){
+ //and remove
+ Lazarus.debug("cleaning up forms "+ ids.join(","));
+ Lazarus.removeForms(ids);
+ }
+ }
+ }
+}
+
+/**
+* empties all the tables in the database
+* except those specified.
+*/
+Lazarus.emptyDB = function(ignoreFormTypes){
+ //always remove text data
+ Lazarus.db.exe('DELETE FROM textdata');
+ Lazarus.db.exe('DELETE FROM textdata_fulltext');
+
+ if (typeof ignoreFormTypes == "undefined"){
+ Lazarus.db.exe('DELETE FROM forms');
+ Lazarus.db.exe('DELETE FROM forms_fulltext');
+ }
+ else {
+ var ids = Lazarus.db.getColumn('SELECT id FROM forms WHERE savetype NOT IN ('+ ignoreFormTypes +')');
+ Lazarus.removeForms(ids);
+ }
+}
+
+/**
+* completely remove the database
+*/
+Lazarus.killDB = function(){
+ Lazarus.file.kill("%profile%/lazarus.sqlite");
+ Lazarus.file.kill("%profile%/lazarus-backup.sqlite");
+}
+
+/**
+* run any update functions
+*/
+Lazarus.runUpdates = function(prevVersion){
+ for(var key in Lazarus.updates){
+ if (Lazarus.versionCompare(key, prevVersion) == 1){
+ Lazarus.debug("running update "+ key);
+ try {
+ Lazarus.updates[key](prevVersion);
+ }catch(e){
+ Lazarus.error(e);
+ }
+ }
+ }
+}
+
+/**
+* fire a "clear-private-data" event if the user has asked to clear private data on shutdown with no prompt
+*/
+Lazarus.fireClearPrivateDataOnShutdown = function(){
+ if (Lazarus.getPref("privacy.sanitize.sanitizeOnShutdown", false) && !Lazarus.getPref("privacy.sanitize.promptOnSanitize", true)){
+ Lazarus.Event.fire("clear-private-data");
+ }
+}
+
+/**
+* clear private data if all the preferences say so
+*/
+Lazarus.onClearPrivateData = function(action){
+ if (Lazarus.getExtPref("privacy.item.saved.forms")){
+ Lazarus.emptyDB(Lazarus.FORM_TYPE_TEMPLATE);
+ Lazarus.debug("ClearPrivateData: removing all forms");
+ }
+}
+
+/**
+* return TRUE if lazarus requires a password before data can be decrypted
+*/
+Lazarus.isPasswordSet = function(){
+ //we test this by attempting to decrypt the private key with a blank password
+ var encb64Key = Lazarus.db.getStr("SELECT value FROM settings WHERE name = 'private-key'");
+ //we need to unencrypt the private key
+ return Lazarus.Crypto.AESDecrypt(encb64Key, "") ? false : true;
+}
+
+/*
+* encrypts a string
+*/
+Lazarus.encrypt = function(str){
+ return Lazarus.Crypto.encrypt(str);
+}
+
+/*
+* encrypts a string
+*/
+Lazarus.decrypt = function(str){
+ return Lazarus.Crypto.decrypt(str);
+}
+
+
+/*
+* encrypts a string
+*/
+Lazarus.encrypt2 = function(str){
+ //hmmm interesting, the encrypted string is not always the same.
+ //a certain amount of decryption information must be kept within the encrypted string
+ //so we cannot compare encrypted string against each other to test if they are the same
+ //all encrypted string must be DECRYPTED before comparison.
+
+ //Unable to decrypt extended characters (eg \u8888)!
+ //IMPORTANT: extended characters are not correctly encoded/decoded in the bsao function used by the encryptor, so (as of 2008-07-24)
+ //we'll be encoding all strings before encrypting them, and then decoding afterwards.
+ //by adding a 4 character header ("uri:") we can tell which type of string we have (encodeURIComponent).
+
+ Lazarus.decoderRing = Lazarus.decoderRing || Components.classes["@mozilla.org/security/sdr;1"].getService(Components.interfaces.nsISecretDecoderRing);
+ var encStr = encodeURIComponent(str);
+ return "uri:"+ Lazarus.decoderRing.encryptString(encStr);
+}
+
+/**
+* decrypt a string
+*/
+Lazarus.decrypt2 = function(str){
+ //IMPORTANT: extended characters (eg \u8888) are not correctly encoded/decoded, so (as of 2008-07-24)
+ //we'll be encoding all strings before encrypting them, and then decoding afterwards.
+ //by inspecting the 4 character header we can tell which type of string we have.
+ Lazarus.decoderRing = Lazarus.decoderRing || Components.classes["@mozilla.org/security/sdr;1"].getService(Components.interfaces.nsISecretDecoderRing);
+ if (str.indexOf("uri:") == 0){
+ str = str.substr(4);
+ var strEnc = Lazarus.decoderRing.decryptString(str);
+ return decodeURIComponent(strEnc);
+ }
+ else {
+ return Lazarus.decoderRing.decryptString(str);
+ }
+}
+
+/**
+* calulate md5 hash of a string
+* ref: http://developer.mozilla.org/en/docs/nsICryptoHash#Computing_the_Hash_of_a_String
+*/
+Lazarus.md5 = function(str){
+
+ var nsICryptoHash = Components.classes['@mozilla.org/security/hash;1'].createInstance(Components.interfaces.nsICryptoHash);
+
+ var converter = Components.classes["@mozilla.org/intl/scriptableunicodeconverter"].createInstance(Components.interfaces.nsIScriptableUnicodeConverter);
+ converter.charset = "UTF-8";
+ // result is an out parameter,
+ // result.value will contain the array length
+ var result = {};
+ // data is an array of bytes
+ var data = converter.convertToByteArray(str, result);
+
+ nsICryptoHash.init(Components.interfaces.nsICryptoHash.MD5);
+ nsICryptoHash.update(data, data.length);
+ var hash = nsICryptoHash.finish(false);
+
+ // Unpack the binary data bin2hex style
+ var ascii = [];
+ var len = hash.length;
+ for (var i = 0; i < len; ++i) {
+ var c = hash.charCodeAt(i);
+ var ones = c % 16;
+ var tens = c >> 4;
+ ascii.push(String.fromCharCode(tens + (tens > 9 ? 87 : 48)) + String.fromCharCode(ones + (ones > 9 ? 87 : 48)));
+ }
+ return ascii.join('');
+}
+
+
+/**
+* return TRUE if master password is set
+*/
+Lazarus.isMasterPasswordSet = function(){
+ var secmodDB = Components.classes["@mozilla.org/security/pkcs11moduledb;1"].getService(Components.interfaces.nsIPKCS11ModuleDB);
+ var slot = secmodDB.findSlotByName("");
+ return (slot && (slot.status == Components.interfaces.nsIPKCS11Slot.SLOT_NOT_LOGGED_IN || slot.status == Components.interfaces.nsIPKCS11Slot.SLOT_LOGGED_IN));
+}
+
+/**
+* return TRUE if a user has set the firefox master password and has not yet logged in.
+*/
+Lazarus.isMasterPasswordRequired = function(){
+ var secmodDB = Components.classes["@mozilla.org/security/pkcs11moduledb;1"].getService(Components.interfaces.nsIPKCS11ModuleDB);
+ var slot = secmodDB.findSlotByName("");
+ return (slot && slot.status == Components.interfaces.nsIPKCS11Slot.SLOT_NOT_LOGGED_IN);
+}
+
+/**
+*
+*/
+Lazarus.enterMasterPassword = function(){
+
+ //encrypting a string should open the enter master password dialog
+ try {
+ var decoderRing = Components.classes["@mozilla.org/security/sdr;1"].getService(Components.interfaces.nsISecretDecoderRing);
+ decoderRing.encryptString("dummy");
+ return true;
+ }
+ catch(e){
+ return false;
+ }
+}
+
+
+Lazarus.openGenerateKeysDialog = function(){
+ window.open("chrome://lazarus/content/generate-keys.xul", "LazarusGenerateKeys", "chrome,dialog,modal,resizable,centerscreen");
+}
+
+/**
+* dictionary of notification to display in the popup.
+*/
+Lazarus.notifications = {};
+Lazarus.notifications["password-required"] = {
+ "buttons" : [
+ {
+ "label-string": "notification.password.required.button1.label",
+ "accessKey-string": "notification.password.required.button1.accesskey",
+ "callback": function(notif, label){
+ Lazarus.showEnterPasswordDialog();
+ Lazarus.refreshIcon();
+ }
+ }
+ ],
+ "text-string" : "notification.password.required.text",
+ "iconUrl" : "chrome://lazarus/skin/lazarus-error.png"
+}
+
+Lazarus.notifications["password-required-autofill"] = {
+ "buttons" : [
+ {
+ "label-string": "notification.password.required.autofill.button1.label",
+ "accessKey-string": "notification.password.required.autofill.button1.accesskey",
+ "callback": function(notif, label){
+ Lazarus.showEnterPasswordDialog();
+ Lazarus.refreshIcon();
+ Lazarus.autofillDoc(content.document);
+ }
+ }
+ ],
+ "text-string" : "notification.password.required.autofill.text",
+ "iconUrl" : "chrome://lazarus/skin/lazarus-error.png"
+}
+
+Lazarus.notifications["form-restored"] = {
+ "buttons" : [
+ {
+ "label-string": "notification.form.restored.button1.label",
+ "accessKey-string": "notification.form.restored.button1.accesskey",
+ "callback": function(notif, label){
+
+ var data = Lazarus.$('lazarus-notification').getAttribute('lazarus-data');
+ var page = 'donate.html';
+ page += (data) ? ('?'+ data) : '';
+ Lazarus.openLazarusWebsite(page);
+ }
+ }
+ ],
+ "text-string" : "notification.form.restored.text",
+ "iconUrl" : "chrome://lazarus/skin/lazarus.png"
+}
+
+/**
+* displays the lazarus notification box
+*/
+Lazarus.showNotificationBox = function(id, text, data){
+ var notif = Lazarus.notifications[id];
+
+ if (Lazarus.$('lazarus-notification').currentNotification && Lazarus.$('lazarus-notification').currentNotificationId == id){
+ //do nothing, notification box is already shown
+ }
+ else if (notif){
+ //translate text, and buttons
+ if (text){ + notif["text"] = text; + } + else if (!notif["text"] && notif["text-string"]){
+ notif["text"] = Lazarus.getString(notif["text-string"]);
+ }
+
+ for (var i=0; i<notif.buttons.length; i++){
+ var button = notif.buttons[i];
+ if (!button["label"] && button["label-string"]){
+ button["label"] = Lazarus.getString(button["label-string"]);
+ }
+ if (!button["accessKey"] && button["accessKey-string"]){
+ button["accessKey"] = Lazarus.getString(button["accessKey-string"]);
+ }
+ }
+
+ var notificationBox = Lazarus.$('lazarus-notification');
+ notificationBox.setAttribute('lazarus-data', data || '');
+ notificationBox.currentNotificationId = id;
+ notificationBox.appendNotification(notif["text"], id, notif["iconUrl"], notificationBox.PRIORITY_INFO_LOW, notif["buttons"]);
+ }
+ else {
+ Lazarus.error(Error("Lazarus: invalid notification id ["+ id +"]"));
+ }
+}
+
+/**
+* closes the notification box
+*/
+Lazarus.closeNotificationBox = function(immediate){
+ Lazarus.$('lazarus-notification').removeAllNotifications(immediate);
+}
+
+/**
+* returns a list of the templates found in the database
+*/
+Lazarus.getTemplateNames = function(){
+ return Lazarus.db.getColumn("SELECT formname FROM forms WHERE savetype = "+ Lazarus.FORM_TYPE_TEMPLATE +" ORDER BY formname");
+}
+
+/**
+* handle user clicking on the "Save form as template..." menuitem
+*/
+Lazarus.onSaveFormMenuItem = function(){
+ //find the current form
+ var form = Lazarus.findFormFromElement(gContextMenu.target, "form");
+
+ //and show the save as template dialog
+ if (form){
+ var info = Lazarus.formInfo(form, Lazarus.FORM_TYPE_NORMAL);
+ var args = {};
+ args.templateName = '';
+ //add the names of the current templates within the database
+ args.templateNames = Lazarus.getTemplateNames();
+ //suggest using the default name for this form
+ args.defaultName = Lazarus.getMenuItemText(info.formtext) || ("["+ Lazarus.getString("untitled") +"]");
+ //WYSIWYG and TEXTAREAs cannot be autofilled
+ args.isTextarea = form.isTextarea;
+ args.isIframe = form.isIframe;
+
+ //IMPORTANT: must use open dialog here otherwise the arguments are not passed.
+ window.openDialog("chrome://lazarus/content/template-save.xul", "LazarusTemplateSave", "chrome,modal,resizable", args);
+ if (args.templateName){
+ Lazarus.saveForm(form, Lazarus.FORM_TYPE_TEMPLATE, args.templateName, args.autofill);
+ }
+ }
+ //or throw an error
+ else {
+ alert(Lazarus.getString("error.form.object.not.found"));
+ Lazarus.error(Error("Unable to find form in the document"));
+ }
+}
+
+/**
+* compare two version numbers (eg 2.0.0.1beta3, 2.0.0rc1)
+* return 1 if version1 > version2
+* return 0 if version1 == version2
+* return -1 if version1 < version2
+*/
+Lazarus.versionCompare = function(version1, version2){
+
+ function getValueOfVersionSegment(seg){
+
+ if (typeof seg === "undefined"){
+ return 0;
+ }
+ else if (/^\d+$/.test(seg)){
+ return parseInt(seg);
+ }
+ else {
+ switch(seg){
+ case "rc": return -1;
+ case "beta": return -2;
+ case "alpha": return -3;
+ case "dev": return -4;
+ default:
+ throw Error("Lazarus.versionCompare: Unknown version fragment ["+ seg +"]");
+ }
+ }
+ }
+
+ //verify version strings
+ var regexVerify = /^[\d\.(dev|alpha|beta|rc)]+$/i;
+
+ if (!regexVerify.test(version1)){
+ Lazarus.error("Lazarus.versionCompare: Invalid version string ["+ version1 +"]");
+ version1 = "0";
+ }
+ else if (!regexVerify.test(version2)){
+ Lazarus.error("Lazarus.versionCompare: Invalid version string ["+ version2 +"]");
+ version2 = "0";
+ }
+
+ //split each version into sections
+ var ver1 = version1.toLowerCase().replace(/(\w)(\d)/g, "$1.$2").replace(/(\d)(\w)/g, "$1.$2").split(/[\.\b]/g);
+ var ver2 = version2.toLowerCase().replace(/(\w)(\d)/g, "$1.$2").replace(/(\d)(\w)/g, "$1.$2").split(/[\.\b]/g);
+
+ //compare each section until a non match occurs
+ var maxLen = Math.max(ver1.length, ver2.length);
+ for (var i=0; i<maxLen; i++){
+ //convert the version segment into a numeric value
+ var seg1 = (typeof ver1[i] === "undefined") ? 0 : getValueOfVersionSegment(ver1[i]);
+ var seg2 = (typeof ver2[i] === "undefined") ? 0 : getValueOfVersionSegment(ver2[i]);
+
+ if (seg1 > seg2){
+ return 1;
+ }
+ else if (seg1 < seg2){
+ return -1;
+ }
+ }
+ //all parts are equal
+ return 0;
+}
+
+
+/**
+* return the path to this extension install directory
+* gives correct path even if extension is not installed in a users profile directory.
+*/
+Lazarus.getExtensionDir = function(extId){
+ // the extension's id from install.rdf
+ extId = extId || Lazarus.guid;
+ var em = Components.classes["@mozilla.org/extensions/manager;1"].getService(Components.interfaces.nsIExtensionManager);
+ return em.getInstallLocation(extId).getItemFile(extId, "install.rdf").parent.path;
+}
+
+/**
+* check the website to see if there is a new update available
+*/
+Lazarus.checkForUpdates = function(){
+ //never check more than once a day
+ if (Lazarus.getExtPref("lastUpdateCheck") + (24 * 60 * 60) < Lazarus.timestamp()){
+ Lazarus.setExtPref("lastUpdateCheck", Lazarus.timestamp());
+
+ // create browser
+ var browser = document.createElement("browser");
+ browser.collapsed = true;
+ //browser.style.height = "0px";
+ document.documentElement.appendChild(browser);
+
+ // set restrictions as needed
+ browser.webNavigation.allowAuth = false;
+ browser.webNavigation.allowImages = true;
+ browser.webNavigation.allowJavascript = true; // so the google analytic code works
+ browser.webNavigation.allowMetaRedirects = false;
+ browser.webNavigation.allowPlugins = false;
+ browser.webNavigation.allowSubframes = false;
+
+ // listen for load
+ browser.addEventListener("DOMContentReady", function(evt){
+ // the document of the HTML in the DOM
+ var doc = evt.originalTarget;
+ //check URL (could be about:blank)
+ if (doc && doc.URL != "about:blank"){
+ var ele = doc.getElementById('latest-stable');
+ if (ele && ele.innerHTML){
+ Lazarus.setExtPref("lastestStableVersion", ele.innerHTML);
+ }
+ var ele = doc.getElementById('latest-beta');
+ if (ele && ele.innerHTML){
+ Lazarus.setExtPref("lastestBetaVersion", ele.innerHTML);
+ }
+ // remove browser element when done
+ //mouse pointer has loading symbol unless we do this?
+ browser.contentDocument.location = "about:blank";
+ browser.parentNode.removeChild(browser);
+
+ Lazarus.updateVersionIcons();
+ }
+ }, true);
+
+ // load update check
+ browser.contentDocument.location.href = "http://lazarus.interclue.com/version-check.php";
+
+ //check again tomorrow
+ setTimeout(Lazarus.checkForUpdates, 24 * 60 * 60 * 1000);
+ }
+ else {
+ //didn't check, so check again in 10 minutes
+ setTimeout(Lazarus.checkForUpdates, 10 * 60 * 1000);
+ }
+}
+
+/**
+* displays the "new beta" or "new stable" icons is a newer version is available.
+*/
+Lazarus.updateVersionIcons = function(){
+ var stable = Lazarus.getExtPref("lastestStableVersion", "");
+ var beta = Lazarus.getExtPref("lastestBetaVersion", "");
+ var thisVersion = Lazarus.getVersionStr();
+ if (beta && Lazarus.getExtPref("checkForUpdatesBeta") && Lazarus.versionCompare(thisVersion, beta) == -1){
+ //show the new beta icon
+ Lazarus.$("lazarus-update-beta-statusbar-image").hidden = false;
+ }
+ else if (stable && Lazarus.getExtPref("checkForUpdates") && Lazarus.versionCompare(thisVersion, stable) == -1){
+ //show the new stable version icon
+ Lazarus.$("lazarus-update-stable-statusbar-image").hidden = false;
+ }
+}
+
+/**
+* attempts to download the latest versin of Lazarus
+*/
+Lazarus.getUpdate = function(version){
+ var urls = {
+ "stable": "http://lazarus.interclue.com/downloads/lazarus.xpi",
+ "beta" : "http://lazarus.interclue.com/downloads/lazarus-beta.xpi"
+ }
+
+ var url = urls[version] ? urls[version] : urls["stable"];
+ Lazarus.getBrowser().content.location = url;
+}
+
+/**
+* return TRUE if the user is in private Browsing mode (Fx 3.1+)
+*/
+Lazarus.isPrivateBrowsing = function(){
+
+ try {
+ return Components.classes["@mozilla.org/privatebrowsing;1"] && Components.classes["@mozilla.org/privatebrowsing;1"].getService(Components.interfaces.nsIPrivateBrowsingService).privateBrowsingEnabled;
+ }
+ catch(e){
+ return false;
+ }
+}
+
+/**
+* return TRUE if Lazarus is currently disabled because the user is in private browsing mode
+*/
+Lazarus.isDisabledByPrivateBrowsing = function(){
+ return (Lazarus.isPrivateBrowsing() && !Lazarus.getPref("extensions.lazarus.enableInPrivateBrowsingMode"));
+}
+
+/**
+* return an FNV1a hash of the given string
+*/
+Lazarus.FNV1a = function(str, seed){
+
+ //hash = offset_basis
+ var hash = seed ? parseInt(seed, 16) : 2166136261;
+
+ //only calculate the length once
+ var len = str.length;
+
+ //for each octet_of_data to be hashed
+ for (var i=0; i<len; i++){
+
+ hash ^= str.charCodeAt(i);
+
+ //hash = hash * FNV_prime (apparently this bitshifting does the same thing)
+ hash += ((hash << 1) + (hash << 4) + (hash << 7) + (hash << 8) + (hash << 24));
+ }
+
+ return Math.abs(Number(hash & 0x00000000ffffffff)).toString(16);
+}
+
+
+Lazarus.openTextManager = function(){
+
+ //if not logged in close window
+ if (!Lazarus.canDecrypt()){
+ //try loggin in immediately
+ Lazarus.showEnterPasswordDialog();
+ }
+
+ //if they still can't login do nothing
+ if (Lazarus.canDecrypt()){
+ window.open("chrome://lazarus/content/text-manager.xul", "", "chrome,centerscreen,resizable");
+ }
+}
+
+/**
+*
+**/
+Lazarus.refreshMenuIcons = function(){
+
+ var items = {
+ 'lazarus-enterpassword-contextmenuitem': 'chrome://lazarus/skin/lazarus-login.png',
+ 'lazarus-restoretext-submenu': 'chrome://lazarus/skin/lazarus.png',
+ 'lazarus-restoretextdisabled-contextmenuitem': 'chrome://lazarus/skin/lazarus-disable.png',
+ 'lazarus-restoreform-contextmenuitem': 'chrome://lazarus/skin/lazarus.png',
+ 'lazarus-restoreform-submenu': 'chrome://lazarus/skin/lazarus.png',
+ 'lazarus-submenu-menuitem-donate': 'chrome://lazarus/skin/donate.png',
+ 'lazarus-saveform-contextmenuitem': 'chrome://lazarus/skin/lazarus-save.png',
+ 'lazarus-domaindisabled-contextmenuitem': 'chrome://lazarus/skin/lazarus-disabled.png',
+ 'lazarus-privatebrowsing-contextmenuitem': 'chrome://lazarus/skin/lazarus-disabled.png'
+ };
+
+ var show = Lazarus.getPref('extensions.lazarus.showContextMenuIcons');
+
+ for(var id in items){
+ Lazarus.setMenuIcon(document.getElementById(id), show ? items[id] : null);
+ }
+}
+
+
+/**
+*
+*/
+Lazarus.setMenuIcon = function(ele, iconURL){
+ var classname = ele.tagName.toLowerCase() +"-iconic";
+
+ if (iconURL){
+ ele.setAttribute("image", iconURL);
+ Lazarus.addClass(ele, classname);
+ }
+ else {
+ //remove the icon
+ ele.setAttribute("image", "");
+ Lazarus.removeClass(ele, classname);
+ }
+}
+
+
+/**
+* return TRUE if class exists
+*/
+Lazarus.classExists = function(ele, classname){
+ var currName = ele.getAttribute('class') || '';
+ return ((" "+ currName.toLowerCase() +" ").indexOf(" "+ classname.toLowerCase() +" ") > -1);
+}
+
+
+/**
+* adds a classname to an element if the name doesn't exist
+*/
+Lazarus.addClass = function(ele, classname){
+ if (!Lazarus.classExists(ele, classname)){
+ ele.setAttribute('class', (ele.getAttribute('class') || '') +" "+ classname);
+ }
+}
+
+
+/**
+* remove a classname from an element
+*/
+Lazarus.removeClass = function(ele, classname){
+ if (Lazarus.classExists(ele, classname)){
+ var newClassname = ele.getAttribute('class').toLowerCase().replace(classname.toLowerCase(), "").replace(/\s+/g, ' ').replace(/^\s+|\s+$/g, '');
+ ele.setAttribute('class', newClassname);
+ }
+}
+
+/**
+*
+*/
+Lazarus.test = function(evt){
+
+
+ Lazarus.cleanDB();
+ return;
+
+
+ alert(1);
+ Lazarus.backgroundTask(function(){
+
+ var x = 1234
+ for(var i=0; i<20000000; i++){
+ x = x/100;
+ }
+
+ //try throwing an error in the middle of the function
+ x = a.b.c;
+
+ return x;
+
+
+ }, function(result){
+ alert(result);
+ })
+
+
+ return;
+
+
+ alert("Private = "+ Lazarus.isPrivateBrowsing());
+ return;
+
+
+
+//~ Unit.assert("Lazarus.canEncrypt()", true, "should be able to encrypt");
+
+//~ alert(Unit.getLog());
+//~ return;
+
+
+ var len = 10;
+
+ var p = new Profiler("decrypt");
+ var enc = [];
+ var dec = [];
+ for (var i=0; i<len; i++){
+ enc[i] = Lazarus.encrypt("abc"+ Math.random());
+ }
+ p.mark("encrypted "+ len +" strings");
+ for (var i=0; i<len; i++){
+ dec[i] = Lazarus.decrypt(enc[i]);
+ }
+
+ debug(p.stop("decrypted"));
+ return;
+
+
+
+
+ debug(Lazarus.db.rs("SELECT hashed_text FROM textdata_fulltext LIMIT 1"));
+ return;
+
+ Lazarus.openTextManager();
+ return;
+
+ var str = prompt("Enter search string");
+ if (str){
+ debug(str, Lazarus.hashQuery(str));
+ var rs = Lazarus.db.rs("SELECT id, text_encrypted FROM textdata JOIN textdata_fulltext ON textdata.id = textdata_fulltext.docid WHERE textdata_fulltext.hashed_text MATCH ?1", Lazarus.hashQuery(str));
+ debug(rs);
+
+ var text = '';
+ for (var i=0; i<rs.length; i++){
+ text += rs[i].id +":"+ Lazarus.decrypt(rs[i].text_encrypted) +"\n";
+ }
+ alert(text ? text : "== no results ==");
+ }
+ return;
+
+
+
+ alert(Lazarus.hash("chongo <Landon Curt Noll> /\\../\\"));
+ return;
+
+
+ var p = new Profiler('encrypt large file');
+ var s = Lazarus.file.readBinary("%profile%/test.sqlite");
+ p.mark("Read file");
+ var enc = Lazarus.encrypt(s);
+ p.mark("Encrypt file");
+ var dec = Lazarus.decrypt(enc);
+ p.mark("Decrypt file : "+ (dec == s));
+
+ //Lazarus.file.readBinary("%profile%/test.sqlite");
+ alert(p.stop("finished"));
+ return;
+
+
+
+ alert(Lazarus.enterMasterPassword());
+ return;
+
+ alert(1);
+ Lazarus.isMasterPasswordSet();
+ alert(2);
+
+
+ Lazarus.isMasterPasswordSet();
+ return;
+
+
+ Lazarus.db.exe("DELETE FROM settings WHERE name = 'public-key' OR name = 'private-key'");
+ return;
+
+
+ var p = new Profiler("encryption");
+
+ var str = "{testing...}";
+
+ var str = '';
+ for (var i=0; i<200000; i++){
+ str += "A";
+ }
+
+ var enc = Lazarus.Crypto.encrypt(str);
+ debug(enc);
+ p.mark("Encrypted string (Hybrid) of length "+ str.length);
+
+ var dec = Lazarus.Crypto.decrypt(enc);
+ p.mark("Decrypted string (Hybrid): "+ ((dec == str) ? "success" : "FAILED!"));
+
+ //compare with SSD encryption
+ var enc = Lazarus.encrypt(str);
+ p.mark("Encrypted string (SSD) of length "+ str.length);
+
+ var dec = Lazarus.decrypt(enc);
+
+ alert(p.stop("Decrypted string (SSD): "+ ((dec == str) ? "success" : "FAILED!")));
+ return;
+
+
+ var keyPair = Lazarus.Crypto.generateRSAKeyPair("{password}");
+
+ p.mark("Generate RSA key pair");
+
+ var symKey = Lazarus.Crypto.crypto.generateRandomKey();
+
+ //attempting to amke a smaller symkey
+ symKey = btoa(atob(symKey).substr(0, 16));
+
+ p.mark("Generate symetric key ["+ atob(symKey).length +" characters]");
+
+ var wrapped = Lazarus.Crypto.crypto.wrapSymmetricKey(symKey, keyPair.pubkey);
+
+ p.mark("RSA Encrypt symetric key");
+
+ var unwrapped = Lazarus.Crypto.crypto.unwrapSymmetricKey(wrapped, keyPair.privkey,
+ keyPair.password,
+ keyPair.passphraseSalt,
+ keyPair.privkeyWrapIV);
+
+
+ p.mark("RSA Decrypt symetric key");
+
+ debug(unwrapped, symKey == unwrapped);
+
+ alert(p.stop(symKey == unwrapped ? "success" : "failed!"));
+
+
+ return;
+
+
+ var str = '';
+ for (var i=0; i<200000; i++){
+ str += "A";
+ }
+
+
+ str = "a very short string.";
+
+ //speed test
+ var p = new Profiler("Encrypting a str of length :"+ str.length);
+
+ var keypair = Lazarus.Crypto.generateKeyPair(512);
+
+ p.mark("generate RSA key pair (512 bit)");
+
+
+ debug(Lazarus.Crypto.crypto);
+
+ var enc = Lazarus.Crypto.encrypt(str, keypair.public);
+ p.mark("hybrid encrypt a string");
+
+
+ var dec = Lazarus.Crypto.decrypt(enc, keypair.private);
+ debug(dec);
+
+
+ p.mark("hybrid decrypt a string"+ ((str == dec) ? "success" : "FAILED!"));
+
+ alert(p.stop());
+ //var enc = Lazarus.Crypto.encrypt("a string \u5555");
+
+ //debug(atob(Lazarus.Crypto.crypto.generateRandomIV()));
+ //debug(atob(Lazarus.Crypto.crypto.generateRandomKey()));
+
+ return;
+
+
+ //labs.mozilla.com/Weave/Crypto
+ //interclue.com/Lazarus/Crypto2
+
+ try {
+ var xx = Components.classes["@labs.mozilla.com/Weave/Crypto;1"].
+ createInstance(Components.interfaces.IWeaveCrypto);
+ }
+ catch(e){
+ if (confirm("Failed to create Weave:crypto object, weave not installed?\nInstall Weave now?")){
+ Lazarus.openURL('http://dev.izeal.com/xpi/lazarus/weave-0.2.98.xpi', true, true);
+ }
+ }
+
+
+ debug(xx);
+ return;
+
+
+ var randIV = xx.generateRandomIV();
+ //debug(randIV);
+
+ var randKey = xx.generateRandomKey();
+ //debug(randKey);
+
+ var str = '';
+ for (var i=0; i<200000; i++){
+ str += "A";
+ }
+
+ //speed test
+ var p = new Profiler("Encrypting a str of length :"+ str.length);
+ var enc = xx.encrypt(str, randKey, randIV);
+ p.mark("weave (aes-256-cbc): encrypted");
+ var dec = xx.decrypt(enc, randKey, randIV);
+ p.mark("weave (aes-256-cbc): decrypted : "+ ((str == dec) ? "success" : "FAILED!"));
+ var enc = Lazarus.encrypt(str);
+ p.mark("SecretKeyRing: encrypted");
+ var dec = Lazarus.decrypt(enc);
+ p.mark("SecretKeyRing: decrypted : "+ ((str == dec) ? "success" : "FAILED!"));
+
+ alert(p.stop());
+
+
+ //alert("Private = "+ Lazarus.isPrivateBrowsing());
+
+ return;
+ //~ var pbs = Components.classes["@mozilla.org/privatebrowsing;1"].getService(Components.interfaces.nsIPrivateBrowsingService);
+
+ //~ // are we currently in the Private Browsing mode?
+ //~ var inPrivateBrowsingMode = pbs.privateBrowsingEnabled;
+ //~ alert(inPrivateBrowsingMode);
+ //~ return;
+
+
+
+ //~ var pbs = Components.classes["@mozilla.org/browser/privatebrowsing;1"]
+ //~ .getService(Components.interfaces.nsIPrivateBrowsingService);
+
+ //~ pbs.privateBrowsing = !pbs.privateBrowsing;
+ //~ alert(pbs.privateBrowsing);
+
+ //~ return;
+
+
+ //http://lazarus.interclue.com/
+ var url = "http://tinyurl.com/5qhvvw";
+ var log = [];
+ var xmlhttp = new XMLHttpRequest();
+ xmlhttp.open("HEAD", url);
+ xmlhttp.onreadystatechange = function(){
+ try {
+ //debug(xmlhttp.readyState);
+ //debug(xmlhttp.status);
+ //debug(xmlhttp.getAllResponseHeaders());
+ }catch(e){}
+
+ if (xmlhttp.readyState == 4){
+ Lazarus.dump(xmlhttp.channel.originalURI);
+ Lazarus.dump(xmlhttp.channel.URI);
+ }
+
+ }
+ xmlhttp.send(null);
+ return;
+
+ //Lazarus.checkForUpdates();
+ return;
+
+ alert(decodeURIComponent(encodeURIComponent("\u2663")))
+
+ var str = "\u2663";
+ alert(str +" ["+ str.length +"]");
+ var enc = Lazarus.encrypt(str);
+ alert(enc);
+ var s = Lazarus.decrypt(enc);
+ alert(s);
+ //~ var alertsService = Components.classes["@mozilla.org/alerts-service;1"]
+ //~ .getService(Components.interfaces.nsIAlertsService);
+//~ alertsService.showAlertNotification("chrome://mozapps/skin/downloads/downloadIcon.png",
+ //~ "Alert title", "Alert text goes here.",
+ //~ false, "", null);
+
+//~ function setToNormalZ(aWindow) {
+ //~ aWindow.QueryInterface(Components.interfaces.nsIInterfaceRequestor);
+ //~ var webnav = aWindow.getInterface(Components.interfaces.nsIWebNavigation);
+ //~ var dsti = webnav.QueryInterface(Components.interfaces.nsIDocShellTreeItem);
+ //~ var treeowner = dsti.treeOwner;
+ //~ var ifreq = treeowner.QueryInterface(Components.interfaces.nsIInterfaceRequestor);
+ //~ var xulwin = ifreq.getInterface(Components.interfaces.nsIXULWindow);
+ //~ xulwin.zLevel = 9;
+//~ }
+
+
+ //~ var win = window.open("chrome://lazarus/content/test.xul", "test", "chrome");
+
+
+//~ setTimeout(function(){
+ //~ //
+ //~ //alert("a");
+ //~ setToNormalZ(win);
+
+//~ }, 3000);
+
+//~ <?xml-stylesheet href="chrome://global/skin/alerts/alert.css" type="text/css"?>
+
+//~ <window id="alertNotification"
+ //~ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ //~ windowtype="alert:alert"
+ //~ xmlns:xhtml2="http://www.w3.org/TR/xhtml2"
+ //~ xmlns:wairole="http://www.w3.org/2005/01/wai-rdf/GUIRoleTaxonomy#"
+ //~ xhtml2:role="wairole:alert"
+ //~ align="start"
+ //~ onload="onAlertLoad()">
+
+
+ //~ if (Lazarus.state == Lazarus.STATE_DISABLED){
+ //~ Lazarus.enable();
+ //~ }
+ //~ else {
+ //~ Lazarus.disable();
+ //~ }
+
+ //Lazarus.warning("test");
+ //Lazarus.updates["1.0.1beta4"]();
+
+ //~ Lazarus.debug(Lazarus.getBaseDomain('www.127.0.0.1'));
+ //~ Lazarus.debug(Lazarus.getBaseDomain('domain.tv.com'));
+ //~ Lazarus.debug(Lazarus.getBaseDomain('domain.com.tv'));
+ //~ Lazarus.debug(Lazarus.getBaseDomain('domain.tv'));
+ //~ Lazarus.debug(Lazarus.getBaseDomain('domain.com'));
+ //~ Lazarus.debug(Lazarus.getBaseDomain('secure.email.website.co.uk'));
+ //~ Lazarus.debug(Lazarus.getBaseDomain('this.is.a.worst.shortly.subdomain.thisIsMyMainWebsite.com.cl'));
+}
+
+
+
+
+Lazarus.icon = "data:image/png;base64,\
+iVBORw0KGgoAAAANSUhEUgAAAAsAAAAOCAYAAAD5YeaVAAAALHRFWHRDcmVhdGlvbiBUaW1lAFRo\
+dSAxMiBOb3YgMjAwOSAxMzoxOToxNiArMTIwMJCaV2oAAAAHdElNRQfZCwwAFimayURVAAAACXBI\
+WXMAAAsSAAALEgHS3X78AAAABGdBTUEAALGPC/xhBQAAASZJREFUeNq1ks1KAmEUhh+/GZkGs6IS\
+gzYhhREEwbRw56IbSaIbaNe+K2gvFEWB11Cgm4LUKKGfRRqVpKVTpkJpOpOJow6ufVfn8D7nfC8f\
+B4YlR39zesHc8wPrRpNx9xildJrw9hZZyxdWkUggV3XWLhPcn8XZcQoyX2W0/mVdODPSesWBojh5\
+D+/yYTp4q+g93wZ//mA6Fc4XFlnaO8L38sS8XuIuEukxslWsSISaE3xLPvzVCv56g9rGJpVcltWW\
+fWiDYzHU/Ct4vDy6RwlcX3Fyk4Ji0f4JbWlae1CKpwjsH3MQCrHciSkGMieTNEwTwzSYUhQmJRVv\
+xzIG4H9Fo4haHa8QuFwqs8GgPYLc3xQ8iN9bpvM51GqZmUKhvaw5tJPo6g+jvlxgBqGatwAAAABJ\
+RU5ErkJggg==";
\ No newline at end of file diff --git a/offline-submit/post-data.txt b/offline-submit/post-data.txt new file mode 100644 index 0000000..85a8e0a --- /dev/null +++ b/offline-submit/post-data.txt @@ -0,0 +1,79 @@ +delta_ts=2009-12-09+19%3A42%3A04&longdesclength=6&id=544623&token=1260958169-c8aa4a3bf3735866fc1e8a398d5c878c&short_desc=%5Babrt%5D+crash+detected+in+compiz-0.8.2-20.fc12+%230++in+__longjmp+%28%29+from+%2Flib64%2Fld-linux-x86-64.so.2&newalias=__longjmp&product=Fedora&component=compiz&version=12&rep_platform=x86_64&op_sys=Linux&priority=low&bug_severity=medium&assigned_to=adel.gadllah%40gmail.com&qa_contact=extras-qa%40fedoraproject.org&bug_file_loc=&status_whiteboard=abrt_hash%3A9d6ed64860473c6d40b0b90137f67f48417a51e7&keywords=&dependson=&blocked=&newcc=&cf_fixed_in=&cf_cust_facing=---&cf_issuetracker=&cf_internal_whiteboard=&cf_devel_whiteboard=&cf_qa_whiteboard=&cf_release_notes=&cf_build_id=&cf_targetrelease=---&defined_cf_partner=&defined_cf_verified=&cf_clone_of=&defined_cf_conditional_nak=&cf_environment=&flag_type-65=X&flag_type-67=X&flag_type-16=X&requestee_type-16=&flag_type-66=X&defined_bit-41=1&defined_bit-68=1&defined_bit-31=1&defined_bit-58=1&defined_bit-50=1&defined_bit-83=1&defined_bit-21=1&defined_bit-32=1&defined_bit-15=1&defined_bit-12=1&defined_bit-161=1&defined_bit-44=1&defined_bit-22=1&defined_bit-11=1&defined_bit-48=1&defined_bit-10=1&defined_bit-93=1&estimated_time=0.0&work_time=0&remaining_time=0.0&deadline=&external_id=0&external_bug_id=&comment=&needinfo_role=&needinfo_from=&bug_status=NEW&resolution=NOTABUG&dup_id=&defined_isprivate_2522533=1&defined_isprivate_2521233=1&defined_isprivate_2520112=1&defined_isprivate_2519826=1&defined_isprivate_2518162=1&defined_isprivate_2518161=1 + +which is + +delta_ts 2009-12-09 19:42:04 +longdesclength 6 +id 544623 +token 1260958169-c8aa4a3bf3735866fc1e8a398d5c878c +short_desc [abrt] crash detected in compiz-0.8.2-20.fc12 #0 in __longjmp () from /lib64/ld-linux-x86-64.so.2 +newalias __longjmp +product Fedora +component compiz +version 12 +rep_platform x86_64 +op_sys Linux +priority low +bug_severity medium +assigned_to adel.gadllah@gmail.com +qa_contact extras-qa@fedoraproject.org +bug_file_loc +status_whiteboard abrt_hash:9d6ed64860473c6d40b0b90137f67f48417a51e7 +keywords +dependson +blocked +newcc +cf_fixed_in +cf_cust_facing --- +cf_issuetracker +cf_internal_whiteboard +cf_devel_whiteboard +cf_qa_whiteboard +cf_release_notes +cf_build_id +cf_targetrelease --- +defined_cf_partner +defined_cf_verified +cf_clone_of +defined_cf_conditional_nak +cf_environment +flag_type-65 X +flag_type-67 X +flag_type-16 X +requestee_type-16 +flag_type-66 X +defined_bit-41 1 +defined_bit-68 1 +defined_bit-31 1 +defined_bit-58 1 +defined_bit-50 1 +defined_bit-83 1 +defined_bit-21 1 +defined_bit-32 1 +defined_bit-15 1 +defined_bit-12 1 +defined_bit-161 1 +defined_bit-44 1 +defined_bit-22 1 +defined_bit-11 1 +defined_bit-48 1 +defined_bit-10 1 +defined_bit-93 1 +estimated_time 0.0 +work_time 0 +remaining_time 0.0 +deadline +external_id 0 +external_bug_id +comment +needinfo_role +needinfo_from +bug_status NEW +resolution NOTABUG +dup_id +defined_isprivate_2522533 1 +defined_isprivate_2521233 1 +defined_isprivate_2520112 1 +defined_isprivate_2519826 1 +defined_isprivate_2518162 1 +defined_isprivate_2518161 1 diff --git a/serialized.txt b/serialized.txt new file mode 100644 index 0000000..72e1a5f --- /dev/null +++ b/serialized.txt @@ -0,0 +1,2 @@ +serForm: +({dataOut:"delta_ts=undefined&delta_ts=undefined&delta_ts=undefined&delta_ts=undefined&delta_ts=undefined&delta_ts=undefined&delta_ts=undefined&delta_ts=undefined&delta_ts=undefined&delta_ts=undefined&delta_ts=undefined&delta_ts=undefined&delta_ts=undefined&delta_ts=undefined&delta_ts=undefined&delta_ts=undefined&delta_ts=undefined&delta_ts=undefined&delta_ts=undefined&longdesclength=undefined&id=undefined&id=undefined&id=undefined&id=undefined&id=undefined&id=undefined&token=undefined&token=undefined&token=undefined&token=undefined&token=undefined&token=undefined&token=undefined&token=undefined&token=undefined&token=undefined&token=undefined&token=undefined&token=undefined&token=undefined&token=undefined&token=undefined&token=undefined&token=undefined&token=undefined&token=undefined&token=undefined&token=undefined&token=undefined&token=undefined&token=undefined&token=undefined&token=undefined&token=undefined&token=undefined&token=undefined&token=undefined&token=undefined&token=undefined&token=undefined&token=undefined&token=undefined&token=undefined&token=undefined&token=undefined&token=undefined&token=undefined&token=undefined&token=undefined&short_desc=undefined&short_desc=undefined&short_desc=undefined&short_desc=undefined&short_desc=undefined&short_desc=undefined&short_desc=undefined&short_desc=undefined&short_desc=undefined&short_desc=undefined&short_desc=undefined&short_desc=undefined&short_desc=undefined&short_desc=undefined&short_desc=undefined&short_desc=undefined&short_desc=undefined&short_desc=undefined&short_desc=undefined&short_desc=undefined&short_desc=undefined&short_desc=undefined&short_desc=undefined&short_desc=undefined&short_desc=undefined&short_desc=undefined&short_desc=undefined&short_desc=undefined&short_desc=undefined&short_desc=undefined&short_desc=undefined&short_desc=undefined&short_desc=undefined&short_desc=undefined&short_desc=undefined&short_desc=undefined&short_desc=undefined&short_desc=undefined&short_desc=undefined&short_desc=undefined&short_desc=undefined&short_desc=undefined&short_desc=undefined&short_desc=undefined&short_desc=undefined&short_desc=undefined&short_desc=undefined&short_desc=undefined&short_desc=undefined&short_desc=undefined&short_desc=undefined&short_desc=undefined&short_desc=undefined&short_desc=undefined&short_desc=undefined&short_desc=undefined&short_desc=undefined&short_desc=undefined&short_desc=undefined&short_desc=undefined&short_desc=undefined&short_desc=undefined&short_desc=undefined&&product=undefined&product=undefined&product=undefined&product=undefined&product=undefined&product=undefined&component=undefined&component=undefined&component=undefined&component=undefined&component=undefined&component=undefined&component=undefined&component=undefined&component=undefined&version=undefined&version=undefined&rep_platform=undefined&rep_platform=undefined&rep_platform=undefined&rep_platform=undefined&op_sys=undefined&op_sys=undefined&op_sys=undefined&op_sys=undefined&op_sys=undefined&priority=undefined&priority=undefined&priority=undefined&bug_severity=undefined&bug_severity=undefined&bug_severity=undefined&bug_severity=undefined&bug_severity=undefined&bug_severity=undefined&assigned_to=undefined&assigned_to=undefined&assigned_to=undefined&assigned_to=undefined&assigned_to=undefined&assigned_to=undefined&assigned_to=undefined&assigned_to=undefined&assigned_to=undefined&assigned_to=undefined&assigned_to=undefined&assigned_to=undefined&assigned_to=undefined&assigned_to=undefined&assigned_to=undefined&assigned_to=undefined&assigned_to=undefined&assigned_to=undefined&assigned_to=undefined&qa_contact=undefined&qa_contact=undefined&qa_contact=undefined&qa_contact=undefined&qa_contact=undefined&qa_contact=undefined&qa_contact=undefined&qa_contact=undefined&qa_contact=undefined&qa_contact=undefined&qa_contact=undefined&qa_contact=undefined&qa_contact=undefined&qa_contact=undefined&qa_contact=undefined&qa_contact=undefined&qa_contact=undefined&qa_contact=undefined&qa_contact=undefined&qa_contact=undefined&qa_contact=undefined&qa_contact=undefined&qa_contact=undefined&qa_contact=undefined&qa_contact=undefined&qa_contact=undefined&qa_contact=undefined&&status_whiteboard=undefined&status_whiteboard=undefined&status_whiteboard=undefined&status_whiteboard=undefined&status_whiteboard=undefined&status_whiteboard=undefined&status_whiteboard=undefined&status_whiteboard=undefined&status_whiteboard=undefined&status_whiteboard=undefined&status_whiteboard=undefined&status_whiteboard=undefined&status_whiteboard=undefined&status_whiteboard=undefined&status_whiteboard=undefined&status_whiteboard=undefined&status_whiteboard=undefined&status_whiteboard=undefined&status_whiteboard=undefined&status_whiteboard=undefined&status_whiteboard=undefined&status_whiteboard=undefined&status_whiteboard=undefined&status_whiteboard=undefined&status_whiteboard=undefined&status_whiteboard=undefined&status_whiteboard=undefined&status_whiteboard=undefined&status_whiteboard=undefined&status_whiteboard=undefined&status_whiteboard=undefined&status_whiteboard=undefined&status_whiteboard=undefined&status_whiteboard=undefined&status_whiteboard=undefined&status_whiteboard=undefined&status_whiteboard=undefined&status_whiteboard=undefined&status_whiteboard=undefined&status_whiteboard=undefined&status_whiteboard=undefined&status_whiteboard=undefined&status_whiteboard=undefined&status_whiteboard=undefined&status_whiteboard=undefined&status_whiteboard=undefined&status_whiteboard=undefined&status_whiteboard=undefined&status_whiteboard=undefined&status_whiteboard=undefined&keywords=undefined&keywords=undefined&keywords=undefined&keywords=undefined&keywords=undefined&keywords=undefined&keywords=undefined&keywords=undefined&&&&&&cf_cust_facing=undefined&cf_cust_facing=undefined&cf_cust_facing=undefined&&&cf_devel_whiteboard=undefined&cf_devel_whiteboard=undefined&cf_devel_whiteboard=undefined&cf_devel_whiteboard=undefined&cf_devel_whiteboard=undefined&cf_devel_whiteboard=undefined&cf_devel_whiteboard=undefined&cf_devel_whiteboard=undefined&&&&cf_targetrelease=undefined&cf_targetrelease=undefined&cf_targetrelease=undefined&&&&&&&&&flag_type-65=undefined&flag_type-67=undefined&flag_type-16=undefined&&flag_type-66=undefined&defined_bit-41=undefined&defined_bit-68=undefined&defined_bit-31=undefined&defined_bit-58=undefined&defined_bit-50=undefined&defined_bit-83=undefined&defined_bit-21=undefined&defined_bit-32=undefined&defined_bit-15=undefined&defined_bit-12=undefined&defined_bit-161=undefined&defined_bit-44=undefined&defined_bit-22=undefined&defined_bit-11=undefined&defined_bit-48=undefined&defined_bit-10=undefined&defined_bit-93=undefined&estimated_time=undefined&estimated_time=undefined&estimated_time=undefined&work_time=undefined&remaining_time=undefined&remaining_time=undefined&remaining_time=undefined&&external_id=undefined&&&&&bug_status=undefined&bug_status=undefined&bug_status=undefined&bug_status=undefined&bug_status=undefined&bug_status=undefined&bug_status=undefined&bug_status=undefined&resolution=undefined&resolution=undefined&resolution=undefined&resolution=undefined&resolution=undefined&resolution=undefined&resolution=undefined&&defined_isprivate_2565431=undefined&defined_isprivate_2555537=undefined&defined_isprivate_2496782=undefined&defined_isprivate_2496777=undefined&defined_isprivate_2494690=undefined&defined_isprivate_2494684=undefined&defined_isprivate_2494683=undefined", name:"changeform", method:"post", acceptCharset:"", action:"https://bugzilla.redhat.com/process_bug.cgi", enctype:"", cookie:"rh_omni_tc=70160000000H4AoAAK; s_vi=[CS]v1|25979917051D2C29-6000013700076A9D[CE]; __qca=P0-1013915279-1261384255803; Bugzilla_login=74116; Bugzilla_logincookie=0MwDAbAIPF; LASTORDER=bugs.bug_status%2Cbugs.bug_id; DEFAULTFORMAT=specific; VERSION-Fedora=rawhide; BUGLIST=540856%3A551750%3A551892%3A551907%3A552418%3A552428%3A552481%3A552498%3A552517%3A552523%3A552552%3A552558%3A552559%3A552601%3A552605%3A552620; Bugzilla_javascript=1", autocomplete:"off", bugNo:"540856"}) diff --git a/skip-process-bug.js b/skip-process-bug.js index 76c705b..6120ede 100644 --- a/skip-process-bug.js +++ b/skip-process-bug.js @@ -20,6 +20,7 @@ var callback = function(document){ var options = {}; options.matches = [ "https://bugzilla.redhat.com/process_bug.cgi", + "https://bugzilla.redhat.com/post_bug.cgi", "https://bugzilla.mozilla.org/post_bug.cgi", "https://bugzilla.mozilla.org/process_bug.cgi" ]; |