blob: 863e8b05db40b7150db3647bb527ba231d73642a [file] [log] [blame]
/*
* 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<AutofillProfile> profiles;
profiles.push_back(*mAutofillProfile);
updateProfileLabel();
mTabContents->profile()->GetPersonalDataManager()->SetProfiles(&profiles);
}
bool WebAutofill::updateProfileLabel()
{
std::vector<AutofillProfile*> 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