Bug 58724 - should autofill password while page is loading, not onload. Patch by Michael Wu <michael.wu@mozilla.com>, r=enndeakin

This commit is contained in:
jwalden%mit.edu 2006-06-28 02:25:23 +00:00
parent 6c5611731e
commit c7f36c5764
2 changed files with 234 additions and 197 deletions

View File

@ -76,6 +76,7 @@
static const char kPMPropertiesURL[] = "chrome://passwordmgr/locale/passwordmgr.properties";
static PRBool sRememberPasswords = PR_FALSE;
static PRBool sPrefsInitialized = PR_FALSE;
static PRBool sPasswordsLoaded = PR_FALSE;
static nsIStringBundle* sPMBundle;
static nsISecretDecoderRing* sDecoderRing;
@ -237,6 +238,11 @@ nsPasswordManager::GetInstance()
}
}
// We fail to load passwords during early initialization
// This wrapper function allows us to handle that error and defer
// password loading until later
sPasswordManager->LoadPasswords();
NS_ADDREF(sPasswordManager); // addref the return result
return sPasswordManager;
}
@ -278,20 +284,6 @@ nsPasswordManager::Init()
NS_ASSERTION(progress, "No web progress service");
progress->AddProgressListener(this, nsIWebProgress::NOTIFY_STATE_DOCUMENT);
// Now read in the signon file
nsXPIDLCString signonFile;
mPrefBranch->GetCharPref("SignonFileName", getter_Copies(signonFile));
NS_ASSERTION(!signonFile.IsEmpty(), "Fallback for signon filename not present");
NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(mSignonFile));
mSignonFile->AppendNative(signonFile);
nsCAutoString path;
mSignonFile->GetNativePath(path);
ReadPasswords(mSignonFile);
return NS_OK;
}
@ -329,6 +321,13 @@ nsPasswordManager::Register(nsIComponentManager* aCompMgr,
PR_TRUE,
getter_Copies(prevEntry));
catman->AddCategoryEntry("app-startup",
"Password Manager",
NS_PASSWORDMANAGER_CONTRACTID,
PR_TRUE,
PR_TRUE,
getter_Copies(prevEntry));
return NS_OK;
}
@ -765,7 +764,8 @@ nsPasswordManager::Observe(nsISupports* aSubject,
NS_ASSERTION(branch == mPrefBranch, "unexpected pref change notification");
branch->GetBoolPref("rememberSignons", &sRememberPasswords);
}
} else if (!strcmp(aTopic, "app-startup"))
GetInstance();
return NS_OK;
}
@ -777,10 +777,8 @@ nsPasswordManager::OnStateChange(nsIWebProgress* aWebProgress,
PRUint32 aStateFlags,
nsresult aStatus)
{
// We're only interested in successful document loads.
if (NS_FAILED(aStatus) ||
!(aStateFlags & nsIWebProgressListener::STATE_IS_DOCUMENT) ||
!(aStateFlags & nsIWebProgressListener::STATE_STOP))
if (!(aStateFlags & nsIWebProgressListener::STATE_IS_DOCUMENT) ||
!(aStateFlags & nsIWebProgressListener::STATE_TRANSFERRING))
return NS_OK;
// Don't do anything if the global signon pref is disabled
@ -791,185 +789,12 @@ nsPasswordManager::OnStateChange(nsIWebProgress* aWebProgress,
nsresult rv = aWebProgress->GetDOMWindow(getter_AddRefs(domWin));
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIDOMDocument> domDoc;
domWin->GetDocument(getter_AddRefs(domDoc));
NS_ASSERTION(domDoc, "DOM window should always have a document!");
nsCOMPtr<nsIDOMEventTarget> targ = do_QueryInterface(domWin);
// For now, only prefill forms in HTML documents.
nsCOMPtr<nsIDOMHTMLDocument> htmlDoc = do_QueryInterface(domDoc);
if (!htmlDoc)
return NS_OK;
nsIDOMEventListener* listener = NS_STATIC_CAST(nsIDOMFocusListener*, this);
if (aStateFlags & nsIWebProgressListener::STATE_TRANSFERRING && NS_SUCCEEDED(aStatus))
targ->AddEventListener(NS_LITERAL_STRING("DOMContentLoaded"), listener, PR_TRUE);
nsCOMPtr<nsIDOMHTMLCollection> forms;
htmlDoc->GetForms(getter_AddRefs(forms));
nsCOMPtr<nsIDocument> doc = do_QueryInterface(domDoc);
nsCAutoString realm;
if (!GetPasswordRealm(doc->GetDocumentURI(), realm))
return NS_OK;
SignonHashEntry* hashEnt;
if (!mSignonTable.Get(realm, &hashEnt))
return NS_OK;
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. Note that we only need to worry about a
// single login per form.
for (PRUint32 i = 0; i < formCount; ++i) {
nsCOMPtr<nsIDOMNode> formNode;
forms->Item(i, getter_AddRefs(formNode));
nsCOMPtr<nsIForm> form = do_QueryInterface(formNode);
SignonDataEntry* firstMatch = nsnull;
PRBool attachedToInput = PR_FALSE;
PRBool prefilledUser = PR_FALSE;
nsCOMPtr<nsIDOMHTMLInputElement> userField, passField;
nsCOMPtr<nsIDOMHTMLInputElement> temp;
nsAutoString fieldType;
for (SignonDataEntry* e = hashEnt->head; e; e = e->next) {
nsCOMPtr<nsISupports> foundNode;
if (!(e->userField).IsEmpty()) {
form->ResolveName(e->userField, getter_AddRefs(foundNode));
temp = do_QueryInterface(foundNode);
}
nsAutoString oldUserValue;
if (temp) {
temp->GetType(fieldType);
if (!fieldType.Equals(NS_LITERAL_STRING("text")))
continue;
temp->GetValue(oldUserValue);
userField = temp;
}
if (!(e->passField).IsEmpty()) {
form->ResolveName(e->passField, getter_AddRefs(foundNode));
temp = do_QueryInterface(foundNode);
}
else if (userField) {
// No password field name was supplied, try to locate one in the form,
// but only if we have a username field.
nsCOMPtr<nsIFormControl> fc(do_QueryInterface(foundNode));
PRInt32 index = -1;
form->IndexOfControl(fc, &index);
if (index >= 0) {
PRUint32 count;
form->GetElementCount(&count);
PRUint32 i;
temp = nsnull;
// Search forwards
nsCOMPtr<nsIFormControl> passField;
for (i = index + 1; i < count; ++i) {
form->GetElementAt(i, getter_AddRefs(passField));
if (passField && passField->GetType() == NS_FORM_INPUT_PASSWORD) {
foundNode = passField;
temp = do_QueryInterface(foundNode);
}
}
if (!temp && index != 0) {
// Search backwards
i = index;
do {
form->GetElementAt(i, getter_AddRefs(passField));
if (passField && passField->GetType() == NS_FORM_INPUT_PASSWORD) {
foundNode = passField;
temp = do_QueryInterface(foundNode);
}
} while (i-- != 0);
}
}
}
nsAutoString oldPassValue;
if (temp) {
temp->GetType(fieldType);
if (!fieldType.Equals(NS_LITERAL_STRING("password")))
continue;
temp->GetValue(oldPassValue);
passField = temp;
} else {
continue;
}
if (!oldUserValue.IsEmpty()) {
// The page has prefilled a username.
// If it matches any of our saved usernames, prefill the password
// for that username. If there are multiple saved usernames,
// we will also attach the autocomplete listener.
prefilledUser = PR_TRUE;
nsAutoString userValue;
if (NS_FAILED(DecryptData(e->userValue, userValue)))
goto done;
if (userValue.Equals(oldUserValue)) {
nsAutoString passValue;
if (NS_FAILED(DecryptData(e->passValue, passValue)))
goto done;
passField->SetValue(passValue);
}
}
if (firstMatch && userField && !attachedToInput) {
// We've found more than one possible signon for this form.
// Listen for blur and autocomplete events on the username field so
// that we can attempt to prefill the password after the user has
// entered the username.
AttachToInput(userField);
attachedToInput = PR_TRUE;
} else {
firstMatch = e;
}
}
// If we found more than one match, attachedToInput will be true,
// but if we found just one, we need to attach the autocomplete listener,
// and fill in the username and password only if the HTML didn't prefill
// the username.
if (firstMatch && !attachedToInput) {
if (userField)
AttachToInput(userField);
if (!prefilledUser){
nsAutoString buffer;
if (userField) {
if (NS_FAILED(DecryptData(firstMatch->userValue, buffer)))
goto done;
userField->SetValue(buffer);
}
if (NS_FAILED(DecryptData(firstMatch->passValue, buffer)))
goto done;
passField->SetValue(buffer);
}
}
}
done:
nsCOMPtr<nsIDOMEventTarget> targ = do_QueryInterface(domDoc);
targ->AddEventListener(NS_LITERAL_STRING("unload"),
NS_STATIC_CAST(nsIDOMLoadListener*, this), PR_FALSE);
@ -1366,7 +1191,190 @@ nsPasswordManager::Blur(nsIDOMEvent* aEvent)
NS_IMETHODIMP
nsPasswordManager::HandleEvent(nsIDOMEvent* aEvent)
{
return FillPassword(aEvent);
nsAutoString type;
aEvent->GetType(type);
if (!type.EqualsLiteral("DOMContentLoaded"))
return FillPassword(aEvent);
nsCOMPtr<nsIDOMEventTarget> target;
aEvent->GetTarget(getter_AddRefs(target));
nsCOMPtr<nsIDOMDocument> domDoc = do_QueryInterface(target);
// For now, only prefill forms in HTML documents.
nsCOMPtr<nsIDOMHTMLDocument> htmlDoc = do_QueryInterface(domDoc);
if (!htmlDoc)
return NS_OK;
nsCOMPtr<nsIDOMHTMLCollection> forms;
htmlDoc->GetForms(getter_AddRefs(forms));
nsCOMPtr<nsIDocument> doc = do_QueryInterface(domDoc);
nsCAutoString realm;
if (!GetPasswordRealm(doc->GetDocumentURI(), realm))
return NS_OK;
SignonHashEntry* hashEnt;
if (!mSignonTable.Get(realm, &hashEnt))
return NS_OK;
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. Note that we only need to worry about a
// single login per form.
for (PRUint32 i = 0; i < formCount; ++i) {
nsCOMPtr<nsIDOMNode> formNode;
forms->Item(i, getter_AddRefs(formNode));
nsCOMPtr<nsIForm> form = do_QueryInterface(formNode);
SignonDataEntry* firstMatch = nsnull;
PRBool attachedToInput = PR_FALSE;
PRBool prefilledUser = PR_FALSE;
nsCOMPtr<nsIDOMHTMLInputElement> userField, passField;
nsCOMPtr<nsIDOMHTMLInputElement> temp;
nsAutoString fieldType;
for (SignonDataEntry* e = hashEnt->head; e; e = e->next) {
nsCOMPtr<nsISupports> foundNode;
if (!(e->userField).IsEmpty()) {
form->ResolveName(e->userField, getter_AddRefs(foundNode));
temp = do_QueryInterface(foundNode);
}
nsAutoString oldUserValue;
if (temp) {
temp->GetType(fieldType);
if (!fieldType.Equals(NS_LITERAL_STRING("text")))
continue;
temp->GetValue(oldUserValue);
userField = temp;
}
if (!(e->passField).IsEmpty()) {
form->ResolveName(e->passField, getter_AddRefs(foundNode));
temp = do_QueryInterface(foundNode);
}
else if (userField) {
// No password field name was supplied, try to locate one in the form,
// but only if we have a username field.
nsCOMPtr<nsIFormControl> fc(do_QueryInterface(foundNode));
PRInt32 index = -1;
form->IndexOfControl(fc, &index);
if (index >= 0) {
PRUint32 count;
form->GetElementCount(&count);
PRUint32 i;
temp = nsnull;
// Search forwards
nsCOMPtr<nsIFormControl> passField;
for (i = index + 1; i < count; ++i) {
form->GetElementAt(i, getter_AddRefs(passField));
if (passField && passField->GetType() == NS_FORM_INPUT_PASSWORD) {
foundNode = passField;
temp = do_QueryInterface(foundNode);
}
}
if (!temp && index != 0) {
// Search backwards
i = index;
do {
form->GetElementAt(i, getter_AddRefs(passField));
if (passField && passField->GetType() == NS_FORM_INPUT_PASSWORD) {
foundNode = passField;
temp = do_QueryInterface(foundNode);
}
} while (i-- != 0);
}
}
}
nsAutoString oldPassValue;
if (temp) {
temp->GetType(fieldType);
if (!fieldType.Equals(NS_LITERAL_STRING("password")))
continue;
temp->GetValue(oldPassValue);
passField = temp;
} else {
continue;
}
if (!oldUserValue.IsEmpty()) {
// The page has prefilled a username.
// If it matches any of our saved usernames, prefill the password
// for that username. If there are multiple saved usernames,
// we will also attach the autocomplete listener.
prefilledUser = PR_TRUE;
nsAutoString userValue;
if (NS_FAILED(DecryptData(e->userValue, userValue)))
return NS_OK;
if (userValue.Equals(oldUserValue)) {
nsAutoString passValue;
if (NS_FAILED(DecryptData(e->passValue, passValue)))
return NS_OK;
passField->SetValue(passValue);
}
}
if (firstMatch && userField && !attachedToInput) {
// We've found more than one possible signon for this form.
// Listen for blur and autocomplete events on the username field so
// that we can attempt to prefill the password after the user has
// entered the username.
AttachToInput(userField);
attachedToInput = PR_TRUE;
} else {
firstMatch = e;
}
}
// If we found more than one match, attachedToInput will be true,
// but if we found just one, we need to attach the autocomplete listener,
// and fill in the username and password only if the HTML didn't prefill
// the username.
if (firstMatch && !attachedToInput) {
if (userField)
AttachToInput(userField);
if (!prefilledUser){
nsAutoString buffer;
if (userField) {
if (NS_FAILED(DecryptData(firstMatch->userValue, buffer)))
return NS_OK;
userField->SetValue(buffer);
}
if (NS_FAILED(DecryptData(firstMatch->passValue, buffer)))
return NS_OK;
passField->SetValue(buffer);
}
}
}
return NS_OK;
}
// Autocomplete implementation
@ -1610,6 +1618,10 @@ nsPasswordManager::Unload(nsIDOMEvent* aEvent)
if (domDoc)
mAutoCompleteInputs.Enumerate(RemoveForDOMDocumentEnumerator, domDoc);
nsIDOMEventListener* listener = NS_STATIC_CAST(nsIDOMFocusListener*, this);
if (target)
target->RemoveEventListener(NS_LITERAL_STRING("DOMContentLoaded"), listener, PR_TRUE);
return NS_OK;
}
@ -1704,6 +1716,30 @@ nsPasswordManager::WriteSignonEntryEnumerator(const nsACString& aKey,
return PL_DHASH_NEXT;
}
// Wrapper function for ReadPasswords
void
nsPasswordManager::LoadPasswords()
{
if (sPasswordsLoaded)
return;
nsXPIDLCString signonFile;
mPrefBranch->GetCharPref("SignonFileName", getter_Copies(signonFile));
NS_ASSERTION(!signonFile.IsEmpty(), "Fallback for signon filename not present");
NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(mSignonFile));
if (!mSignonFile)
return;
mSignonFile->AppendNative(signonFile);
nsCAutoString path;
mSignonFile->GetNativePath(path);
if (NS_SUCCEEDED(ReadPasswords(mSignonFile)));
sPasswordsLoaded = PR_TRUE;
}
void
nsPasswordManager::WritePasswords(nsIFile* aPasswordFile)
{

View File

@ -144,6 +144,7 @@ public:
nsIAutoCompleteResult** aResult);
protected:
void LoadPasswords();
void WritePasswords(nsIFile* aPasswordFile);
void AddSignonData(const nsACString& aRealm, SignonDataEntry* aEntry);