diff --git a/toolkit/components/passwordmgr/base/nsPasswordManager.cpp b/toolkit/components/passwordmgr/base/nsPasswordManager.cpp index afce981a0e98..b05c883d78c0 100644 --- a/toolkit/components/passwordmgr/base/nsPasswordManager.cpp +++ b/toolkit/components/passwordmgr/base/nsPasswordManager.cpp @@ -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 domDoc; - domWin->GetDocument(getter_AddRefs(domDoc)); - NS_ASSERTION(domDoc, "DOM window should always have a document!"); + nsCOMPtr targ = do_QueryInterface(domWin); - // For now, only prefill forms in HTML documents. - nsCOMPtr 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 forms; - htmlDoc->GetForms(getter_AddRefs(forms)); - - nsCOMPtr 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 formNode; - forms->Item(i, getter_AddRefs(formNode)); - - nsCOMPtr form = do_QueryInterface(formNode); - SignonDataEntry* firstMatch = nsnull; - PRBool attachedToInput = PR_FALSE; - PRBool prefilledUser = PR_FALSE; - nsCOMPtr userField, passField; - nsCOMPtr temp; - nsAutoString fieldType; - - for (SignonDataEntry* e = hashEnt->head; e; e = e->next) { - - nsCOMPtr 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 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 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 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 target; + aEvent->GetTarget(getter_AddRefs(target)); + + nsCOMPtr domDoc = do_QueryInterface(target); + // For now, only prefill forms in HTML documents. + nsCOMPtr htmlDoc = do_QueryInterface(domDoc); + if (!htmlDoc) + return NS_OK; + + nsCOMPtr forms; + htmlDoc->GetForms(getter_AddRefs(forms)); + + nsCOMPtr 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 formNode; + forms->Item(i, getter_AddRefs(formNode)); + + nsCOMPtr form = do_QueryInterface(formNode); + SignonDataEntry* firstMatch = nsnull; + PRBool attachedToInput = PR_FALSE; + PRBool prefilledUser = PR_FALSE; + nsCOMPtr userField, passField; + nsCOMPtr temp; + nsAutoString fieldType; + + for (SignonDataEntry* e = hashEnt->head; e; e = e->next) { + + nsCOMPtr 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 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 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) { diff --git a/toolkit/components/passwordmgr/base/nsPasswordManager.h b/toolkit/components/passwordmgr/base/nsPasswordManager.h index a99bd2461c05..aef216359f2a 100644 --- a/toolkit/components/passwordmgr/base/nsPasswordManager.h +++ b/toolkit/components/passwordmgr/base/nsPasswordManager.h @@ -144,6 +144,7 @@ public: nsIAutoCompleteResult** aResult); protected: + void LoadPasswords(); void WritePasswords(nsIFile* aPasswordFile); void AddSignonData(const nsACString& aRealm, SignonDataEntry* aEntry);