Bug 470455 - test_database_sync_embed_visits.js leaks, r=sdwilsh
[wine-gecko.git] / toolkit / components / satchel / src / nsFormHistory.cpp
blob0fbd3bf113b02fa5ae4e5b7927b6003fda2093b0
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* ***** BEGIN LICENSE BLOCK *****
3 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 * The contents of this file are subject to the Mozilla Public License Version
6 * 1.1 (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 * http://www.mozilla.org/MPL/
10 * Software distributed under the License is distributed on an "AS IS" basis,
11 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 * for the specific language governing rights and limitations under the
13 * License.
15 * The Original Code is Mozilla Communicator client code.
17 * The Initial Developer of the Original Code is
18 * Netscape Communications Corporation.
19 * Portions created by the Initial Developer are Copyright (C) 1998
20 * the Initial Developer. All Rights Reserved.
22 * Contributor(s):
23 * Joe Hewitt <hewitt@netscape.com> (Original Author)
25 * Alternatively, the contents of this file may be used under the terms of
26 * either the GNU General Public License Version 2 or later (the "GPL"), or
27 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
28 * in which case the provisions of the GPL or the LGPL are applicable instead
29 * of those above. If you wish to allow use of your version of this file only
30 * under the terms of either the GPL or the LGPL, and not to allow others to
31 * use your version of this file under the terms of the MPL, indicate your
32 * decision by deleting the provisions above and replace them with the notice
33 * and other provisions required by the GPL or the LGPL. If you do not delete
34 * the provisions above, a recipient may use your version of this file under
35 * the terms of any one of the MPL, the GPL or the LGPL.
37 * ***** END LICENSE BLOCK ***** */
39 #include "nsFormHistory.h"
41 #include "nsIServiceManager.h"
42 #include "nsIObserverService.h"
43 #include "nsICategoryManager.h"
44 #include "nsIDirectoryService.h"
45 #include "nsAppDirectoryServiceDefs.h"
46 #include "nsMorkCID.h"
47 #include "nsIMdbFactoryFactory.h"
48 #include "nsQuickSort.h"
49 #include "nsCRT.h"
50 #include "nsString.h"
51 #include "nsUnicharUtils.h"
52 #include "nsReadableUtils.h"
53 #include "nsIDOMNode.h"
54 #include "nsIDOMHTMLFormElement.h"
55 #include "nsIDOMHTMLInputElement.h"
56 #include "nsIDOMHTMLCollection.h"
57 #include "nsIPrefService.h"
58 #include "nsIPrefBranch.h"
59 #include "nsIPrefBranch2.h"
60 #include "nsVoidArray.h"
61 #include "nsCOMArray.h"
63 static void SwapBytes(PRUnichar* aDest, const PRUnichar* aSrc, PRUint32 aLen)
65 for(PRUint32 i = 0; i < aLen; i++)
67 PRUnichar aChar = *aSrc++;
68 *aDest++ = (0xff & (aChar >> 8)) | (aChar << 8);
72 #define PREF_FORMFILL_BRANCH "browser.formfill."
73 #define PREF_FORMFILL_ENABLE "enable"
75 // upper bounds on saved form data, more isn't useful.
76 #define FORMFILL_NAME_MAX_LEN 1000
77 #define FORMFILL_VALUE_MAX_LEN 4000
79 static const char *kFormHistoryFileName = "formhistory.dat";
81 NS_INTERFACE_MAP_BEGIN(nsFormHistory)
82 NS_INTERFACE_MAP_ENTRY(nsIFormHistory2)
83 NS_INTERFACE_MAP_ENTRY(nsIObserver)
84 NS_INTERFACE_MAP_ENTRY(nsIFormSubmitObserver)
85 NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
86 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIObserver)
87 NS_INTERFACE_MAP_END_THREADSAFE
89 NS_IMPL_THREADSAFE_ADDREF(nsFormHistory)
90 NS_IMPL_THREADSAFE_RELEASE(nsFormHistory)
92 mdb_column nsFormHistory::kToken_ValueColumn = 0;
93 mdb_column nsFormHistory::kToken_NameColumn = 0;
95 PRBool nsFormHistory::gFormHistoryEnabled = PR_FALSE;
96 PRBool nsFormHistory::gPrefsInitialized = PR_FALSE;
98 nsFormHistory::nsFormHistory() :
99 mEnv(nsnull),
100 mStore(nsnull),
101 mTable(nsnull),
102 mReverseByteOrder(PR_FALSE)
104 NS_ASSERTION(!gFormHistory, "nsFormHistory must be used as a service");
105 gFormHistory = this;
108 nsFormHistory::~nsFormHistory()
110 NS_ASSERTION(gFormHistory == this,
111 "nsFormHistory must be used as a service");
112 CloseDatabase();
113 gFormHistory = nsnull;
116 nsresult
117 nsFormHistory::Init()
119 nsCOMPtr<nsIObserverService> service = do_GetService("@mozilla.org/observer-service;1");
120 if (service)
121 service->AddObserver(this, NS_EARLYFORMSUBMIT_SUBJECT, PR_TRUE);
123 return NS_OK;
126 nsFormHistory *nsFormHistory::gFormHistory = nsnull;
128 /* static */ PRBool
129 nsFormHistory::FormHistoryEnabled()
131 if (!gPrefsInitialized) {
132 nsCOMPtr<nsIPrefService> prefService = do_GetService(NS_PREFSERVICE_CONTRACTID);
134 prefService->GetBranch(PREF_FORMFILL_BRANCH,
135 getter_AddRefs(gFormHistory->mPrefBranch));
136 gFormHistory->mPrefBranch->GetBoolPref(PREF_FORMFILL_ENABLE,
137 &gFormHistoryEnabled);
139 nsCOMPtr<nsIPrefBranch2> branchInternal =
140 do_QueryInterface(gFormHistory->mPrefBranch);
141 branchInternal->AddObserver(PREF_FORMFILL_ENABLE, gFormHistory, PR_TRUE);
143 gPrefsInitialized = PR_TRUE;
146 return gFormHistoryEnabled;
150 ////////////////////////////////////////////////////////////////////////
151 //// nsIFormHistory2
153 NS_IMETHODIMP
154 nsFormHistory::GetHasEntries(PRBool *aHasEntries)
156 nsresult rv = OpenDatabase(); // lazily ensure that the database is open
157 NS_ENSURE_SUCCESS(rv, rv);
159 PRUint32 rowCount;
160 mdb_err err = mTable->GetCount(mEnv, &rowCount);
161 NS_ENSURE_TRUE(!err, NS_ERROR_FAILURE);
163 *aHasEntries = rowCount != 0;
164 return NS_OK;
167 NS_IMETHODIMP
168 nsFormHistory::AddEntry(const nsAString &aName, const nsAString &aValue)
170 if (!FormHistoryEnabled())
171 return NS_OK;
173 nsresult rv = OpenDatabase(); // lazily ensure that the database is open
174 NS_ENSURE_SUCCESS(rv, rv);
176 nsCOMPtr<nsIMdbRow> row;
177 AppendRow(aName, aValue, getter_AddRefs(row));
178 return NS_OK;
181 NS_IMETHODIMP
182 nsFormHistory::EntryExists(const nsAString &aName, const nsAString &aValue, PRBool *_retval)
184 return EntriesExistInternal(&aName, &aValue, _retval);
187 NS_IMETHODIMP
188 nsFormHistory::NameExists(const nsAString &aName, PRBool *_retval)
190 return EntriesExistInternal(&aName, nsnull, _retval);
193 NS_IMETHODIMP
194 nsFormHistory::RemoveEntry(const nsAString &aName, const nsAString &aValue)
196 return NS_ERROR_NOT_IMPLEMENTED;
199 NS_IMETHODIMP
200 nsFormHistory::RemoveEntriesForName(const nsAString &aName)
202 return RemoveEntriesInternal(&aName);
205 NS_IMETHODIMP
206 nsFormHistory::RemoveAllEntries()
208 nsresult rv = RemoveEntriesInternal(nsnull);
210 if (NS_SUCCEEDED(rv))
211 rv = InitByteOrder(PR_TRUE);
213 rv |= Flush();
215 return rv;
218 NS_IMETHODIMP
219 nsFormHistory::RemoveEntriesByTimeframe(PRInt64 aStartTime, PRInt64 aEndTime)
221 return NS_ERROR_NOT_IMPLEMENTED;
224 NS_IMETHODIMP
225 nsFormHistory::GetDBConnection()
227 return NS_ERROR_NOT_IMPLEMENTED;
230 ////////////////////////////////////////////////////////////////////////
231 //// nsIObserver
233 NS_IMETHODIMP
234 nsFormHistory::Observe(nsISupports *aSubject, const char *aTopic, const PRUnichar *aData)
236 if (!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) {
237 mPrefBranch->GetBoolPref(PREF_FORMFILL_ENABLE, &gFormHistoryEnabled);
240 return NS_OK;
243 ////////////////////////////////////////////////////////////////////////
244 //// nsIFormSubmitObserver
246 NS_IMETHODIMP
247 nsFormHistory::Notify(nsIDOMHTMLFormElement* formElt, nsIDOMWindowInternal* aWindow, nsIURI* aActionURL, PRBool* aCancelSubmit)
249 if (!FormHistoryEnabled())
250 return NS_OK;
252 nsresult rv = OpenDatabase(); // lazily ensure that the database is open
253 NS_ENSURE_SUCCESS(rv, rv);
255 nsCOMPtr<nsIDOMHTMLCollection> elts;
256 formElt->GetElements(getter_AddRefs(elts));
258 const char *textString = "text";
260 PRUint32 length;
261 elts->GetLength(&length);
262 for (PRUint32 i = 0; i < length; ++i) {
263 nsCOMPtr<nsIDOMNode> node;
264 elts->Item(i, getter_AddRefs(node));
265 nsCOMPtr<nsIDOMHTMLInputElement> inputElt = do_QueryInterface(node);
266 if (inputElt) {
267 // Filter only inputs that are of type "text"
268 nsAutoString type;
269 inputElt->GetType(type);
270 if (type.EqualsIgnoreCase(textString)) {
271 // If this input has a name/id and value, add it to the database
272 nsAutoString value;
273 inputElt->GetValue(value);
274 if (!value.IsEmpty()) {
275 nsAutoString name;
276 inputElt->GetName(name);
277 if (name.IsEmpty())
278 inputElt->GetId(name);
280 if (!name.IsEmpty())
281 AppendRow(name, value, nsnull);
287 return NS_OK;
290 ////////////////////////////////////////////////////////////////////////
291 //// Database I/O
293 class SatchelErrorHook : public nsIMdbErrorHook
295 public:
296 NS_DECL_ISUPPORTS
298 // nsIMdbErrorHook
299 NS_IMETHOD OnErrorString(nsIMdbEnv* ev, const char* inAscii);
300 NS_IMETHOD OnErrorYarn(nsIMdbEnv* ev, const mdbYarn* inYarn);
301 NS_IMETHOD OnWarningString(nsIMdbEnv* ev, const char* inAscii);
302 NS_IMETHOD OnWarningYarn(nsIMdbEnv* ev, const mdbYarn* inYarn);
303 NS_IMETHOD OnAbortHintString(nsIMdbEnv* ev, const char* inAscii);
304 NS_IMETHOD OnAbortHintYarn(nsIMdbEnv* ev, const mdbYarn* inYarn);
307 // nsIMdbErrorHook has no IID!
308 NS_IMPL_ISUPPORTS0(SatchelErrorHook)
310 NS_IMETHODIMP
311 SatchelErrorHook::OnErrorString(nsIMdbEnv *ev, const char *inAscii)
313 printf("mork error: %s\n", inAscii);
314 return NS_OK;
317 NS_IMETHODIMP
318 SatchelErrorHook::OnErrorYarn(nsIMdbEnv *ev, const mdbYarn* inYarn)
320 printf("mork error yarn: %p\n", (void*)inYarn);
321 return NS_OK;
324 NS_IMETHODIMP
325 SatchelErrorHook::OnWarningString(nsIMdbEnv *ev, const char *inAscii)
327 printf("mork warning: %s\n", inAscii);
328 return NS_OK;
331 NS_IMETHODIMP
332 SatchelErrorHook::OnWarningYarn(nsIMdbEnv *ev, const mdbYarn *inYarn)
334 printf("mork warning yarn: %p\n", (void*)inYarn);
335 return NS_OK;
338 NS_IMETHODIMP
339 SatchelErrorHook::OnAbortHintString(nsIMdbEnv *ev, const char *inAscii)
341 printf("mork abort: %s\n", inAscii);
342 return NS_OK;
345 NS_IMETHODIMP
346 SatchelErrorHook::OnAbortHintYarn(nsIMdbEnv *ev, const mdbYarn *inYarn)
348 printf("mork abort yarn: %p\n", (void*)inYarn);
349 return NS_OK;
352 nsresult
353 nsFormHistory::OpenDatabase()
355 if (mStore)
356 return NS_OK;
358 // Get a handle to the database file
359 nsCOMPtr <nsIFile> historyFile;
360 nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(historyFile));
361 NS_ENSURE_SUCCESS(rv, rv);
362 historyFile->Append(NS_ConvertUTF8toUTF16(kFormHistoryFileName));
364 // Get an Mdb Factory
365 nsCOMPtr <nsIMdbFactoryService> mdbFactory = do_GetService(NS_MORK_CONTRACTID, &rv);
366 NS_ENSURE_SUCCESS(rv, rv);
367 rv = mdbFactory->GetMdbFactory(getter_AddRefs(mMdbFactory));
368 NS_ENSURE_SUCCESS(rv, rv);
370 // Create the Mdb environment
371 mdb_err err = mMdbFactory->MakeEnv(nsnull, &mEnv);
372 NS_ASSERTION(err == 0, "ERROR: Unable to create Form History mdb");
373 mEnv->SetAutoClear(PR_TRUE);
374 NS_ENSURE_TRUE(!err, NS_ERROR_FAILURE);
375 mEnv->SetErrorHook(new SatchelErrorHook());
377 nsCAutoString filePath;
378 historyFile->GetNativePath(filePath);
379 PRBool exists = PR_TRUE;
380 historyFile->Exists(&exists);
382 PRBool createdNew = PR_FALSE;
384 if (!exists || NS_FAILED(rv = OpenExistingFile(filePath.get()))) {
385 // If the file doesn't exist, or we fail trying to open it,
386 // then make sure it is deleted and then create an empty database file
387 historyFile->Remove(PR_FALSE);
388 rv = CreateNewFile(filePath.get());
389 createdNew = PR_TRUE;
391 NS_ENSURE_SUCCESS(rv, rv);
393 // Get the initial size of the file, needed later for Commit
394 historyFile->GetFileSize(&mFileSizeOnDisk);
396 rv = InitByteOrder(createdNew);
398 /* // TESTING: Add a row to the database
399 nsAutoString foopy;
400 foopy.AssignWithConversion("foopy");
401 nsAutoString oogly;
402 oogly.AssignWithConversion("oogly");
403 AppendRow(foopy, oogly, nsnull);
404 Flush(); */
406 /* // TESTING: Dump the contents of the database
407 PRUint32 count = 0;
408 mdb_err err = mTable->GetCount(mEnv, &count);
409 printf("%d rows in form history\n", count);
411 for (mdb_pos pos = count - 1; pos >= 0; --pos) {
412 nsCOMPtr<nsIMdbRow> row;
413 err = mTable->PosToRow(mEnv, pos, getter_AddRefs(row));
415 nsAutoString name;
416 GetRowValue(row, kToken_NameColumn, name);
417 nsAutoString value;
418 GetRowValue(row, kToken_ValueColumn, value);
419 printf("ROW: %s - %s\n", ToNewCString(name), ToNewCString(value));
420 } */
422 return rv;
425 nsresult
426 nsFormHistory::OpenExistingFile(const char *aPath)
428 nsCOMPtr<nsIMdbFile> oldFile;
429 nsIMdbHeap* dbHeap = 0;
430 mdb_err err = mMdbFactory->OpenOldFile(mEnv, dbHeap, aPath, mdbBool_kFalse, getter_AddRefs(oldFile));
431 NS_ENSURE_TRUE(!err && oldFile, NS_ERROR_FAILURE);
433 mdb_bool canOpen = 0;
434 mdbYarn outFormat = {nsnull, 0, 0, 0, 0, nsnull};
435 err = mMdbFactory->CanOpenFilePort(mEnv, oldFile, &canOpen, &outFormat);
436 NS_ENSURE_TRUE(!err && canOpen, NS_ERROR_FAILURE);
438 nsCOMPtr<nsIMdbThumb> thumb;
439 mdbOpenPolicy policy = {{0, 0}, 0, 0};
440 err = mMdbFactory->OpenFileStore(mEnv, dbHeap, oldFile, &policy, getter_AddRefs(thumb));
441 NS_ENSURE_TRUE(!err && thumb, NS_ERROR_FAILURE);
443 PRBool done;
444 mdb_err thumbErr = UseThumb(thumb, &done);
446 if (err == 0 && done)
447 err = mMdbFactory->ThumbToOpenStore(mEnv, thumb, &mStore);
448 NS_ENSURE_TRUE(!err, NS_ERROR_FAILURE);
450 nsresult rv = CreateTokens();
451 NS_ENSURE_SUCCESS(rv, rv);
453 mdbOid oid = {kToken_RowScope, 1};
454 err = mStore->GetTable(mEnv, &oid, &mTable);
455 NS_ENSURE_TRUE(!err, NS_ERROR_FAILURE);
456 if (!mTable) {
457 NS_WARNING("ERROR: Form history file is corrupt, now deleting it.");
458 return NS_ERROR_FAILURE;
461 err = mTable->GetMetaRow(mEnv, &oid, nsnull, getter_AddRefs(mMetaRow));
462 if (err)
463 NS_WARNING("Could not get meta row");
465 if (NS_FAILED(thumbErr))
466 err = thumbErr;
468 return err ? NS_ERROR_FAILURE : NS_OK;
471 nsresult
472 nsFormHistory::CreateNewFile(const char *aPath)
474 nsIMdbHeap* dbHeap = 0;
475 nsCOMPtr<nsIMdbFile> newFile;
476 mdb_err err = mMdbFactory->CreateNewFile(mEnv, dbHeap, aPath, getter_AddRefs(newFile));
477 NS_ENSURE_TRUE(!err && newFile, NS_ERROR_FAILURE);
479 nsCOMPtr <nsIMdbTable> oldTable = mTable;;
480 nsCOMPtr <nsIMdbStore> oldStore = mStore;
481 mdbOpenPolicy policy = {{0, 0}, 0, 0};
482 err = mMdbFactory->CreateNewFileStore(mEnv, dbHeap, newFile, &policy, &mStore);
483 NS_ENSURE_TRUE(!err, NS_ERROR_FAILURE);
485 nsresult rv = CreateTokens();
486 NS_ENSURE_SUCCESS(rv, rv);
488 // Create the one and only table in the database
489 err = mStore->NewTable(mEnv, kToken_RowScope, kToken_Kind, PR_TRUE, nsnull, &mTable);
490 NS_ENSURE_TRUE(!err && mTable, NS_ERROR_FAILURE);
492 mdbOid oid = {kToken_RowScope, 1};
493 err = mTable->GetMetaRow(mEnv, &oid, nsnull, getter_AddRefs(mMetaRow));
494 if (err) {
495 NS_WARNING("Could not get meta row");
496 return NS_ERROR_FAILURE;
499 // oldTable will only be set if we detected a corrupt db, and are
500 // trying to restore data from it.
501 if (oldTable)
502 CopyRowsFromTable(oldTable);
504 // Force a commit now to get it written out.
505 nsCOMPtr<nsIMdbThumb> thumb;
506 err = mStore->CompressCommit(mEnv, getter_AddRefs(thumb));
507 NS_ENSURE_TRUE(!err, NS_ERROR_FAILURE);
509 PRBool done;
510 err = UseThumb(thumb, &done);
512 return err || !done ? NS_ERROR_FAILURE : NS_OK;
515 nsresult
516 nsFormHistory::CloseDatabase()
518 Flush();
520 mMetaRow = nsnull;
522 if (mTable)
523 mTable->Release();
525 if (mStore)
526 mStore->Release();
528 if (mEnv)
529 mEnv->Release();
531 mTable = nsnull;
532 mEnv = nsnull;
533 mStore = nsnull;
535 return NS_OK;
538 nsresult
539 nsFormHistory::CreateTokens()
541 mdb_err err;
543 if (!mStore)
544 return NS_ERROR_NOT_INITIALIZED;
546 err = mStore->StringToToken(mEnv, "ns:formhistory:db:row:scope:formhistory:all", &kToken_RowScope);
547 if (err != 0) return NS_ERROR_FAILURE;
549 err = mStore->StringToToken(mEnv, "ns:formhistory:db:table:kind:formhistory", &kToken_Kind);
550 if (err != 0) return NS_ERROR_FAILURE;
552 err = mStore->StringToToken(mEnv, "Value", &kToken_ValueColumn);
553 if (err != 0) return NS_ERROR_FAILURE;
555 err = mStore->StringToToken(mEnv, "Name", &kToken_NameColumn);
556 if (err != 0) return NS_ERROR_FAILURE;
558 err = mStore->StringToToken(mEnv, "ByteOrder", &kToken_ByteOrder);
559 if (err != 0) return NS_ERROR_FAILURE;
561 return NS_OK;
564 nsresult
565 nsFormHistory::Flush()
567 if (!mStore || !mTable)
568 return NS_OK;
570 mdb_err err;
572 nsCOMPtr<nsIMdbThumb> thumb;
573 err = mStore->CompressCommit(mEnv, getter_AddRefs(thumb));
575 if (err == 0)
576 err = UseThumb(thumb, nsnull);
578 return err ? NS_ERROR_FAILURE : NS_OK;
581 mdb_err
582 nsFormHistory::UseThumb(nsIMdbThumb *aThumb, PRBool *aDone)
584 mdb_count total;
585 mdb_count current;
586 mdb_bool done;
587 mdb_bool broken;
588 mdb_err err;
590 do {
591 err = aThumb->DoMore(mEnv, &total, &current, &done, &broken);
592 } while ((err == 0) && !broken && !done);
594 if (aDone)
595 *aDone = done;
597 return err ? NS_ERROR_FAILURE : NS_OK;
600 nsresult
601 nsFormHistory::CopyRowsFromTable(nsIMdbTable *sourceTable)
603 nsCOMPtr<nsIMdbTableRowCursor> rowCursor;
604 mdb_err err = sourceTable->GetTableRowCursor(mEnv, -1, getter_AddRefs(rowCursor));
605 NS_ENSURE_TRUE(!err, NS_ERROR_FAILURE);
607 nsCOMPtr<nsIMdbRow> row;
608 mdb_pos pos;
609 do {
610 rowCursor->NextRow(mEnv, getter_AddRefs(row), &pos);
611 if (!row)
612 break;
614 mdbOid rowId;
615 rowId.mOid_Scope = kToken_RowScope;
616 rowId.mOid_Id = mdb_id(-1);
618 nsCOMPtr<nsIMdbRow> newRow;
619 mTable->NewRow(mEnv, &rowId, getter_AddRefs(newRow));
620 newRow->SetRow(mEnv, row);
621 mTable->AddRow(mEnv, newRow);
622 } while (row);
623 return NS_OK;
626 nsresult
627 nsFormHistory::AppendRow(const nsAString &aName, const nsAString &aValue, nsIMdbRow **aResult)
629 if (!mTable)
630 return NS_ERROR_NOT_INITIALIZED;
632 if (aName.Length() > FORMFILL_NAME_MAX_LEN ||
633 aValue.Length() > FORMFILL_VALUE_MAX_LEN)
634 return NS_ERROR_INVALID_ARG;
636 PRBool exists = PR_TRUE;
637 EntryExists(aName, aValue, &exists);
638 if (exists)
639 return NS_OK;
641 mdbOid rowId;
642 rowId.mOid_Scope = kToken_RowScope;
643 rowId.mOid_Id = mdb_id(-1);
645 nsCOMPtr<nsIMdbRow> row;
646 mdb_err err = mTable->NewRow(mEnv, &rowId, getter_AddRefs(row));
647 if (err != 0)
648 return NS_ERROR_FAILURE;
650 SetRowValue(row, kToken_NameColumn, aName);
651 SetRowValue(row, kToken_ValueColumn, aValue);
653 if (aResult) {
654 *aResult = row;
655 NS_ADDREF(*aResult);
658 return NS_OK;
661 nsresult
662 nsFormHistory::SetRowValue(nsIMdbRow *aRow, mdb_column aCol, const nsAString &aValue)
664 PRInt32 len = aValue.Length() * sizeof(PRUnichar);
665 PRUnichar *swapval = nsnull;
666 mdbYarn yarn = {nsnull, len, len, 0, 0, nsnull};
667 const nsPromiseFlatString& buffer = PromiseFlatString(aValue);
669 if (mReverseByteOrder) {
670 swapval = new PRUnichar[aValue.Length()];
671 if (!swapval)
672 return NS_ERROR_OUT_OF_MEMORY;
673 SwapBytes(swapval, buffer.get(), aValue.Length());
674 yarn.mYarn_Buf = swapval;
676 else
677 yarn.mYarn_Buf = (void*)buffer.get();
679 mdb_err err = aRow->AddColumn(mEnv, aCol, &yarn);
681 delete swapval;
683 return err ? NS_ERROR_FAILURE : NS_OK;
686 nsresult
687 nsFormHistory::GetRowValue(nsIMdbRow *aRow, mdb_column aCol, nsAString &aValue)
689 mdbYarn yarn;
690 mdb_err err = aRow->AliasCellYarn(mEnv, aCol, &yarn);
691 if (err != 0)
692 return NS_ERROR_FAILURE;
694 aValue.Truncate(0);
695 if (!yarn.mYarn_Fill)
696 return NS_OK;
698 switch (yarn.mYarn_Form) {
699 case 0: { // unicode
700 PRUint32 len = yarn.mYarn_Fill / sizeof(PRUnichar);
701 if (mReverseByteOrder) {
702 PRUnichar *swapval = new PRUnichar[len];
703 if (!swapval)
704 return NS_ERROR_OUT_OF_MEMORY;
705 SwapBytes(swapval, (const PRUnichar*)yarn.mYarn_Buf, len);
706 aValue.Assign(swapval, len);
707 delete swapval;
709 else
710 aValue.Assign((const PRUnichar *)yarn.mYarn_Buf, len);
711 break;
713 default:
714 return NS_ERROR_UNEXPECTED;
717 return NS_OK;
720 nsresult
721 nsFormHistory::AutoCompleteSearch(const nsAString &aInputName,
722 const nsAString &aInputValue,
723 nsIAutoCompleteMdbResult2 *aPrevResult,
724 nsIAutoCompleteResult **aResult)
726 if (!FormHistoryEnabled())
727 return NS_OK;
729 nsresult rv = OpenDatabase(); // lazily ensure that the database is open
730 NS_ENSURE_SUCCESS(rv, rv);
732 nsCOMPtr<nsIAutoCompleteMdbResult2> result;
734 if (aPrevResult) {
735 result = aPrevResult;
737 PRUint32 rowCount;
738 result->GetMatchCount(&rowCount);
740 for (PRInt32 i = rowCount-1; i >= 0; --i) {
741 nsIMdbRow *row;
742 result->GetRowAt(i, &row);
743 if (!RowMatch(row, aInputName, aInputValue, nsnull))
744 result->RemoveValueAt(i, PR_FALSE);
746 } else {
747 result = do_CreateInstance("@mozilla.org/autocomplete/mdb-result;1");
749 result->SetSearchString(aInputValue);
750 result->Init(mEnv, mTable);
751 result->SetTokens(kToken_ValueColumn, nsIAutoCompleteMdbResult2::kUnicharType, nsnull, nsIAutoCompleteMdbResult2::kUnicharType);
752 result->SetReverseByteOrder(mReverseByteOrder);
754 // Get a cursor to iterate through all rows in the database
755 nsCOMPtr<nsIMdbTableRowCursor> rowCursor;
756 mdb_err err = mTable->GetTableRowCursor(mEnv, -1, getter_AddRefs(rowCursor));
757 NS_ENSURE_TRUE(!err, NS_ERROR_FAILURE);
759 // Store only the matching values
760 nsAutoVoidArray matchingValues;
761 nsCOMArray<nsIMdbRow> matchingRows;
763 nsCOMPtr<nsIMdbRow> row;
764 mdb_pos pos;
765 do {
766 rowCursor->NextRow(mEnv, getter_AddRefs(row), &pos);
767 if (!row)
768 break;
770 PRUnichar *value = 0; // We will own the allocated string value
771 if (RowMatch(row, aInputName, aInputValue, &value)) {
772 matchingRows.AppendObject(row);
773 matchingValues.AppendElement(value);
775 } while (row);
777 // Turn auto array into flat array for quick sort, now that we
778 // know how many items there are
779 PRUint32 count = matchingRows.Count();
781 if (count > 0) {
782 PRUint32* items = new PRUint32[count];
783 PRUint32 i;
784 for (i = 0; i < count; ++i)
785 items[i] = i;
787 NS_QuickSort(items, count, sizeof(PRUint32),
788 SortComparison, &matchingValues);
790 for (i = 0; i < count; ++i) {
791 // Place the sorted result into the autocomplete result
792 result->AddRow(matchingRows[items[i]]);
794 // Free up these strings we owned.
795 NS_Free(matchingValues[i]);
798 delete[] items;
801 PRUint32 matchCount;
802 result->GetMatchCount(&matchCount);
803 if (matchCount > 0) {
804 result->SetSearchResult(nsIAutoCompleteResult::RESULT_SUCCESS);
805 result->SetDefaultIndex(0);
806 } else {
807 result->SetSearchResult(nsIAutoCompleteResult::RESULT_NOMATCH);
808 result->SetDefaultIndex(-1);
812 *aResult = result;
813 NS_IF_ADDREF(*aResult);
815 return NS_OK;
819 nsFormHistory::SortComparison(const void *v1, const void *v2, void *closureVoid)
821 PRUint32 *index1 = (PRUint32 *)v1;
822 PRUint32 *index2 = (PRUint32 *)v2;
823 nsAutoVoidArray *array = (nsAutoVoidArray *)closureVoid;
825 PRUnichar *s1 = (PRUnichar *)array->ElementAt(*index1);
826 PRUnichar *s2 = (PRUnichar *)array->ElementAt(*index2);
828 return nsCRT::strcmp(s1, s2);
831 PRBool
832 nsFormHistory::RowMatch(nsIMdbRow *aRow, const nsAString &aInputName, const nsAString &aInputValue, PRUnichar **aValue)
834 nsAutoString name;
835 GetRowValue(aRow, kToken_NameColumn, name);
837 if (name.Equals(aInputName)) {
838 nsAutoString value;
839 GetRowValue(aRow, kToken_ValueColumn, value);
840 if (Compare(Substring(value, 0, aInputValue.Length()), aInputValue, nsCaseInsensitiveStringComparator()) == 0) {
841 if (aValue)
842 *aValue = ToNewUnicode(value);
843 return PR_TRUE;
847 return PR_FALSE;
850 nsresult
851 nsFormHistory::EntriesExistInternal(const nsAString *aName, const nsAString *aValue, PRBool *_retval)
853 // Unfortunately we have to do a brute force search through the database
854 // because mork didn't bother to implement any indexing functionality
856 *_retval = PR_FALSE;
858 nsresult rv = OpenDatabase(); // lazily ensure that the database is open
859 NS_ENSURE_SUCCESS(rv, rv);
861 // Get a cursor to iterate through all rows in the database
862 nsCOMPtr<nsIMdbTableRowCursor> rowCursor;
863 mdb_err err = mTable->GetTableRowCursor(mEnv, -1, getter_AddRefs(rowCursor));
864 NS_ENSURE_TRUE(!err, NS_ERROR_FAILURE);
866 nsCOMPtr<nsIMdbRow> row;
867 mdb_pos pos;
868 do {
869 rowCursor->NextRow(mEnv, getter_AddRefs(row), &pos);
870 if (!row)
871 break;
873 // Check if the name and value combination match this row
874 nsAutoString name;
875 GetRowValue(row, kToken_NameColumn, name);
877 if (Compare(name, *aName, nsCaseInsensitiveStringComparator()) == 0) {
878 nsAutoString value;
879 GetRowValue(row, kToken_ValueColumn, value);
880 if (!aValue || Compare(value, *aValue, nsCaseInsensitiveStringComparator()) == 0) {
881 *_retval = PR_TRUE;
882 break;
885 } while (1);
887 return NS_OK;
890 nsresult
891 nsFormHistory::RemoveEntriesInternal(const nsAString *aName)
893 nsresult rv = OpenDatabase(); // lazily ensure that the database is open
894 NS_ENSURE_SUCCESS(rv, rv);
896 if (!mTable) return NS_OK;
898 mdb_err err;
899 mdb_count count;
900 nsAutoString name;
901 err = mTable->GetCount(mEnv, &count);
902 if (err != 0) return NS_ERROR_FAILURE;
904 // Begin the batch.
905 int marker;
906 err = mTable->StartBatchChangeHint(mEnv, &marker);
907 NS_ASSERTION(err == 0, "unable to start batch");
908 if (err != 0) return NS_ERROR_FAILURE;
910 for (mdb_pos pos = count - 1; pos >= 0; --pos) {
911 nsCOMPtr<nsIMdbRow> row;
912 err = mTable->PosToRow(mEnv, pos, getter_AddRefs(row));
913 NS_ASSERTION(err == 0, "unable to get row");
914 if (err != 0)
915 break;
917 NS_ASSERTION(row != nsnull, "no row");
918 if (! row)
919 continue;
921 // Check if the name matches this row
922 GetRowValue(row, kToken_NameColumn, name);
924 if (!aName || Compare(name, *aName, nsCaseInsensitiveStringComparator()) == 0) {
926 // Officially cut the row *now*, before notifying any observers:
927 // that way, any re-entrant calls won't find the row.
928 err = mTable->CutRow(mEnv, row);
929 NS_ASSERTION(err == 0, "couldn't cut row");
930 if (err != 0)
931 continue;
933 // possibly avoid leakage
934 err = row->CutAllColumns(mEnv);
935 NS_ASSERTION(err == 0, "couldn't cut all columns");
936 // we'll notify regardless of whether we could successfully
937 // CutAllColumns or not.
942 // Finish the batch.
943 err = mTable->EndBatchChangeHint(mEnv, &marker);
944 NS_ASSERTION(err == 0, "error ending batch");
946 return (err == 0) ? NS_OK : NS_ERROR_FAILURE;
950 nsresult
951 nsFormHistory::InitByteOrder(PRBool aForce)
953 // bigEndian and littleEndian are endianness markers that are stored in
954 // the formhistory db as UTF-16. Define them to be strings easily
955 // recognized in either endianness.
956 nsAutoString bigEndianByteOrder((PRUnichar*)"BBBB", 2);
957 nsAutoString littleEndianByteOrder((PRUnichar*)"llll", 2);
958 #ifdef IS_BIG_ENDIAN
959 nsAutoString nativeByteOrder(bigEndianByteOrder);
960 #else
961 nsAutoString nativeByteOrder(littleEndianByteOrder);
962 #endif
964 nsAutoString fileByteOrder;
965 nsresult rv = NS_OK;
967 if (!aForce)
968 rv = GetByteOrder(fileByteOrder);
970 if (aForce || NS_FAILED(rv) ||
971 !(fileByteOrder.Equals(bigEndianByteOrder) ||
972 fileByteOrder.Equals(littleEndianByteOrder))) {
973 #if defined(XP_MACOSX) && defined(IS_LITTLE_ENDIAN)
974 // The formhistory db did not carry endiannes information until the
975 // initial x86 Mac release. There are a lot of users out there who
976 // will be switching from ppc versions to x86, and their unmarked
977 // formhistory files are big-endian. On x86 Macs, unless aForce is set
978 // (indicating formhistory reset or a brand-new db), use big-endian byte
979 // ordering and turn swapping on.
980 if (aForce) {
981 mReverseByteOrder = PR_FALSE;
982 rv = SaveByteOrder(nativeByteOrder);
984 else {
985 mReverseByteOrder = PR_TRUE;
986 rv = SaveByteOrder(bigEndianByteOrder);
988 #else
989 mReverseByteOrder = PR_FALSE;
990 rv = SaveByteOrder(nativeByteOrder);
991 #endif
993 else
994 mReverseByteOrder = !fileByteOrder.Equals(nativeByteOrder);
996 return rv;
999 nsresult
1000 nsFormHistory::GetByteOrder(nsAString& aByteOrder)
1002 NS_ENSURE_SUCCESS(OpenDatabase(), NS_ERROR_FAILURE);
1004 mdb_err err = GetRowValue(mMetaRow, kToken_ByteOrder, aByteOrder);
1005 NS_ENSURE_FALSE(err, NS_ERROR_FAILURE);
1007 return NS_OK;
1010 nsresult
1011 nsFormHistory::SaveByteOrder(const nsAString& aByteOrder)
1013 NS_ENSURE_SUCCESS(OpenDatabase(), NS_ERROR_FAILURE);
1015 mdb_err err = SetRowValue(mMetaRow, kToken_ByteOrder, aByteOrder);
1016 NS_ENSURE_FALSE(err, NS_ERROR_FAILURE);
1018 return NS_OK;