/* * Copyright 2010, The Android Open Source Project * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "config.h" #include "WebAutofill.h" #if ENABLE(WEB_AUTOFILL) #include "AutoFillHostAndroid.h" #include "Frame.h" #include "FormData.h" #include "FormManagerAndroid.h" #include "FrameLoader.h" #include "HTMLFormControlElement.h" #include "MainThreadProxy.h" #include "Node.h" #include "Page.h" #include "Settings.h" #include "WebFrame.h" #include "WebRequestContext.h" #include "WebUrlLoaderClient.h" #include "WebViewCore.h" #define NO_PROFILE_SET 0 #define FORM_NOT_AUTOFILLABLE -1 namespace android { WebAutofill::WebAutofill() : mQueryId(1) , mWebViewCore(0) , mLastSearchDomVersion(0) , mParsingForms(false) { mTabContents = new TabContents(); setEmptyProfile(); } void WebAutofill::init() { if (mAutofillManager) return; mFormManager = new FormManager(); // We use the WebView's WebRequestContext, which may be a private browsing context. ASSERT(mWebViewCore); mAutofillManager = new AutofillManager(mTabContents.get()); mAutofillHost = new AutoFillHostAndroid(this); mTabContents->SetProfileRequestContext(new AndroidURLRequestContextGetter(mWebViewCore->webRequestContext(), WebUrlLoaderClient::ioThread())); mTabContents->SetAutoFillHost(mAutofillHost.get()); } WebAutofill::~WebAutofill() { cleanUpQueryMap(); mUniqueIdMap.clear(); } void WebAutofill::cleanUpQueryMap() { for (AutofillQueryFormDataMap::iterator it = mQueryMap.begin(); it != mQueryMap.end(); it++) delete it->second; mQueryMap.clear(); } void WebAutofill::searchDocument(WebCore::Frame* frame) { if (!enabled()) return; MutexLocker lock(mFormsSeenMutex); init(); cleanUpQueryMap(); mUniqueIdMap.clear(); mForms.clear(); mQueryId = 1; ASSERT(mFormManager); ASSERT(mAutofillManager); mAutofillManager->Reset(); mFormManager->Reset(); mFormManager->ExtractForms(frame); mFormManager->GetFormsInFrame(frame, FormManager::REQUIRE_AUTOCOMPLETE, &mForms); // Needs to be done on a Chrome thread as it will make a URL request to the Autofill server. // TODO: Use our own Autofill thread instead of the IO thread. // TODO: For now, block here. Would like to make this properly async. base::Thread* thread = WebUrlLoaderClient::ioThread(); mParsingForms = true; thread->message_loop()->PostTask(FROM_HERE, NewRunnableMethod(this, &WebAutofill::formsSeenImpl)); while (mParsingForms) mFormsSeenCondition.wait(mFormsSeenMutex); } // Called on the Chromium IO thread. void WebAutofill::formsSeenImpl() { MutexLocker lock(mFormsSeenMutex); mAutofillManager->OnFormsSeenWrapper(mForms); mParsingForms = false; mFormsSeenCondition.signal(); } void WebAutofill::formFieldFocused(WebCore::HTMLFormControlElement* formFieldElement) { if (!enabled()) { // In case that we've just been disabled and the last time we got autofill // suggestions we told Java about them, clear that bit Java side now // we're disabled. mWebViewCore->setWebTextViewAutoFillable(FORM_NOT_AUTOFILLABLE, string16()); return; } ASSERT(formFieldElement); Document* doc = formFieldElement->document(); Frame* frame = doc->frame(); // FIXME: Autofill only works in main frame for now. Should consider // child frames. if (frame != frame->page()->mainFrame()) return; unsigned domVersion = doc->domTreeVersion(); ASSERT(domVersion > 0); if (mLastSearchDomVersion != domVersion) { // Need to extract forms as DOM version has changed since the last time // we searched. searchDocument(formFieldElement->document()->frame()); mLastSearchDomVersion = domVersion; } ASSERT(mFormManager); // Get the FormField from the Node. webkit_glue::FormField* formField = new webkit_glue::FormField; FormManager::HTMLFormControlElementToFormField(formFieldElement, FormManager::EXTRACT_NONE, formField); formField->label = FormManager::LabelForElement(*formFieldElement); webkit_glue::FormData* form = new webkit_glue::FormData; mFormManager->FindFormWithFormControlElement(formFieldElement, FormManager::REQUIRE_AUTOCOMPLETE, form); mQueryMap[mQueryId] = new FormDataAndField(form, formField); bool suggestions = mAutofillManager->OnQueryFormFieldAutoFillWrapper(*form, *formField); mQueryId++; if (!suggestions) { ASSERT(mWebViewCore); // Tell Java no autofill suggestions for this form. mWebViewCore->setWebTextViewAutoFillable(FORM_NOT_AUTOFILLABLE, string16()); return; } } void WebAutofill::querySuccessful(const string16& value, const string16& label, int uniqueId) { if (!enabled()) return; // Store the unique ID for the query and inform java that autofill suggestions for this form are available. // Pass java the queryId so that it can pass it back if the user decides to use autofill. mUniqueIdMap[mQueryId] = uniqueId; ASSERT(mWebViewCore); mWebViewCore->setWebTextViewAutoFillable(mQueryId, mAutofillProfile->Label()); } void WebAutofill::fillFormFields(int queryId) { if (!enabled()) return; webkit_glue::FormData* form = mQueryMap[queryId]->form(); webkit_glue::FormField* field = mQueryMap[queryId]->field(); ASSERT(form); ASSERT(field); AutofillQueryToUniqueIdMap::iterator iter = mUniqueIdMap.find(queryId); if (iter == mUniqueIdMap.end()) { // The user has most likely tried to Autofill the form again without // refocussing the form field. The UI should protect against this // but stop here to be certain. return; } mAutofillManager->OnFillAutoFillFormDataWrapper(queryId, *form, *field, iter->second); mUniqueIdMap.erase(iter); } void WebAutofill::fillFormInPage(int queryId, const webkit_glue::FormData& form) { if (!enabled()) return; // FIXME: Pass a pointer to the Node that triggered the Autofill flow here instead of 0. // The consquence of passing 0 is that we should always fail the test in FormManader::ForEachMathcingFormField():169 // that says "only overwrite an elements current value if the user triggered autofill through that element" // for elements that have a value already. But by a quirk of Android text views we are OK. We should still // fix this though. mFormManager->FillForm(form, 0); } bool WebAutofill::enabled() const { Page* page = mWebViewCore->mainFrame()->page(); return page ? page->settings()->autoFillEnabled() : false; } void WebAutofill::setProfile(const string16& fullName, const string16& emailAddress, const string16& companyName, const string16& addressLine1, const string16& addressLine2, const string16& city, const string16& state, const string16& zipCode, const string16& country, const string16& phoneNumber) { if (!mAutofillProfile) mAutofillProfile.set(new AutofillProfile()); // Update the profile. // Constants for Autofill field types are found in external/chromium/chrome/browser/autofill/field_types.h. mAutofillProfile->SetInfo(AutofillFieldType(NAME_FULL), fullName); mAutofillProfile->SetInfo(AutofillFieldType(EMAIL_ADDRESS), emailAddress); mAutofillProfile->SetInfo(AutofillFieldType(COMPANY_NAME), companyName); mAutofillProfile->SetInfo(AutofillFieldType(ADDRESS_HOME_LINE1), addressLine1); mAutofillProfile->SetInfo(AutofillFieldType(ADDRESS_HOME_LINE2), addressLine2); mAutofillProfile->SetInfo(AutofillFieldType(ADDRESS_HOME_CITY), city); mAutofillProfile->SetInfo(AutofillFieldType(ADDRESS_HOME_STATE), state); mAutofillProfile->SetInfo(AutofillFieldType(ADDRESS_HOME_ZIP), zipCode); mAutofillProfile->SetInfo(AutofillFieldType(ADDRESS_HOME_COUNTRY), country); mAutofillProfile->SetInfo(AutofillFieldType(PHONE_HOME_WHOLE_NUMBER), phoneNumber); std::vector profiles; profiles.push_back(*mAutofillProfile); updateProfileLabel(); mTabContents->profile()->GetPersonalDataManager()->SetProfiles(&profiles); } bool WebAutofill::updateProfileLabel() { std::vector profiles; profiles.push_back(mAutofillProfile.get()); return AutofillProfile::AdjustInferredLabels(&profiles); } void WebAutofill::clearProfiles() { if (!mAutofillProfile) return; // For now Chromium only ever knows about one profile, so we can just // remove it. If we support multiple profiles in the future // we need to remove them all here. std::string profileGuid = mAutofillProfile->guid(); mTabContents->profile()->GetPersonalDataManager()->RemoveProfile(profileGuid); setEmptyProfile(); } void WebAutofill::setEmptyProfile() { // Set an empty profile. This will ensure that when autofill is enabled, // we will still search the document for autofillable forms and inform // java of their presence so we can invite the user to set up // their own profile. // Chromium code will strip the values sent into the profile so we need them to be // at least one non-whitespace character long. We need to set all fields of the // profile to a non-empty string so that any field type can trigger the autofill // suggestion. Autofill will not detect form fields if the profile value for that // field is an empty string. static const string16 empty = string16(ASCIIToUTF16("a")); setProfile(empty, empty, empty, empty, empty, empty, empty, empty, empty, empty); } } #endif