Bug 439716 - Form Manager should be a JavaScript-based component. p=zpao+dolske, r=zpao, r=dolske, r=gavin
This commit is contained in:
Родитель
46686de686
Коммит
80d9553709
|
@ -148,10 +148,6 @@
|
|||
#define NS_FORMFILLCONTROLLER_CID \
|
||||
{ 0x895db6c7, 0xdbdf, 0x40ea, { 0x9f, 0x64, 0xb1, 0x75, 0x3, 0x32, 0x43, 0xdc } }
|
||||
|
||||
// {A2059C0E-5A58-4c55-AB7C-26F0557546EF}
|
||||
#define NS_FORMHISTORY_CID \
|
||||
{ 0xa2059c0e, 0x5a58, 0x4c55, { 0xab, 0x7c, 0x26, 0xf0, 0x55, 0x75, 0x46, 0xef } }
|
||||
|
||||
// {59648a91-5a60-4122-8ff2-54b839c84aed}
|
||||
#define NS_GLOBALHISTORY_CID \
|
||||
{ 0x59648a91, 0x5a60, 0x4122, { 0x8f, 0xf2, 0x54, 0xb8, 0x39, 0xc8, 0x4a, 0xed} }
|
||||
|
|
|
@ -51,12 +51,12 @@ LIBXUL_LIBRARY = 1
|
|||
EXPORT_LIBRARY = 1
|
||||
|
||||
EXTRA_COMPONENTS = \
|
||||
nsFormHistory.js \
|
||||
nsFormAutoComplete.js \
|
||||
$(NULL)
|
||||
|
||||
|
||||
CPPSRCS = nsFormFillController.cpp \
|
||||
nsStorageFormHistory.cpp \
|
||||
$(NULL)
|
||||
|
||||
LOCAL_INCLUDES = \
|
||||
|
|
|
@ -61,8 +61,8 @@ FormAutoComplete.prototype = {
|
|||
},
|
||||
|
||||
_prefBranch : null,
|
||||
_debug : false, // mirrors browser.formfill.debug
|
||||
_enabled : true, // mirrors browser.formfill.enable preference
|
||||
_debug : true, // mirrors browser.formfill.debug
|
||||
_enabled : true, // mirrors browser.formfill.enable preference
|
||||
_agedWeight : 2,
|
||||
_bucketSize : 1,
|
||||
_maxTimeGroupings : 25,
|
||||
|
@ -133,6 +133,7 @@ FormAutoComplete.prototype = {
|
|||
}
|
||||
} else if (topic == "xpcom-shutdown") {
|
||||
self._dbStmts = null;
|
||||
self.__formHistory = null;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -39,7 +39,6 @@
|
|||
|
||||
#include "nsFormFillController.h"
|
||||
|
||||
#include "nsStorageFormHistory.h"
|
||||
#include "nsIFormAutoComplete.h"
|
||||
#include "nsIAutoCompleteSimpleResult.h"
|
||||
#include "nsString.h"
|
||||
|
@ -1210,16 +1209,10 @@ nsFormFillController::IsEventTrusted(nsIDOMEvent *aEvent)
|
|||
return isTrusted;
|
||||
}
|
||||
|
||||
NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsFormHistory, Init)
|
||||
NS_GENERIC_FACTORY_CONSTRUCTOR(nsFormFillController)
|
||||
|
||||
static const nsModuleComponentInfo components[] =
|
||||
{
|
||||
{ "HTML Form History",
|
||||
NS_FORMHISTORY_CID,
|
||||
NS_FORMHISTORY_CONTRACTID,
|
||||
nsFormHistoryConstructor },
|
||||
|
||||
{ "HTML Form Fill Controller",
|
||||
NS_FORMFILLCONTROLLER_CID,
|
||||
"@mozilla.org/satchel/form-fill-controller;1",
|
||||
|
|
|
@ -0,0 +1,939 @@
|
|||
/* ***** BEGIN LICENSE BLOCK *****
|
||||
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License Version
|
||||
* 1.1 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
* http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing rights and limitations under the
|
||||
* License.
|
||||
*
|
||||
* The Original Code is mozilla.org code.
|
||||
*
|
||||
* The Initial Developer of the Original Code is Mozilla Foundation.
|
||||
* Portions created by the Initial Developer are Copyright (C) 2010
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
* Justin Dolske <dolske@mozilla.com> (original authors)
|
||||
* Paul O’Shannessy <paul@oshannessy.com> (original authors)
|
||||
*
|
||||
* Alternatively, the contents of this file may be used under the terms of
|
||||
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
* in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
* of those above. If you wish to allow use of your version of this file only
|
||||
* under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
* use your version of this file under the terms of the MPL, indicate your
|
||||
* decision by deleting the provisions above and replace them with the notice
|
||||
* and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
* the provisions above, a recipient may use your version of this file under
|
||||
* the terms of any one of the MPL, the GPL or the LGPL.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
const Cr = Components.results;
|
||||
|
||||
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Components.utils.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
const DB_VERSION = 3;
|
||||
const DAY_IN_MS = 86400000; // 1 day in milliseconds
|
||||
|
||||
function FormHistory() {
|
||||
this.init();
|
||||
}
|
||||
|
||||
FormHistory.prototype = {
|
||||
classDescription : "FormHistory",
|
||||
contractID : "@mozilla.org/satchel/form-history;1",
|
||||
classID : Components.ID("{0c1bb408-71a2-403f-854a-3a0659829ded}"),
|
||||
QueryInterface : XPCOMUtils.generateQI([Ci.nsIFormHistory2, Ci.nsIObserver, Ci.nsIFormSubmitObserver, Ci.nsISupportsWeakReference]),
|
||||
|
||||
debug : true,
|
||||
enabled : true,
|
||||
saveHttpsForms : true,
|
||||
prefBranch : null,
|
||||
|
||||
// The current database schema.
|
||||
dbSchema : {
|
||||
tables : {
|
||||
moz_formhistory: {
|
||||
"id" : "INTEGER PRIMARY KEY",
|
||||
"fieldname" : "TEXT NOT NULL",
|
||||
"value" : "TEXT NOT NULL",
|
||||
"timesUsed" : "INTEGER",
|
||||
"firstUsed" : "INTEGER",
|
||||
"lastUsed" : "INTEGER",
|
||||
"guid" : "TEXT"
|
||||
},
|
||||
},
|
||||
indices : {
|
||||
moz_formhistory_index : {
|
||||
table : "moz_formhistory",
|
||||
columns : ["fieldname"]
|
||||
},
|
||||
moz_formhistory_lastused_index : {
|
||||
table : "moz_formhistory",
|
||||
columns : ["lastUsed"]
|
||||
},
|
||||
moz_formhistory_guid_index : {
|
||||
table : "moz_formhistory",
|
||||
columns : ["guid"]
|
||||
},
|
||||
}
|
||||
},
|
||||
dbConnection : null, // The database connection
|
||||
dbStmts : null, // Database statements for memoization
|
||||
dbFile : null,
|
||||
|
||||
_uuidService: null,
|
||||
get uuidService() {
|
||||
if (!this._uuidService)
|
||||
this._uuidService = Cc["@mozilla.org/uuid-generator;1"].
|
||||
getService(Ci.nsIUUIDGenerator);
|
||||
return this._uuidService;
|
||||
},
|
||||
|
||||
// Private Browsing Service
|
||||
// If the service is not available, null will be returned.
|
||||
_privBrowsingSvc : undefined,
|
||||
get privBrowsingSvc() {
|
||||
if (this._privBrowsingSvc == undefined) {
|
||||
if ("@mozilla.org/privatebrowsing;1" in Cc)
|
||||
this._privBrowsingSvc = Cc["@mozilla.org/privatebrowsing;1"].
|
||||
getService(Ci.nsIPrivateBrowsingService);
|
||||
else
|
||||
this._privBrowsingSvc = null;
|
||||
}
|
||||
return this._privBrowsingSvc;
|
||||
},
|
||||
|
||||
|
||||
log : function (message) {
|
||||
if (!this.debug)
|
||||
return;
|
||||
dump("FormHistory: " + message + "\n");
|
||||
Services.console.logStringMessage("FormHistory: " + message);
|
||||
},
|
||||
|
||||
|
||||
init : function() {
|
||||
let self = this;
|
||||
this.prefBranch = Services.prefs.getBranch("browser.formfill.");
|
||||
this.prefBranch.QueryInterface(Ci.nsIPrefBranch2);
|
||||
this.prefBranch.addObserver("", this, true);
|
||||
this.updatePrefs();
|
||||
|
||||
this.dbStmts = {};
|
||||
|
||||
// Add observers
|
||||
Services.obs.addObserver(this, "earlyformsubmit", false);
|
||||
Services.obs.addObserver(function() { self.expireOldEntries() }, "idle-daily", false);
|
||||
Services.obs.addObserver(function() { self.expireOldEntries() }, "formhistory-expire-now", false);
|
||||
|
||||
try {
|
||||
this.dbFile = Services.dirsvc.get("ProfD", Ci.nsIFile).clone();
|
||||
this.dbFile.append("formhistory.sqlite");
|
||||
this.log("Opening database at " + this.dbFile.path);
|
||||
|
||||
this.dbInit();
|
||||
} catch (e) {
|
||||
this.log("Initialization failed: " + e);
|
||||
// If dbInit fails...
|
||||
if (e.result == Cr.NS_ERROR_FILE_CORRUPTED) {
|
||||
this.dbCleanup(true);
|
||||
this.dbInit();
|
||||
} else {
|
||||
throw "Initialization failed";
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
/* ---- nsIFormHistory2 interfaces ---- */
|
||||
|
||||
|
||||
get hasEntries() {
|
||||
return (this.countAllEntries() > 0);
|
||||
},
|
||||
|
||||
|
||||
addEntry : function (name, value) {
|
||||
if (!this.enabled ||
|
||||
this.privBrowsingSvc && this.privBrowsingSvc.privateBrowsingEnabled)
|
||||
return;
|
||||
|
||||
this.log("addEntry for " + name + "=" + value);
|
||||
|
||||
let now = Date.now() * 1000; // microseconds
|
||||
|
||||
let [id, guid] = this.getExistingEntryID(name, value);
|
||||
|
||||
if (id != -1) {
|
||||
// Update existing entry
|
||||
let stmt;
|
||||
let query = "UPDATE moz_formhistory SET timesUsed = timesUsed + 1, lastUsed = :lastUsed WHERE id = :id";
|
||||
let params = {
|
||||
lastUsed : now,
|
||||
id : id
|
||||
};
|
||||
|
||||
try {
|
||||
stmt = this.dbCreateStatement(query, params);
|
||||
stmt.execute();
|
||||
this.sendStringNotification("modifyEntry", name, value, guid);
|
||||
} catch (e) {
|
||||
this.log("addEntry (modify) failed: " + e);
|
||||
throw e;
|
||||
} finally {
|
||||
stmt.reset();
|
||||
}
|
||||
|
||||
} else {
|
||||
// Add new entry
|
||||
guid = this.generateGUID();
|
||||
|
||||
let query = "INSERT INTO moz_formhistory (fieldname, value, timesUsed, firstUsed, lastUsed, guid) " +
|
||||
"VALUES (:fieldname, :value, :timesUsed, :firstUsed, :lastUsed, :guid)";
|
||||
let params = {
|
||||
fieldname : name,
|
||||
value : value,
|
||||
timesUsed : 1,
|
||||
firstUsed : now,
|
||||
lastUsed : now,
|
||||
guid : guid
|
||||
};
|
||||
|
||||
try {
|
||||
stmt = this.dbCreateStatement(query, params);
|
||||
stmt.execute();
|
||||
this.sendStringNotification("addEntry", name, value, guid);
|
||||
} catch (e) {
|
||||
this.log("addEntry (create) failed: " + e);
|
||||
throw e;
|
||||
} finally {
|
||||
stmt.reset();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
removeEntry : function (name, value) {
|
||||
this.log("removeEntry for " + name + "=" + value);
|
||||
|
||||
let [id, guid] = this.getExistingEntryID(name, value);
|
||||
this.sendStringNotification("before-removeEntry", name, value, guid);
|
||||
|
||||
let stmt;
|
||||
let query = "DELETE FROM moz_formhistory WHERE id = :id";
|
||||
let params = { id : id };
|
||||
|
||||
try {
|
||||
stmt = this.dbCreateStatement(query, params);
|
||||
stmt.execute();
|
||||
this.sendStringNotification("removeEntry", name, value, guid);
|
||||
} catch (e) {
|
||||
this.log("removeEntry failed: " + e);
|
||||
throw e;
|
||||
} finally {
|
||||
stmt.reset();
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
removeEntriesForName : function (name) {
|
||||
this.log("removeEntriesForName with name=" + name);
|
||||
|
||||
this.sendStringNotification("before-removeEntriesForName", name);
|
||||
|
||||
let stmt;
|
||||
let query = "DELETE FROM moz_formhistory WHERE fieldname = :fieldname";
|
||||
let params = { fieldname : name };
|
||||
|
||||
try {
|
||||
stmt = this.dbCreateStatement(query, params);
|
||||
stmt.execute();
|
||||
this.sendStringNotification("removeEntriesForName", name);
|
||||
} catch (e) {
|
||||
this.log("removeEntriesForName failed: " + e);
|
||||
throw e;
|
||||
} finally {
|
||||
stmt.reset();
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
removeAllEntries : function () {
|
||||
this.log("removeAllEntries");
|
||||
|
||||
this.sendNotification("before-removeAllEntries", null);
|
||||
|
||||
let stmt;
|
||||
let query = "DELETE FROM moz_formhistory";
|
||||
|
||||
try {
|
||||
stmt = this.dbCreateStatement(query);
|
||||
stmt.execute();
|
||||
this.sendNotification("removeAllEntries", null);
|
||||
} catch (e) {
|
||||
this.log("removeEntriesForName failed: " + e);
|
||||
throw e;
|
||||
} finally {
|
||||
stmt.reset();
|
||||
}
|
||||
|
||||
// privacy cleanup, if there's an old mork formhistory around, just delete it
|
||||
let oldFile = Services.dirsvc.get("ProfD", Ci.nsIFile);
|
||||
oldFile.append("formhistory.dat");
|
||||
if (oldFile.exists())
|
||||
oldFile.remove(false);
|
||||
},
|
||||
|
||||
|
||||
nameExists : function (name) {
|
||||
this.log("nameExists for name=" + name);
|
||||
let stmt;
|
||||
let query = "SELECT COUNT(1) AS numEntries FROM moz_formhistory WHERE fieldname = :fieldname";
|
||||
let params = { fieldname : name };
|
||||
try {
|
||||
stmt = this.dbCreateStatement(query, params);
|
||||
stmt.executeStep();
|
||||
return (stmt.row.numEntries > 0);
|
||||
} catch (e) {
|
||||
this.log("nameExists failed: " + e);
|
||||
throw e;
|
||||
} finally {
|
||||
stmt.reset();
|
||||
}
|
||||
},
|
||||
|
||||
entryExists : function (name, value) {
|
||||
this.log("entryExists for " + name + "=" + value);
|
||||
let [id, guid] = this.getExistingEntryID(name, value);
|
||||
this.log("entryExists: id=" + id);
|
||||
return (id != -1);
|
||||
},
|
||||
|
||||
removeEntriesByTimeframe : function (beginTime, endTime) {
|
||||
this.log("removeEntriesByTimeframe for " + beginTime + " to " + endTime);
|
||||
|
||||
this.sendIntNotification("before-removeEntriesByTimeframe", beginTime, endTime);
|
||||
|
||||
let stmt;
|
||||
let query = "DELETE FROM moz_formhistory WHERE firstUsed >= :beginTime AND firstUsed <= :endTime";
|
||||
let params = {
|
||||
beginTime : beginTime,
|
||||
endTime : endTime
|
||||
};
|
||||
try {
|
||||
stmt = this.dbCreateStatement(query, params);
|
||||
stmt.executeStep();
|
||||
this.sendIntNotification("removeEntriesByTimeframe", beginTime, endTime);
|
||||
} catch (e) {
|
||||
this.log("removeEntriesByTimeframe failed: " + e);
|
||||
throw e;
|
||||
} finally {
|
||||
stmt.reset();
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
|
||||
get DBConnection() {
|
||||
return this.dbConnection;
|
||||
},
|
||||
|
||||
|
||||
/* ---- nsIObserver interface ---- */
|
||||
|
||||
|
||||
observe : function (subject, topic, data) {
|
||||
if (topic == "nsPref:changed")
|
||||
this.updatePrefs();
|
||||
else
|
||||
this.log("Oops! Unexpected notification: " + topic);
|
||||
},
|
||||
|
||||
|
||||
/* ---- nsIFormSubmitObserver interfaces ---- */
|
||||
|
||||
|
||||
notify : function(form, domWin, actionURI, cancelSubmit) {
|
||||
if (!this.enabled)
|
||||
return;
|
||||
|
||||
this.log("Form submit observer notified.");
|
||||
|
||||
if (!this.saveHttpsForms) {
|
||||
if (actionURI.schemeIs("https"))
|
||||
return;
|
||||
if (form.ownerDocument.documentURIObject.schemeIs("https"))
|
||||
return;
|
||||
}
|
||||
|
||||
if (form.hasAttribute("autocomplete") &&
|
||||
form.getAttribute("autocomplete").toLowerCase() == "off")
|
||||
return;
|
||||
|
||||
// Open a transaction so that multiple additions are efficient.
|
||||
this.dbConnection.beginTransaction();
|
||||
|
||||
try {
|
||||
let savedCount = 0;
|
||||
for (let i = 0; i < form.elements.length; i++) {
|
||||
let input = form.elements[i];
|
||||
if (!(input instanceof Ci.nsIDOMHTMLInputElement))
|
||||
continue;
|
||||
|
||||
// Only use inputs that hold text values (not including type="password")
|
||||
if (!input.mozIsTextField(true))
|
||||
continue;
|
||||
|
||||
// Bug 394612: If Login Manager marked this input, don't save it.
|
||||
// The login manager will deal with remembering it.
|
||||
|
||||
// Don't save values when autocomplete=off is present.
|
||||
if (input.hasAttribute("autocomplete") &&
|
||||
input.getAttribute("autocomplete").toLowerCase() == "off")
|
||||
continue;
|
||||
|
||||
let value = input.value.trim();
|
||||
|
||||
// Don't save empty or unchanged values.
|
||||
if (!value || value == input.defaultValue.trim())
|
||||
continue;
|
||||
|
||||
// Don't save credit card numbers.
|
||||
if (this.isValidCCNumber(value)) {
|
||||
this.log("skipping saving a credit card number");
|
||||
continue;
|
||||
}
|
||||
|
||||
let name = input.name || input.id;
|
||||
if (!name)
|
||||
continue;
|
||||
|
||||
// Limit stored data to 200 characters.
|
||||
if (name.length > 200 || value.length > 200) {
|
||||
this.log("skipping input that has a name/value too large");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Limit number of fields stored per form.
|
||||
if (savedCount++ >= 100) {
|
||||
this.log("not saving any more entries for this form.");
|
||||
break;
|
||||
}
|
||||
|
||||
this.addEntry(name, value);
|
||||
}
|
||||
} catch (e) {
|
||||
// Empty
|
||||
} finally {
|
||||
// Save whatever we've added so far.
|
||||
this.dbConnection.commitTransaction();
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
/* ---- helpers ---- */
|
||||
|
||||
|
||||
generateGUID : function() {
|
||||
// string like: "{f60d9eac-9421-4abc-8491-8e8322b063d4}"
|
||||
let uuid = this.uuidService.generateUUID().toString();
|
||||
let raw = ""; // A string with the low bytes set to random values
|
||||
let bytes = 0;
|
||||
for (let i = 1; bytes < 12 ; i+= 2) {
|
||||
// Skip dashes
|
||||
if (uuid[i] == "-")
|
||||
i++;
|
||||
let hexVal = parseInt(uuid[i] + uuid[i + 1], 16);
|
||||
raw += String.fromCharCode(hexVal);
|
||||
bytes++;
|
||||
}
|
||||
return btoa(raw);
|
||||
},
|
||||
|
||||
|
||||
sendStringNotification : function (changeType, str1, str2, str3) {
|
||||
function wrapit(str) {
|
||||
let wrapper = Cc["@mozilla.org/supports-string;1"].
|
||||
createInstance(Ci.nsISupportsString);
|
||||
wrapper.data = str;
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
let strData;
|
||||
if (arguments.length == 2) {
|
||||
// Just 1 string, no need to put it in an array
|
||||
strData = wrapit(str1);
|
||||
} else {
|
||||
// 3 strings, put them in an array.
|
||||
strData = Cc["@mozilla.org/array;1"].
|
||||
createInstance(Ci.nsIMutableArray);
|
||||
strData.appendElement(wrapit(str1), false);
|
||||
strData.appendElement(wrapit(str2), false);
|
||||
strData.appendElement(wrapit(str3), false);
|
||||
}
|
||||
this.sendNotification(changeType, strData);
|
||||
},
|
||||
|
||||
|
||||
sendIntNotification : function (changeType, int1, int2) {
|
||||
function wrapit(int) {
|
||||
let wrapper = Cc["@mozilla.org/supports-PRInt64;1"].
|
||||
createInstance(Ci.nsISupportsPRInt64);
|
||||
wrapper.data = int;
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
let intData;
|
||||
if (arguments.length == 2) {
|
||||
// Just 1 int, no need for an array
|
||||
intData = wrapit(int1);
|
||||
} else {
|
||||
// 2 ints, put them in an array.
|
||||
intData = Cc["@mozilla.org/array;1"].
|
||||
createInstance(Ci.nsIMutableArray);
|
||||
intData.appendElement(wrapit(int1), false);
|
||||
intData.appendElement(wrapit(int2), false);
|
||||
}
|
||||
this.sendNotification(changeType, intData);
|
||||
},
|
||||
|
||||
|
||||
sendNotification : function (changeType, data) {
|
||||
Services.obs.notifyObservers(data, "satchel-storage-changed", changeType);
|
||||
},
|
||||
|
||||
|
||||
getExistingEntryID : function (name, value) {
|
||||
let id = -1, guid = null;
|
||||
let stmt;
|
||||
let query = "SELECT id, guid FROM moz_formhistory WHERE fieldname = :fieldname AND value = :value";
|
||||
let params = {
|
||||
fieldname : name,
|
||||
value : value
|
||||
};
|
||||
try {
|
||||
stmt = this.dbCreateStatement(query, params);
|
||||
if (stmt.executeStep()) {
|
||||
id = stmt.row.id;
|
||||
guid = stmt.row.guid;
|
||||
}
|
||||
} catch (e) {
|
||||
this.log("getExistingEntryID failed: " + e);
|
||||
throw e;
|
||||
} finally {
|
||||
stmt.reset();
|
||||
}
|
||||
|
||||
return [id, guid];
|
||||
},
|
||||
|
||||
|
||||
countAllEntries : function () {
|
||||
let query = "SELECT COUNT(1) AS numEntries FROM moz_formhistory";
|
||||
|
||||
let stmt, numEntries;
|
||||
try {
|
||||
stmt = this.dbCreateStatement(query, null);
|
||||
stmt.executeStep();
|
||||
numEntries = stmt.row.numEntries;
|
||||
} catch (e) {
|
||||
this.log("countAllEntries failed: " + e);
|
||||
throw e;
|
||||
} finally {
|
||||
stmt.reset();
|
||||
}
|
||||
|
||||
this.log("countAllEntries: counted entries: " + numEntries);
|
||||
return numEntries;
|
||||
},
|
||||
|
||||
|
||||
expireOldEntries : function () {
|
||||
this.log("expireOldEntries");
|
||||
|
||||
// Determine how many days of history we're supposed to keep.
|
||||
let expireDays = 180;
|
||||
try {
|
||||
expireDays = this.prefBranch.getIntPref("expire_days");
|
||||
} catch (e) { /* ignore */ }
|
||||
|
||||
let expireTime = Date.now() - expireDays * DAY_IN_MS;
|
||||
expireTime *= 1000; // switch to microseconds
|
||||
|
||||
this.sendIntNotification("before-expireOldEntries", expireTime);
|
||||
|
||||
let beginningCount = this.countAllEntries();
|
||||
|
||||
// Purge the form history...
|
||||
let stmt;
|
||||
let query = "DELETE FROM moz_formhistory WHERE lastUsed <= :expireTime";
|
||||
let params = { expireTime : expireTime };
|
||||
|
||||
try {
|
||||
stmt = this.dbCreateStatement(query, params);
|
||||
stmt.execute();
|
||||
} catch (e) {
|
||||
this.log("expireOldEntries failed: " + e);
|
||||
throw e;
|
||||
} finally {
|
||||
stmt.reset();
|
||||
}
|
||||
|
||||
let endingCount = this.countAllEntries();
|
||||
|
||||
// If we expired a large batch of entries, shrink the DB to reclaim wasted
|
||||
// space. This is expected to happen when entries predating timestamps
|
||||
// (added in the v.1 schema) expire in mass, 180 days after the DB was
|
||||
// upgraded -- entries not used since then expire all at once.
|
||||
if (beginningCount - endingCount > 500)
|
||||
this.dbConnection.executeSimpleSQL("VACUUM");
|
||||
|
||||
this.sendIntNotification("expireOldEntries", expireTime);
|
||||
},
|
||||
|
||||
|
||||
updatePrefs : function () {
|
||||
this.debug = this.prefBranch.getBoolPref("debug");
|
||||
this.enabled = this.prefBranch.getBoolPref("enable");
|
||||
this.saveHttpsForms = this.prefBranch.getBoolPref("saveHttpsForms");
|
||||
},
|
||||
|
||||
|
||||
// Implements the Luhn checksum algorithm as described at
|
||||
// http://wikipedia.org/wiki/Luhn_algorithm
|
||||
isValidCCNumber : function (ccNumber) {
|
||||
// Remove dashes and whitespace
|
||||
ccNumber = ccNumber.replace(/[\-\s]/g, '');
|
||||
|
||||
let len = ccNumber.length;
|
||||
if (len != 9 && len != 15 && len != 16)
|
||||
return false;
|
||||
|
||||
if (!/^\d+$/.test(ccNumber))
|
||||
return false;
|
||||
|
||||
let total = 0;
|
||||
for (let i = 0; i < len; i++) {
|
||||
let ch = parseInt(ccNumber[len - i - 1]);
|
||||
if (i % 2 == 1) {
|
||||
// Double it, add digits together if > 10
|
||||
ch *= 2;
|
||||
if (ch > 9)
|
||||
ch -= 9;
|
||||
}
|
||||
total += ch;
|
||||
}
|
||||
return total % 10 == 0;
|
||||
},
|
||||
|
||||
|
||||
|
||||
//**************************************************************************//
|
||||
// Database Creation & Access
|
||||
|
||||
/*
|
||||
* dbCreateStatement
|
||||
*
|
||||
* Creates a statement, wraps it, and then does parameter replacement
|
||||
* Will use memoization so that statements can be reused.
|
||||
*/
|
||||
dbCreateStatement : function (query, params) {
|
||||
let stmt = this.dbStmts[query];
|
||||
// Memoize the statements
|
||||
if (!stmt) {
|
||||
this.log("Creating new statement for query: " + query);
|
||||
stmt = this.dbConnection.createStatement(query);
|
||||
this.dbStmts[query] = stmt;
|
||||
}
|
||||
// Replace parameters, must be done 1 at a time
|
||||
if (params)
|
||||
for (let i in params)
|
||||
stmt.params[i] = params[i];
|
||||
return stmt;
|
||||
},
|
||||
|
||||
|
||||
/*
|
||||
* dbInit
|
||||
*
|
||||
* Attempts to initialize the database. This creates the file if it doesn't
|
||||
* exist, performs any migrations, etc.
|
||||
*/
|
||||
dbInit : function () {
|
||||
this.log("Initializing Database");
|
||||
|
||||
let storage = Cc["@mozilla.org/storage/service;1"].
|
||||
getService(Ci.mozIStorageService);
|
||||
this.dbConnection = storage.openDatabase(this.dbFile);
|
||||
let version = this.dbConnection.schemaVersion;
|
||||
|
||||
// Note: Firefox 3 didn't set a schema value, so it started from 0.
|
||||
// So we can't depend on a simple version == 0 check
|
||||
if (version == 0 && !this.dbConnection.tableExists("moz_formhistory"))
|
||||
this.dbCreate();
|
||||
else if (version != DB_VERSION)
|
||||
this.dbMigrate(version);
|
||||
},
|
||||
|
||||
|
||||
dbCreate: function () {
|
||||
this.log("Creating DB -- tables");
|
||||
for (let name in this.dbSchema.tables) {
|
||||
let table = this.dbSchema.tables[name];
|
||||
let tSQL = [[col, table[col]].join(" ") for (col in table)].join(", ");
|
||||
this.dbConnection.createTable(name, tSQL);
|
||||
}
|
||||
|
||||
this.log("Creating DB -- indices");
|
||||
for (let name in this.dbSchema.indices) {
|
||||
let index = this.dbSchema.indices[name];
|
||||
let statement = "CREATE INDEX IF NOT EXISTS " + name + " ON " + index.table +
|
||||
"(" + index.columns.join(", ") + ")";
|
||||
this.dbConnection.executeSimpleSQL(statement);
|
||||
}
|
||||
|
||||
this.dbConnection.schemaVersion = DB_VERSION;
|
||||
},
|
||||
|
||||
|
||||
dbMigrate : function (oldVersion) {
|
||||
this.log("Attempting to migrate from version " + oldVersion);
|
||||
|
||||
if (oldVersion > DB_VERSION) {
|
||||
this.log("Downgrading to version " + DB_VERSION);
|
||||
// User's DB is newer. Sanity check that our expected columns are
|
||||
// present, and if so mark the lower version and merrily continue
|
||||
// on. If the columns are borked, something is wrong so blow away
|
||||
// the DB and start from scratch. [Future incompatible upgrades
|
||||
// should swtich to a different table or file.]
|
||||
|
||||
if (!this.dbAreExpectedColumnsPresent())
|
||||
throw Components.Exception("DB is missing expected columns",
|
||||
Cr.NS_ERROR_FILE_CORRUPTED);
|
||||
|
||||
// Change the stored version to the current version. If the user
|
||||
// runs the newer code again, it will see the lower version number
|
||||
// and re-upgrade (to fixup any entries the old code added).
|
||||
this.dbConnection.schemaVersion = DB_VERSION;
|
||||
return;
|
||||
}
|
||||
|
||||
// Upgrade to newer version...
|
||||
|
||||
this.dbConnection.beginTransaction();
|
||||
|
||||
try {
|
||||
for (let v = oldVersion + 1; v <= DB_VERSION; v++) {
|
||||
this.log("Upgrading to version " + v + "...");
|
||||
let migrateFunction = "dbMigrateToVersion" + v;
|
||||
this[migrateFunction]();
|
||||
}
|
||||
} catch (e) {
|
||||
this.log("Migration failed: " + e);
|
||||
this.dbConnection.rollbackTransaction();
|
||||
throw e;
|
||||
}
|
||||
|
||||
this.dbConnection.schemaVersion = DB_VERSION;
|
||||
this.dbConnection.commitTransaction();
|
||||
this.log("DB migration completed.");
|
||||
},
|
||||
|
||||
|
||||
/*
|
||||
* dbMigrateToVersion1
|
||||
*
|
||||
* Updates the DB schema to v1 (bug 463154).
|
||||
* Adds firstUsed, lastUsed, timesUsed columns.
|
||||
*/
|
||||
dbMigrateToVersion1 : function () {
|
||||
// Check to see if the new columns already exist (could be a v1 DB that
|
||||
// was downgraded to v0). If they exist, we don't need to add them.
|
||||
let query;
|
||||
["timesUsed", "firstUsed", "lastUsed"].forEach(function(column) {
|
||||
if (!this.dbColumnExists(column)) {
|
||||
query = "ALTER TABLE moz_formhistory ADD COLUMN " + column + " INTEGER";
|
||||
this.dbConnection.executeSimpleSQL(query);
|
||||
}
|
||||
}, this);
|
||||
|
||||
// Set the default values for the new columns.
|
||||
//
|
||||
// Note that we set the timestamps to 24 hours in the past. We want a
|
||||
// timestamp that's recent (so that "keep form history for 90 days"
|
||||
// doesn't expire things surprisingly soon), but not so recent that
|
||||
// "forget the last hour of stuff" deletes all freshly migrated data.
|
||||
let stmt;
|
||||
query = "UPDATE moz_formhistory " +
|
||||
"SET timesUsed = 1, firstUsed = :time, lastUsed = :time " +
|
||||
"WHERE timesUsed isnull OR firstUsed isnull or lastUsed isnull";
|
||||
let params = { time: (Date.now() - DAY_IN_MS) * 1000 }
|
||||
try {
|
||||
stmt = this.dbCreateStatement(query, params);
|
||||
stmt.execute();
|
||||
} catch (e) {
|
||||
this.log("Failed setting timestamps: " + e);
|
||||
throw e;
|
||||
} finally {
|
||||
stmt.reset();
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
/*
|
||||
* dbMigrateToVersion2
|
||||
*
|
||||
* Updates the DB schema to v2 (bug 243136).
|
||||
* Adds lastUsed index, removes moz_dummy_table
|
||||
*/
|
||||
dbMigrateToVersion2 : function () {
|
||||
let query = "DROP TABLE IF EXISTS moz_dummy_table";
|
||||
this.dbConnection.executeSimpleSQL(query);
|
||||
|
||||
query = "CREATE INDEX IF NOT EXISTS moz_formhistory_lastused_index ON moz_formhistory (lastUsed)";
|
||||
this.dbConnection.executeSimpleSQL(query);
|
||||
},
|
||||
|
||||
|
||||
/*
|
||||
* dbMigrateToVersion3
|
||||
*
|
||||
* Updates the DB schema to v3 (bug 506402).
|
||||
* Adds guid column and index.
|
||||
*/
|
||||
dbMigrateToVersion3 : function () {
|
||||
// Check to see if GUID column already exists, add if needed
|
||||
let query;
|
||||
if (!this.dbColumnExists("guid")) {
|
||||
query = "ALTER TABLE moz_formhistory ADD COLUMN guid TEXT";
|
||||
this.dbConnection.executeSimpleSQL(query);
|
||||
|
||||
query = "CREATE INDEX IF NOT EXISTS moz_formhistory_guid_index ON moz_formhistory (guid)";
|
||||
this.dbConnection.executeSimpleSQL(query);
|
||||
}
|
||||
|
||||
// Get a list of IDs for existing logins
|
||||
let ids = [];
|
||||
query = "SELECT id FROM moz_formhistory WHERE guid isnull";
|
||||
let stmt;
|
||||
try {
|
||||
stmt = this.dbCreateStatement(query);
|
||||
while (stmt.executeStep())
|
||||
ids.push(stmt.row.id);
|
||||
} catch (e) {
|
||||
this.log("Failed getting IDs: " + e);
|
||||
throw e;
|
||||
} finally {
|
||||
stmt.reset();
|
||||
}
|
||||
|
||||
// Generate a GUID for each login and update the DB.
|
||||
query = "UPDATE moz_formhistory SET guid = :guid WHERE id = :id";
|
||||
for each (let id in ids) {
|
||||
let params = {
|
||||
id : id,
|
||||
guid : this.generateGUID()
|
||||
};
|
||||
|
||||
try {
|
||||
stmt = this.dbCreateStatement(query, params);
|
||||
stmt.execute();
|
||||
} catch (e) {
|
||||
this.log("Failed setting GUID: " + e);
|
||||
throw e;
|
||||
} finally {
|
||||
stmt.reset();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
/*
|
||||
* dbAreExpectedColumnsPresent
|
||||
*
|
||||
* Sanity check to ensure that the columns this version of the code expects
|
||||
* are present in the DB we're using.
|
||||
*/
|
||||
dbAreExpectedColumnsPresent : function () {
|
||||
for (let name in this.dbSchema.tables) {
|
||||
let table = this.dbSchema.tables[name];
|
||||
let query = "SELECT " +
|
||||
[col for (col in table)].join(", ") +
|
||||
" FROM " + name;
|
||||
try {
|
||||
let stmt = this.dbConnection.createStatement(query);
|
||||
// (no need to execute statement, if it compiled we're good)
|
||||
stmt.finalize();
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
this.log("verified that expected columns are present in DB.");
|
||||
return true;
|
||||
},
|
||||
|
||||
|
||||
/*
|
||||
* dbColumnExists
|
||||
*
|
||||
* Checks to see if the named column already exists.
|
||||
*/
|
||||
dbColumnExists : function (columnName) {
|
||||
let query = "SELECT " + columnName + " FROM moz_formhistory";
|
||||
try {
|
||||
let stmt = this.dbConnection.createStatement(query);
|
||||
// (no need to execute statement, if it compiled we're good)
|
||||
stmt.finalize();
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
/*
|
||||
* dbCleanup
|
||||
*
|
||||
* Called when database creation fails. Finalizes database statements,
|
||||
* closes the database connection, deletes the database file.
|
||||
*/
|
||||
dbCleanup : function (backup) {
|
||||
this.log("Cleaning up DB file - close & remove & backup=" + backup)
|
||||
|
||||
// Create backup file
|
||||
if (backup) {
|
||||
let storage = Cc["@mozilla.org/storage/service;1"].
|
||||
getService(Ci.mozIStorageService);
|
||||
|
||||
let backupFile = this.dbFile.leafName + ".corrupt";
|
||||
storage.backupDatabaseFile(this.dbFile, backupFile);
|
||||
}
|
||||
|
||||
// Finalize all statements to free memory, avoid errors later
|
||||
for each (let stmt in this.dbStmts)
|
||||
stmt.finalize();
|
||||
this.dbStmts = [];
|
||||
|
||||
// Close the connection, ignore 'already closed' error
|
||||
try { this.dbConnection.close() } catch(e) {}
|
||||
this.dbFile.remove(false);
|
||||
}
|
||||
};
|
||||
|
||||
let component = [FormHistory];
|
||||
function NSGetModule (compMgr, fileSpec) {
|
||||
return XPCOMUtils.generateModule(component);
|
||||
}
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -1,149 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* ***** BEGIN LICENSE BLOCK *****
|
||||
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License Version
|
||||
* 1.1 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
* http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing rights and limitations under the
|
||||
* License.
|
||||
*
|
||||
* The Original Code is Mozilla Communicator client code.
|
||||
*
|
||||
* The Initial Developer of the Original Code is
|
||||
* Netscape Communications Corporation.
|
||||
* Portions created by the Initial Developer are Copyright (C) 1998
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
* Joe Hewitt <hewitt@netscape.com> (Original Author)
|
||||
*
|
||||
* Alternatively, the contents of this file may be used under the terms of
|
||||
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
* in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
* of those above. If you wish to allow use of your version of this file only
|
||||
* under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
* use your version of this file under the terms of the MPL, indicate your
|
||||
* decision by deleting the provisions above and replace them with the notice
|
||||
* and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
* the provisions above, a recipient may use your version of this file under
|
||||
* the terms of any one of the MPL, the GPL or the LGPL.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
#ifndef __nsFormHistory__
|
||||
#define __nsFormHistory__
|
||||
|
||||
#include "nsIFormHistory.h"
|
||||
#include "nsIFormSubmitObserver.h"
|
||||
#include "nsString.h"
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsIObserver.h"
|
||||
#include "nsIPrefBranch.h"
|
||||
#include "nsIUUIDGenerator.h"
|
||||
#include "nsWeakReference.h"
|
||||
#include "nsIMutableArray.h"
|
||||
|
||||
#include "mozIStorageService.h"
|
||||
#include "mozIStorageConnection.h"
|
||||
#include "mozIStorageStatement.h"
|
||||
|
||||
#include "nsServiceManagerUtils.h"
|
||||
#include "nsToolkitCompsCID.h"
|
||||
|
||||
class nsIAutoCompleteSimpleResult;
|
||||
class nsIAutoCompleteResult;
|
||||
class nsFormHistory;
|
||||
class nsIObserverService;
|
||||
template <class E> class nsTArray;
|
||||
|
||||
#define NS_IFORMHISTORYPRIVATE_IID \
|
||||
{0xc4a47315, 0xaeb5, 0x4039, {0x9f, 0x34, 0x45, 0x11, 0xb3, 0xa7, 0x58, 0xdd}}
|
||||
|
||||
class nsIFormHistoryPrivate : public nsISupports
|
||||
{
|
||||
public:
|
||||
NS_DECLARE_STATIC_IID_ACCESSOR(NS_IFORMHISTORYPRIVATE_IID)
|
||||
|
||||
mozIStorageConnection* GetStorageConnection() { return mDBConn; }
|
||||
|
||||
protected:
|
||||
nsCOMPtr<mozIStorageConnection> mDBConn;
|
||||
};
|
||||
|
||||
NS_DEFINE_STATIC_IID_ACCESSOR(nsIFormHistoryPrivate, NS_IFORMHISTORYPRIVATE_IID)
|
||||
|
||||
class nsFormHistory : public nsIFormHistory2,
|
||||
public nsIFormHistoryPrivate,
|
||||
public nsIObserver,
|
||||
public nsIFormSubmitObserver,
|
||||
public nsSupportsWeakReference
|
||||
{
|
||||
public:
|
||||
NS_DECL_ISUPPORTS
|
||||
NS_DECL_NSIFORMHISTORY2
|
||||
NS_DECL_NSIOBSERVER
|
||||
|
||||
// nsIFormSubmitObserver
|
||||
NS_IMETHOD Notify(nsIDOMHTMLFormElement* formElt, nsIDOMWindowInternal* window, nsIURI* actionURL, PRBool* cancelSubmit);
|
||||
|
||||
nsFormHistory();
|
||||
nsresult Init();
|
||||
|
||||
private:
|
||||
~nsFormHistory();
|
||||
bool IsValidCCNumber(const nsAString &aString);
|
||||
|
||||
protected:
|
||||
// Database I/O
|
||||
nsresult OpenDatabase(PRBool *aDoImport);
|
||||
nsresult CloseDatabase();
|
||||
nsresult GetDatabaseFile(nsIFile** aFile);
|
||||
|
||||
nsresult dbMigrate();
|
||||
nsresult dbCleanup();
|
||||
nsresult MigrateToVersion1();
|
||||
nsresult MigrateToVersion2();
|
||||
nsresult MigrateToVersion3();
|
||||
PRBool dbAreExpectedColumnsPresent();
|
||||
|
||||
nsresult CreateTable();
|
||||
nsresult CreateStatements();
|
||||
|
||||
static nsresult InitPrefs();
|
||||
static PRBool SaveHttpsForms();
|
||||
static PRBool FormHistoryEnabled();
|
||||
static nsFormHistory *gFormHistory;
|
||||
static PRBool gFormHistoryEnabled;
|
||||
static PRBool gSaveHttpsForms;
|
||||
static PRBool gPrefsInitialized;
|
||||
|
||||
nsresult GenerateGUID(nsACString &guid);
|
||||
nsresult ExpireOldEntries();
|
||||
PRInt32 CountAllEntries();
|
||||
PRInt64 GetExistingEntryID (const nsAString &aName, const nsAString &aValue);
|
||||
PRInt64 GetExistingEntryID (const nsAString &aName, const nsAString &aValue, nsAString &aGuid);
|
||||
|
||||
nsresult SendNotification(const nsAString &aChangeType, nsISupports *aData);
|
||||
nsresult SendNotification(const nsAString &aChangeType, const nsAString &aName);
|
||||
nsresult SendNotification(const nsAString &aChangeType, const nsAString &aName, const nsAString &aValue, const nsAutoString &aGuid);
|
||||
nsresult SendNotification(const nsAString &aChangeType, const PRInt64 &aNumber);
|
||||
nsresult SendNotification(const nsAString &aChangeType, const PRInt64 &aOne, const PRInt64 &aTwo);
|
||||
|
||||
nsCOMPtr<nsIUUIDGenerator> mUUIDService;
|
||||
nsCOMPtr<nsIPrefBranch> mPrefBranch;
|
||||
nsCOMPtr<nsIObserverService> mObserverService;
|
||||
nsCOMPtr<mozIStorageService> mStorageService;
|
||||
nsCOMPtr<mozIStorageStatement> mDBFindEntry;
|
||||
nsCOMPtr<mozIStorageStatement> mDBFindEntryByName;
|
||||
nsCOMPtr<mozIStorageStatement> mDBSelectEntries;
|
||||
nsCOMPtr<mozIStorageStatement> mDBInsertNameValue;
|
||||
nsCOMPtr<mozIStorageStatement> mDBUpdateEntry;
|
||||
};
|
||||
|
||||
#endif // __nsFormHistory__
|
Загрузка…
Ссылка в новой задаче