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
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.
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"
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() :
102 mReverseByteOrder(PR_FALSE
)
104 NS_ASSERTION(!gFormHistory
, "nsFormHistory must be used as a service");
108 nsFormHistory::~nsFormHistory()
110 NS_ASSERTION(gFormHistory
== this,
111 "nsFormHistory must be used as a service");
113 gFormHistory
= nsnull
;
117 nsFormHistory::Init()
119 nsCOMPtr
<nsIObserverService
> service
= do_GetService("@mozilla.org/observer-service;1");
121 service
->AddObserver(this, NS_EARLYFORMSUBMIT_SUBJECT
, PR_TRUE
);
126 nsFormHistory
*nsFormHistory::gFormHistory
= nsnull
;
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 ////////////////////////////////////////////////////////////////////////
154 nsFormHistory::GetHasEntries(PRBool
*aHasEntries
)
156 nsresult rv
= OpenDatabase(); // lazily ensure that the database is open
157 NS_ENSURE_SUCCESS(rv
, rv
);
160 mdb_err err
= mTable
->GetCount(mEnv
, &rowCount
);
161 NS_ENSURE_TRUE(!err
, NS_ERROR_FAILURE
);
163 *aHasEntries
= rowCount
!= 0;
168 nsFormHistory::AddEntry(const nsAString
&aName
, const nsAString
&aValue
)
170 if (!FormHistoryEnabled())
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
));
182 nsFormHistory::EntryExists(const nsAString
&aName
, const nsAString
&aValue
, PRBool
*_retval
)
184 return EntriesExistInternal(&aName
, &aValue
, _retval
);
188 nsFormHistory::NameExists(const nsAString
&aName
, PRBool
*_retval
)
190 return EntriesExistInternal(&aName
, nsnull
, _retval
);
194 nsFormHistory::RemoveEntry(const nsAString
&aName
, const nsAString
&aValue
)
196 return NS_ERROR_NOT_IMPLEMENTED
;
200 nsFormHistory::RemoveEntriesForName(const nsAString
&aName
)
202 return RemoveEntriesInternal(&aName
);
206 nsFormHistory::RemoveAllEntries()
208 nsresult rv
= RemoveEntriesInternal(nsnull
);
210 if (NS_SUCCEEDED(rv
))
211 rv
= InitByteOrder(PR_TRUE
);
218 ////////////////////////////////////////////////////////////////////////
222 nsFormHistory::Observe(nsISupports
*aSubject
, const char *aTopic
, const PRUnichar
*aData
)
224 if (!strcmp(aTopic
, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID
)) {
225 mPrefBranch
->GetBoolPref(PREF_FORMFILL_ENABLE
, &gFormHistoryEnabled
);
231 ////////////////////////////////////////////////////////////////////////
232 //// nsIFormSubmitObserver
235 nsFormHistory::Notify(nsIDOMHTMLFormElement
* formElt
, nsIDOMWindowInternal
* aWindow
, nsIURI
* aActionURL
, PRBool
* aCancelSubmit
)
237 if (!FormHistoryEnabled())
240 nsresult rv
= OpenDatabase(); // lazily ensure that the database is open
241 NS_ENSURE_SUCCESS(rv
, rv
);
243 nsCOMPtr
<nsIDOMHTMLCollection
> elts
;
244 formElt
->GetElements(getter_AddRefs(elts
));
246 const char *textString
= "text";
249 elts
->GetLength(&length
);
250 for (PRUint32 i
= 0; i
< length
; ++i
) {
251 nsCOMPtr
<nsIDOMNode
> node
;
252 elts
->Item(i
, getter_AddRefs(node
));
253 nsCOMPtr
<nsIDOMHTMLInputElement
> inputElt
= do_QueryInterface(node
);
255 // Filter only inputs that are of type "text"
257 inputElt
->GetType(type
);
258 if (type
.EqualsIgnoreCase(textString
)) {
259 // If this input has a name/id and value, add it to the database
261 inputElt
->GetValue(value
);
262 if (!value
.IsEmpty()) {
264 inputElt
->GetName(name
);
266 inputElt
->GetId(name
);
269 AppendRow(name
, value
, nsnull
);
278 ////////////////////////////////////////////////////////////////////////
281 class SatchelErrorHook
: public nsIMdbErrorHook
287 NS_IMETHOD
OnErrorString(nsIMdbEnv
* ev
, const char* inAscii
);
288 NS_IMETHOD
OnErrorYarn(nsIMdbEnv
* ev
, const mdbYarn
* inYarn
);
289 NS_IMETHOD
OnWarningString(nsIMdbEnv
* ev
, const char* inAscii
);
290 NS_IMETHOD
OnWarningYarn(nsIMdbEnv
* ev
, const mdbYarn
* inYarn
);
291 NS_IMETHOD
OnAbortHintString(nsIMdbEnv
* ev
, const char* inAscii
);
292 NS_IMETHOD
OnAbortHintYarn(nsIMdbEnv
* ev
, const mdbYarn
* inYarn
);
295 // nsIMdbErrorHook has no IID!
296 NS_IMPL_ISUPPORTS0(SatchelErrorHook
)
299 SatchelErrorHook::OnErrorString(nsIMdbEnv
*ev
, const char *inAscii
)
301 printf("mork error: %s\n", inAscii
);
306 SatchelErrorHook::OnErrorYarn(nsIMdbEnv
*ev
, const mdbYarn
* inYarn
)
308 printf("mork error yarn: %p\n", (void*)inYarn
);
313 SatchelErrorHook::OnWarningString(nsIMdbEnv
*ev
, const char *inAscii
)
315 printf("mork warning: %s\n", inAscii
);
320 SatchelErrorHook::OnWarningYarn(nsIMdbEnv
*ev
, const mdbYarn
*inYarn
)
322 printf("mork warning yarn: %p\n", (void*)inYarn
);
327 SatchelErrorHook::OnAbortHintString(nsIMdbEnv
*ev
, const char *inAscii
)
329 printf("mork abort: %s\n", inAscii
);
334 SatchelErrorHook::OnAbortHintYarn(nsIMdbEnv
*ev
, const mdbYarn
*inYarn
)
336 printf("mork abort yarn: %p\n", (void*)inYarn
);
341 nsFormHistory::OpenDatabase()
346 // Get a handle to the database file
347 nsCOMPtr
<nsIFile
> historyFile
;
348 nsresult rv
= NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR
, getter_AddRefs(historyFile
));
349 NS_ENSURE_SUCCESS(rv
, rv
);
350 historyFile
->Append(NS_ConvertUTF8toUTF16(kFormHistoryFileName
));
352 // Get an Mdb Factory
353 nsCOMPtr
<nsIMdbFactoryService
> mdbFactory
= do_GetService(NS_MORK_CONTRACTID
, &rv
);
354 NS_ENSURE_SUCCESS(rv
, rv
);
355 rv
= mdbFactory
->GetMdbFactory(getter_AddRefs(mMdbFactory
));
356 NS_ENSURE_SUCCESS(rv
, rv
);
358 // Create the Mdb environment
359 mdb_err err
= mMdbFactory
->MakeEnv(nsnull
, &mEnv
);
360 NS_ASSERTION(err
== 0, "ERROR: Unable to create Form History mdb");
361 mEnv
->SetAutoClear(PR_TRUE
);
362 NS_ENSURE_TRUE(!err
, NS_ERROR_FAILURE
);
363 mEnv
->SetErrorHook(new SatchelErrorHook());
365 nsCAutoString filePath
;
366 historyFile
->GetNativePath(filePath
);
367 PRBool exists
= PR_TRUE
;
368 historyFile
->Exists(&exists
);
370 PRBool createdNew
= PR_FALSE
;
372 if (!exists
|| NS_FAILED(rv
= OpenExistingFile(filePath
.get()))) {
373 // If the file doesn't exist, or we fail trying to open it,
374 // then make sure it is deleted and then create an empty database file
375 historyFile
->Remove(PR_FALSE
);
376 rv
= CreateNewFile(filePath
.get());
377 createdNew
= PR_TRUE
;
379 NS_ENSURE_SUCCESS(rv
, rv
);
381 // Get the initial size of the file, needed later for Commit
382 historyFile
->GetFileSize(&mFileSizeOnDisk
);
384 rv
= InitByteOrder(createdNew
);
386 /* // TESTING: Add a row to the database
388 foopy.AssignWithConversion("foopy");
390 oogly.AssignWithConversion("oogly");
391 AppendRow(foopy, oogly, nsnull);
394 /* // TESTING: Dump the contents of the database
396 mdb_err err = mTable->GetCount(mEnv, &count);
397 printf("%d rows in form history\n", count);
399 for (mdb_pos pos = count - 1; pos >= 0; --pos) {
400 nsCOMPtr<nsIMdbRow> row;
401 err = mTable->PosToRow(mEnv, pos, getter_AddRefs(row));
404 GetRowValue(row, kToken_NameColumn, name);
406 GetRowValue(row, kToken_ValueColumn, value);
407 printf("ROW: %s - %s\n", ToNewCString(name), ToNewCString(value));
414 nsFormHistory::OpenExistingFile(const char *aPath
)
416 nsCOMPtr
<nsIMdbFile
> oldFile
;
417 nsIMdbHeap
* dbHeap
= 0;
418 mdb_err err
= mMdbFactory
->OpenOldFile(mEnv
, dbHeap
, aPath
, mdbBool_kFalse
, getter_AddRefs(oldFile
));
419 NS_ENSURE_TRUE(!err
&& oldFile
, NS_ERROR_FAILURE
);
421 mdb_bool canOpen
= 0;
422 mdbYarn outFormat
= {nsnull
, 0, 0, 0, 0, nsnull
};
423 err
= mMdbFactory
->CanOpenFilePort(mEnv
, oldFile
, &canOpen
, &outFormat
);
424 NS_ENSURE_TRUE(!err
&& canOpen
, NS_ERROR_FAILURE
);
426 nsCOMPtr
<nsIMdbThumb
> thumb
;
427 mdbOpenPolicy policy
= {{0, 0}, 0, 0};
428 err
= mMdbFactory
->OpenFileStore(mEnv
, dbHeap
, oldFile
, &policy
, getter_AddRefs(thumb
));
429 NS_ENSURE_TRUE(!err
&& thumb
, NS_ERROR_FAILURE
);
432 mdb_err thumbErr
= UseThumb(thumb
, &done
);
434 if (err
== 0 && done
)
435 err
= mMdbFactory
->ThumbToOpenStore(mEnv
, thumb
, &mStore
);
436 NS_ENSURE_TRUE(!err
, NS_ERROR_FAILURE
);
438 nsresult rv
= CreateTokens();
439 NS_ENSURE_SUCCESS(rv
, rv
);
441 mdbOid oid
= {kToken_RowScope
, 1};
442 err
= mStore
->GetTable(mEnv
, &oid
, &mTable
);
443 NS_ENSURE_TRUE(!err
, NS_ERROR_FAILURE
);
445 NS_WARNING("ERROR: Form history file is corrupt, now deleting it.");
446 return NS_ERROR_FAILURE
;
449 err
= mTable
->GetMetaRow(mEnv
, &oid
, nsnull
, getter_AddRefs(mMetaRow
));
451 NS_WARNING("Could not get meta row");
453 if (NS_FAILED(thumbErr
))
456 return err
? NS_ERROR_FAILURE
: NS_OK
;
460 nsFormHistory::CreateNewFile(const char *aPath
)
462 nsIMdbHeap
* dbHeap
= 0;
463 nsCOMPtr
<nsIMdbFile
> newFile
;
464 mdb_err err
= mMdbFactory
->CreateNewFile(mEnv
, dbHeap
, aPath
, getter_AddRefs(newFile
));
465 NS_ENSURE_TRUE(!err
&& newFile
, NS_ERROR_FAILURE
);
467 nsCOMPtr
<nsIMdbTable
> oldTable
= mTable
;;
468 nsCOMPtr
<nsIMdbStore
> oldStore
= mStore
;
469 mdbOpenPolicy policy
= {{0, 0}, 0, 0};
470 err
= mMdbFactory
->CreateNewFileStore(mEnv
, dbHeap
, newFile
, &policy
, &mStore
);
471 NS_ENSURE_TRUE(!err
, NS_ERROR_FAILURE
);
473 nsresult rv
= CreateTokens();
474 NS_ENSURE_SUCCESS(rv
, rv
);
476 // Create the one and only table in the database
477 err
= mStore
->NewTable(mEnv
, kToken_RowScope
, kToken_Kind
, PR_TRUE
, nsnull
, &mTable
);
478 NS_ENSURE_TRUE(!err
&& mTable
, NS_ERROR_FAILURE
);
480 mdbOid oid
= {kToken_RowScope
, 1};
481 err
= mTable
->GetMetaRow(mEnv
, &oid
, nsnull
, getter_AddRefs(mMetaRow
));
483 NS_WARNING("Could not get meta row");
484 return NS_ERROR_FAILURE
;
487 // oldTable will only be set if we detected a corrupt db, and are
488 // trying to restore data from it.
490 CopyRowsFromTable(oldTable
);
492 // Force a commit now to get it written out.
493 nsCOMPtr
<nsIMdbThumb
> thumb
;
494 err
= mStore
->CompressCommit(mEnv
, getter_AddRefs(thumb
));
495 NS_ENSURE_TRUE(!err
, NS_ERROR_FAILURE
);
498 err
= UseThumb(thumb
, &done
);
500 return err
|| !done
? NS_ERROR_FAILURE
: NS_OK
;
504 nsFormHistory::CloseDatabase()
527 nsFormHistory::CreateTokens()
532 return NS_ERROR_NOT_INITIALIZED
;
534 err
= mStore
->StringToToken(mEnv
, "ns:formhistory:db:row:scope:formhistory:all", &kToken_RowScope
);
535 if (err
!= 0) return NS_ERROR_FAILURE
;
537 err
= mStore
->StringToToken(mEnv
, "ns:formhistory:db:table:kind:formhistory", &kToken_Kind
);
538 if (err
!= 0) return NS_ERROR_FAILURE
;
540 err
= mStore
->StringToToken(mEnv
, "Value", &kToken_ValueColumn
);
541 if (err
!= 0) return NS_ERROR_FAILURE
;
543 err
= mStore
->StringToToken(mEnv
, "Name", &kToken_NameColumn
);
544 if (err
!= 0) return NS_ERROR_FAILURE
;
546 err
= mStore
->StringToToken(mEnv
, "ByteOrder", &kToken_ByteOrder
);
547 if (err
!= 0) return NS_ERROR_FAILURE
;
553 nsFormHistory::Flush()
555 if (!mStore
|| !mTable
)
560 nsCOMPtr
<nsIMdbThumb
> thumb
;
561 err
= mStore
->CompressCommit(mEnv
, getter_AddRefs(thumb
));
564 err
= UseThumb(thumb
, nsnull
);
566 return err
? NS_ERROR_FAILURE
: NS_OK
;
570 nsFormHistory::UseThumb(nsIMdbThumb
*aThumb
, PRBool
*aDone
)
579 err
= aThumb
->DoMore(mEnv
, &total
, ¤t
, &done
, &broken
);
580 } while ((err
== 0) && !broken
&& !done
);
585 return err
? NS_ERROR_FAILURE
: NS_OK
;
589 nsFormHistory::CopyRowsFromTable(nsIMdbTable
*sourceTable
)
591 nsCOMPtr
<nsIMdbTableRowCursor
> rowCursor
;
592 mdb_err err
= sourceTable
->GetTableRowCursor(mEnv
, -1, getter_AddRefs(rowCursor
));
593 NS_ENSURE_TRUE(!err
, NS_ERROR_FAILURE
);
595 nsCOMPtr
<nsIMdbRow
> row
;
598 rowCursor
->NextRow(mEnv
, getter_AddRefs(row
), &pos
);
603 rowId
.mOid_Scope
= kToken_RowScope
;
604 rowId
.mOid_Id
= mdb_id(-1);
606 nsCOMPtr
<nsIMdbRow
> newRow
;
607 mTable
->NewRow(mEnv
, &rowId
, getter_AddRefs(newRow
));
608 newRow
->SetRow(mEnv
, row
);
609 mTable
->AddRow(mEnv
, newRow
);
615 nsFormHistory::AppendRow(const nsAString
&aName
, const nsAString
&aValue
, nsIMdbRow
**aResult
)
618 return NS_ERROR_NOT_INITIALIZED
;
620 if (aName
.Length() > FORMFILL_NAME_MAX_LEN
||
621 aValue
.Length() > FORMFILL_VALUE_MAX_LEN
)
622 return NS_ERROR_INVALID_ARG
;
624 PRBool exists
= PR_TRUE
;
625 EntryExists(aName
, aValue
, &exists
);
630 rowId
.mOid_Scope
= kToken_RowScope
;
631 rowId
.mOid_Id
= mdb_id(-1);
633 nsCOMPtr
<nsIMdbRow
> row
;
634 mdb_err err
= mTable
->NewRow(mEnv
, &rowId
, getter_AddRefs(row
));
636 return NS_ERROR_FAILURE
;
638 SetRowValue(row
, kToken_NameColumn
, aName
);
639 SetRowValue(row
, kToken_ValueColumn
, aValue
);
650 nsFormHistory::SetRowValue(nsIMdbRow
*aRow
, mdb_column aCol
, const nsAString
&aValue
)
652 PRInt32 len
= aValue
.Length() * sizeof(PRUnichar
);
653 PRUnichar
*swapval
= nsnull
;
654 mdbYarn yarn
= {nsnull
, len
, len
, 0, 0, nsnull
};
655 const nsPromiseFlatString
& buffer
= PromiseFlatString(aValue
);
657 if (mReverseByteOrder
) {
658 swapval
= new PRUnichar
[aValue
.Length()];
660 return NS_ERROR_OUT_OF_MEMORY
;
661 SwapBytes(swapval
, buffer
.get(), aValue
.Length());
662 yarn
.mYarn_Buf
= swapval
;
665 yarn
.mYarn_Buf
= (void*)buffer
.get();
667 mdb_err err
= aRow
->AddColumn(mEnv
, aCol
, &yarn
);
671 return err
? NS_ERROR_FAILURE
: NS_OK
;
675 nsFormHistory::GetRowValue(nsIMdbRow
*aRow
, mdb_column aCol
, nsAString
&aValue
)
678 mdb_err err
= aRow
->AliasCellYarn(mEnv
, aCol
, &yarn
);
680 return NS_ERROR_FAILURE
;
683 if (!yarn
.mYarn_Fill
)
686 switch (yarn
.mYarn_Form
) {
688 PRUint32 len
= yarn
.mYarn_Fill
/ sizeof(PRUnichar
);
689 if (mReverseByteOrder
) {
690 PRUnichar
*swapval
= new PRUnichar
[len
];
692 return NS_ERROR_OUT_OF_MEMORY
;
693 SwapBytes(swapval
, (const PRUnichar
*)yarn
.mYarn_Buf
, len
);
694 aValue
.Assign(swapval
, len
);
698 aValue
.Assign((const PRUnichar
*)yarn
.mYarn_Buf
, len
);
702 return NS_ERROR_UNEXPECTED
;
709 nsFormHistory::AutoCompleteSearch(const nsAString
&aInputName
,
710 const nsAString
&aInputValue
,
711 nsIAutoCompleteMdbResult2
*aPrevResult
,
712 nsIAutoCompleteResult
**aResult
)
714 if (!FormHistoryEnabled())
717 nsresult rv
= OpenDatabase(); // lazily ensure that the database is open
718 NS_ENSURE_SUCCESS(rv
, rv
);
720 nsCOMPtr
<nsIAutoCompleteMdbResult2
> result
;
723 result
= aPrevResult
;
726 result
->GetMatchCount(&rowCount
);
728 for (PRInt32 i
= rowCount
-1; i
>= 0; --i
) {
730 result
->GetRowAt(i
, &row
);
731 if (!RowMatch(row
, aInputName
, aInputValue
, nsnull
))
732 result
->RemoveValueAt(i
, PR_FALSE
);
735 result
= do_CreateInstance("@mozilla.org/autocomplete/mdb-result;1");
737 result
->SetSearchString(aInputValue
);
738 result
->Init(mEnv
, mTable
);
739 result
->SetTokens(kToken_ValueColumn
, nsIAutoCompleteMdbResult2::kUnicharType
, nsnull
, nsIAutoCompleteMdbResult2::kUnicharType
);
740 result
->SetReverseByteOrder(mReverseByteOrder
);
742 // Get a cursor to iterate through all rows in the database
743 nsCOMPtr
<nsIMdbTableRowCursor
> rowCursor
;
744 mdb_err err
= mTable
->GetTableRowCursor(mEnv
, -1, getter_AddRefs(rowCursor
));
745 NS_ENSURE_TRUE(!err
, NS_ERROR_FAILURE
);
747 // Store only the matching values
748 nsAutoVoidArray matchingValues
;
749 nsCOMArray
<nsIMdbRow
> matchingRows
;
751 nsCOMPtr
<nsIMdbRow
> row
;
754 rowCursor
->NextRow(mEnv
, getter_AddRefs(row
), &pos
);
758 PRUnichar
*value
= 0; // We will own the allocated string value
759 if (RowMatch(row
, aInputName
, aInputValue
, &value
)) {
760 matchingRows
.AppendObject(row
);
761 matchingValues
.AppendElement(value
);
765 // Turn auto array into flat array for quick sort, now that we
766 // know how many items there are
767 PRUint32 count
= matchingRows
.Count();
770 PRUint32
* items
= new PRUint32
[count
];
772 for (i
= 0; i
< count
; ++i
)
775 NS_QuickSort(items
, count
, sizeof(PRUint32
),
776 SortComparison
, &matchingValues
);
778 for (i
= 0; i
< count
; ++i
) {
779 // Place the sorted result into the autocomplete result
780 result
->AddRow(matchingRows
[items
[i
]]);
782 // Free up these strings we owned.
783 NS_Free(matchingValues
[i
]);
790 result
->GetMatchCount(&matchCount
);
791 if (matchCount
> 0) {
792 result
->SetSearchResult(nsIAutoCompleteResult::RESULT_SUCCESS
);
793 result
->SetDefaultIndex(0);
795 result
->SetSearchResult(nsIAutoCompleteResult::RESULT_NOMATCH
);
796 result
->SetDefaultIndex(-1);
801 NS_IF_ADDREF(*aResult
);
807 nsFormHistory::SortComparison(const void *v1
, const void *v2
, void *closureVoid
)
809 PRUint32
*index1
= (PRUint32
*)v1
;
810 PRUint32
*index2
= (PRUint32
*)v2
;
811 nsAutoVoidArray
*array
= (nsAutoVoidArray
*)closureVoid
;
813 PRUnichar
*s1
= (PRUnichar
*)array
->ElementAt(*index1
);
814 PRUnichar
*s2
= (PRUnichar
*)array
->ElementAt(*index2
);
816 return nsCRT::strcmp(s1
, s2
);
820 nsFormHistory::RowMatch(nsIMdbRow
*aRow
, const nsAString
&aInputName
, const nsAString
&aInputValue
, PRUnichar
**aValue
)
823 GetRowValue(aRow
, kToken_NameColumn
, name
);
825 if (name
.Equals(aInputName
)) {
827 GetRowValue(aRow
, kToken_ValueColumn
, value
);
828 if (Compare(Substring(value
, 0, aInputValue
.Length()), aInputValue
, nsCaseInsensitiveStringComparator()) == 0) {
830 *aValue
= ToNewUnicode(value
);
839 nsFormHistory::EntriesExistInternal(const nsAString
*aName
, const nsAString
*aValue
, PRBool
*_retval
)
841 // Unfortunately we have to do a brute force search through the database
842 // because mork didn't bother to implement any indexing functionality
846 nsresult rv
= OpenDatabase(); // lazily ensure that the database is open
847 NS_ENSURE_SUCCESS(rv
, rv
);
849 // Get a cursor to iterate through all rows in the database
850 nsCOMPtr
<nsIMdbTableRowCursor
> rowCursor
;
851 mdb_err err
= mTable
->GetTableRowCursor(mEnv
, -1, getter_AddRefs(rowCursor
));
852 NS_ENSURE_TRUE(!err
, NS_ERROR_FAILURE
);
854 nsCOMPtr
<nsIMdbRow
> row
;
857 rowCursor
->NextRow(mEnv
, getter_AddRefs(row
), &pos
);
861 // Check if the name and value combination match this row
863 GetRowValue(row
, kToken_NameColumn
, name
);
865 if (Compare(name
, *aName
, nsCaseInsensitiveStringComparator()) == 0) {
867 GetRowValue(row
, kToken_ValueColumn
, value
);
868 if (!aValue
|| Compare(value
, *aValue
, nsCaseInsensitiveStringComparator()) == 0) {
879 nsFormHistory::RemoveEntriesInternal(const nsAString
*aName
)
881 nsresult rv
= OpenDatabase(); // lazily ensure that the database is open
882 NS_ENSURE_SUCCESS(rv
, rv
);
884 if (!mTable
) return NS_OK
;
889 err
= mTable
->GetCount(mEnv
, &count
);
890 if (err
!= 0) return NS_ERROR_FAILURE
;
894 err
= mTable
->StartBatchChangeHint(mEnv
, &marker
);
895 NS_ASSERTION(err
== 0, "unable to start batch");
896 if (err
!= 0) return NS_ERROR_FAILURE
;
898 for (mdb_pos pos
= count
- 1; pos
>= 0; --pos
) {
899 nsCOMPtr
<nsIMdbRow
> row
;
900 err
= mTable
->PosToRow(mEnv
, pos
, getter_AddRefs(row
));
901 NS_ASSERTION(err
== 0, "unable to get row");
905 NS_ASSERTION(row
!= nsnull
, "no row");
909 // Check if the name matches this row
910 GetRowValue(row
, kToken_NameColumn
, name
);
912 if (!aName
|| Compare(name
, *aName
, nsCaseInsensitiveStringComparator()) == 0) {
914 // Officially cut the row *now*, before notifying any observers:
915 // that way, any re-entrant calls won't find the row.
916 err
= mTable
->CutRow(mEnv
, row
);
917 NS_ASSERTION(err
== 0, "couldn't cut row");
921 // possibly avoid leakage
922 err
= row
->CutAllColumns(mEnv
);
923 NS_ASSERTION(err
== 0, "couldn't cut all columns");
924 // we'll notify regardless of whether we could successfully
925 // CutAllColumns or not.
931 err
= mTable
->EndBatchChangeHint(mEnv
, &marker
);
932 NS_ASSERTION(err
== 0, "error ending batch");
934 return (err
== 0) ? NS_OK
: NS_ERROR_FAILURE
;
939 nsFormHistory::InitByteOrder(PRBool aForce
)
941 // bigEndian and littleEndian are endianness markers that are stored in
942 // the formhistory db as UTF-16. Define them to be strings easily
943 // recognized in either endianness.
944 nsAutoString
bigEndianByteOrder((PRUnichar
*)"BBBB", 2);
945 nsAutoString
littleEndianByteOrder((PRUnichar
*)"llll", 2);
947 nsAutoString
nativeByteOrder(bigEndianByteOrder
);
949 nsAutoString
nativeByteOrder(littleEndianByteOrder
);
952 nsAutoString fileByteOrder
;
956 rv
= GetByteOrder(fileByteOrder
);
958 if (aForce
|| NS_FAILED(rv
) ||
959 !(fileByteOrder
.Equals(bigEndianByteOrder
) ||
960 fileByteOrder
.Equals(littleEndianByteOrder
))) {
961 #if defined(XP_MACOSX) && defined(IS_LITTLE_ENDIAN)
962 // The formhistory db did not carry endiannes information until the
963 // initial x86 Mac release. There are a lot of users out there who
964 // will be switching from ppc versions to x86, and their unmarked
965 // formhistory files are big-endian. On x86 Macs, unless aForce is set
966 // (indicating formhistory reset or a brand-new db), use big-endian byte
967 // ordering and turn swapping on.
969 mReverseByteOrder
= PR_FALSE
;
970 rv
= SaveByteOrder(nativeByteOrder
);
973 mReverseByteOrder
= PR_TRUE
;
974 rv
= SaveByteOrder(bigEndianByteOrder
);
977 mReverseByteOrder
= PR_FALSE
;
978 rv
= SaveByteOrder(nativeByteOrder
);
982 mReverseByteOrder
= !fileByteOrder
.Equals(nativeByteOrder
);
988 nsFormHistory::GetByteOrder(nsAString
& aByteOrder
)
990 NS_ENSURE_SUCCESS(OpenDatabase(), NS_ERROR_FAILURE
);
992 mdb_err err
= GetRowValue(mMetaRow
, kToken_ByteOrder
, aByteOrder
);
993 NS_ENSURE_FALSE(err
, NS_ERROR_FAILURE
);
999 nsFormHistory::SaveByteOrder(const nsAString
& aByteOrder
)
1001 NS_ENSURE_SUCCESS(OpenDatabase(), NS_ERROR_FAILURE
);
1003 mdb_err err
= SetRowValue(mMetaRow
, kToken_ByteOrder
, aByteOrder
);
1004 NS_ENSURE_FALSE(err
, NS_ERROR_FAILURE
);