/* * 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->FormsSeen(mForms); mParsingForms = false; mFormsSeenCondition.signal(); } void WebAutoFill::formFieldFocused(WebCore::HTMLFormControlElement* formFieldElement) { 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; } if (!enabled()) { // In case that we've just been disabled and the last time we got autofill // suggestions and told Java about them, clear that bit Java side now // we're disabled. mWebViewCore->setWebTextViewAutoFillable(FORM_NOT_AUTOFILLABLE, string16()); return; } // Get the FormField from the Node. webkit_glue::FormField* formField = new webkit_glue::FormField; FormManager::HTMLFormControlElementToFormField(formFieldElement, FormManager::EXTRACT_NONE, formField); formField->set_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->GetAutoFillSuggestions(*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->FillAutoFillFormData(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(AutoFillType(NAME_FULL), fullName); mAutoFillProfile->SetInfo(AutoFillType(EMAIL_ADDRESS), emailAddress); mAutoFillProfile->SetInfo(AutoFillType(COMPANY_NAME), companyName); mAutoFillProfile->SetInfo(AutoFillType(ADDRESS_HOME_LINE1), addressLine1); mAutoFillProfile->SetInfo(AutoFillType(ADDRESS_HOME_LINE2), addressLine2); mAutoFillProfile->SetInfo(AutoFillType(ADDRESS_HOME_CITY), city); mAutoFillProfile->SetInfo(AutoFillType(ADDRESS_HOME_STATE), state); mAutoFillProfile->SetInfo(AutoFillType(ADDRESS_HOME_ZIP), zipCode); mAutoFillProfile->SetInfo(AutoFillType(ADDRESS_HOME_COUNTRY), country); mAutoFillProfile->SetInfo(AutoFillType(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