Lots of changes and bugfixes:

- Enable multiple username support by listening to change events for the username field when there is more than one stored login for the form.
 - Make the signon hashtable store a SignonHashEntry, which has a pointer to the first SignonDataEntry.  This lets us adjust the head of the linked list without a hashtable operation, and also fixes a case where we would continue using a deleted entry, leading to a crash.
- Change FindPasswordEntryFromSignonData to FnidPasswordEntryInternal, and refactor it a bit.
- Clean up prefill logic so that multiple forms on a page could be prefilled.
- Fix bug where the last input was used as the username field, instead of the input just before the password.
- Make sure we write out all entries for each realm.
- Make sure we include all entries in each realm when returning an enumerator.
- Remove some form manager cruft I missed in passwordManager.js.
This commit is contained in:
bryner%brianryner.com 2003-08-03 04:50:09 +00:00
parent 9d0045d939
commit c7d4a59b14
3 changed files with 295 additions and 136 deletions

View File

@ -66,6 +66,8 @@
#include "nsIDOMWindowInternal.h"
#include "nsCURILoader.h"
#include "nsAppDirectoryServiceDefs.h"
#include "nsIDOMEventTarget.h"
#include "nsIDOMHTMLFormElement.h"
static const char kPMPropertiesURL[] = "chrome://passwordmgr/locale/passwordmgr.properties";
static PRBool sRememberPasswords = PR_FALSE;
@ -92,6 +94,19 @@ public:
~SignonDataEntry() { delete next; }
};
class nsPasswordManager::SignonHashEntry
{
// Wraps a pointer to the linked list of SignonDataEntry objects.
// This allows us to adjust the head of the linked list without a
// hashtable operation.
public:
SignonDataEntry* head;
SignonHashEntry(SignonDataEntry* aEntry) : head(aEntry) { }
~SignonHashEntry() { delete head; }
};
class nsPasswordManager::PasswordEntry : public nsIPassword
{
public:
@ -164,6 +179,8 @@ NS_INTERFACE_MAP_BEGIN(nsPasswordManager)
NS_INTERFACE_MAP_ENTRY(nsIObserver)
NS_INTERFACE_MAP_ENTRY(nsIFormSubmitObserver)
NS_INTERFACE_MAP_ENTRY(nsIWebProgressListener)
NS_INTERFACE_MAP_ENTRY(nsIDOMFormListener)
NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener)
NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIPasswordManager)
NS_INTERFACE_MAP_END
@ -316,30 +333,27 @@ NS_IMETHODIMP
nsPasswordManager::RemoveUser(const nsACString& aHost, const nsAString& aUser)
{
SignonDataEntry* entry, *prevEntry = nsnull;
SignonHashEntry* hashEnt;
if (!mSignonTable.Get(aHost, &entry) || !entry)
if (!mSignonTable.Get(aHost, &hashEnt))
return NS_ERROR_FAILURE;
for (; entry; prevEntry = entry, entry = entry->next) {
for (entry = hashEnt->head; entry; prevEntry = entry, entry = entry->next) {
nsAutoString ptUser;
DecryptData(entry->userValue, ptUser);
if (ptUser.Equals(aUser)) {
if (prevEntry) {
if (prevEntry)
prevEntry->next = entry->next;
entry->next = nsnull;
delete entry;
} else {
// the hashtable will delete the entry
SignonDataEntry* next;
if ((next = entry->next)) {
entry->next = nsnull;
mSignonTable.Put(aHost, next);
} else {
mSignonTable.Remove(aHost);
}
}
else
hashEnt->head = entry->next;
entry->next = nsnull;
delete entry;
if (!hashEnt->head)
mSignonTable.Remove(aHost); // deletes hashEnt
WriteSignonFile();
@ -368,13 +382,13 @@ nsPasswordManager::RemoveReject(const nsACString& aHost)
/* static */ PLDHashOperator PR_CALLBACK
nsPasswordManager::BuildArrayEnumerator(const nsACString& aKey,
SignonDataEntry* aEntry,
SignonHashEntry* aEntry,
void* aUserData)
{
nsIMutableArray* array = NS_STATIC_CAST(nsIMutableArray*, aUserData);
nsCOMPtr<nsIPassword> passwordEntry = new PasswordEntry(aKey, aEntry);
array->AppendElement(passwordEntry, PR_FALSE);
for (SignonDataEntry* e = aEntry->head; e; e = e->next)
array->AppendElement(new PasswordEntry(aKey, e), PR_FALSE);
return PL_DHASH_NEXT;
}
@ -443,22 +457,25 @@ struct findEntryContext {
/* static */ PLDHashOperator PR_CALLBACK
nsPasswordManager::FindEntryEnumerator(const nsACString& aKey,
SignonDataEntry* aEntry,
SignonHashEntry* aEntry,
void* aUserData)
{
findEntryContext* context = NS_STATIC_CAST(findEntryContext*, aUserData);
nsPasswordManager* manager = context->manager;
nsresult rv;
rv = manager->FindPasswordEntryFromSignonData(aEntry,
context->hostURI,
context->username,
context->password,
context->hostURIFound,
context->usernameFound,
context->passwordFound);
if (NS_SUCCEEDED(rv)) {
SignonDataEntry* entry = nsnull;
rv = manager->FindPasswordEntryInternal(aEntry->head,
context->username,
context->password,
nsString(),
&entry);
if (NS_SUCCEEDED(rv) && entry) {
context->matched = PR_TRUE;
context->hostURIFound.Assign(context->hostURI);
DecryptData(entry->userValue, context->usernameFound);
DecryptData(entry->passValue, context->passwordFound);
return PL_DHASH_STOP;
}
@ -474,11 +491,22 @@ nsPasswordManager::FindPasswordEntry(const nsACString& aHostURI,
nsAString& aPasswordFound)
{
if (!aHostURI.IsEmpty()) {
SignonDataEntry* entry;
if (mSignonTable.Get(aHostURI, &entry) && entry) {
return FindPasswordEntryFromSignonData(entry, aHostURI, aUsername,
aPassword, aHostURIFound,
aUsernameFound, aPasswordFound);
SignonHashEntry* hashEnt;
if (mSignonTable.Get(aHostURI, &hashEnt)) {
SignonDataEntry* entry;
nsresult rv = FindPasswordEntryInternal(hashEnt->head,
aUsername,
aPassword,
nsString(),
&entry);
if (NS_SUCCEEDED(rv) && entry) {
aHostURIFound.Assign(aHostURI);
DecryptData(entry->userValue, aUsernameFound);
DecryptData(entry->passValue, aPasswordFound);
}
return rv;
}
return NS_ERROR_FAILURE;
@ -549,48 +577,64 @@ nsPasswordManager::OnStateChange(nsIWebProgress* aWebProgress,
if (uri)
uri->GetPrePath(realm);
// Only prefill if there is exactly one username saved for this
// realm. Otherwise, we'll wait to prefill the password when the user
// autocompletes the username.
SignonDataEntry* entry;
if (!mSignonTable.Get(realm, &entry) || !entry ||
(entry && entry->next))
SignonHashEntry* hashEnt;
if (!mSignonTable.Get(realm, &hashEnt))
return NS_OK;
// Locate the username field by searching each form on the page
PRUint32 formCount;
forms->GetLength(&formCount);
// We can auto-prefill the username and password if there is only
// one stored login that matches the username and password field names
// on the form in question.
for (PRUint32 i = 0; i < formCount; ++i) {
nsCOMPtr<nsIDOMNode> formNode;
forms->Item(i, getter_AddRefs(formNode));
nsCOMPtr<nsIForm> form = do_QueryInterface(formNode);
nsCOMPtr<nsISupports> foundNode;
form->ResolveName(entry->userField, getter_AddRefs(foundNode));
SignonDataEntry* firstMatch = nsnull;
nsCOMPtr<nsIDOMHTMLInputElement> userField, passField;
nsCOMPtr<nsIDOMHTMLInputElement> userField = do_QueryInterface(foundNode);
for (SignonDataEntry* e = hashEnt->head; e; e = e->next) {
nsCOMPtr<nsISupports> foundNode;
form->ResolveName(e->userField, getter_AddRefs(foundNode));
userField = do_QueryInterface(foundNode);
if (!foundNode && !userField)
continue;
if (!userField)
continue;
// Ensure that the password field is also present, otherwise
// this is not a login form.
form->ResolveName(e->passField, getter_AddRefs(foundNode));
passField = do_QueryInterface(foundNode);
form->ResolveName(entry->passField, getter_AddRefs(foundNode));
nsCOMPtr<nsIDOMHTMLInputElement> passField = do_QueryInterface(foundNode);
if (!foundNode || !passField)
continue;
if (!passField)
continue;
nsAutoString buffer;
if (firstMatch) {
// We've found more than one possible signon for this form.
// Attach an onchange handler to the username field so that we can
// attempt to prefill the password after the user has
// entered the username.
DecryptData(entry->userValue, buffer);
userField->SetValue(buffer);
nsCOMPtr<nsIDOMEventTarget> targ = do_QueryInterface(userField);
targ->AddEventListener(NS_LITERAL_STRING("change"), this, PR_FALSE);
firstMatch = nsnull;
break; // on to the next form
} else {
firstMatch = e;
}
}
DecryptData(entry->passValue, buffer);
passField->SetValue(buffer);
if (firstMatch) {
nsAutoString buffer;
DecryptData(firstMatch->userValue, buffer);
userField->SetValue(buffer);
DecryptData(firstMatch->passValue, buffer);
passField->SetValue(buffer);
}
}
return NS_OK;
@ -662,7 +706,7 @@ nsPasswordManager::Notify(nsIContent* aFormNode,
nsCOMPtr<nsIDOMHTMLInputElement> userField;
nsCOMArray<nsIDOMHTMLInputElement> passFields;
PRUint32 i;
PRUint32 i, firstPasswordIndex = numControls;
for (i = 0; i < numControls; ++i) {
@ -672,6 +716,8 @@ nsPasswordManager::Notify(nsIContent* aFormNode,
if (control->GetType() == NS_FORM_INPUT_PASSWORD) {
nsCOMPtr<nsIDOMHTMLInputElement> elem = do_QueryInterface(control);
passFields.AppendObject(elem);
if (firstPasswordIndex == numControls)
firstPasswordIndex = i;
}
}
@ -682,7 +728,7 @@ nsPasswordManager::Notify(nsIContent* aFormNode,
case 1: // normal login
{
// Search backwards from the password field to find a username field.
for (PRUint32 j = i - 1; j >= 0; --j) {
for (PRUint32 j = firstPasswordIndex - 1; j >= 0; --j) {
nsCOMPtr<nsIFormControl> control;
formElement->GetElementAt(j, getter_AddRefs(control));
@ -726,13 +772,14 @@ nsPasswordManager::Notify(nsIContent* aFormNode,
passFields.ObjectAt(0)->GetValue(passValue);
passFields.ObjectAt(0)->GetName(passFieldName);
SignonDataEntry* entry;
SignonHashEntry* hashEnt;
if (mSignonTable.Get(realm, &entry)) {
if (mSignonTable.Get(realm, &hashEnt)) {
SignonDataEntry* entry;
nsAutoString buffer;
for (; entry; entry = entry->next) {
for (entry = hashEnt->head; entry; entry = entry->next) {
if (entry->userField.Equals(userFieldName) &&
entry->passField.Equals(passFieldName)) {
@ -772,7 +819,7 @@ nsPasswordManager::Notify(nsIContent* aFormNode,
&selection);
if (selection == 0) {
entry = new SignonDataEntry();
SignonDataEntry* entry = new SignonDataEntry();
entry->userField.Assign(userFieldName);
entry->passField.Assign(passFieldName);
EncryptDataUCS2(userValue, entry->userValue);
@ -804,9 +851,12 @@ nsPasswordManager::Notify(nsIContent* aFormNode,
passFields.ObjectAt(k)->GetValue(valueN);
if (!value0.Equals(valueN)) {
SignonDataEntry* entry = nsnull;
SignonHashEntry* hashEnt;
if (mSignonTable.Get(realm, &hashEnt)) {
SignonDataEntry* entry = hashEnt->head;
if (mSignonTable.Get(realm, &entry) && entry) {
if (entry->next) {
// Multiple stored logons, prompt for which username is
@ -892,6 +942,98 @@ nsPasswordManager::Notify(nsIContent* aFormNode,
return NS_OK;
}
// nsIDOMFormListener implementation
NS_IMETHODIMP
nsPasswordManager::Submit(nsIDOMEvent* aEvent)
{
// Note, form submission is not handled here (to do so would mean
// hooking up an event listener to each form as it's created).
// See ::Notify().
return NS_OK;
}
NS_IMETHODIMP
nsPasswordManager::Reset(nsIDOMEvent* aEvent)
{
return NS_OK;
}
NS_IMETHODIMP
nsPasswordManager::Change(nsIDOMEvent* aEvent)
{
// Try to prefill the password for the just-changed username.
nsCOMPtr<nsIDOMEventTarget> target;
aEvent->GetTarget(getter_AddRefs(target));
nsCOMPtr<nsIDOMHTMLInputElement> userField = do_QueryInterface(target);
if (!userField)
return NS_OK;
nsCOMPtr<nsIContent> fieldContent = do_QueryInterface(userField);
nsIDocument* doc = fieldContent->GetDocument();
nsCOMPtr<nsIURI> documentURL;
doc->GetDocumentURL(getter_AddRefs(documentURL));
nsCAutoString realm;
documentURL->GetPrePath(realm);
nsAutoString userValue;
userField->GetValue(userValue);
nsAutoString fieldName;
userField->GetName(fieldName);
SignonHashEntry* hashEnt;
if (!mSignonTable.Get(realm, &hashEnt))
return NS_OK;
SignonDataEntry* foundEntry;
FindPasswordEntryInternal(hashEnt->head, userValue, nsString(),
fieldName, &foundEntry);
if (!foundEntry)
return NS_OK;
nsCOMPtr<nsIDOMHTMLFormElement> formEl;
userField->GetForm(getter_AddRefs(formEl));
if (!formEl)
return NS_OK;
nsCOMPtr<nsIForm> form = do_QueryInterface(formEl);
nsCOMPtr<nsISupports> foundNode;
form->ResolveName(foundEntry->passField, getter_AddRefs(foundNode));
nsCOMPtr<nsIDOMHTMLInputElement> passField = do_QueryInterface(foundNode);
if (!passField)
return NS_OK;
nsAutoString passValue;
DecryptData(foundEntry->passValue, passValue);
passField->SetValue(passValue);
return NS_OK;
}
NS_IMETHODIMP
nsPasswordManager::Select(nsIDOMEvent* aEvent)
{
return NS_OK;
}
NS_IMETHODIMP
nsPasswordManager::Input(nsIDOMEvent* aEvent)
{
return NS_OK;
}
NS_IMETHODIMP
nsPasswordManager::HandleEvent(nsIDOMEvent* aEvent)
{
return NS_OK;
}
// internal methods
/*
@ -907,6 +1049,8 @@ Format of the single signon file:
<Encrypted Saved URL #1 username field value>
*<Saved URL #1 password field name>
<Encrypted Saved URL #1 password field value>
<Saved URL #1 username #2 field name>
<.....>
.
<Saved URL #2 realm>
.....
@ -1028,7 +1172,7 @@ nsPasswordManager::WriteRejectEntryEnumerator(const nsACString& aKey,
/* static */ PLDHashOperator PR_CALLBACK
nsPasswordManager::WriteSignonEntryEnumerator(const nsACString& aKey,
SignonDataEntry* aEntry,
SignonHashEntry* aEntry,
void* aUserData)
{
nsIOutputStream* stream = NS_STATIC_CAST(nsIOutputStream*, aUserData);
@ -1038,22 +1182,24 @@ nsPasswordManager::WriteSignonEntryEnumerator(const nsACString& aKey,
buffer.Append(NS_LINEBREAK);
stream->Write(buffer.get(), buffer.Length(), &bytesWritten);
NS_ConvertUCS2toUTF8 userField(aEntry->userField);
userField.Append(NS_LINEBREAK);
stream->Write(userField.get(), userField.Length(), &bytesWritten);
for (SignonDataEntry* e = aEntry->head; e; e = e->next) {
NS_ConvertUCS2toUTF8 userField(e->userField);
userField.Append(NS_LINEBREAK);
stream->Write(userField.get(), userField.Length(), &bytesWritten);
buffer.Assign(NS_ConvertUCS2toUTF8(aEntry->userValue));
buffer.Append(NS_LINEBREAK);
stream->Write(buffer.get(), buffer.Length(), &bytesWritten);
buffer.Assign(NS_ConvertUCS2toUTF8(e->userValue));
buffer.Append(NS_LINEBREAK);
stream->Write(buffer.get(), buffer.Length(), &bytesWritten);
buffer.Assign("*");
buffer.Append(NS_ConvertUCS2toUTF8(aEntry->passField));
buffer.Append(NS_LINEBREAK);
stream->Write(buffer.get(), buffer.Length(), &bytesWritten);
buffer.Assign("*");
buffer.Append(NS_ConvertUCS2toUTF8(e->passField));
buffer.Append(NS_LINEBREAK);
stream->Write(buffer.get(), buffer.Length(), &bytesWritten);
buffer.Assign(NS_ConvertUCS2toUTF8(aEntry->passValue));
buffer.Append(NS_LINEBREAK);
stream->Write(buffer.get(), buffer.Length(), &bytesWritten);
buffer.Assign(NS_ConvertUCS2toUTF8(e->passValue));
buffer.Append(NS_LINEBREAK);
stream->Write(buffer.get(), buffer.Length(), &bytesWritten);
}
buffer.Assign("." NS_LINEBREAK);
stream->Write(buffer.get(), buffer.Length(), &bytesWritten);
@ -1090,13 +1236,14 @@ nsPasswordManager::AddSignonData(const nsACString& aRealm,
SignonDataEntry* aEntry)
{
// See if there is already an entry for this URL
SignonDataEntry* oldEntry;
if (mSignonTable.Get(aRealm, &oldEntry) && oldEntry) {
SignonHashEntry* hashEnt;
if (mSignonTable.Get(aRealm, &hashEnt)) {
// Add this one at the front of the linked list
aEntry->next = oldEntry;
aEntry->next = hashEnt->head;
hashEnt->head = aEntry;
} else {
mSignonTable.Put(aRealm, new SignonHashEntry(aEntry));
}
mSignonTable.Put(aRealm, aEntry);
}
/* static */ nsresult
@ -1176,16 +1323,14 @@ nsPasswordManager::EnsureDecoderRing()
}
nsresult
nsPasswordManager::FindPasswordEntryFromSignonData(SignonDataEntry* aEntry,
const nsACString& aHost,
const nsAString& aUser,
const nsAString& aPassword,
nsACString& aHostFound,
nsAString& aUserFound,
nsAString& aPasswordFound)
nsPasswordManager::FindPasswordEntryInternal(const SignonDataEntry* aEntry,
const nsAString& aUser,
const nsAString& aPassword,
const nsAString& aUserField,
SignonDataEntry** aResult)
{
// host has already been checked, so just look for user/password match.
SignonDataEntry* entry = aEntry;
const SignonDataEntry* entry = aEntry;
nsAutoString buffer;
for (; entry; entry = entry->next) {
@ -1199,26 +1344,34 @@ nsPasswordManager::FindPasswordEntryFromSignonData(SignonDataEntry* aEntry,
matched = aUser.Equals(buffer);
}
if (matched) {
if (aPassword.IsEmpty()) {
matched = PR_TRUE;
} else {
DecryptData(entry->passValue, buffer);
matched = aPassword.Equals(buffer);
}
if (!matched)
continue;
if (matched)
break;
if (aPassword.IsEmpty()) {
matched = PR_TRUE;
} else {
DecryptData(entry->passValue, buffer);
matched = aPassword.Equals(buffer);
}
if (!matched)
continue;
if (aUserField.IsEmpty())
matched = PR_TRUE;
else
matched = entry->userField.Equals(aUserField);
if (matched)
break;
}
if (entry) {
aHostFound.Assign(aHost);
DecryptData(entry->userValue, aUserFound);
DecryptData(entry->passValue, aPasswordFound);
*aResult = NS_CONST_CAST(SignonDataEntry*, entry);
return NS_OK;
}
*aResult = nsnull;
return NS_ERROR_FAILURE;
}

View File

@ -46,6 +46,7 @@
#include "nsIPrompt.h"
#include "nsIFormSubmitObserver.h"
#include "nsIWebProgressListener.h"
#include "nsIDOMFormListener.h"
/* 360565c4-2ef3-4f6a-bab9-94cca891b2a7 */
#define NS_PASSWORDMANAGER_CID \
@ -70,24 +71,14 @@ class nsPasswordManager : public nsIPasswordManager,
public nsIObserver,
public nsIFormSubmitObserver,
public nsIWebProgressListener,
public nsIDOMFormListener,
public nsSupportsWeakReference
{
public:
class SignonDataEntry;
class SignonHashEntry;
class PasswordEntry;
NS_DECL_ISUPPORTS
NS_DECL_NSIPASSWORDMANAGER
NS_DECL_NSIPASSWORDMANAGERINTERNAL
NS_DECL_NSIOBSERVER
NS_DECL_NSIWEBPROGRESSLISTENER
// nsIFormSubmitObserver
NS_IMETHOD Notify(nsIContent* aFormNode,
nsIDOMWindowInternal* aWindow,
nsIURI* aActionURL,
PRBool* aCancelSubmit);
nsPasswordManager();
virtual ~nsPasswordManager();
@ -115,22 +106,43 @@ public:
nsAString& aEncrypted);
NS_DECL_ISUPPORTS
NS_DECL_NSIPASSWORDMANAGER
NS_DECL_NSIPASSWORDMANAGERINTERNAL
NS_DECL_NSIOBSERVER
NS_DECL_NSIWEBPROGRESSLISTENER
// nsIFormSubmitObserver
NS_IMETHOD Notify(nsIContent* aFormNode,
nsIDOMWindowInternal* aWindow,
nsIURI* aActionURL,
PRBool* aCancelSubmit);
// nsIDOMFormListener
NS_IMETHOD Submit(nsIDOMEvent* aEvent);
NS_IMETHOD Reset(nsIDOMEvent* aEvent);
NS_IMETHOD Change(nsIDOMEvent* aEvent);
NS_IMETHOD Select(nsIDOMEvent* aEvent);
NS_IMETHOD Input(nsIDOMEvent* aEvent);
// nsIDOMEventListener
NS_IMETHOD HandleEvent(nsIDOMEvent* aEvent);
protected:
void ReadSignonFile();
void WriteSignonFile();
void AddSignonData(const nsACString& aRealm, SignonDataEntry* aEntry);
nsresult FindPasswordEntryFromSignonData(SignonDataEntry* aEntry,
const nsACString& aHost,
const nsAString& aUser,
const nsAString& aPassword,
nsACString& aHostFound,
nsAString& aUserFound,
nsAString& aPasswordFound);
nsresult FindPasswordEntryInternal(const SignonDataEntry* aEntry,
const nsAString& aUser,
const nsAString& aPassword,
const nsAString& aUserField,
SignonDataEntry** aResult);
static PLDHashOperator PR_CALLBACK FindEntryEnumerator(const nsACString& aKey,
SignonDataEntry* aEntry,
SignonHashEntry* aEntry,
void* aUserData);
static PLDHashOperator PR_CALLBACK WriteRejectEntryEnumerator(const nsACString& aKey,
@ -138,11 +150,11 @@ protected:
void* aUserData);
static PLDHashOperator PR_CALLBACK WriteSignonEntryEnumerator(const nsACString& aKey,
SignonDataEntry* aEntry,
SignonHashEntry* aEntry,
void* aUserData);
static PLDHashOperator PR_CALLBACK BuildArrayEnumerator(const nsACString& aKey,
SignonDataEntry* aEntry,
SignonHashEntry* aEntry,
void* aUserData);
static PLDHashOperator PR_CALLBACK BuildRejectArrayEnumerator(const nsACString& aKey,
@ -151,7 +163,7 @@ protected:
static void EnsureDecoderRing();
nsClassHashtable<nsCStringHashKey,SignonDataEntry> mSignonTable;
nsClassHashtable<nsCStringHashKey,SignonHashEntry> mSignonTable;
nsDataHashtable<nsCStringHashKey,PRInt32> mRejectTable;
nsCOMPtr<nsIFile> mSignonFile;

View File

@ -69,10 +69,8 @@ function Startup() {
}
function Shutdown() {
if (isPasswordManager) {
kObserverService.removeObserver(signonReloadDisplay, "signonChanged");
kObserverService.removeObserver(signonReloadDisplay, "signonSelectUser");
}
kObserverService.removeObserver(signonReloadDisplay, "signonChanged");
kObserverService.removeObserver(signonReloadDisplay, "signonSelectUser");
}
var signonReloadDisplay = {
@ -355,11 +353,7 @@ function TrimString(string)
}
function doHelpButton() {
if (isPasswordManager) {
openHelp("password_mgr");
} else {
openHelp("forms_sites");
}
openHelp("password_mgr");
}