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
);
219 nsFormHistory::RemoveEntriesByTimeframe(PRInt64 aStartTime
, PRInt64 aEndTime
)
221 return NS_ERROR_NOT_IMPLEMENTED
;
225 nsFormHistory::GetDBConnection()
227 return NS_ERROR_NOT_IMPLEMENTED
;
230 ////////////////////////////////////////////////////////////////////////
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
);
243 ////////////////////////////////////////////////////////////////////////
244 //// nsIFormSubmitObserver
247 nsFormHistory::Notify(nsIDOMHTMLFormElement
* formElt
, nsIDOMWindowInternal
* aWindow
, nsIURI
* aActionURL
, PRBool
* aCancelSubmit
)
249 if (!FormHistoryEnabled())
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";
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
);
267 // Filter only inputs that are of type "text"
269 inputElt
->GetType(type
);
270 if (type
.EqualsIgnoreCase(textString
)) {
271 // If this input has a name/id and value, add it to the database
273 inputElt
->GetValue(value
);
274 if (!value
.IsEmpty()) {
276 inputElt
->GetName(name
);
278 inputElt
->GetId(name
);
281 AppendRow(name
, value
, nsnull
);
290 ////////////////////////////////////////////////////////////////////////
293 class SatchelErrorHook
: public 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
)
311 SatchelErrorHook::OnErrorString(nsIMdbEnv
*ev
, const char *inAscii
)
313 printf("mork error: %s\n", inAscii
);
318 SatchelErrorHook::OnErrorYarn(nsIMdbEnv
*ev
, const mdbYarn
* inYarn
)
320 printf("mork error yarn: %p\n", (void*)inYarn
);
325 SatchelErrorHook::OnWarningString(nsIMdbEnv
*ev
, const char *inAscii
)
327 printf("mork warning: %s\n", inAscii
);
332 SatchelErrorHook::OnWarningYarn(nsIMdbEnv
*ev
, const mdbYarn
*inYarn
)
334 printf("mork warning yarn: %p\n", (void*)inYarn
);
339 SatchelErrorHook::OnAbortHintString(nsIMdbEnv
*ev
, const char *inAscii
)
341 printf("mork abort: %s\n", inAscii
);
346 SatchelErrorHook::OnAbortHintYarn(nsIMdbEnv
*ev
, const mdbYarn
*inYarn
)
348 printf("mork abort yarn: %p\n", (void*)inYarn
);
353 nsFormHistory::OpenDatabase()
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
400 foopy.AssignWithConversion("foopy");
402 oogly.AssignWithConversion("oogly");
403 AppendRow(foopy, oogly, nsnull);
406 /* // TESTING: Dump the contents of the database
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));
416 GetRowValue(row, kToken_NameColumn, name);
418 GetRowValue(row, kToken_ValueColumn, value);
419 printf("ROW: %s - %s\n", ToNewCString(name), ToNewCString(value));
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
);
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
);
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
));
463 NS_WARNING("Could not get meta row");
465 if (NS_FAILED(thumbErr
))
468 return err
? NS_ERROR_FAILURE
: NS_OK
;
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
));
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.
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
);
510 err
= UseThumb(thumb
, &done
);
512 return err
|| !done
? NS_ERROR_FAILURE
: NS_OK
;
516 nsFormHistory::CloseDatabase()
539 nsFormHistory::CreateTokens()
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
;
565 nsFormHistory::Flush()
567 if (!mStore
|| !mTable
)
572 nsCOMPtr
<nsIMdbThumb
> thumb
;
573 err
= mStore
->CompressCommit(mEnv
, getter_AddRefs(thumb
));
576 err
= UseThumb(thumb
, nsnull
);
578 return err
? NS_ERROR_FAILURE
: NS_OK
;
582 nsFormHistory::UseThumb(nsIMdbThumb
*aThumb
, PRBool
*aDone
)
591 err
= aThumb
->DoMore(mEnv
, &total
, ¤t
, &done
, &broken
);
592 } while ((err
== 0) && !broken
&& !done
);
597 return err
? NS_ERROR_FAILURE
: NS_OK
;
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
;
610 rowCursor
->NextRow(mEnv
, getter_AddRefs(row
), &pos
);
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
);
627 nsFormHistory::AppendRow(const nsAString
&aName
, const nsAString
&aValue
, nsIMdbRow
**aResult
)
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
);
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
));
648 return NS_ERROR_FAILURE
;
650 SetRowValue(row
, kToken_NameColumn
, aName
);
651 SetRowValue(row
, kToken_ValueColumn
, aValue
);
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()];
672 return NS_ERROR_OUT_OF_MEMORY
;
673 SwapBytes(swapval
, buffer
.get(), aValue
.Length());
674 yarn
.mYarn_Buf
= swapval
;
677 yarn
.mYarn_Buf
= (void*)buffer
.get();
679 mdb_err err
= aRow
->AddColumn(mEnv
, aCol
, &yarn
);
683 return err
? NS_ERROR_FAILURE
: NS_OK
;
687 nsFormHistory::GetRowValue(nsIMdbRow
*aRow
, mdb_column aCol
, nsAString
&aValue
)
690 mdb_err err
= aRow
->AliasCellYarn(mEnv
, aCol
, &yarn
);
692 return NS_ERROR_FAILURE
;
695 if (!yarn
.mYarn_Fill
)
698 switch (yarn
.mYarn_Form
) {
700 PRUint32 len
= yarn
.mYarn_Fill
/ sizeof(PRUnichar
);
701 if (mReverseByteOrder
) {
702 PRUnichar
*swapval
= new PRUnichar
[len
];
704 return NS_ERROR_OUT_OF_MEMORY
;
705 SwapBytes(swapval
, (const PRUnichar
*)yarn
.mYarn_Buf
, len
);
706 aValue
.Assign(swapval
, len
);
710 aValue
.Assign((const PRUnichar
*)yarn
.mYarn_Buf
, len
);
714 return NS_ERROR_UNEXPECTED
;
721 nsFormHistory::AutoCompleteSearch(const nsAString
&aInputName
,
722 const nsAString
&aInputValue
,
723 nsIAutoCompleteMdbResult2
*aPrevResult
,
724 nsIAutoCompleteResult
**aResult
)
726 if (!FormHistoryEnabled())
729 nsresult rv
= OpenDatabase(); // lazily ensure that the database is open
730 NS_ENSURE_SUCCESS(rv
, rv
);
732 nsCOMPtr
<nsIAutoCompleteMdbResult2
> result
;
735 result
= aPrevResult
;
738 result
->GetMatchCount(&rowCount
);
740 for (PRInt32 i
= rowCount
-1; i
>= 0; --i
) {
742 result
->GetRowAt(i
, &row
);
743 if (!RowMatch(row
, aInputName
, aInputValue
, nsnull
))
744 result
->RemoveValueAt(i
, PR_FALSE
);
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
;
766 rowCursor
->NextRow(mEnv
, getter_AddRefs(row
), &pos
);
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
);
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();
782 PRUint32
* items
= new PRUint32
[count
];
784 for (i
= 0; i
< count
; ++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
]);
802 result
->GetMatchCount(&matchCount
);
803 if (matchCount
> 0) {
804 result
->SetSearchResult(nsIAutoCompleteResult::RESULT_SUCCESS
);
805 result
->SetDefaultIndex(0);
807 result
->SetSearchResult(nsIAutoCompleteResult::RESULT_NOMATCH
);
808 result
->SetDefaultIndex(-1);
813 NS_IF_ADDREF(*aResult
);
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
);
832 nsFormHistory::RowMatch(nsIMdbRow
*aRow
, const nsAString
&aInputName
, const nsAString
&aInputValue
, PRUnichar
**aValue
)
835 GetRowValue(aRow
, kToken_NameColumn
, name
);
837 if (name
.Equals(aInputName
)) {
839 GetRowValue(aRow
, kToken_ValueColumn
, value
);
840 if (Compare(Substring(value
, 0, aInputValue
.Length()), aInputValue
, nsCaseInsensitiveStringComparator()) == 0) {
842 *aValue
= ToNewUnicode(value
);
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
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
;
869 rowCursor
->NextRow(mEnv
, getter_AddRefs(row
), &pos
);
873 // Check if the name and value combination match this row
875 GetRowValue(row
, kToken_NameColumn
, name
);
877 if (Compare(name
, *aName
, nsCaseInsensitiveStringComparator()) == 0) {
879 GetRowValue(row
, kToken_ValueColumn
, value
);
880 if (!aValue
|| Compare(value
, *aValue
, nsCaseInsensitiveStringComparator()) == 0) {
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
;
901 err
= mTable
->GetCount(mEnv
, &count
);
902 if (err
!= 0) return NS_ERROR_FAILURE
;
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");
917 NS_ASSERTION(row
!= nsnull
, "no row");
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");
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.
943 err
= mTable
->EndBatchChangeHint(mEnv
, &marker
);
944 NS_ASSERTION(err
== 0, "error ending batch");
946 return (err
== 0) ? NS_OK
: NS_ERROR_FAILURE
;
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);
959 nsAutoString
nativeByteOrder(bigEndianByteOrder
);
961 nsAutoString
nativeByteOrder(littleEndianByteOrder
);
964 nsAutoString fileByteOrder
;
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.
981 mReverseByteOrder
= PR_FALSE
;
982 rv
= SaveByteOrder(nativeByteOrder
);
985 mReverseByteOrder
= PR_TRUE
;
986 rv
= SaveByteOrder(bigEndianByteOrder
);
989 mReverseByteOrder
= PR_FALSE
;
990 rv
= SaveByteOrder(nativeByteOrder
);
994 mReverseByteOrder
= !fileByteOrder
.Equals(nativeByteOrder
);
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
);
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
);