summaryrefslogtreecommitdiffstats
path: root/toolkit/components/satchel/nsFormHistory.js
diff options
context:
space:
mode:
authorMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
committerMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
commit5f8de423f190bbb79a62f804151bc24824fa32d8 (patch)
tree10027f336435511475e392454359edea8e25895d /toolkit/components/satchel/nsFormHistory.js
parent49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff)
downloadUXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip
Add m-esr52 at 52.6.0
Diffstat (limited to 'toolkit/components/satchel/nsFormHistory.js')
-rw-r--r--toolkit/components/satchel/nsFormHistory.js894
1 files changed, 894 insertions, 0 deletions
diff --git a/toolkit/components/satchel/nsFormHistory.js b/toolkit/components/satchel/nsFormHistory.js
new file mode 100644
index 000000000..d68be2d58
--- /dev/null
+++ b/toolkit/components/satchel/nsFormHistory.js
@@ -0,0 +1,894 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+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");
+
+XPCOMUtils.defineLazyModuleGetter(this, "Deprecated",
+ "resource://gre/modules/Deprecated.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "AppConstants",
+ "resource://gre/modules/AppConstants.jsm");
+
+const DB_VERSION = 4;
+const DAY_IN_MS = 86400000; // 1 day in milliseconds
+
+function FormHistory() {
+ Deprecated.warning(
+ "nsIFormHistory2 is deprecated and will be removed in a future version",
+ "https://bugzilla.mozilla.org/show_bug.cgi?id=879118");
+ this.init();
+}
+
+FormHistory.prototype = {
+ classID : Components.ID("{0c1bb408-71a2-403f-854a-3a0659829ded}"),
+ QueryInterface : XPCOMUtils.generateQI([Ci.nsIFormHistory2,
+ Ci.nsIObserver,
+ Ci.nsIMessageListener,
+ Ci.nsISupportsWeakReference,
+ ]),
+
+ debug : true,
+ enabled : true,
+
+ // 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"
+ },
+ moz_deleted_formhistory: {
+ "id" : "INTEGER PRIMARY KEY",
+ "timeDeleted" : "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"]
+ },
+ }
+ },
+ 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;
+ },
+
+ log : function log(message) {
+ if (!this.debug)
+ return;
+ dump("FormHistory: " + message + "\n");
+ Services.console.logStringMessage("FormHistory: " + message);
+ },
+
+
+ init : function init() {
+ this.updatePrefs();
+
+ this.dbStmts = {};
+
+ // Add observer
+ Services.obs.addObserver(this, "profile-before-change", true);
+ },
+
+ /* ---- nsIFormHistory2 interfaces ---- */
+
+
+ get hasEntries() {
+ return (this.countAllEntries() > 0);
+ },
+
+
+ addEntry : function addEntry(name, value) {
+ if (!this.enabled)
+ return;
+
+ this.log("addEntry for " + name + "=" + value);
+
+ let now = Date.now() * 1000; // microseconds
+
+ let [id, guid] = this.getExistingEntryID(name, value);
+ let stmt;
+
+ if (id != -1) {
+ // Update existing entry.
+ 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 {
+ if (stmt) {
+ 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 {
+ if (stmt) {
+ stmt.reset();
+ }
+ }
+ }
+ },
+
+
+ removeEntry : function removeEntry(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 };
+ let existingTransactionInProgress;
+
+ try {
+ // Don't start a transaction if one is already in progress since we can't nest them.
+ existingTransactionInProgress = this.dbConnection.transactionInProgress;
+ if (!existingTransactionInProgress)
+ this.dbConnection.beginTransaction();
+ this.moveToDeletedTable("VALUES (:guid, :timeDeleted)", {
+ guid: guid,
+ timeDeleted: Date.now()
+ });
+
+ // remove from the formhistory database
+ stmt = this.dbCreateStatement(query, params);
+ stmt.execute();
+ this.sendStringNotification("removeEntry", name, value, guid);
+ } catch (e) {
+ if (!existingTransactionInProgress)
+ this.dbConnection.rollbackTransaction();
+ this.log("removeEntry failed: " + e);
+ throw e;
+ } finally {
+ if (stmt) {
+ stmt.reset();
+ }
+ }
+ if (!existingTransactionInProgress)
+ this.dbConnection.commitTransaction();
+ },
+
+
+ removeEntriesForName : function removeEntriesForName(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 };
+ let existingTransactionInProgress;
+
+ try {
+ // Don't start a transaction if one is already in progress since we can't nest them.
+ existingTransactionInProgress = this.dbConnection.transactionInProgress;
+ if (!existingTransactionInProgress)
+ this.dbConnection.beginTransaction();
+ this.moveToDeletedTable(
+ "SELECT guid, :timeDeleted FROM moz_formhistory " +
+ "WHERE fieldname = :fieldname", {
+ fieldname: name,
+ timeDeleted: Date.now()
+ });
+
+ stmt = this.dbCreateStatement(query, params);
+ stmt.execute();
+ this.sendStringNotification("removeEntriesForName", name);
+ } catch (e) {
+ if (!existingTransactionInProgress)
+ this.dbConnection.rollbackTransaction();
+ this.log("removeEntriesForName failed: " + e);
+ throw e;
+ } finally {
+ if (stmt) {
+ stmt.reset();
+ }
+ }
+ if (!existingTransactionInProgress)
+ this.dbConnection.commitTransaction();
+ },
+
+
+ removeAllEntries : function removeAllEntries() {
+ this.log("removeAllEntries");
+
+ this.sendNotification("before-removeAllEntries", null);
+
+ let stmt;
+ let query = "DELETE FROM moz_formhistory";
+ let existingTransactionInProgress;
+
+ try {
+ // Don't start a transaction if one is already in progress since we can't nest them.
+ existingTransactionInProgress = this.dbConnection.transactionInProgress;
+ if (!existingTransactionInProgress)
+ this.dbConnection.beginTransaction();
+ // TODO: Add these items to the deleted items table once we've sorted
+ // out the issues from bug 756701
+ stmt = this.dbCreateStatement(query);
+ stmt.execute();
+ this.sendNotification("removeAllEntries", null);
+ } catch (e) {
+ if (!existingTransactionInProgress)
+ this.dbConnection.rollbackTransaction();
+ this.log("removeAllEntries failed: " + e);
+ throw e;
+ } finally {
+ if (stmt) {
+ stmt.reset();
+ }
+ }
+ if (!existingTransactionInProgress)
+ this.dbConnection.commitTransaction();
+ },
+
+
+ nameExists : function nameExists(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 {
+ if (stmt) {
+ stmt.reset();
+ }
+ }
+ },
+
+ entryExists : function entryExists(name, value) {
+ this.log("entryExists for " + name + "=" + value);
+ let [id] = this.getExistingEntryID(name, value);
+ this.log("entryExists: id=" + id);
+ return (id != -1);
+ },
+
+ removeEntriesByTimeframe : function removeEntriesByTimeframe(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
+ };
+ let existingTransactionInProgress;
+
+ try {
+ // Don't start a transaction if one is already in progress since we can't nest them.
+ existingTransactionInProgress = this.dbConnection.transactionInProgress;
+ if (!existingTransactionInProgress)
+ this.dbConnection.beginTransaction();
+ this.moveToDeletedTable(
+ "SELECT guid, :timeDeleted FROM moz_formhistory " +
+ "WHERE firstUsed >= :beginTime AND firstUsed <= :endTime", {
+ beginTime: beginTime,
+ endTime: endTime
+ });
+
+ stmt = this.dbCreateStatement(query, params);
+ stmt.executeStep();
+ this.sendIntNotification("removeEntriesByTimeframe", beginTime, endTime);
+ } catch (e) {
+ if (!existingTransactionInProgress)
+ this.dbConnection.rollbackTransaction();
+ this.log("removeEntriesByTimeframe failed: " + e);
+ throw e;
+ } finally {
+ if (stmt) {
+ stmt.reset();
+ }
+ }
+ if (!existingTransactionInProgress)
+ this.dbConnection.commitTransaction();
+ },
+
+ moveToDeletedTable : function moveToDeletedTable(values, params) {
+ if (AppConstants.platform == "android") {
+ this.log("Moving entries to deleted table.");
+
+ let stmt;
+
+ try {
+ // Move the entries to the deleted items table.
+ let query = "INSERT INTO moz_deleted_formhistory (guid, timeDeleted) ";
+ if (values) query += values;
+ stmt = this.dbCreateStatement(query, params);
+ stmt.execute();
+ } catch (e) {
+ this.log("Moving deleted entries failed: " + e);
+ throw e;
+ } finally {
+ if (stmt) {
+ stmt.reset();
+ }
+ }
+ }
+ },
+
+ get dbConnection() {
+ // Make sure dbConnection can't be called from now to prevent infinite loops.
+ delete FormHistory.prototype.dbConnection;
+
+ try {
+ this.dbFile = Services.dirsvc.get("ProfD", Ci.nsIFile).clone();
+ this.dbFile.append("formhistory.sqlite");
+ this.log("Opening database at " + this.dbFile.path);
+
+ FormHistory.prototype.dbConnection = this.dbOpen();
+ this.dbInit();
+ } catch (e) {
+ this.log("Initialization failed: " + e);
+ // If dbInit fails...
+ if (e.result == Cr.NS_ERROR_FILE_CORRUPTED) {
+ this.dbCleanup();
+ FormHistory.prototype.dbConnection = this.dbOpen();
+ this.dbInit();
+ } else {
+ throw "Initialization failed";
+ }
+ }
+
+ return FormHistory.prototype.dbConnection;
+ },
+
+ get DBConnection() {
+ return this.dbConnection;
+ },
+
+
+ /* ---- nsIObserver interface ---- */
+
+
+ observe : function observe(subject, topic, data) {
+ switch (topic) {
+ case "nsPref:changed":
+ this.updatePrefs();
+ break;
+ case "profile-before-change":
+ this._dbClose(false);
+ break;
+ default:
+ this.log("Oops! Unexpected notification: " + topic);
+ break;
+ }
+ },
+
+
+ /* ---- 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 {
+ if (stmt) {
+ 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 {
+ if (stmt) {
+ stmt.reset();
+ }
+ }
+
+ this.log("countAllEntries: counted entries: " + numEntries);
+ return numEntries;
+ },
+
+
+ updatePrefs : function () {
+ this.debug = Services.prefs.getBoolPref("browser.formfill.debug");
+ this.enabled = Services.prefs.getBoolPref("browser.formfill.enable");
+ },
+
+ // 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;
+ },
+
+ /*
+ * dbOpen
+ *
+ * Open a connection with the database and returns it.
+ *
+ * @returns a db connection object.
+ */
+ dbOpen : function () {
+ this.log("Open Database");
+
+ let storage = Cc["@mozilla.org/storage/service;1"].
+ getService(Ci.mozIStorageService);
+ return storage.openDatabase(this.dbFile);
+ },
+
+ /*
+ * 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 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];
+ this.dbCreateTable(name, table);
+ }
+
+ 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;
+ },
+
+ dbCreateTable: function(name, table) {
+ let tSQL = Object.keys(table).map(col => [col, table[col]].join(" ")).join(", ");
+ this.log("Creating table " + name + " with " + tSQL);
+ this.dbConnection.createTable(name, tSQL);
+ },
+
+ 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 {
+ if (stmt) {
+ 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 {
+ if (stmt) {
+ stmt.reset();
+ }
+ }
+
+ // Generate a GUID for each login and update the DB.
+ query = "UPDATE moz_formhistory SET guid = :guid WHERE id = :id";
+ for (let id of 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 {
+ if (stmt) {
+ stmt.reset();
+ }
+ }
+ }
+ },
+
+ dbMigrateToVersion4 : function () {
+ if (!this.dbConnection.tableExists("moz_deleted_formhistory")) {
+ this.dbCreateTable("moz_deleted_formhistory", this.dbSchema.tables.moz_deleted_formhistory);
+ }
+ },
+
+ /*
+ * 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 " +
+ Object.keys(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;
+ }
+ },
+
+ /**
+ * _dbClose
+ *
+ * Finalize all statements and close the connection.
+ *
+ * @param aBlocking - Should we spin the loop waiting for the db to be
+ * closed.
+ */
+ _dbClose : function FH__dbClose(aBlocking) {
+ for (let query in this.dbStmts) {
+ let stmt = this.dbStmts[query];
+ stmt.finalize();
+ }
+ this.dbStmts = {};
+
+ let connectionDescriptor = Object.getOwnPropertyDescriptor(FormHistory.prototype, "dbConnection");
+ // Return if the database hasn't been opened.
+ if (!connectionDescriptor || connectionDescriptor.value === undefined)
+ return;
+
+ let completed = false;
+ try {
+ this.dbConnection.asyncClose(function () { completed = true; });
+ } catch (e) {
+ completed = true;
+ Components.utils.reportError(e);
+ }
+
+ let thread = Services.tm.currentThread;
+ while (aBlocking && !completed) {
+ thread.processNextEvent(true);
+ }
+ },
+
+ /*
+ * dbCleanup
+ *
+ * Called when database creation fails. Finalizes database statements,
+ * closes the database connection, deletes the database file.
+ */
+ dbCleanup : function () {
+ this.log("Cleaning up DB file - close & remove & backup")
+
+ // Create backup file
+ let storage = Cc["@mozilla.org/storage/service;1"].
+ getService(Ci.mozIStorageService);
+ let backupFile = this.dbFile.leafName + ".corrupt";
+ storage.backupDatabaseFile(this.dbFile, backupFile);
+
+ this._dbClose(true);
+ this.dbFile.remove(false);
+ }
+};
+
+var component = [FormHistory];
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory(component);