// iforms.cpp : Implementation of CIntelliForms

#include "priv.h"
#include <iehelpid.h>
#include <pstore.h>
#include "hlframe.h"
#include "iformsp.h"
#include "shldisp.h"
#include "opsprof.h"
#include "resource.h"

#include <mluisupp.h>


// {E161255A-37C3-11d2-BCAA-00C04FD929DB}
static const GUID c_PStoreType =
{ 0xe161255a, 0x37c3, 0x11d2, { 0xbc, 0xaa, 0x0, 0xc0, 0x4f, 0xd9, 0x29, 0xdb } };
const TCHAR c_szIntelliForms[] = TEXT("Internet Explorer");

#define TF_IFORMS TF_CUSTOM2

// ALLOW_SHELLUIOC_HOST code will allow us to host intelliforms
//  from the Shell UI OC (shuioc.cpp). This is used for the
//  HTML Find dialog
#define ALLOW_SHELLUIOC_HOST

CIntelliForms *GetIntelliFormsFromDoc(IHTMLDocument2 *pDoc2);

inline void MyToLower(LPWSTR pwszStr)
{
    if (g_fRunningOnNT)
    {
        CharLowerBuffW(pwszStr, lstrlenW(pwszStr));
    }
    else
    {
        // Ideally we would use the code page contained in the string instead of
        //  the system code page.
        CHAR chBuf[MAX_PATH];
        SHUnicodeToAnsi(pwszStr, chBuf, ARRAYSIZE(chBuf));
        CharLowerBuffA(chBuf, lstrlenA(chBuf));
        SHAnsiToUnicode(chBuf, pwszStr, lstrlenW(pwszStr)+1);
    }
}


//=================== Exported functions =====================
// Exported for inetCPL
HRESULT ClearAutoSuggestForForms(DWORD dwClear)
{
    CIntelliForms *pObj = new CIntelliForms();

    if (pObj)
    {
        HRESULT hr;

        hr = pObj->ClearStore(dwClear);

        pObj->Release();

        return hr;
    }

    return E_OUTOFMEMORY;
}

HRESULT SetIdAutoSuggestForForms(const GUID *pguidId, void *pIntelliForms)
{
    CIntelliForms *pThis = (CIntelliForms *)pIntelliForms;

    if (pThis)
    {
        if (GUID_NULL == *pguidId)
        {
            pThis->m_guidUserId = c_PStoreType;
        }
        else
        {
            pThis->m_guidUserId = *pguidId;
        }

        return S_OK;
    }

    return E_FAIL;
}


// called from iedisp.cpp
void AttachIntelliForms(void *pvOmWindow, HWND hwnd, IHTMLDocument2 *pDoc2, void **ppIntelliForms)
{
static DWORD s_dwAdminRestricted = 0xFE;

    CIEFrameAuto::COmWindow *pOmWindow = (CIEFrameAuto::COmWindow *)pvOmWindow;

    ASSERT(ppIntelliForms && *ppIntelliForms==NULL);

    if (s_dwAdminRestricted == 0xFE)
    {
        s_dwAdminRestricted = CIntelliForms::IsAdminRestricted(c_szRegValFormSuggestRestrict) &&
                              CIntelliForms::IsAdminRestricted(c_szRegValSavePasswords);
    }

    if (s_dwAdminRestricted)
    {
        return;
    }

    // If we're not hosted by internet explorer, we don't want to enable Intelliforms
    //  unless dochost explicitly overrides this
    if (!IsInternetExplorerApp() &&
        !(pOmWindow && (DOCHOSTUIFLAG_ENABLE_FORMS_AUTOCOMPLETE & pOmWindow->IEFrameAuto()->GetDocHostFlags())))
    {
        return;
    }

    if (!hwnd && pOmWindow)
    {
        pOmWindow->IEFrameAuto()->get_HWND((LONG_PTR *)&hwnd);
    }

    if (!hwnd || !pDoc2 || !ppIntelliForms || (*ppIntelliForms != NULL))
    {
        return;
    }

#ifndef ALLOW_SHELLUIOC_HOST
    if (!pOmWindow)
    {
        return;
    }
#else
    if (!pOmWindow)
    {
        // Script is asking to attach to this document
        // Deny their request if another CIntelliForms is already attached
        if (NULL != GetIntelliFormsFromDoc(pDoc2))
        {
            return;
        }
    }
#endif

    CIntelliForms *pForms = new CIntelliForms();

    if (pForms)
    {
        if (SUCCEEDED(pForms->Init(pOmWindow, pDoc2, hwnd)))
        {
            *ppIntelliForms = pForms;
        }
        else
        {
            pForms->Release();
        }
    }
}

void ReleaseIntelliForms(void *pIntelliForms)
{
    CIntelliForms *pForms = (CIntelliForms *) pIntelliForms;

    if (pForms)
    {
        pForms->UnInit();
        pForms->Release();
    }
}

HRESULT IntelliFormsActiveElementChanged(void *pIntelliForms, IHTMLElement * pHTMLElement)
{
    CIntelliForms *pForms = (CIntelliForms *) pIntelliForms;

    if (pForms)
        return pForms->ActiveElementChanged(pHTMLElement);
    return E_FAIL;
}

INT_PTR CALLBACK AskUserDlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
HRESULT IncrementAskCount();

HRESULT IntelliFormsDoAskUser(HWND hwndBrowser, void *pv)
{
    // Make sure that we haven't asked them yet
    if (S_OK == IncrementAskCount())
    {
        // Modal dialog to ask the user our little question
        SHFusionDialogBoxParam(MLGetHinst(), MAKEINTRESOURCE(IDD_AUTOSUGGEST_ASK_USER),
                hwndBrowser, AskUserDlgProc, NULL);
    }

    return S_OK;
}

// Linked list of active CIntelliform objects to translate from
//  IHTMLDocument2->CIntelliforms when script calls window.external.saveforms
// Protected by g_csDll
CIntelliForms *g_pIntelliFormsFirst=NULL;

// Translate this pDoc2 to an existing instance of CIntelliForms
// Will return NULL if no CIntelliForms attached to this doc
// NO REFCOUNT IS ADDED TO THE RETURN
CIntelliForms *GetIntelliFormsFromDoc(IHTMLDocument2 *pDoc2)
{
    if (!pDoc2)
    {
        return NULL;
    }

    ENTERCRITICAL;
    CIntelliForms *pNext = g_pIntelliFormsFirst;
    IUnknown *punkDoc;
    CIntelliForms *pIForms=NULL;

    pDoc2->QueryInterface(IID_IUnknown, (void **)&punkDoc);

    if (punkDoc)
    {
        while (pNext)
        {
            if (pNext->GetDocument() == punkDoc)
            {
                pIForms = pNext;
                break;
            }
            pNext=pNext->GetNext();
        }

        punkDoc->Release();
    }

    LEAVECRITICAL;

    return pIForms;
}

// called from shuioc.cpp
HRESULT IntelliFormsSaveForm(IHTMLDocument2 *pDoc2, VARIANT *pvarForm)
{
    HRESULT hrRet = S_FALSE;
    IHTMLFormElement *pForm=NULL;
    CIntelliForms *pIForms=NULL;

    if (pvarForm->vt == VT_DISPATCH)
    {
        pvarForm->pdispVal->QueryInterface(IID_IHTMLFormElement, (void **)&pForm);
    }

    if (pForm)
    {
        pIForms = GetIntelliFormsFromDoc(pDoc2);

        if (pIForms)
        {
            // Should validate that pIForms was created on this thread
            hrRet = pIForms->ScriptSubmit(pForm);
        }

        pForm->Release();
    }

    return hrRet;
}

const TCHAR c_szYes[] = TEXT("yes");
const TCHAR c_szNo[] = TEXT("no");

INT_PTR AutoSuggestDlgProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam);

#ifdef CHECKBOX_HELP
const DWORD c_aIFormsHelpIds[] = {
        IDC_AUTOSUGGEST_NEVER, IDH_INTELLIFORM_PW_PROMPT,
        0, 0
};
#endif

const WCHAR c_wszVCardPrefix[] = L"vCard.";

BOOL CIntelliForms::CAutoSuggest::s_fRegisteredWndClass = FALSE;

// Must be in same order as EVENT enum type
// All events we need to sink anywhere
CEventSinkCallback::EventSinkEntry CEventSinkCallback::EventsToSink[] =
{
    { EVENT_KEYDOWN,    L"onkeydown",   L"keydown"  },
    { EVENT_KEYPRESS,   L"onkeypress",  L"keypress" },
    { EVENT_MOUSEDOWN,  L"onmousedown", L"mousedown"},
    { EVENT_DBLCLICK,   L"ondblclick",  L"dblclick" },
    { EVENT_FOCUS,      L"onfocus",     L"focus"    },
    { EVENT_BLUR,       L"onblur",      L"blur"     },
    { EVENT_SUBMIT,     L"onsubmit",    L"submit"   },
    { EVENT_SCROLL,     L"onscroll",    L"scroll"   },
    { EVENT_COMPOSITION,NULL,           L"composition"},
    { EVENT_NOTIFY,     NULL,           L"notify"   },
};

// Fake edit window class
const WCHAR c_szEditWndClass[] = TEXT("IntelliFormClass");

// Minimum dropdown width
const int MINIMUM_WIDTH=100;

// Submit number to ask user to enable us
const int ASK_USER_ON_SUBMIT_N = 2;

void GetStuffFromEle(IUnknown *punkEle, IHTMLWindow2 **ppWin2, IHTMLDocument2 **ppDoc2)
{
    if (ppWin2)
        *ppWin2=NULL;

    if (ppDoc2)
        *ppDoc2=NULL;

    IHTMLElement *pEle=NULL;
    punkEle->QueryInterface(IID_IHTMLElement, (void **)&pEle);

    if (pEle)
    {
        IDispatch *pDisp=NULL;
        pEle->get_document(&pDisp);
        if (pDisp)
        {
            IHTMLDocument2 *pDoc2 = NULL;
            pDisp->QueryInterface(IID_IHTMLDocument2, (void **)&pDoc2);
            if (pDoc2)
            {
                if (ppWin2)
                {
                    pDoc2->get_parentWindow(ppWin2);
                }

                if (ppDoc2)
                {
                    *ppDoc2 = pDoc2;
                }
                else
                {
                    pDoc2->Release();
                }
            }
            pDisp->Release();
        }

        pEle->Release();
    }
}

void Win3FromDoc2(IHTMLDocument2 *pDoc2, IHTMLWindow3 **ppWin3)
{
    *ppWin3=NULL;

    IHTMLWindow2 *pWin2=NULL;

    if (SUCCEEDED(pDoc2->get_parentWindow(&pWin2)) && pWin2)
    {
        pWin2->QueryInterface(IID_IHTMLWindow3, (void **)ppWin3);
        pWin2->Release();
    }
}

// Increment the count of whether we've asked the user to enable us or not. We won't
//  ask them on the first form submit since installing ie5.
HRESULT IncrementAskCount()
{
    DWORD dwData, dwSize, dwType;
    dwSize = sizeof(dwData);

    // c_szRegValAskUser contains the number of form submits
    //  0 means we've already asked user whether to enable us
    //  1 means we've already had one form submit, and should ask the user this time
    //  value not present means we haven't had any form submits

    if ((ERROR_SUCCESS == SHGetValue(HKEY_CURRENT_USER,
            c_szRegKeyIntelliForms, c_szRegValAskUser, &dwType, &dwData, &dwSize)) &&
        dwType == REG_DWORD)
    {
        if (dwData == 0)
        {
            // Shouldn't get this far
            TraceMsg(TF_IFORMS|TF_WARNING, "IntelliFormsDoAskUser: Already asked user");
            return E_FAIL;      // Already asked user
        }
    }
    else
    {
        dwData = 0;
    }

    if (dwData+1 < ASK_USER_ON_SUBMIT_N)
    {
        dwData ++;
        SHSetValue(HKEY_CURRENT_USER, c_szRegKeyIntelliForms, c_szRegValAskUser,
            REG_DWORD, &dwData, sizeof(dwData));

        TraceMsg(TF_IFORMS, "IntelliFormsDoAskUser incrementing submit count. Not asking user.");

        return E_FAIL;      // Don't ask the user
    }

    return S_OK;            // Let's ask the user
}


/////////////////////////////////////////////////////////////////////////////
// CIntelliForms

CIntelliForms::CIntelliForms()
{
    TraceMsg(TF_IFORMS, "CIntelliForms::CIntelliForms");

    m_cRef = 1;

    m_iRestoredIndex = -1;

    m_fRestricted = IsAdminRestricted(c_szRegValFormSuggestRestrict);
    m_fRestrictedPW = IsAdminRestricted(c_szRegValSavePasswords);
    m_guidUserId = c_PStoreType;
    
    // Add us to global linked list
    ENTERCRITICAL;
    m_pNext = g_pIntelliFormsFirst;
    g_pIntelliFormsFirst = this;
    LEAVECRITICAL;
}

CIntelliForms::~CIntelliForms()
{
    // Remove us from global linked list
    ENTERCRITICAL;

    CIntelliForms *pLast=NULL, *pNext = g_pIntelliFormsFirst;

    while (pNext && pNext != this)
    {
        pLast = pNext;
        pNext=pNext->m_pNext;
    }

    ASSERT(pNext == this);

    if (pNext)
    {
        if (pLast)
        {
            pLast->m_pNext = m_pNext;
        }
        else
        {
            g_pIntelliFormsFirst = m_pNext;
        }
    }
    LEAVECRITICAL;

    TraceMsg(TF_IFORMS, "CIntelliForms::~CIntelliForms");
}

// Called when document is ready to attach to
// We don't support re-initting
HRESULT CIntelliForms::Init(CIEFrameAuto::COmWindow *pOmWindow, IHTMLDocument2 *pDoc2, HWND hwndBrowser)
{
    HRESULT hr;

    ASSERT(pDoc2 && hwndBrowser);

#ifndef ALLOW_SHELLUIOC_HOST
    if (pOmWindow == NULL)
    {
        return E_INVALIDARG;
    }
#endif

    // Connect to get active element changed notifications

    m_pOmWindow = pOmWindow;
    if (pOmWindow)
    {
        pOmWindow->AddRef();
    }

    m_pDoc2 = pDoc2;
    pDoc2->AddRef();
    pDoc2->QueryInterface(IID_IUnknown, (void **)&m_punkDoc2);

    m_hwndBrowser = hwndBrowser;

    m_iRestoredIndex = -1;

    hr = S_OK;

#ifdef ALLOW_SHELLUIOC_HOST
    if (!pOmWindow && (hr == S_OK))
    {
        // Check for the current active element since the page is requesting
        //  us to attach to an existing document
        IHTMLElement *pHTMLElement = NULL;

        m_pDoc2->get_activeElement(&pHTMLElement);
        ActiveElementChanged(pHTMLElement);

        if (pHTMLElement)
            pHTMLElement->Release();
    }
#endif

    GetUrl();       // Init Url member variables so we don't get the url on the
                    //   wrong thread in the FillEnumerator call

    TraceMsg(TF_IFORMS, "CIntelliForms::Init hr=%08x", hr);

    return hr;
}

HRESULT CIntelliForms::UnInit()
{
    if (m_fInModalDialog)
    {
        // Lifetime management. If UnInit is called during modal dialog, we keep ourself
        //  alive. Use Enter/LeaveModalDialog to ensure correct use
        ASSERT(m_fUninitCalled == FALSE);       // Should only be called once...
        m_fUninitCalled = TRUE;
        return S_FALSE;
    }


    // Destroy this now, before we free other member variables, to ensure CAutoSuggest doesn't
    //  try to access us on a second thread.
    if (m_pAutoSuggest)
    {
        m_pAutoSuggest->SetParent(NULL);
        m_pAutoSuggest->DetachFromInput();
        delete m_pAutoSuggest;
        m_pAutoSuggest = NULL;
    }

    if (m_hdpaForms && m_pSink)
    {
        IHTMLElement2 *pEle2;
        EVENTS events[] = { EVENT_SUBMIT };

        for (int i=DPA_GetPtrCount(m_hdpaForms)-1; i>=0; i--)
        {
            ((IHTMLFormElement *)(DPA_FastGetPtr(m_hdpaForms, i)))->QueryInterface(IID_IHTMLElement2, (void **)&pEle2);
            m_pSink->UnSinkEvents(pEle2, ARRAYSIZE(events), events);
            pEle2->Release();
        }
    }

    SysFreeString(m_bstrFullUrl);
    m_bstrFullUrl = NULL;

    SysFreeString(m_bstrUrl);
    m_bstrUrl = NULL;

    if (m_pwszUrlHash)
    {
        LocalFree((void *)m_pwszUrlHash);
        m_pwszUrlHash = NULL;
    }

    // Unhook regular event sink
    if (m_pSink)
    {
#ifndef ALLOW_SHELLUIOC_HOST
        ASSERT(m_pOmWindow);
#endif
        if (m_pOmWindow)
        {
            IHTMLWindow3 *pWin3=NULL;

            Win3FromDoc2(m_pDoc2, &pWin3);

            if (pWin3)
            {
                EVENTS events[] = { EVENT_SCROLL };
                m_pSink->UnSinkEvents(pWin3, ARRAYSIZE(events), events);
                pWin3->Release();
            }
        }

        m_pSink->SetParent(NULL);
        m_pSink->Release();
        m_pSink=NULL;
    }

    // Unhook designer event sink
    if (m_pEditSink)
    {
        m_pEditSink->Attach(NULL);
        m_pEditSink->SetParent(NULL);
        m_pEditSink->Release();
        m_pEditSink=NULL;
    }

    // SAFERELEASE (and ATOMICRELEASE) macro in shdocvw is actually function which requires IUnknown
    ATOMICRELEASET(m_pOmWindow, CIEFrameAuto::COmWindow);
    SAFERELEASE(m_pDoc2);
    SAFERELEASE(m_punkDoc2);

    FreeElementList();
    FreeFormList();

    if (m_pslPasswords)
    {
        delete m_pslPasswords;
        m_pslPasswords = NULL;
    }

    ReleasePStore();

    TraceMsg(TF_IFORMS, "CIntelliForms::UnInit");

    return S_OK;
}

STDMETHODIMP CIntelliForms::QueryInterface(REFIID riid, void **ppv)
{
    *ppv = NULL;

    if ((IID_IPropertyNotifySink == riid) ||
        (IID_IUnknown == riid))
    {
        *ppv = (IPropertyNotifySink *)this;
    }

    if (NULL != *ppv)
    {
        ((IUnknown *)*ppv)->AddRef();
        return S_OK;
    }

    return E_NOINTERFACE;
}

STDMETHODIMP_(ULONG) CIntelliForms::AddRef(void)
{
    return ++m_cRef;
}

STDMETHODIMP_(ULONG) CIntelliForms::Release(void)
{
    if (--m_cRef == 0)
    {
        delete this;
        return 0;
    }

    return m_cRef;
}


HRESULT CIntelliForms::ActiveElementChanged(IHTMLElement * pHTMLElement)
{
    ASSERT(m_pDoc2);

    // Detach the AutoSuggest object and destroy it
    if (m_pAutoSuggest)
    {
        m_pAutoSuggest->DetachFromInput();
        delete m_pAutoSuggest;
        m_pAutoSuggest=NULL;
    }

    if (m_pDoc2)
    {
        IHTMLElement *pEle=pHTMLElement;

        if (pEle)
        {
            BOOL fPassword=FALSE;
            IHTMLInputTextElement *pTextEle = NULL;

            if (SUCCEEDED(ShouldAttachToElement(pEle, TRUE, NULL, &pTextEle, NULL, &fPassword)))
            {
                BOOL fEnabledInCPL = IsEnabledInCPL();
                BOOL fEnabledPW = IsEnabledRestorePW();

                // We need to watch user activity if...
                if (fEnabledInCPL ||        // Intelliforms is enabled
                    fEnabledPW ||           // Or Restore Passwords is enabled
                    !AskedUserToEnable())   // Or we may ask them to enable us
                {
                    m_pAutoSuggest = new CAutoSuggest(this, fEnabledInCPL, fEnabledPW);

                    if (m_pAutoSuggest)
                    {
                        if (!m_pSink)
                        {
                            m_pSink = new CEventSink(this);

                            if (m_pSink)
                            {
#ifndef ALLOW_SHELLUIOC_HOST
                                // Don't sink scroll event if hosted by ShellUIOC
                                //  or jscript.dll asserts on unload
                                ASSERT(m_pOmWindow);
#endif
                                if (m_pOmWindow)
                                {
                                    IHTMLWindow3 *pWin3=NULL;

                                    Win3FromDoc2(m_pDoc2, &pWin3);

                                    if (pWin3)
                                    {
                                        EVENTS events[] = { EVENT_SCROLL };
                                        m_pSink->SinkEvents(pWin3, ARRAYSIZE(events), events);
                                        pWin3->Release();
                                    }
                                }
                            }
                        }

                        // Hook up designer sink for IME event
                        if (!m_pEditSink)
                        {
                            m_pEditSink = new CEditEventSink(this);

                            if (m_pEditSink)
                            {
                                m_pEditSink->Attach(pEle);
                            }
                        }

                        if (!m_pSink || FAILED(m_pAutoSuggest->AttachToInput(pTextEle)))
                        {
                            delete m_pAutoSuggest;
                            m_pAutoSuggest = NULL;
                        }
                    }
                }

                pTextEle->Release();
            }
            else
            {
                ASSERT(!pTextEle);

                if (fPassword)
                {
                    m_fHitPWField = TRUE;
                }
            }
            //
            // Don't release pEle
        }
    }

    return S_OK;
}

// Helper functions
BOOL CIntelliForms::AskedUserToEnable()
{
    DWORD dwType, dwSize;
    DWORD dwVal;
    DWORD dwRet;

    dwSize = sizeof(dwVal);

    dwRet = SHGetValue(HKEY_CURRENT_USER, c_szRegKeyIntelliForms, c_szRegValAskUser,
                            &dwType, &dwVal, &dwSize);

    if ((dwRet == ERROR_SUCCESS) && (dwType == REG_DWORD))
    {
        return (dwVal == 0) ? TRUE : FALSE;
    }

    return FALSE;
}

BOOL CIntelliForms::IsEnabledInRegistry(LPCTSTR pszKey, LPCTSTR pszValue, BOOL fDefault)
{
    DWORD dwType, dwSize;
    TCHAR szEnabled[16];
    DWORD dwRet;

    dwSize = sizeof(szEnabled);

    dwRet = SHGetValue(HKEY_CURRENT_USER, pszKey, pszValue, &dwType, szEnabled, &dwSize);

    if (dwRet == ERROR_INSUFFICIENT_BUFFER)
    {
        // Invalid value in registry.
        ASSERT(dwRet == ERROR_SUCCESS);
        return FALSE;
    }

    if (dwRet == ERROR_SUCCESS)
    {
        if ((dwType == REG_SZ) &&
            (!StrCmp(szEnabled, TEXT("yes"))))
        {
            // Enabled
            return TRUE;
        }
        else
        {
            // Disabled
            return FALSE;
        }
    }

    // Value not found
    return fDefault;
}

BOOL CIntelliForms::IsAdminRestricted(LPCTSTR pszRegVal)
{
    DWORD lSize;
    DWORD  lValue;

    lValue = 0; // clear it
    lSize = sizeof(lValue);
    if (ERROR_SUCCESS !=
        SHGetValue(HKEY_CURRENT_USER, c_szRegKeyRestrict, pszRegVal, NULL, (LPBYTE)&lValue, &lSize ))
    {
        return FALSE;
    }

    ASSERT(lSize == sizeof(lValue));

    return (0 != lValue) ? TRUE : FALSE;
}

BOOL CIntelliForms::IsEnabledForPage()
{
    if (!m_fCheckedIfEnabled)
    {
        m_fCheckedIfEnabled = TRUE;

        // We will have our Url in m_bstrFullUrl, only if it is https: protocol
        if (m_bstrFullUrl)
        {
            ASSERT(!StrCmpNIW(m_bstrFullUrl, L"https:", 5));

            m_fEnabledForPage = TRUE;

            // See if this page is in the internet cache. If not, we won't intelliform
            //  for this page either.
            if (!GetUrlCacheEntryInfoW(m_bstrFullUrl, NULL, NULL) && (GetLastError() != ERROR_INSUFFICIENT_BUFFER))
            {
                // Failed - it's not in the cache
                m_fEnabledForPage = FALSE;
            }
        }
        else
        {
            // Url is not https: so always enable Intelliforms
            m_fEnabledForPage = TRUE;
        }
    }

    return m_fEnabledForPage;
}

HRESULT CIntelliForms::GetBodyEle(IHTMLElement2 **ppEle2)
{
    if (!m_pDoc2 || !ppEle2)
    {
        return E_INVALIDARG;
    }

    *ppEle2=NULL;

    IHTMLElement *pBodyEle=NULL;

    m_pDoc2->get_body(&pBodyEle);

    if (pBodyEle)
    {
        pBodyEle->QueryInterface(IID_IHTMLElement2, (void **)ppEle2);
        pBodyEle->Release();
    }

    return (*ppEle2) ? S_OK : E_FAIL;
}

// static
BOOL CIntelliForms::IsElementEnabled(IHTMLElement *pEle)
{
    BOOL fEnabled=TRUE;
    BSTR bstrAttribute;

    VARIANT varVal;
    varVal.vt = VT_EMPTY;

    // First check "AutoComplete=OFF"
    bstrAttribute=SysAllocString(L"AutoComplete");

    if (bstrAttribute &&
        SUCCEEDED(pEle->getAttribute(bstrAttribute, 0, &varVal)))
    {
        if (varVal.vt == VT_BSTR)
        {
            if (!StrCmpIW(varVal.bstrVal, L"off"))
            {
                // We are disabled.
                fEnabled=FALSE;
            }
        }

        VariantClear(&varVal);
    }

    SysFreeString(bstrAttribute);

    // Then check "READONLY" attribute
    if (fEnabled)
    {
        IHTMLInputElement *pInputEle=NULL;

        pEle->QueryInterface(IID_IHTMLInputElement, (void **)&pInputEle);

        if (pInputEle)
        {
            VARIANT_BOOL vbReadOnly=VARIANT_FALSE;

            pInputEle->get_readOnly(&vbReadOnly);

            if (vbReadOnly)
            {
                // We are read only.
                fEnabled=FALSE;
            }

            pInputEle->Release();
        }
    }

    return fEnabled;
}

// static
HRESULT CIntelliForms::ShouldAttachToElement(IUnknown                *punkEle,
                                             BOOL                     fCheckForm,
                                             IHTMLElement2          **ppEle2,
                                             IHTMLInputTextElement  **ppITE,
                                             IHTMLFormElement       **ppFormEle,
                                             BOOL                    *pfPassword)
{
    IHTMLInputTextElement *pITE = NULL;

    if (ppEle2)
    {
        *ppEle2 = NULL;
    }

    if (ppITE)
    {
        *ppITE = NULL;
    }

    if (ppFormEle)
    {
        *ppFormEle = NULL;
    }

    punkEle->QueryInterface(IID_IHTMLInputTextElement, (void **)&pITE);

    if (NULL == pITE)
    {
        // Not an input text element. Do not attach.
        return E_FAIL;
    }

    HRESULT hr = E_FAIL;

    IHTMLElement2 *pEle2        = NULL;
    IHTMLElement *pEle          = NULL;
    IHTMLFormElement *pFormEle  = NULL;

    punkEle->QueryInterface(IID_IHTMLElement2, (void **)&pEle2);
    punkEle->QueryInterface(IID_IHTMLElement, (void **)&pEle);

    if (pEle2 && pEle)
    {
        // type=text is all that's allowed
        BSTR bstrType=NULL;

        if (SUCCEEDED(pITE->get_type(&bstrType)) && bstrType)
        {
            if (!StrCmpICW(bstrType, L"text"))
            {
                // FormSuggest=off attribute turns us off for this element
                if (IsElementEnabled(pEle))
                {
                    IHTMLElement *pFormHTMLEle=NULL;

                    if (fCheckForm || ppFormEle)
                    {
                        pITE->get_form(&pFormEle);

                        if (pFormEle)
                        {
                            pFormEle->QueryInterface(IID_IHTMLElement, (void **)&pFormHTMLEle);
                        }
                        else
                        {
                            // This may be valid if element is not in form
                            TraceMsg(TF_IFORMS, "Iforms: pITE->get_form() returned NULL");
                        }
                    }

                    // FormSuggest=off for form turns us off for this form
                    if (pFormEle &&
                        (!fCheckForm || (pFormHTMLEle && IsElementEnabled(pFormHTMLEle))))
                    {
                        hr = S_OK;
                        if (ppEle2)
                        {
                            *ppEle2 = pEle2;
                            pEle2->AddRef();
                        }
                        if (ppFormEle)
                        {
                            *ppFormEle = pFormEle;
                            pFormEle->AddRef();
                        }
                        if (ppITE)
                        {
                            *ppITE = pITE;
                            pITE->AddRef();
                        }
                    }

                    SAFERELEASE(pFormHTMLEle);
                    SAFERELEASE(pFormEle);
                }
            }
            else
            {
                if (pfPassword && !StrCmpICW(bstrType, L"password") && IsElementEnabled(pEle))
                {
                    TraceMsg(TF_IFORMS, "IForms: Password field detected.");
                    *pfPassword = TRUE;
                }
            }

            SysFreeString(bstrType);
        }
        else
        {
            TraceMsg(TF_IFORMS, "IntelliForms disabled for single element via attribute");
        }
    }

    SAFERELEASE(pITE);
    SAFERELEASE(pEle2);
    SAFERELEASE(pEle);

    return hr;
}

// Get the URL that we're located at, with query string/anchor stripped.
LPCWSTR CIntelliForms::GetUrl()
{
    if (m_bstrUrl)
    {
        return m_bstrUrl;
    }

    if (m_pOmWindow)
    {
        m_pOmWindow->IEFrameAuto()->get_LocationURL(&m_bstrUrl);
    }
#ifdef ALLOW_SHELLUIOC_HOST
    else
    {
        IHTMLLocation *pHTMLLocation=NULL;

        m_pDoc2->get_location(&pHTMLLocation);

        if (NULL != pHTMLLocation)
        {
            pHTMLLocation->get_href(&m_bstrUrl);
            pHTMLLocation->Release();
        }
    }
#endif

    if (m_bstrUrl)
    {
        PARSEDURLW puW = {0};
        puW.cbSize = sizeof(puW);

        // Save the full url for a security check, if we are https protocol
        if (SUCCEEDED(ParseURLW(m_bstrUrl, &puW)))
        {
            if (puW.nScheme == URL_SCHEME_HTTPS)
            {
                m_bstrFullUrl = SysAllocString(m_bstrUrl);
                if (!m_bstrFullUrl)
                {
                    SysFreeString(m_bstrUrl);
                    m_bstrUrl=NULL;
                }
            }
        }
    }

    if (m_bstrUrl)
    {
        // Strip off any query string or anchor
        LPWSTR lpUrl = m_bstrUrl;
        while (*lpUrl)
        {
            if ((*lpUrl == L'?') || (*lpUrl == L'#'))
            {
                *lpUrl = L'\0';
                break;
            }
            lpUrl ++;
        }

        return m_bstrUrl;
    }

    TraceMsg(TF_WARNING|TF_IFORMS, "CIntelliForms::GetUrl() failing!");
    return L"";     // We can assume non-NULL pointer
}

// hook our "Submit" event sink to this form
HRESULT CIntelliForms::AttachToForm(IHTMLFormElement *pFormEle)
{
    ASSERT(m_pSink);

    if (m_pSink)
    {
        IHTMLElement2 *pEle2 = NULL;

        pFormEle->QueryInterface(IID_IHTMLElement2, (void **)&pEle2);

        if (pEle2)
        {
            // Sink event for the form
            EVENTS events[] = { EVENT_SUBMIT };
            m_pSink->SinkEvents(pEle2, ARRAYSIZE(events), events);
        }

        SAFERELEASE(pEle2);

        return S_OK;
    }

    return E_OUTOFMEMORY;
}

// Returns TRUE if nothing but spaces in string
inline BOOL IsEmptyString(LPCWSTR lpwstr)
{
    while (*lpwstr && (*lpwstr == L' ')) lpwstr++;
    return (*lpwstr == 0);
}

// called for each element in the form we are submitting
HRESULT CIntelliForms::SubmitElement(IHTMLInputTextElement *pITE, FILETIME ftSubmit, BOOL fEnabledInCPL)
{
    if (m_fRestricted) return E_FAIL;

    HRESULT hrRet = S_OK;

    BSTR bstrName;

    CIntelliForms::GetName(pITE, &bstrName);

    if (bstrName && bstrName[0])
    {
        BSTR bstrValue=NULL;

        pITE->get_value(&bstrValue);

        if (bstrValue && bstrValue[0] && !IsEmptyString(bstrValue))
        {
            if (fEnabledInCPL)
            {
                TraceMsg(TF_IFORMS, "IForms: Saving field \"%ws\" as \"%ws\"", bstrName, bstrValue);

                CStringList *psl;

                if (FAILED(ReadFromStore(bstrName, &psl)))
                {
                    CStringList_New(&psl);
                }

                if (psl)
                {
                    HRESULT hr;

                    if (SUCCEEDED(hr = psl->AddString(bstrValue, ftSubmit)))
                    {
                        if ((S_OK == hr) ||
                            (psl->NumStrings() > CStringList::MAX_STRINGS / 4))
                        {
                            // We added a non-duplicate string, or we updated the
                            //  last submit time of an existing string
                            WriteToStore(bstrName, psl);
                        }
                    }

                    delete psl;
                }
            }
            else
            {
                hrRet = S_FALSE;   // Tell caller that we didn't save because we were disabled
            }
        }

        SysFreeString(bstrValue);
    }

    SysFreeString(bstrName);

    return hrRet;
}

HRESULT CIntelliForms::HandleFormSubmit(IHTMLFormElement *pForm)
{
    IUnknown *punkForm=NULL;

    if (!pForm)
    {
        // We currently require a form element even from script
        return E_INVALIDARG;
    }

    if (!m_hdpaElements || !m_hdpaForms)
    {
        return S_OK;
    }

    // Make sure we're enabled
    BOOL fEnabledInCPL = IsEnabledInCPL();
    if (fEnabledInCPL || IsEnabledRestorePW() || !AskedUserToEnable())
    {
        pForm->QueryInterface(IID_IUnknown, (void **)&punkForm);

        if (punkForm)
        {
            IHTMLFormElement *pThisFormEle;
            IUnknown *punkThisForm;
            FILETIME ftSubmit;
            int     iCount=0;
            BOOL    fShouldAskUser=FALSE;
            IHTMLInputTextElement *pFirstEle=NULL;

            GetSystemTimeAsFileTime(&ftSubmit);

            // Go through list of 'changed' elements and save their values
            //  make sure we loop backwards since we nuke elements as we find them
            for (int i=DPA_GetPtrCount(m_hdpaElements)-1; i>=0; i--)
            {
                IHTMLInputTextElement *pITE = ((IHTMLInputTextElement *)(DPA_FastGetPtr(m_hdpaElements, i)));

                if (SUCCEEDED(pITE->get_form(&pThisFormEle)) && pThisFormEle)
                {
                    if (SUCCEEDED(pThisFormEle->QueryInterface(IID_IUnknown, (void **)&punkThisForm)))
                    {
                        if (punkThisForm == punkForm)
                        {
                            // Verify that we're still allowed to save this element
                            if (SUCCEEDED(ShouldAttachToElement(pITE, TRUE, NULL, NULL, NULL, NULL)))
                            {
                                iCount ++;

                                if (!pFirstEle)
                                {
                                    pFirstEle = pITE;
                                    pFirstEle->AddRef();
                                }

                                // Don't save non-password stuff for non-cached pages
                                if (IsEnabledForPage())
                                {
                                    // Won't actually save the value if fEnabledInCPL is FALSE
                                    if (S_FALSE == SubmitElement(pITE, ftSubmit, fEnabledInCPL))
                                    {
                                        // We would have saved this if we were enabled
                                        fShouldAskUser = TRUE;
                                    }
                                }

                                // Remove this element from the DPA to prevent any possibility of
                                //  saving before more user input takes place
                                pITE->Release();
                                DPA_DeletePtr(m_hdpaElements, i);
                            }
                        }
                        else
                        {
                            TraceMsg(TF_IFORMS, "IForms: User input in different form than was submitted...?");
                        }

                        punkThisForm->Release();
                    }

                    pThisFormEle->Release();
                }
                else
                {
                    // It shouldn't be in our DPA if it isn't in a form...
                    TraceMsg(TF_WARNING|TF_IFORMS, "Iforms: pITE->get_form() returned NULL!");
                }
            }

            if (0 == DPA_GetPtrCount(m_hdpaElements))
            {
                DPA_Destroy(m_hdpaElements);
                m_hdpaElements=NULL;
            }

            if (m_fHitPWField || (m_iRestoredIndex != -1))
            {
                // ?? why not check iCount==1 here?
                if (pFirstEle)
                {
                    // May have restored PW and may have changed or entered it
                    SavePassword(pForm, ftSubmit, pFirstEle);

                    // WARNING - after returning from "SavePassword" our object may be invalid
                    //  if we got released/detached during modal dialog
                }
            }
            else if (fShouldAskUser)
            {
                // Possibly ask user if they want to enable intelliforms, only if
                //  this isn't a login
                if (m_pOmWindow)
                {
                    m_pOmWindow->IntelliFormsAskUser(NULL);
                }
                fShouldAskUser = FALSE;
            }

            if (fShouldAskUser)
            {
                // If we should ask the user but we're not going to (login form),
                //  increment our count anyway so that we ask them as soon as we can
                IncrementAskCount();
            }

            punkForm->Release();
            SAFERELEASE(pFirstEle);
        }
    }

    return S_OK;
}

HRESULT CIntelliForms::HandleEvent(IHTMLElement *pEle, EVENTS Event, IHTMLEventObj *pEventObj)
{
    TraceMsg(TF_IFORMS, "CIntelliForms::HandleEvent Event=%ws", EventsToSink[Event].pwszEventName);

    if (Event == EVENT_SUBMIT)
    {
        // Save strings for modified text inputs when appropriate
        IHTMLFormElement *pFormEle = NULL;

        if (pEle)
        {
            pEle->QueryInterface(IID_IHTMLFormElement, (void **)&pFormEle);
            if (pFormEle)
            {
                HandleFormSubmit(pFormEle);
                // Warning - "this" may be detached/destroyed at this point
                pFormEle->Release();
            }
        }
    }
    else
    {
        ASSERT(Event == EVENT_SCROLL);
        if (m_pAutoSuggest)
            m_pAutoSuggest->UpdateDropdownPosition();
    }

    return S_OK;
}

HRESULT CIntelliForms::PreHandleEvent(DISPID inEvtDispId, IHTMLEventObj* pIEventObj)
{
    if ((inEvtDispId == 0) &&
        (m_pAutoSuggest != NULL) &&
        (m_pAutoSuggest->AttachedElement() != NULL))
    {
        BSTR bstrType = NULL;
        CEventSinkCallback::EVENTS Event = EVENT_BOGUS;

        pIEventObj->get_type(&bstrType);

        if (bstrType)
        {
#if 0
            // Spew wParam and lParam
            IHTMLEventObj3 *pObj3 = NULL;

            pIEventObj->QueryInterface(IID_PPV_ARG(IHTMLEventObj3, &pObj3));

            if (pObj3)
            {
                long lLong=0;
                long wWord=0;
                pObj3->get_imeCompositionChange(&lLong);
                pObj3->get_imeNotifyCommand(&wWord);
                TraceMsg(TF_ALWAYS, "PreHandleEvent: %ws - wWord=0x%04x   lLong=0x%08x", bstrType, wWord, lLong);
                pObj3->Release();
            }
#endif
            if (!StrCmp(bstrType, L"composition"))
            {
                Event = EVENT_COMPOSITION;
            }
            else if (!StrCmp(bstrType, L"notify"))
            {
                Event = EVENT_NOTIFY;
            }

            if (Event != EVENT_BOGUS)
            {
                // Trident doesn't set srcElement on eventobj, so just use the one
                // we're attached to
                IHTMLElement *pEle;

                m_pAutoSuggest->AttachedElement()->QueryInterface(IID_IHTMLElement, (void **)&pEle);

                if (pEle)
                {
                    m_pAutoSuggest->HandleEvent(pEle, Event, pIEventObj);
                    pEle->Release();
                }
            }

            SysFreeString(bstrType);
        }
        
    }

    return S_FALSE;     // S_FALSE so that Trident will still process this
}


// Our passwords are stored in username/value pairs
// Search every other string for the username
HRESULT CIntelliForms::FindPasswordEntry(LPCWSTR pwszValue, int *piIndex)
{
    ASSERT(m_pslPasswords);
    ASSERT(!(m_pslPasswords->NumStrings() & 1));   // Should be even number

    int i;

    for (i=0; i<m_pslPasswords->NumStrings(); i += 2)
    {
        if (!StrCmpIW(pwszValue, m_pslPasswords->GetString(i)))
        {
            // Found it
            *piIndex = i+1;
            return S_OK;
        }
    }

    return E_FAIL;
}

// Convert url to string based on shlwapi UrlHash return
LPCWSTR CIntelliForms::GetUrlHash()
{
    BYTE bBuf[15];

    if (m_pwszUrlHash)
    {
        return m_pwszUrlHash;
    }

    LPCWSTR pwszUrl = GetUrl();

    if (!pwszUrl || !*pwszUrl)
    {
        return NULL;
    }

    if (SUCCEEDED(UrlHashW(pwszUrl, bBuf, ARRAYSIZE(bBuf))))
    {
        // Translate this array of bytes into 7-bit chars
        m_pwszUrlHash = (LPWSTR)LocalAlloc(LMEM_FIXED, sizeof(WCHAR)*(ARRAYSIZE(bBuf)+1));

        if (m_pwszUrlHash)
        {
            for (int i=0; i<ARRAYSIZE(bBuf); i++)
            {
                // Translate each char into 32-96 range
                ((LPWSTR)m_pwszUrlHash)[i] = (WCHAR)((bBuf[i] & 0x3F) + 0x20);
            }
            ((LPWSTR)m_pwszUrlHash)[i] = L'\0';
        }

        return m_pwszUrlHash;
    }

    return NULL;
}

// Tells us if passwords are present for this url
BOOL CIntelliForms::ArePasswordsSaved()
{
    if (!m_fRestrictedPW)
    {
        DWORD dwVal, dwSize=sizeof(dwVal);
        LPCWSTR pwsz = GetUrlHash();

        if (pwsz && (ERROR_SUCCESS == SHGetValueW(HKEY_CURRENT_USER, c_wszRegKeyIntelliFormsSPW, pwsz, NULL, &dwVal, &dwSize)))
        {
            return TRUE;
        }
    }

    return FALSE;
}

// Will return password list in m_pslPasswords, if passwords are saved
BOOL CIntelliForms::LoadPasswords()
{
    if (!m_fCheckedPW)
    {
        m_fCheckedPW = TRUE;

        // Check if passwords are present without hitting pstore
        if (ArePasswordsSaved())
        {
            // We should have passwords for this url. Hit PStore.
            ReadFromStore(GetUrl(), &m_pslPasswords, TRUE);

            m_iRestoredIndex = -1;
        }
    }
    else if (m_pslPasswords)
    {
        // If we already have passwords, double check the registry in case the user
        //  nuked saved stuff via inetcpl
        if (!ArePasswordsSaved())
        {
            delete m_pslPasswords;
            m_pslPasswords=NULL;
            m_iRestoredIndex = -1;
        }
    }

    return (m_pslPasswords != NULL);
}

void CIntelliForms::SavePasswords()
{
    if (m_pslPasswords && m_bstrUrl)
    {
        WriteToStore(m_bstrUrl, m_pslPasswords);
        SetPasswordsAreSaved(TRUE);
    }
}

// Mark that we have passwords saved for this url
void CIntelliForms::SetPasswordsAreSaved(BOOL fSaved)
{
    LPCWSTR pwsz = GetUrlHash();

    if (pwsz)
    {
        if (fSaved)
        {
            DWORD dwSize = sizeof(DWORD);
            DWORD dw = 0;
            SHSetValueW(HKEY_CURRENT_USER, c_wszRegKeyIntelliFormsSPW, pwsz, REG_DWORD, &dw, sizeof(dw));
        }
        else
        {
            SHDeleteValueW(HKEY_CURRENT_USER, c_wszRegKeyIntelliFormsSPW, pwsz);
        }

    }
}

// enumerates form & gets password fields
class CDetectLoginForm
{
public:
    CDetectLoginForm() { m_pNameEle=m_pPasswordEle=m_pPasswordEle2=NULL; }
    ~CDetectLoginForm() { SAFERELEASE(m_pNameEle); SAFERELEASE(m_pPasswordEle); }

    HRESULT ParseForm(IHTMLFormElement *pFormEle, BOOL fRestoring);

    IHTMLInputTextElement *GetNameEle() { return m_pNameEle; }
    IHTMLInputTextElement *GetPasswordEle() { return m_pPasswordEle; }

protected:
    IHTMLInputTextElement  *m_pNameEle;
    IHTMLInputTextElement  *m_pPasswordEle;

    IHTMLInputTextElement  *m_pPasswordEle2;

    static HRESULT s_PasswordCB(IDispatch *pDispEle, DWORD_PTR dwCBData);
};

// if SUCCEEDED(hr), GetNameEle and GetPasswordEle are guaranteed non-NULL
HRESULT CDetectLoginForm::ParseForm(IHTMLFormElement *pFormEle, BOOL fRestoring)
{
    if (m_pPasswordEle || m_pNameEle || m_pPasswordEle2)
    {
        return E_FAIL;
    }

    CIntelliForms::CEnumCollection<IHTMLFormElement>::EnumCollection(pFormEle, s_PasswordCB, (DWORD_PTR)this);

    // For forms with two password fields (possibly used for login *and* new accounts)
    //  we clear the second field on PW restore and require it to be blank for saving.
    // Ideally, we would detect this as a password change situation as well.
    if (m_pPasswordEle2)
    {
        if (fRestoring)
        {
            BSTR bstrEmpty=SysAllocString(L"");
            if (bstrEmpty)
            {
                m_pPasswordEle2->put_value(bstrEmpty);
                SysFreeString(bstrEmpty);
            }
        }
        else
        {
            BSTR bstrVal=NULL;

            m_pPasswordEle2->get_value(&bstrVal);

            if (bstrVal && bstrVal[0])
            {
                // Failure! Second password field isn't empty.
                SAFERELEASE(m_pNameEle);
                SAFERELEASE(m_pPasswordEle);
            }

            SysFreeString(bstrVal);
        }

        SAFERELEASE(m_pPasswordEle2);   // Always release this
    }

    if (m_pPasswordEle && m_pNameEle)
    {
        return S_OK;
    }

    SAFERELEASE(m_pNameEle);
    SAFERELEASE(m_pPasswordEle);
    ASSERT(!m_pPasswordEle2);

    return E_FAIL;
}

// Password callback for CEnumCollection to find username and password fields
//   in a login form
HRESULT CDetectLoginForm::s_PasswordCB(IDispatch *pDispEle, DWORD_PTR dwCBData)
{
    CDetectLoginForm *pThis = (CDetectLoginForm *)dwCBData;

    HRESULT hr=S_OK;

    IHTMLInputTextElement *pTextEle=NULL;

    pDispEle->QueryInterface(IID_IHTMLInputTextElement, (void **)&pTextEle);

    if (pTextEle)
    {
        BSTR bstrType;

        pTextEle->get_type(&bstrType);

        if (bstrType)
        {
            if (!StrCmpICW(bstrType, L"text"))
            {
                // Assume this is the 'name' field
                if (pThis->m_pNameEle)
                {
                    // Whoops, we've already got a name field. Can't have two...
                    hr = E_ABORT;
                }
                else
                {
                    pThis->m_pNameEle = pTextEle;
                    pTextEle->AddRef();
                }
            }
            else if (!StrCmpICW(bstrType, L"password"))
            {
                // Assume this is the 'password' field
                if (pThis->m_pPasswordEle)
                {
                    // Whoops, we've already got a password field. Can't have two...
                    //  ...oh wait, yes we can...
                    if (pThis->m_pPasswordEle2)
                    {
                        // ...but we definitely can't have three!!!
                        hr = E_ABORT;
                    }
                    else
                    {
                        pThis->m_pPasswordEle2 = pTextEle;
                        pTextEle->AddRef();
                    }
                }
                else
                {
                    pThis->m_pPasswordEle = pTextEle;
                    pTextEle->AddRef();
                }
            }

            SysFreeString(bstrType);
        }

        pTextEle->Release();
    }

    if (hr == E_ABORT)
    {
        SAFERELEASE(pThis->m_pNameEle);
        SAFERELEASE(pThis->m_pPasswordEle);
        SAFERELEASE(pThis->m_pPasswordEle2);
    }

    return hr;
}

// Fill in passwords for this username, if one is available
HRESULT CIntelliForms::AutoFillPassword(IHTMLInputTextElement *pTextEle, LPCWSTR pwszUsername)
{
    BSTR bstrUrl = NULL;

    if (!pTextEle || !pwszUsername)
        return E_INVALIDARG;

    if (!IsEnabledRestorePW() || !LoadPasswords())
    {
        // We have no passwords for this url
        return S_FALSE;
    }

    int iIndex;

    if (SUCCEEDED(FindPasswordEntry(pwszUsername, &iIndex)))
    {
        // Returns index of password in m_pslPasswords
        ASSERT(iIndex>=0 && iIndex<m_pslPasswords->NumStrings() && (iIndex&1));

        FILETIME ft;

        // StringTime==0 indicates user said "no" to saving password
        if (SUCCEEDED(m_pslPasswords->GetStringTime(iIndex, &ft)) && (FILETIME_TO_INT(ft) != 0))
        {
            TraceMsg(TF_IFORMS, "IntelliForms found saved password");

            // We have a password saved for this specific username. Fill it in.
            CDetectLoginForm LoginForm;
            IHTMLFormElement *pFormEle=NULL;
            HRESULT hr = E_FAIL;

            pTextEle->get_form(&pFormEle);
            if (pFormEle)
            {
                // See if this is a valid form: One plain text input, One password input. Find the fields.
                hr = LoginForm.ParseForm(pFormEle, TRUE);

                pFormEle->Release();
            }
            else
            {
                // Shouldn't get this far if we don't have a form for this element
                TraceMsg(TF_WARNING|TF_IFORMS, "Iforms: pITE->get_form() returned NULL!");
            }

            if (SUCCEEDED(hr))
            {
                BSTR bstrPW=NULL;
                m_pslPasswords->GetBSTR(iIndex, &bstrPW);
                if (bstrPW)
                {
                    LoginForm.GetPasswordEle()->put_value(bstrPW);
                    SysFreeString(bstrPW);
                    m_iRestoredIndex = iIndex;

                    // We restored this password. sink the SUBMIT for this form (if we haven't yet)
                    UserInput(pTextEle);
                }
            }
        }
        else
        {
            // User previously said 'no' to remembering passwords
            m_iRestoredIndex = -1;
        }
    }

    return S_OK;
}

HRESULT CIntelliForms::DeletePassword(LPCWSTR pwszUsername)
{
    // If we have a password, ask them if they want to delete it.
    if (LoadPasswords())
    {
        int iIndex;

        if (SUCCEEDED(FindPasswordEntry(pwszUsername, &iIndex)))
        {
            // If they previously said "no", delete without asking - they don't actually
            //  have a password saved
            // Otherwise, ask and delete only if they say "yes"
            FILETIME ft;
            if (FAILED(m_pslPasswords->GetStringTime(iIndex, &ft)) ||
                (0 == FILETIME_TO_INT(ft)) ||
                (IDYES == DialogBoxParam(MLGetHinst(), MAKEINTRESOURCE(IDD_AUTOSUGGEST_DELETEPASSWORD),
                                m_hwndBrowser, AutoSuggestDlgProc, IDD_AUTOSUGGEST_DELETEPASSWORD)))
            {
                // Delete username then password from string list
                if (SUCCEEDED(m_pslPasswords->DeleteString(iIndex-1)) &&
                    SUCCEEDED(m_pslPasswords->DeleteString(iIndex-1)))
                {
                    TraceMsg(TF_IFORMS, "Deleting password for user \"%ws\"", pwszUsername);
                    ASSERT(!(m_pslPasswords->NumStrings() & 1));

                    if (m_iRestoredIndex == iIndex)
                    {
                        m_iRestoredIndex = -1;
                    }
                    else if (m_iRestoredIndex > iIndex)
                    {
                        m_iRestoredIndex -= 2;
                    }

                    if (m_pslPasswords->NumStrings() == 0)
                    {
                        // No more strings for this url. Nuke it.
                        DeleteFromStore(GetUrl());
                        SetPasswordsAreSaved(FALSE);
                        delete m_pslPasswords;
                        m_pslPasswords = NULL;
                        ASSERT(m_iRestoredIndex == -1);
                    }
                    else
                    {
                        SavePasswords();
                    }
                }
            }
        }
    }

    return S_OK;
}

HRESULT CIntelliForms::SavePassword(IHTMLFormElement *pFormEle, FILETIME ftSubmit, IHTMLInputTextElement *pFirstEle)
{
    if (m_fRestrictedPW ||
        !IsEnabledRestorePW())
    {
        return S_FALSE;
    }

    BOOL fAskUser = TRUE;

    // First let's check for previously saved entries for this username
    if (LoadPasswords())
    {
        int iIndex;

        BSTR bstrUserName=NULL;

        pFirstEle->get_value(&bstrUserName);

        if (bstrUserName)
        {
            if (SUCCEEDED(FindPasswordEntry(bstrUserName, &iIndex)))
            {
                FILETIME ft;
                if (SUCCEEDED(m_pslPasswords->GetStringTime(iIndex, &ft)))
                {
                    if (FILETIME_TO_INT(ft) == 0)
                    {
                        // StringTime==0 means user previously said "no".
                        TraceMsg(TF_IFORMS, "IForms not asking about saving password");
                        fAskUser = FALSE;
                    }
                    else if (m_iRestoredIndex != iIndex)
                    {
                        // User previously said "yes" - but we didn't restore it for some reason
                        // Can happen with "back" button then submit
                        TraceMsg(TF_WARNING|TF_IFORMS, "IForms - user saved password and we didn't restore it");

                        // Write regkey in case that was the problem - we'll work next time
                        SetPasswordsAreSaved(TRUE);
                        m_iRestoredIndex = iIndex;
                    }
                }
            }
            else
            {
                m_iRestoredIndex = -1;
            }

            SysFreeString(bstrUserName);
        }
    }

    // Then lets ask the user if they'd like to save the password for this username
    if (fAskUser)
    {
        CDetectLoginForm LoginForm;

        // See if this is a valid form: One plain text input, One password input. Find the fields.

        if (SUCCEEDED(LoginForm.ParseForm(pFormEle, FALSE)))
        {
            TraceMsg(TF_IFORMS, "IForms Successfully detected 'save password' form");
            BSTR bstrUsername=NULL;
            BSTR bstrPassword=NULL;

            LoginForm.GetNameEle()->get_value(&bstrUsername);
            LoginForm.GetPasswordEle()->get_value(&bstrPassword);

            if (bstrUsername && bstrPassword)
            {
                if (m_iRestoredIndex != -1)
                {
                    // We have a previously saved password. See if our current entry is the same.
                    if (!StrCmpW(bstrPassword, m_pslPasswords->GetString(m_iRestoredIndex)))
                    {
                        // They're the same... nothing to do...
                        TraceMsg(TF_IFORMS, "IForms - user entered PW same as saved PW - nothing to do");
                        // Check to see that the username case is the same, just to be sure
                        if (StrCmpW(bstrUsername, m_pslPasswords->GetString(m_iRestoredIndex-1)))
                        {
                            TraceMsg(TF_IFORMS, "IForms - except change the username's case");
                            if (SUCCEEDED(m_pslPasswords->ReplaceString(m_iRestoredIndex-1, bstrUsername)))
                            {
                                SavePasswords();
                            }
                            else
                            {
                                // Something went horribly wrong!
                                delete m_pslPasswords;
                                m_pslPasswords=NULL;
                            }
                        }
                    }
                    else
                    {
                        // Ask the user if we want to change the saved password
                        INT_PTR iMB;

                        EnterModalDialog();

                        iMB = DialogBoxParam(MLGetHinst(), MAKEINTRESOURCE(IDD_AUTOSUGGEST_CHANGEPASSWORD),
                                    m_hwndBrowser, AutoSuggestDlgProc, IDD_AUTOSUGGEST_CHANGEPASSWORD);

                        if (IDYES == iMB)
                        {
                            // Delete the old one and add the new one. Update filetimes.
                            if (SUCCEEDED(m_pslPasswords->ReplaceString(m_iRestoredIndex, bstrPassword)))
                            {
                                m_pslPasswords->SetStringTime(m_iRestoredIndex, ftSubmit);
                                SavePasswords();
                                TraceMsg(TF_IFORMS, "IForms successfully saved changed password");
                            }
                            else
                            {
                                TraceMsg(TF_IFORMS|TF_WARNING, "IForms couldn't change password!");
                                delete m_pslPasswords;
                                m_pslPasswords = NULL;
                            }
                        }

                        LeaveModalDialog();
                    }
                }
                else
                {
                    // We don't have a previously saved password for this user. See if they want to save it.
                    // If the password is empty, don't bother asking or saving
                    if (IsEnabledAskPW() && bstrPassword[0])
                    {
                        EnterModalDialog();

                        INT_PTR iMB = DialogBoxParam(MLGetHinst(), MAKEINTRESOURCE(IDD_AUTOSUGGEST_SAVEPASSWORD),
                                        m_hwndBrowser, AutoSuggestDlgProc, IDD_AUTOSUGGEST_SAVEPASSWORD);

                        // If we can't load passwords, then create a new list
                        if (!LoadPasswords())
                        {
                            CStringList_New(&m_pslPasswords);
                            if (m_pslPasswords)
                                m_pslPasswords->SetListData(LIST_DATA_PASSWORD);
                        }

                        if (m_pslPasswords)
                        {
                            if ((IDCANCEL == iMB) || ((IDNO == iMB) && (!IsEnabledAskPW())))
                            {
                                // If they hit the close box or said "no" and checked "don't ask",
                                //  don't even save the username; we may ask them again next time
                            }
                            else
                            {
                                if (IDYES != iMB)
                                {
                                    // User said "no" but we save the username (no password) and
                                    //   set filetime to 0 which means they said "no"
                                    bstrPassword[0] = L'\0';
                                    ftSubmit.dwLowDateTime = ftSubmit.dwHighDateTime = 0;
                                }
                                else
                                {
                                    TraceMsg(TF_IFORMS, "IForms saving password for user %ws", bstrUsername);
                                }

                                m_pslPasswords->SetAutoScavenge(FALSE);

                                // Save the username and password, or just the username if they said "no"
                                if (SUCCEEDED(m_pslPasswords->AppendString(bstrUsername, ftSubmit)) &&
                                    SUCCEEDED(m_pslPasswords->AppendString(bstrPassword, ftSubmit)))
                                {
                                    SavePasswords();
                                }
                                else
                                {
                                    TraceMsg(TF_WARNING, "IForms couldn't save username/password");
                                    delete m_pslPasswords;
                                    m_pslPasswords=NULL;
                                }
                            }
                        }

                        LeaveModalDialog();
                    }
                }
            }

            SysFreeString(bstrUsername);
            SysFreeString(bstrPassword);
        } // if (SUCCEEDED(ParseForm()))
    }

    return S_OK;
}

// Returns reference to password string list if present. Return value must be used
//  immediately and not destroyed. Used only by CEnumString.
HRESULT CIntelliForms::GetPasswordStringList(CStringList **ppslPasswords)
{
    if (LoadPasswords())
    {
        *ppslPasswords = m_pslPasswords;
        return S_OK;
    }

    *ppslPasswords = NULL;
    return E_FAIL;
}


HRESULT CIntelliForms::CreatePStore()
{
    if (!m_pPStore)
    {
        if (!m_hinstPStore)
        {
            m_hinstPStore = LoadLibrary(TEXT("PSTOREC.DLL"));
        }

        if (m_hinstPStore)
        {
            HRESULT (* pfn)(IPStore **, PST_PROVIDERID *, void *, DWORD) = NULL;

            *(FARPROC *)&pfn = GetProcAddress(m_hinstPStore, "PStoreCreateInstance");

            if (pfn)
            {
                pfn(&m_pPStore, NULL, NULL, 0);
            }
        }
    }

    return m_pPStore ? S_OK : E_FAIL;
}

void CIntelliForms::ReleasePStore()
{
    SAFERELEASE(m_pPStore);
    if (m_hinstPStore)
    {
        FreeLibrary(m_hinstPStore);
        m_hinstPStore = NULL;
    }

    m_fPStoreTypeInit=FALSE;
}

HRESULT CIntelliForms::CreatePStoreAndType()
{
    HRESULT hr;

    hr = CreatePStore();

    if (SUCCEEDED(hr) && !m_fPStoreTypeInit)
    {
        PST_TYPEINFO    typeInfo;

        typeInfo.cbSize = sizeof(typeInfo);
        typeInfo.szDisplayName = (LPTSTR)c_szIntelliForms;

        hr = m_pPStore->CreateType(PST_KEY_CURRENT_USER, &c_PStoreType, &typeInfo, 0);

        if (hr == PST_E_TYPE_EXISTS)
        {
            hr = S_OK;
        }

        if (SUCCEEDED(hr))
        {
            hr = m_pPStore->CreateSubtype(PST_KEY_CURRENT_USER, &c_PStoreType, &m_guidUserId, &typeInfo, NULL, 0);

            if (hr == PST_E_TYPE_EXISTS)
            {
                hr = S_OK;
            }
        }

        if (SUCCEEDED(hr))
        {
            m_fPStoreTypeInit = TRUE;
        }
    }

    return hr;
}

const WCHAR c_szBlob1Value[] = L"StringIndex";
const WCHAR c_szBlob2Value[] = L"StringData";

HRESULT CIntelliForms::WriteToStore(LPCWSTR pwszName, CStringList *psl)
{
    HRESULT hr = E_FAIL;

    TraceMsg(TF_IFORMS, "+WriteToStore");

    if (SUCCEEDED(CreatePStoreAndType()))
    {
        LPBYTE pBlob1, pBlob2;
        DWORD  cbBlob1, cbBlob2;

        if (SUCCEEDED(psl->WriteToBlobs(&pBlob1, &cbBlob1, &pBlob2, &cbBlob2)))
        {
            PST_PROMPTINFO  promptInfo;

            promptInfo.cbSize = sizeof(promptInfo);
            promptInfo.dwPromptFlags = 0;
            promptInfo.hwndApp = NULL;
            promptInfo.szPrompt = NULL;

            LPWSTR pwszValue;

            int iValLen = lstrlenW(c_szBlob1Value) + lstrlenW(pwszName) + 10;

            pwszValue = (LPWSTR) LocalAlloc(LMEM_FIXED, iValLen * sizeof(WCHAR));

            if (pwszValue)
            {
                // Write Index
                wnsprintfW(pwszValue, iValLen, L"%s:%s", pwszName, c_szBlob1Value);
                hr = m_pPStore->WriteItem(PST_KEY_CURRENT_USER, &c_PStoreType, &m_guidUserId, pwszValue,
                                        cbBlob1,
                                        pBlob1,
                                        &promptInfo, PST_CF_NONE, 0);

                if (FAILED(hr))
                {
                    TraceMsg(TF_WARNING | TF_IFORMS, "Failure writing Blob1 (Index).  hr=%x", hr);
                }
                else
                {
                    // Wrote Index successfully.  Write data.
                    wnsprintfW(pwszValue, iValLen, L"%s:%s", pwszName, c_szBlob2Value);
                    hr = m_pPStore->WriteItem(PST_KEY_CURRENT_USER, &c_PStoreType, &m_guidUserId, pwszValue,
                                            cbBlob2,
                                            pBlob2,
                                            &promptInfo, PST_CF_NONE, 0);
                    if (FAILED(hr))
                    {
                        // IE6#16676: This call failed on 64-bit Windows.  Added a warning trace here to facilitate
                        // future debugging.
                        TraceMsg(TF_WARNING | TF_IFORMS, "Failure writing Blob2 (Data).  hr=%x", hr);
                    }
                }

                // If *either* WriteItem failed, we really need to delete both the Index and the Data.
                //
                if (FAILED(hr))
                {
                    // Delete bogus Blobs

                    // Delete Index Blob
                    wnsprintfW(pwszValue, iValLen, L"%s:%s", pwszName, c_szBlob1Value);
                    if (FAILED(m_pPStore->DeleteItem(PST_KEY_CURRENT_USER, &c_PStoreType, &m_guidUserId, pwszValue, &promptInfo, 0)))
                    {
                        TraceMsg(TF_ERROR | TF_IFORMS, "Failure deleting Blob1 (Index).  hr=%x", hr);
                    }

                    // Delete Data Blob
                    wnsprintfW(pwszValue, iValLen, L"%s:%s", pwszName, c_szBlob2Value);
                    if (FAILED(m_pPStore->DeleteItem(PST_KEY_CURRENT_USER, &c_PStoreType, &m_guidUserId, pwszValue, &promptInfo, 0)))
                    {
                        TraceMsg(TF_ERROR | TF_IFORMS, "Failure deleting Blob2 (Data).  hr=%x", hr);
                    }
                }

                LocalFree(pwszValue);
                pwszValue = NULL;
            }

            if (pBlob1)
            {
                LocalFree(pBlob1);
                pBlob1 = NULL;
            }

            if (pBlob2)
            {
                LocalFree(pBlob2);
                pBlob2 = NULL;
            }
        }
    }

    TraceMsg(TF_IFORMS, "-WriteToStore");

    return hr;
}

HRESULT CIntelliForms::ReadFromStore(LPCWSTR pwszName, CStringList **ppsl, BOOL fPasswordList/*=FALSE*/)
{
    HRESULT hr = E_FAIL;

    TraceMsg(TF_IFORMS, "+ReadFromStore");

    *ppsl=NULL;

    if (SUCCEEDED(CreatePStore()))
    {
        PST_PROMPTINFO  promptInfo;

        promptInfo.cbSize = sizeof(promptInfo);
        promptInfo.dwPromptFlags = 0;
        promptInfo.hwndApp = NULL;
        promptInfo.szPrompt = NULL;

        LPWSTR pwszValue;

        int iValLen = lstrlenW(c_szBlob1Value) + lstrlenW(pwszName) + 10;

        pwszValue = (LPWSTR) LocalAlloc(LMEM_FIXED, iValLen * sizeof(WCHAR));

        if (pwszValue)
        {
            DWORD dwBlob1Size, dwBlob2Size;
            LPBYTE pBlob1=NULL, pBlob2=NULL;

            wnsprintfW(pwszValue, iValLen, L"%s:%s", pwszName, c_szBlob1Value);
            hr = m_pPStore->ReadItem(PST_KEY_CURRENT_USER, &c_PStoreType, &m_guidUserId, pwszValue,
                                    &dwBlob1Size,
                                    &pBlob1,
                                    &promptInfo, 0);

            if (SUCCEEDED(hr))
            {
                wnsprintfW(pwszValue, iValLen, L"%s:%s", pwszName, c_szBlob2Value);
                hr = m_pPStore->ReadItem(PST_KEY_CURRENT_USER, &c_PStoreType, &m_guidUserId, pwszValue,
                                        &dwBlob2Size,
                                        &pBlob2,
                                        &promptInfo, 0);

                if (SUCCEEDED(hr))
                {
                    // bogus... have to reallocate here... bogus... bogus...
                    LPBYTE pBlob1b, pBlob2b;

                    pBlob1b=(LPBYTE)LocalAlloc(LMEM_FIXED, dwBlob1Size);
                    pBlob2b=(LPBYTE)LocalAlloc(LMEM_FIXED, dwBlob2Size);

                    if (pBlob1b && pBlob2b)
                    {
                        memcpy(pBlob1b, pBlob1, dwBlob1Size);
                        memcpy(pBlob2b, pBlob2, dwBlob2Size);

                        CStringList_New(ppsl);
                        if (*ppsl)
                        {
                            hr = (*ppsl)->ReadFromBlobs(&pBlob1b, dwBlob1Size, &pBlob2b, dwBlob2Size);

                            if (SUCCEEDED(hr))
                            {
                                INT64 i;

                                if (FAILED((*ppsl)->GetListData(&i)) ||
                                    ((fPasswordList && !(i & LIST_DATA_PASSWORD)) ||
                                     (!fPasswordList && (i & LIST_DATA_PASSWORD))))
                                {
                                    TraceMsg(TF_WARNING|TF_IFORMS, "IForms: Password/nonpassword lists mixed up");
                                    hr = E_FAIL;    // don't allow malicious site to access PW data
                                }
                            }

                            if (FAILED(hr))
                            {
                                delete *ppsl;
                                *ppsl=NULL;
                            }
                        }
                    }
                    else
                    {
                        if (pBlob1b)
                        {
                            LocalFree(pBlob1b);
                            pBlob1b = NULL;
                        }

                        if (pBlob2b)
                        {
                            LocalFree(pBlob2b);
                            pBlob2b = NULL;
                        }
                    }
                }
                else
                {
                    TraceMsg(TF_IFORMS, "Failed reading Blob2.  hr=%x", hr);
                }
            }
            else
            {
                TraceMsg(TF_IFORMS, "Failed reading Blob1.  hr=%x", hr);
            }

            LocalFree(pwszValue);
            pwszValue = NULL;

            if (pBlob1)
            {
                CoTaskMemFree(pBlob1);
                pBlob1 = NULL;
            }

            if (pBlob2)
            {
                CoTaskMemFree(pBlob2);
                pBlob2 = NULL;
            }
        }
    }

    TraceMsg(TF_IFORMS, "-ReadFromStore");

    return hr;
}

HRESULT CIntelliForms::DeleteFromStore(LPCWSTR pwszName)
{
    HRESULT hr=E_FAIL;

    if (SUCCEEDED(CreatePStore()))
    {
        HRESULT hr1, hr2;
        LPWSTR pwszValue;

        int iValLen = lstrlenW(c_szBlob1Value) + lstrlenW(pwszName) + 10;

        pwszValue = (LPWSTR) LocalAlloc(LMEM_FIXED, iValLen * sizeof(WCHAR));

        if (pwszValue)
        {
            PST_PROMPTINFO  promptInfo;

            promptInfo.cbSize = sizeof(promptInfo);
            promptInfo.dwPromptFlags = 0;
            promptInfo.hwndApp = NULL;
            promptInfo.szPrompt = NULL;

            wnsprintfW(pwszValue, iValLen, L"%s:%s", pwszName, c_szBlob1Value);
            hr1 = m_pPStore->DeleteItem(PST_KEY_CURRENT_USER, &c_PStoreType, &m_guidUserId, pwszValue, &promptInfo, 0);

            wnsprintfW(pwszValue, iValLen, L"%s:%s", pwszName, c_szBlob2Value);
            hr2 = m_pPStore->DeleteItem(PST_KEY_CURRENT_USER, &c_PStoreType, &m_guidUserId, pwszValue, &promptInfo, 0);

            if (SUCCEEDED(hr1) && SUCCEEDED(hr2))
            {
                hr = S_OK;
            }

            LocalFree(pwszValue);
            pwszValue = NULL;
        }
    }

    return hr;
}


const int c_iEnumSize=256;

HRESULT CIntelliForms::ClearStore(DWORD dwClear)
{
    BOOL fReleasePStore = (m_pPStore == NULL);

    ASSERT(dwClear <= 2);

    if (dwClear > 2)
    {
        return E_INVALIDARG;
    }

    if (SUCCEEDED(CreatePStoreAndType()))
    {
        IEnumPStoreItems *pEnumItems;
        ULONG cFetched=0;

        do
        {
            if (SUCCEEDED(m_pPStore->EnumItems(PST_KEY_CURRENT_USER, &c_PStoreType, &m_guidUserId, 0, &pEnumItems)))
            {
                LPWSTR pwszName[c_iEnumSize];
                PST_PROMPTINFO  promptInfo;

                promptInfo.cbSize = sizeof(promptInfo);
                promptInfo.dwPromptFlags = 0;
                promptInfo.hwndApp = NULL;
                promptInfo.szPrompt = NULL;

                // Enumerator doesn't keep its state - deleting items while we enumerate makes us
                //  miss some. It does support celt>1... but returns failure codes when it succeeds.
                cFetched = 0;

                pEnumItems->Next(c_iEnumSize, pwszName, &cFetched);

                if (cFetched)
                {
                    for (ULONG i=0; i<cFetched; i++)
                    {
                        ASSERT(pwszName[i]);
                        if (pwszName[i])
                        {
                            BOOL fDelete = TRUE;

                            // Hack to work around PStore string-case bug: first take their
                            //  enum value literally, then convert to lowercase and do it
                            //  again; IE5.0 #71001
                            for (int iHack=0; iHack<2; iHack++)
                            {
                                if (iHack == 1)
                                {
                                    // Convert the pwszName[i] to lowercase... only before
                                    //  the colon...
                                    WCHAR *pwch = StrRChrW(pwszName[i], NULL, L':');
                                    if (pwch)
                                    {
                                        *pwch = L'\0';
                                        MyToLower(pwszName[i]);
                                        *pwch = L':';
                                    }
                                    else
                                        break;
                                }

                                if (dwClear != IECMDID_ARG_CLEAR_FORMS_ALL)
                                {
                                    fDelete = FALSE;

                                    // See if this is a password item or not
                                    // This is pretty annoying. Since our string lists are split
                                    //  into two blobs, we need to find out which one this is and
                                    //  load the index for it.
                                    WCHAR *pwch = StrRChrW(pwszName[i], NULL, L':');
                                    if (pwch)
                                    {
                                        LPWSTR pwszIndexName=NULL;
                                        if (!StrCmpCW(pwch+1, c_szBlob2Value))
                                        {
                                            int iSize = sizeof(WCHAR) * (lstrlenW(pwszName[i])+10);
                                            pwszIndexName = (LPWSTR) LocalAlloc(LMEM_FIXED, iSize);
                                            if (pwszIndexName)
                                            {
                                                *pwch = L'\0';
                                                wnsprintfW(pwszIndexName, iSize, L"%s:%s", pwszName[i], c_szBlob1Value);
                                                *pwch = L':';
                                            }
                                        }

                                        DWORD dwBlob1Size;
                                        LPBYTE pBlob1=NULL;
                                        INT64 iFlags;

                                        if (SUCCEEDED(m_pPStore->ReadItem(
                                                PST_KEY_CURRENT_USER,
                                                &c_PStoreType, &m_guidUserId,
                                                (pwszIndexName) ? pwszIndexName : pwszName[i],
                                                &dwBlob1Size,
                                                &pBlob1,
                                                &promptInfo, 0)) && pBlob1)
                                        {
                                            if (SUCCEEDED(CStringList::GetFlagsFromIndex(pBlob1, &iFlags)))
                                            {
                                                if (((iFlags & LIST_DATA_PASSWORD) && (dwClear == IECMDID_ARG_CLEAR_FORMS_PASSWORDS_ONLY)) ||
                                                    (!(iFlags & LIST_DATA_PASSWORD) && (dwClear == IECMDID_ARG_CLEAR_FORMS_ALL_BUT_PASSWORDS)))
                                                {
                                                    // Delete this item
                                                    fDelete = TRUE;
                                                }
                                            }

                                            CoTaskMemFree(pBlob1);
                                        }
                                        else
                                        {
                                            // The index is already deleted
                                            fDelete = TRUE;
                                        }

                                        if (pwszIndexName)
                                        {
                                            LocalFree(pwszIndexName);
                                            pwszIndexName = NULL;
                                        }
                                    }
                                } // if (dwClear != CLEAR_INTELLIFORMS_ALL)

                                if (fDelete)
                                {
                                    m_pPStore->DeleteItem(PST_KEY_CURRENT_USER, &c_PStoreType, &m_guidUserId, pwszName[i], &promptInfo, 0);
                                }
                            } // for (iHack)

                            CoTaskMemFree(pwszName[i]);
                        } // if (pwszName[i])
                    }
                }

                pEnumItems->Release();
            }
        }
        while (cFetched == c_iEnumSize);  // In case we didn't cover everything in one pass

        if (dwClear == IECMDID_ARG_CLEAR_FORMS_ALL)
        {
            m_pPStore->DeleteSubtype(PST_KEY_CURRENT_USER, &c_PStoreType, &m_guidUserId, 0);
            m_pPStore->DeleteType(PST_KEY_CURRENT_USER, &c_PStoreType, 0);
        }

        if ((dwClear == IECMDID_ARG_CLEAR_FORMS_ALL) ||
            (dwClear == IECMDID_ARG_CLEAR_FORMS_PASSWORDS_ONLY))
        {
            // Delete the urlhash key storing which urls we have passwords saved for
            SHDeleteKey(HKEY_CURRENT_USER, c_szRegKeyIntelliForms);
        }

        TraceMsg(TF_IFORMS, "IForms: ClearStore cleared at least %d entries", cFetched);
    }

    if (fReleasePStore)
    {
        ReleasePStore();
    }

    return S_OK;
}

// static: Get the name from an input element - uses VCARD_NAME attribute if present.
HRESULT CIntelliForms::GetName(IHTMLInputTextElement *pTextEle, BSTR *pbstrName)
{
    IHTMLElement *pEle=NULL;

    *pbstrName = NULL;

    pTextEle->QueryInterface(IID_IHTMLElement, (void **)&pEle);

    if (pEle)
    {
        BSTR bstrAttr = SysAllocString(L"VCARD_NAME");

        if (bstrAttr)
        {
            VARIANT var;
            var.vt = VT_EMPTY;

            pEle->getAttribute(bstrAttr, 0, &var);

            if (var.vt == VT_BSTR && var.bstrVal)
            {
                *pbstrName = var.bstrVal;
            }
            else
            {
                VariantClear(&var);
            }

            SysFreeString(bstrAttr);
        }

        pEle->Release();
    }

    if (!*pbstrName)
    {
        pTextEle->get_name(pbstrName);
    }

    // Convert the name to lowercase
    if (*pbstrName)
    {
        // Call "MyToLower" instead
        if (g_fRunningOnNT)
        {
            CharLowerBuffW(*pbstrName, lstrlenW(*pbstrName));
        }
        else
        {
            // Ideally we would use the code page contained in the string instead of
            //  the system code page.
            CHAR chBuf[MAX_PATH];
            SHUnicodeToAnsi(*pbstrName, chBuf, ARRAYSIZE(chBuf));
            CharLowerBuffA(chBuf, lstrlenA(chBuf));
            SHAnsiToUnicode(chBuf, *pbstrName, SysStringLen(*pbstrName)+1);
        }
    }

    return (*pbstrName) ? S_OK : E_FAIL;
}

// Called when script calls window.external.AutoCompleteSaveForm
HRESULT CIntelliForms::ScriptSubmit(IHTMLFormElement *pForm)
{
    HRESULT hr = E_FAIL;

    if (pForm)
    {
        hr = HandleFormSubmit(pForm);
    }

    return SUCCEEDED(hr) ? S_OK : S_FALSE;
}


// Called when user changes a text field. Mark it "dirty" and sink submit event for form
HRESULT CIntelliForms::UserInput(IHTMLInputTextElement *pTextEle)
{
    AddToElementList(pTextEle);

    IHTMLFormElement *pForm=NULL;
    pTextEle->get_form(&pForm);

    if (pForm)
    {
        if (S_OK == AddToFormList(pForm))
        {
            AttachToForm(pForm);
        }

        pForm->Release();
    }
    else
    {
        TraceMsg(TF_WARNING|TF_IFORMS, "Iforms: pITE->get_form() returned NULL!");
    }


    return S_OK;
}

HRESULT CIntelliForms::AddToElementList(IHTMLInputTextElement *pITE)
{
    if (m_hdpaElements)
    {
        if (SUCCEEDED(FindInElementList(pITE)))
        {
            return S_FALSE;
        }
    }
    else
    {
        m_hdpaElements = DPA_Create(4);
    }

    if (m_hdpaElements)
    {
        TraceMsg(TF_IFORMS, "CIntelliForms::AddToElementList adding");

        if (DPA_AppendPtr(m_hdpaElements, pITE) >= 0)
        {
            pITE->AddRef();
            return S_OK;
        }
    }

    return E_OUTOFMEMORY;
}

HRESULT CIntelliForms::FindInElementList(IHTMLInputTextElement *pITE)
{
    IUnknown *punk;
    HRESULT hr = E_FAIL;

    pITE->QueryInterface(IID_IUnknown, (void **)&punk);

    if (m_hdpaElements)
    {
        for (int i=DPA_GetPtrCount(m_hdpaElements)-1; i>=0; i--)
        {
            IUnknown *punk2;

            ((IUnknown *)DPA_FastGetPtr(m_hdpaElements, i))->QueryInterface(IID_IUnknown, (void **)&punk2);

            if (punk == punk2)
            {
                punk2->Release();
                break;
            }

            punk2->Release();
        }

        if (i >= 0)
        {
            hr = S_OK;
        }
    }

    punk->Release();

    return hr;
}

void CIntelliForms::FreeElementList()
{
    if (m_hdpaElements)
    {
        for (int i=DPA_GetPtrCount(m_hdpaElements)-1; i>=0; i--)
        {
            ((IUnknown *)(DPA_FastGetPtr(m_hdpaElements, i)))->Release();
        }

        DPA_Destroy(m_hdpaElements);
        m_hdpaElements=NULL;
    }
}

HRESULT CIntelliForms::AddToFormList(IHTMLFormElement *pFormEle)
{
    if (m_hdpaForms)
    {
        if (SUCCEEDED(FindInFormList(pFormEle)))
        {
            return S_FALSE;
        }
    }
    else
    {
        m_hdpaForms = DPA_Create(2);
    }

    if (m_hdpaForms)
    {
        if (DPA_AppendPtr(m_hdpaForms, pFormEle) >= 0)
        {
            TraceMsg(TF_IFORMS, "CIntelliForms::AddToFormList adding");

            pFormEle->AddRef();
            return S_OK;
        }
    }

    return E_OUTOFMEMORY;
}

HRESULT CIntelliForms::FindInFormList(IHTMLFormElement *pFormEle)
{
    IUnknown *punk;
    HRESULT hr = E_FAIL;

    pFormEle->QueryInterface(IID_IUnknown, (void **)&punk);

    if (m_hdpaForms)
    {
        for (int i=DPA_GetPtrCount(m_hdpaForms)-1; i>=0; i--)
        {
            IUnknown *punk2;

            ((IUnknown *)DPA_FastGetPtr(m_hdpaForms, i))->QueryInterface(IID_IUnknown, (void **)&punk2);

            if (punk == punk2)
            {
                punk2->Release();
                break;
            }

            punk2->Release();
        }

        if (i >= 0)
        {
            hr = S_OK;
        }
    }

    punk->Release();

    return hr;
}

void CIntelliForms::FreeFormList()
{
    if (m_hdpaForms)
    {
        for (int i=DPA_GetPtrCount(m_hdpaForms)-1; i>=0; i--)
        {
            ((IUnknown *)(DPA_FastGetPtr(m_hdpaForms, i)))->Release();
        }

        DPA_Destroy(m_hdpaForms);
        m_hdpaForms = NULL;
    }
}

//=========================================================================
//
// Event sinking class
//
//  We simply implement IDispatch and make a call into our parent when
//   we receive a sinked event.
//
//=========================================================================
CIntelliForms::CEventSink::CEventSink(CEventSinkCallback *pParent)
{
    TraceMsg(TF_IFORMS, "CIntelliForms::CEventSink::CEventSink");
    DllAddRef();
    m_cRef = 1;
    m_pParent = pParent;
}

CIntelliForms::CEventSink::~CEventSink()
{
    TraceMsg(TF_IFORMS, "CIntelliForms::CEventSink::~CEventSink");
    ASSERT( m_cRef == 0 );
    DllRelease();
}

STDMETHODIMP CIntelliForms::CEventSink::QueryInterface(REFIID riid, void **ppv)
{
    *ppv = NULL;

    if ((IID_IDispatch == riid) ||
        (IID_IUnknown == riid))
    {
        *ppv = (IDispatch *)this;
    }

    if (NULL != *ppv)
    {
        ((IUnknown *)*ppv)->AddRef();
        return S_OK;
    }

    return E_NOINTERFACE;
}

STDMETHODIMP_(ULONG) CIntelliForms::CEventSink::AddRef(void)
{
    return ++m_cRef;
}

STDMETHODIMP_(ULONG) CIntelliForms::CEventSink::Release(void)
{
    if (--m_cRef == 0)
    {
        delete this;
        return 0;
    }

    return m_cRef;
}

HRESULT CIntelliForms::CEventSink::SinkEvents(IHTMLElement2 *pEle2, int iNum, EVENTS *pEvents)
{
    VARIANT_BOOL bSuccess = VARIANT_TRUE;

    for (int i=0; i<iNum; i++)
    {
        BSTR bstrEvent = SysAllocString(CEventSinkCallback::EventsToSink[(int)(pEvents[i])].pwszEventSubscribe);

        if (bstrEvent)
        {
            pEle2->attachEvent(bstrEvent, (IDispatch *)this, &bSuccess);

            SysFreeString(bstrEvent);
        }
        else
        {
            bSuccess = VARIANT_FALSE;
        }

        if (!bSuccess)
            break;
    }

    return (bSuccess) ? S_OK : E_FAIL;
}

HRESULT CIntelliForms::CEventSink::SinkEvents(IHTMLWindow3 *pWin3, int iNum, EVENTS *pEvents)
{
    VARIANT_BOOL bSuccess = VARIANT_TRUE;

    for (int i=0; i<iNum; i++)
    {
        BSTR bstrEvent = SysAllocString(CEventSinkCallback::EventsToSink[(int)(pEvents[i])].pwszEventSubscribe);

        if (bstrEvent)
        {
            pWin3->attachEvent(bstrEvent, (IDispatch *)this, &bSuccess);

            SysFreeString(bstrEvent);
        }
        else
        {
            bSuccess = VARIANT_FALSE;
        }

        if (!bSuccess)
            break;
    }

    return (bSuccess) ? S_OK : E_FAIL;
}

HRESULT CIntelliForms::CEventSink::UnSinkEvents(IHTMLElement2 *pEle2, int iNum, EVENTS *pEvents)
{
    for (int i=0; i<iNum; i++)
    {
        BSTR bstrEvent = SysAllocString(CEventSinkCallback::EventsToSink[(int)(pEvents[i])].pwszEventSubscribe);

        if (bstrEvent)
        {
            pEle2->detachEvent(bstrEvent, (IDispatch *)this);

            SysFreeString(bstrEvent);
        }
    }

    return S_OK;
}

HRESULT CIntelliForms::CEventSink::UnSinkEvents(IHTMLWindow3 *pWin3, int iNum, EVENTS *pEvents)
{
    for (int i=0; i<iNum; i++)
    {
        BSTR bstrEvent = SysAllocString(CEventSinkCallback::EventsToSink[(int)(pEvents[i])].pwszEventSubscribe);

        if (bstrEvent)
        {
            pWin3->detachEvent(bstrEvent, (IDispatch *)this);

            SysFreeString(bstrEvent);
        }
    }

    return S_OK;
}

// IDispatch
STDMETHODIMP CIntelliForms::CEventSink::GetTypeInfoCount(UINT* /*pctinfo*/)
{
    return E_NOTIMPL;
}

STDMETHODIMP CIntelliForms::CEventSink::GetTypeInfo(/* [in] */ UINT /*iTInfo*/,
            /* [in] */ LCID /*lcid*/,
            /* [out] */ ITypeInfo** /*ppTInfo*/)
{
    return E_NOTIMPL;
}

STDMETHODIMP CIntelliForms::CEventSink::GetIDsOfNames(
                REFIID          riid,
                OLECHAR**       rgszNames,
                UINT            cNames,
                LCID            lcid,
                DISPID*         rgDispId)
{
    return E_NOTIMPL;
}

STDMETHODIMP CIntelliForms::CEventSink::Invoke(
            DISPID dispIdMember,
            REFIID, LCID,
            WORD wFlags,
            DISPPARAMS* pDispParams,
            VARIANT* pVarResult,
            EXCEPINFO*,
            UINT* puArgErr)
{
    if (m_pParent && pDispParams && pDispParams->cArgs>=1)
    {
        if (pDispParams->rgvarg[0].vt == VT_DISPATCH)
        {
            IHTMLEventObj *pObj=NULL;

            if (SUCCEEDED(pDispParams->rgvarg[0].pdispVal->QueryInterface(IID_IHTMLEventObj, (void **)&pObj) && pObj))
            {
                EVENTS Event=EVENT_BOGUS;
                BSTR bstrEvent=NULL;

                pObj->get_type(&bstrEvent);

                if (bstrEvent)
                {
                    for (int i=0; i<ARRAYSIZE(CEventSinkCallback::EventsToSink); i++)
                    {
                        if (!StrCmpCW(bstrEvent, CEventSinkCallback::EventsToSink[i].pwszEventName))
                        {
                            Event = (EVENTS) i;
                            break;
                        }
                    }

                    SysFreeString(bstrEvent);
                }

                if (Event != EVENT_BOGUS)
                {
                    IHTMLElement *pEle=NULL;

                    pObj->get_srcElement(&pEle);

                    // EVENT_SCROLL comes from our window so we won't have an
                    //  element for it
                    if (pEle || (Event == EVENT_SCROLL))
                    {
                        // Call the event handler here
                        m_pParent->HandleEvent(pEle, Event, pObj);

                        if (pEle)
                        {
                            pEle->Release();
                        }
                    }
                }

                pObj->Release();
            }
        }
    }

    return S_OK;
}

//=========================================================================
//
// Event sinking class
//
//  We implement IHTMLEditDesigner and make a call into our parent when
//   we receive any event.
//
//=========================================================================
CIntelliForms::CEditEventSink::CEditEventSink(CEditEventSinkCallback *pParent)
{
    TraceMsg(TF_IFORMS, "CIntelliForms::CEditEventSink::CEditEventSink");
    DllAddRef();
    m_cRef = 1;
    m_pParent = pParent;
}

CIntelliForms::CEditEventSink::~CEditEventSink()
{
    TraceMsg(TF_IFORMS, "CIntelliForms::CEditEventSink::~CEditEventSink");
    ASSERT(m_cRef == 0);
    ASSERT(!m_pEditServices);
    DllRelease();
}

STDMETHODIMP CIntelliForms::CEditEventSink::QueryInterface(REFIID riid, void **ppv)
{
    if ((IID_IHTMLEditDesigner == riid) ||
        (IID_IUnknown == riid))
    {
        *ppv = SAFECAST(this, IHTMLEditDesigner *);
    }
    else
    {
        *ppv = NULL;
        return E_NOINTERFACE;
    }

    AddRef();
    return S_OK;
}

STDMETHODIMP_(ULONG) CIntelliForms::CEditEventSink::AddRef(void)
{
    return ++m_cRef;
}

STDMETHODIMP_(ULONG) CIntelliForms::CEditEventSink::Release(void)
{
    if (--m_cRef == 0)
    {
        delete this;
        return 0;
    }

    return m_cRef;
}

HRESULT CIntelliForms::CEditEventSink::Attach(IUnknown *punkElement)
{
    HRESULT hr = S_OK;

    // Detach from any existing element
    if (m_pEditServices)
    {
        m_pEditServices->RemoveDesigner(this);
        m_pEditServices->Release();
        m_pEditServices = NULL;
    }

    // Attach to any new element
    if (punkElement)
    {
        hr = E_FAIL;

        IHTMLDocument2 *pDoc2 = NULL;
        GetStuffFromEle(punkElement, NULL, &pDoc2);

        if (pDoc2)
        {
            IServiceProvider *pSP = NULL;

            pDoc2->QueryInterface(IID_IServiceProvider, (void **)&pSP);

            if (pSP)
            {
                pSP->QueryService(SID_SHTMLEditServices, IID_IHTMLEditServices, (void **)&m_pEditServices);
                pSP->Release();
            }

            if (m_pEditServices)
            {
                hr = m_pEditServices->AddDesigner(this);
            }

            pDoc2->Release();
        }
    }

    return hr;
}

HRESULT CIntelliForms::CEditEventSink::PreHandleEvent(DISPID inEvtDispId, IHTMLEventObj *pIEventObj)
{
    if (m_pParent)
    {
        return m_pParent->PreHandleEvent(inEvtDispId, pIEventObj);
    }

    return S_FALSE;
}

HRESULT CIntelliForms::CEditEventSink::PostHandleEvent(DISPID inEvtDispId, IHTMLEventObj *pIEventObj)
{
    return S_FALSE;
}

HRESULT CIntelliForms::CEditEventSink::TranslateAccelerator(DISPID inEvtDispId, IHTMLEventObj *pIEventObj)
{
    return S_FALSE;
}


//=========================================================================
//
// AutoSuggest class
//
// Handles connecting and disconnecting the AutoComplete object, as well
//  as translating between Trident OM and Edit window messages
//=========================================================================

CIntelliForms::CAutoSuggest::CAutoSuggest(CIntelliForms *pParent, BOOL fEnabled, BOOL fEnabledPW)
{
    TraceMsg(TF_IFORMS, "CIntelliForms::CAutoSuggest::CAutoSuggest");

    m_pParent = pParent;
    m_fEnabled = fEnabled;
    m_fEnabledPW = fEnabledPW;

    ASSERT(m_pEventSink == NULL);
    ASSERT(m_pAutoComplete == NULL);
    ASSERT(m_hwndEdit == NULL);
    ASSERT(m_pTextEle == NULL);

    //
    // bug 81414 : To avoid clashing with app messages used by the edit window, we 
    // use registered messages.
    //
    m_uMsgItemActivate = RegisterWindowMessageA("AC_ItemActivate");
    if (m_uMsgItemActivate == 0)
    {
        m_uMsgItemActivate = WM_APP + 301;
    }

    // Register our window class if necessary
    if (!s_fRegisteredWndClass)
    {
        s_fRegisteredWndClass = TRUE;

        WNDCLASSEXW wndclass =
        {
            sizeof(WNDCLASSEX),
            0,
            CIntelliForms::CAutoSuggest::WndProc,
            0,
            sizeof(DWORD_PTR),
            g_hinst,
            NULL,
            NULL,
            NULL,
            NULL,
            c_szEditWndClass
        };

        if (!RegisterClassEx(&wndclass))
        {
            TraceMsg(TF_IFORMS, "Intelliforms failed to register wnd class!");
        }
    }
}

CIntelliForms::CAutoSuggest::~CAutoSuggest()
{
    TraceMsg(TF_IFORMS, "CIntelliForms::CAutoSuggest::~CAutoSuggest");
    CleanUp();
}

HRESULT CIntelliForms::CAutoSuggest::CleanUp()
{
    SetParent(NULL);
    DetachFromInput();

    return S_OK;
}

// List of all events we sink for an individual INPUT tag
// post IE5.5 we can use CEditEventSink instead of CEventSink for all of these events.
CEventSinkCallback::EVENTS CIntelliForms::CAutoSuggest::s_EventsToSink[] =
        {
            EVENT_KEYPRESS,
            EVENT_KEYDOWN,
            EVENT_MOUSEDOWN,
            EVENT_DBLCLICK,
            EVENT_FOCUS,
            EVENT_BLUR,
        };

HRESULT CIntelliForms::CAutoSuggest::AttachToInput(IHTMLInputTextElement *pTextEle)
{
    HRESULT hr;

    TraceMsg(TF_IFORMS, "CIntelliForms::CAutoSuggest::AttachToInput");

    if (!pTextEle)
        return E_INVALIDARG;

    hr = DetachFromInput();

    if (SUCCEEDED(hr))
    {
        m_pTextEle = pTextEle;
        pTextEle->AddRef();

        if (!m_pEventSink)
        {
            m_pEventSink = new CEventSink(this);

            if (!m_pEventSink)
            {
                hr = E_OUTOFMEMORY;
            }
        }

        if (SUCCEEDED(hr))
        {
            // Hook up our event sink
            IHTMLElement2 *pEle2=NULL;

            hr = pTextEle->QueryInterface(IID_IHTMLElement2, (void **)&pEle2);

            if (pEle2)
            {
                hr = m_pEventSink->SinkEvents(pEle2, ARRAYSIZE(s_EventsToSink), s_EventsToSink);

                pEle2->Release();
            }
        }
    }

    if (FAILED(hr))
    {
        TraceMsg(TF_IFORMS, "IForms: AttachToInput failed");
        DetachFromInput();
    }

    return hr;
}

HRESULT CIntelliForms::CAutoSuggest::DetachFromInput()
{
    if (!m_pTextEle)
    {
        return S_FALSE;
    }

    TraceMsg(TF_IFORMS, "CIntelliForms::CAutoSuggest::DetachFromInput");

    // Auto Fill Password here, since we get ACTIVEELEMENT change before blur event
    BSTR bstrUsername=NULL;
    m_pTextEle->get_value(&bstrUsername);
    if (bstrUsername)
    {
        CheckAutoFillPassword(bstrUsername);
        SysFreeString(bstrUsername);
    }

    if (m_bstrLastUsername)
    {
        SysFreeString(m_bstrLastUsername);
        m_bstrLastUsername=NULL;
    }

    if (m_hwndEdit)
    {
        // This is for subclass wndproc
        SendMessage(m_hwndEdit, WM_KILLFOCUS, 0, 0);
    }

    if (m_pEnumString)
    {
        m_pEnumString->UnInit();
        m_pEnumString->Release();
        m_pEnumString = NULL;
    }

    if (m_pEventSink)
    {
        IHTMLElement2 *pEle2=NULL;

        m_pTextEle->QueryInterface(IID_IHTMLElement2, (void **)&pEle2);

        if (pEle2)
        {
            m_pEventSink->UnSinkEvents(pEle2, ARRAYSIZE(s_EventsToSink), s_EventsToSink);
            pEle2->Release();
        }

        m_pEventSink->SetParent(NULL);
        m_pEventSink->Release();
        m_pEventSink=NULL;
    }

    SAFERELEASE(m_pAutoComplete);
    SAFERELEASE(m_pAutoCompleteDD);

    if (m_hwndEdit)
    {
        DestroyWindow(m_hwndEdit);
        m_hwndEdit = NULL;
    }

    SAFERELEASE(m_pTextEle);

    m_fInitAutoComplete = FALSE;

    return S_OK;
}

// Creates autocomplete and string enumerator.
HRESULT CIntelliForms::CAutoSuggest::CreateAutoComplete()
{
    if (m_fInitAutoComplete)
    {
        return (m_pAutoCompleteDD != NULL) ? S_OK : E_FAIL;
    }

    HRESULT hr = S_OK;

    ASSERT(!m_hwndEdit && !m_pEnumString && !m_pAutoComplete && !m_pAutoCompleteDD);

    // Create the edit window
#ifndef UNIX
    m_hwndEdit = CreateWindowEx(0, c_szEditWndClass, TEXT("IntelliFormProxy"), WS_POPUP,
#else
    m_hwndEdit = CreateWindowEx(WS_EX_MW_UNMANAGED_WINDOW, c_szEditWndClass, TEXT("IntelliFormProxy"), WS_POPUP,
#endif
        300, 200, 200, 50, m_pParent->m_hwndBrowser, NULL, g_hinst, this);

    if (!m_hwndEdit)
    {
        hr = E_OUTOFMEMORY;
    }

    if (SUCCEEDED(hr))
    {
        // Create our enumerator
        m_pEnumString = new CEnumString();

        if (m_pEnumString)
        {
            m_pEnumString->Init(m_pTextEle, m_pParent);

            // Create the AutoComplete Object
            if (!m_pAutoComplete)
            {
                hr = CoCreateInstance(CLSID_AutoComplete, NULL, CLSCTX_INPROC_SERVER, IID_IAutoComplete2, (void **)&m_pAutoComplete);
                if (m_pAutoComplete)
                {
                    m_pAutoComplete->QueryInterface(IID_IAutoCompleteDropDown, (void **)&m_pAutoCompleteDD);
                    if (!m_pAutoCompleteDD)
                    {
                        SAFERELEASE(m_pAutoComplete);
                    }
                }
            }

            if (m_pAutoComplete)
            {
                hr = m_pAutoComplete->Init(m_hwndEdit, (IUnknown *) m_pEnumString, NULL, NULL);

                DWORD dwOptions = ACO_AUTOSUGGEST | ACO_UPDOWNKEYDROPSLIST;

                // Add the RTLREADING option to the dropdown, if the element is RTL
                BSTR bstrDir = NULL;

                IHTMLElement2 *pEle2=NULL;
                m_pTextEle->QueryInterface(IID_IHTMLElement2, (void **)&pEle2);
                if (pEle2)
                {
                    pEle2->get_dir(&bstrDir);
                    pEle2->Release();
                }

                if (bstrDir)
                {
                    if (!StrCmpIW(bstrDir, L"RTL"))
                    {
                        dwOptions |= ACO_RTLREADING;
                    }

                    SysFreeString(bstrDir);
                }

                m_pAutoComplete->SetOptions(dwOptions);
            }
        }
    }

    m_fInitAutoComplete = TRUE;

    ASSERT_MSG(SUCCEEDED(hr), "IForms: CreateAutoComplete failed");

    return hr;
}

void CIntelliForms::CAutoSuggest::CheckAutoFillPassword(LPCWSTR pwszUsername)
{
    // We don't autofill their password unless we know they've hit a key
    if (m_pParent && m_fEnabledPW && m_fAllowAutoFillPW)
    {
        if (m_bstrLastUsername && !StrCmpCW(pwszUsername, m_bstrLastUsername))
        {
            return;
        }

        SysFreeString(m_bstrLastUsername);
        m_bstrLastUsername = SysAllocString(pwszUsername);

        m_pParent->AutoFillPassword(m_pTextEle, pwszUsername);
    }
}

HRESULT GetScreenCoordinates(IUnknown *punkEle, HWND hwnd, long *plLeft, long *plTop, long *plWidth, long *plHeight)
{
    long lScreenLeft=0, lScreenTop=0;
    HRESULT hr = E_FAIL;

    *plLeft = *plTop = *plWidth = *plHeight = 0;

    IHTMLElement2 *pEle2;
    if (SUCCEEDED(punkEle->QueryInterface(IID_IHTMLElement2, (void **)&pEle2)) && pEle2)
    {
        IHTMLRect *pRect=NULL;

        if (SUCCEEDED(pEle2->getBoundingClientRect(&pRect)) && pRect)
        {
            IHTMLWindow2 *pWin2;

            long lLeft, lRight, lTop, lBottom;

            pRect->get_left(&lLeft);
            pRect->get_right(&lRight);
            pRect->get_top(&lTop);
            pRect->get_bottom(&lBottom);

            lBottom -= 2;           // put dropdown on top of edit box
            if (lBottom < lTop)
            {
                lBottom = lTop;
            }

            if (lTop >= 0 && lLeft >= 0)
            {
                GetStuffFromEle(punkEle, &pWin2, NULL);

                if (pWin2)
                {
                    IHTMLWindow3 *pWin3;
                    
                    if (SUCCEEDED(pWin2->QueryInterface(IID_IHTMLWindow3, (void **)&pWin3)) && pWin3)
                    {
                        IHTMLScreen *pScreen = NULL;
                        RECT rcBrowserWnd;

                        pWin3->get_screenLeft(&lScreenLeft);
                        pWin3->get_screenTop(&lScreenTop);

                        // GetClientRect & the screen_* APIs return document coordinates.
                        // We're position using device coordinates.
                        // Use document (currently 96DPI) and device resolutions & transform
                        pWin2->get_screen(&pScreen);
                        if (pScreen)
                        {
                            IHTMLScreen2 * pScreen2 = NULL;

                            if (SUCCEEDED(pScreen->QueryInterface(IID_IHTMLScreen2, (void **)&pScreen2)))
                            {
                                if (pScreen2)
                                {                        
                                    long xDeviceDPI, yDeviceDPI, xLogicalDPI, yLogicalDPI;

                                    pScreen2->get_deviceXDPI(&xDeviceDPI);
                                    pScreen2->get_deviceYDPI(&yDeviceDPI);
                                    pScreen2->get_logicalXDPI(&xLogicalDPI);
                                    pScreen2->get_logicalYDPI(&yLogicalDPI);

                                    lBottom     = (lBottom * yDeviceDPI) / yLogicalDPI;
                                    lTop        = (lTop * yDeviceDPI) / yLogicalDPI;
                                    lScreenTop  = (lScreenTop * yDeviceDPI) / yLogicalDPI;
                                    lLeft       = (lLeft * xDeviceDPI) / xLogicalDPI;
                                    lRight      = (lRight * xDeviceDPI) / xLogicalDPI;
                                    lScreenLeft = (lScreenLeft * xDeviceDPI) / xLogicalDPI;
                                    pScreen2->Release();
                                }
                            }

                            pScreen->Release();
                        }

                        if (GetWindowRect(hwnd, &rcBrowserWnd))
                        {
                            // Clip the right edge to the window
                            if (lRight+lScreenLeft > rcBrowserWnd.right)
                            {
                                lRight = rcBrowserWnd.right - lScreenLeft;
                            }

                            *plLeft = lScreenLeft + lLeft;
                            *plWidth = lRight-lLeft;
                            *plTop = lScreenTop + lTop;
                            *plHeight = lBottom-lTop;

                            hr = S_OK;

                            if (*plWidth < MINIMUM_WIDTH)
                            {
                                // Primitive minimum width for now
                                *plWidth = MINIMUM_WIDTH;
                            }
                        }

                        pWin3->Release();
                    }

                    pWin2->Release();
                }
            }

            pRect->Release();
        }
        pEle2->Release();
    }

    return hr;
}

HRESULT CIntelliForms::CAutoSuggest::UpdateDropdownPosition()
{
    if (m_pTextEle && m_pParent && m_hwndEdit)
    {
        long lLeft, lTop, lWidth, lHeight;

        if (SUCCEEDED(GetScreenCoordinates(m_pTextEle, m_pParent->m_hwndBrowser, &lLeft, &lTop, &lWidth, &lHeight)))
        {
            MoveWindow(m_hwndEdit, lLeft, lTop, lWidth, lHeight, FALSE);
        }
        else
        {
            // Send "escape" key to autocomplete so that it hides the dropdown.
            // This will happen if dropdown moves outside of parent window, for example.
            SendMessage(m_hwndEdit, IF_CHAR, (WPARAM) VK_ESCAPE, 0);
        }
    }

    return S_OK;
}


HRESULT CIntelliForms::CAutoSuggest::HandleEvent(IHTMLElement *pEle, EVENTS Event, IHTMLEventObj *pEventObj)
{
    TraceMsg(TF_IFORMS, "CIntelliForms::CAutoSuggest::HandleEvent Event=%ws", EventsToSink[Event].pwszEventName);

    ASSERT(SHIsSameObject(pEle, m_pTextEle));

    long lKey = 0;
    BOOL fIsComposition = FALSE;

    if (!m_pParent)
    {
        TraceMsg(TF_WARNING|TF_IFORMS, "IForms autosuggest receiving events while invalid");
        return E_FAIL;
    }

    if (Event == EVENT_KEYPRESS || Event == EVENT_KEYDOWN)
    {
        pEventObj->get_keyCode(&lKey);
    }

    if (Event == EVENT_COMPOSITION)
    {
        fIsComposition = TRUE;

        Event = EVENT_KEYPRESS; // Pretend to be a "keypress" for various processing below

    }

    if (Event == EVENT_NOTIFY)
    {
        // Send WM_IME_NOTIFY to AutoComplete so it can hide the dropdown
        //  if necessary
        IHTMLEventObj3 *pObj3 = NULL;

        pEventObj->QueryInterface(IID_PPV_ARG(IHTMLEventObj3, &pObj3));

        if (pObj3)
        {
            LONG_PTR wParam = 0;
            pObj3->get_imeNotifyCommand(&wParam);

            SendMessage(m_hwndEdit, WM_IME_NOTIFY, (WPARAM)wParam, 0);

            pObj3->Release();
        }

        return S_OK;
    }

    if (!m_fEnabled && !m_fEnabledPW)
    {
        // If the dropdown isn't enabled, our only purpose is to tell Intelliforms when
        //  user activity occurs for the first-time enable dialog box.
        if (Event == EVENT_KEYPRESS && lKey != VK_TAB)
        {
            // Add this element to the master list so we save it when we submit
            //  and sink the submit event for this element's form
            MarkDirty();
        }

        return S_OK;
    }

    if (Event == EVENT_KEYDOWN || Event == EVENT_KEYPRESS ||
        Event == EVENT_MOUSEDOWN || Event == EVENT_DBLCLICK)
    {
        m_fAllowAutoFillPW = TRUE;

        // Create our autocomplete object if it hasn't happened yet.
        // If it's "tab" we don't create it; we're leaving the field
        if (lKey != VK_TAB)
        {
            if (FAILED(CreateAutoComplete()))
                return E_FAIL;
        }
        else
        {
            // Add this element to the master list so we save it when we submit
            //  and sink the submit event for this element's form
            MarkDirty();
        }

        ASSERT((m_pEnumString && m_hwndEdit) || (lKey==VK_TAB));
    }

    // If AutoComplete hasn't been initialized there's nothing for us to do
    if (!m_pAutoCompleteDD || !m_hwndEdit)
    {
        return E_FAIL;
    }

    // Update the position of our hidden edit box
    long lLeft, lTop, lWidth, lHeight;

    // call UpdateDropdownPosition instead
    if (SUCCEEDED(GetScreenCoordinates(pEle, m_pParent->m_hwndBrowser, &lLeft, &lTop, &lWidth, &lHeight)))
    {
        MoveWindow(m_hwndEdit, lLeft, lTop, lWidth, lHeight, FALSE);
    }

    switch (Event)
    {
        case EVENT_FOCUS :
            SendMessage(m_hwndEdit, WM_SETFOCUS, 0, 0);
            break;

        case EVENT_BLUR:
        {
            if (m_hwndEdit)
            {
                SendMessage(m_hwndEdit, WM_KILLFOCUS, 0, 0);
            }

            // ensure that script hasn't changed value of edit field?
            BSTR bstrUsername=NULL;
            m_pTextEle->get_value(&bstrUsername);
            if (bstrUsername)
            {
                CheckAutoFillPassword(bstrUsername);
                SysFreeString(bstrUsername);
            }
        }
        break;
        
        case EVENT_MOUSEDOWN:
        case EVENT_DBLCLICK:
        {
            // If the dropdown is invisible, give AutoComplete a downarrow
            long lButton=0;
            pEventObj->get_button(&lButton);
            if ((Event == EVENT_DBLCLICK) ||
                (lButton & 1))                      // Left button down?
            {
                DWORD dwFlags;

                if (SUCCEEDED(m_pAutoCompleteDD->GetDropDownStatus(&dwFlags, NULL)) &&
                    !(dwFlags & ACDD_VISIBLE))
                {
                    TraceMsg(TF_IFORMS, "IForms sending downarrow because of mouse click");
                    PostMessage(m_hwndEdit, IF_KEYDOWN, (WPARAM)VK_DOWN, 0);
                    m_fEscapeHit = FALSE;
                }
            }
        }
        break;
        
        case EVENT_KEYPRESS:
        {
            // Add this element to the master list so we save it when we submit
            //  and sink the submit event for this element's form
            MarkDirty();

            // Ignore ctrl-enter (quickcomplete) (may be unnecessary)
            if (lKey == VK_RETURN)
            {
                VARIANT_BOOL bCtrl;
                if (SUCCEEDED(pEventObj->get_ctrlKey(&bCtrl)) && bCtrl)
                {
                    lKey = 0;
                }
            }

            if (lKey != 0)
            {
                if (lKey == m_lCancelKeyPress)
                {
                    // tell MSHTML to ignore this keystroke (may be tab, enter, escape)
                    TraceMsg(TF_IFORMS, "Intelliforms cancelling default action for EVENT_KEYPRESS=%d", lKey);

                    VARIANT v;
                    v.vt = VT_BOOL;
                    v.boolVal = VARIANT_FALSE;
                    pEventObj->put_returnValue(v);
                    if(!(lKey == VK_DOWN || lKey == VK_UP))
                        pEventObj->put_cancelBubble(VARIANT_TRUE);
                }

                m_lCancelKeyPress = 0;

                // Tell AutoComplete about this keystroke
                if (!m_fEscapeHit)
                {
                    PostMessage(m_hwndEdit, IF_CHAR, (WPARAM)lKey, 0);
                }
            }

            if (fIsComposition)
            {
                // Tell AutoComplete about the new string. This must be a Post so that
                //  Trident handles the event before we send the WM_CHAR to browseui.
                PostMessage(m_hwndEdit, IF_IME_COMPOSITION, 0, 0);
            }
        }
        break;
        
        case EVENT_KEYDOWN:
        {
            long    lKey;
            BOOL    fCancelEvent=FALSE,         // Cancel default MSHTML action?
                    fForwardKeystroke=TRUE;     // Forward keystroke to AutoComplete?

            pEventObj->get_keyCode(&lKey);

            if (m_fEscapeHit)
            {
                // They dismissed the dropdown; don't bring it back unless they ask for it
                if (lKey == VK_DOWN)
                {
                    m_fEscapeHit = FALSE;
                }
                else
                {
                    fForwardKeystroke = FALSE;
                }
            }

            if (lKey != 0)
            {
                if ((lKey == VK_RETURN) || (lKey == VK_TAB))
                {
                    fForwardKeystroke=FALSE;

                    LPWSTR pwszString=NULL;

                    if (SUCCEEDED(m_pAutoCompleteDD->GetDropDownStatus(NULL, &pwszString)) && pwszString)
                    {
                        // User is inside dropdown
                        fForwardKeystroke=TRUE;

                        // Set this value into our edit field
                        SetText(pwszString);

                        // We will fill in their password if they asked for it in m_uMsgItemActivate

                        if (lKey == VK_RETURN)
                        {
                            // Avoid submitting this form
                            fCancelEvent = TRUE;
                        }

                        CoTaskMemFree(pwszString);
                    }
                    else if (lKey == VK_RETURN)
                    {
                        // User's gonna submit. Give 'em their password first.
                        // ensure that script hasn't changed value of edit field?
                        BSTR bstrUsername=NULL;
                        m_pTextEle->get_value(&bstrUsername);
                        if (bstrUsername)
                        {
                            CheckAutoFillPassword(bstrUsername);
                            SysFreeString(bstrUsername);
                        }
                    }
                }
                else if (lKey == VK_DELETE)
                {
                    LPWSTR pwszString=NULL;

                    if (SUCCEEDED(m_pAutoCompleteDD->GetDropDownStatus(NULL, &pwszString)) && pwszString)
                    {
                        // User is inside dropdown
                        fForwardKeystroke=FALSE;

                        // Delete this value from our string lists
                        CStringList *psl=NULL;
                        BSTR bstrName;

                        CIntelliForms::GetName(m_pTextEle, &bstrName);

                        if (bstrName)
                        {
                            int iIndex;

                            if (SUCCEEDED(m_pParent->ReadFromStore(bstrName, &psl)) &&
                                SUCCEEDED(psl->FindString(pwszString, -1, &iIndex, FALSE)))
                            {
                                TraceMsg(TF_IFORMS, "IForms: Deleting string \"%ws\"", pwszString);
                                psl->DeleteString(iIndex);

                                // We deleted string.
                                if (psl->NumStrings() > 0)
                                {
                                    m_pParent->WriteToStore(bstrName, psl);
                                }
                                else
                                {
                                    m_pParent->DeleteFromStore(bstrName);
                                }
                            }
                        }

                        SysFreeString(bstrName);
                        if (psl) delete psl;

                        // avoid deleting a character from the edit window; user was inside dropdown
                        fCancelEvent = TRUE;

                        // Check this url to see if we should maybe delete a password entry
                        m_pParent->DeletePassword(pwszString);

                        // Get AutoComplete to fill in the dropdown again
                        m_pEnumString->ResetEnum();
                        m_pAutoCompleteDD->ResetEnumerator();

                        CoTaskMemFree(pwszString);
                    }
                }

                if (lKey == VK_ESCAPE)
                {
                    DWORD dwFlags;

                    if (SUCCEEDED(m_pAutoCompleteDD->GetDropDownStatus(&dwFlags, NULL)) &&
                        (dwFlags & ACDD_VISIBLE))
                    {
                        fCancelEvent = TRUE;
                        m_fEscapeHit = TRUE;
                    }
                }

                if (lKey == VK_DOWN || lKey == VK_UP)
                {
                    // Cancel the MSHTML events. This will cause MSHTML to return
                    //  S_OK instead of S_FALSE from its TranslateAccelerator, and we
                    //  won't get multiple keystrokes in different panes
                    fCancelEvent = TRUE;
                }

                if (fForwardKeystroke)
                {
                    PostMessage(m_hwndEdit, IF_KEYDOWN, lKey, 0);

                    if (lKey == VK_BACK)
                    {
                        // Never get OnKeyPress for this guy
                        PostMessage(m_hwndEdit, IF_CHAR, lKey, 0);
                    }
                }

                if (fCancelEvent)
                {
                    TraceMsg(TF_IFORMS, "Intelliforms cancelling default action for EVENT_KEYDOWN=%d", lKey);

                    m_lCancelKeyPress = lKey;    // Cancel the EVENT_KEYPRESS when it comes

                    VARIANT v;
                    v.vt = VT_BOOL;
                    v.boolVal = VARIANT_FALSE;
                    pEventObj->put_returnValue(v);
                    if(!(lKey == VK_DOWN || lKey == VK_UP))
                        pEventObj->put_cancelBubble(VARIANT_TRUE);
                }
                else
                {
                    m_lCancelKeyPress = 0;
                }
            }
        }
        break;
    }

    return S_OK;
}

HRESULT CIntelliForms::CAutoSuggest::GetText(int cchTextMax, LPWSTR pszTextOut, LRESULT *lcchCopied)
{
    *pszTextOut = TEXT('\0');
    *lcchCopied = 0;

    if (m_pTextEle)
    {
        BSTR bstr=NULL;
        m_pTextEle->get_value(&bstr);
        if (bstr)
        {
            StrCpyN(pszTextOut, bstr, cchTextMax);
            *lcchCopied = lstrlenW(pszTextOut);     // needed for NT

            SysFreeString(bstr);
        }
    }

    return (*pszTextOut) ? S_OK : E_FAIL;
}

HRESULT CIntelliForms::CAutoSuggest::GetTextLength(int *pcch)
{
    *pcch = 0;

    if (m_pTextEle)
    {
        BSTR bstr=NULL;
        m_pTextEle->get_value(&bstr);
        if (bstr)
        {
            *pcch = SysStringLen(bstr);

            SysFreeString(bstr);
        }
    }

    return S_OK;
}

HRESULT CIntelliForms::CAutoSuggest::SetText(LPCWSTR pszTextIn)
{
    if (m_pTextEle && pszTextIn)
    {
        BSTR bstr=SysAllocString(pszTextIn);

        if (bstr)
        {
            // Even though we know we already have this string in our dropdown, mark
            //  it as dirty so that we sink submit event; can be necessary in saved
            //  password situation.
            MarkDirty();

            // Make sure we don't put a string longer than the max length in this field
            long lMaxLen=-1;
            m_pTextEle->get_maxLength(&lMaxLen);
            if ((lMaxLen >= 0) && (lstrlenW(bstr) > lMaxLen))
            {
                bstr[lMaxLen] = L'\0';
            }

            m_pTextEle->put_value(bstr);
            SysFreeString(bstr);
        }
    }

    TraceMsg(TF_IFORMS, "CIntelliForms::CAutoSuggest::SetText \"%ws\"", pszTextIn);

    return S_OK;
}

#define MY_GWL_THISPTR 0

LRESULT CALLBACK CIntelliForms::CAutoSuggest::WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    CIntelliForms::CAutoSuggest *pThis = (CIntelliForms::CAutoSuggest *)GetWindowLongPtr(hwnd, MY_GWL_THISPTR);

    switch (uMsg)
    {
    case WM_CREATE:
        {
            LPCREATESTRUCT pcs = (LPCREATESTRUCT)lParam;

            if (!pcs || !(pcs->lpCreateParams))
            {
                return -1;
            }
            SetWindowLongPtr(hwnd, MY_GWL_THISPTR, (LONG_PTR) pcs->lpCreateParams);
            return 0;
        }

    case WM_GETTEXT:
        if (pThis)
        {
            LRESULT lcchCopied=0;

            if (g_fRunningOnNT)
            {
                pThis->GetText((int)wParam, (LPWSTR) lParam, &lcchCopied);
            }
            else
            {
                // We are actually an ANSI window. Convert.
                LPWSTR pwszOutBuf = (LPWSTR) LocalAlloc(LPTR, (wParam+1)*sizeof(WCHAR));

                if (pwszOutBuf)
                {
                    pThis->GetText((int)wParam, pwszOutBuf, &lcchCopied);

                    SHUnicodeToAnsi(pwszOutBuf, (LPSTR) lParam, (int)(wParam+1));

                    LocalFree((HLOCAL)pwszOutBuf);
                    pwszOutBuf = NULL;
                }
            }
            return lcchCopied;
        }

        return 0;

    case WM_GETTEXTLENGTH:
        if (pThis)
        {
            int iLen;
            pThis->GetTextLength(&iLen);
            return iLen;
        }

        return 0;

    case EM_GETSEL:
        // Must return zeroes here or autocomp will use uninitialized
        //  values and crash
        if (wParam) (*(DWORD *)wParam) = 0;
        if (lParam) (*(DWORD *)lParam) = 0;
        break;

    case IF_IME_COMPOSITION:
        // Forward a WM_CHAR. Autocomplete will notice that the rest of the string
        //  has changed if necessary (it does a GetText)
        SendMessage(hwnd, WM_CHAR, 32, 0);
        break;

    case IF_CHAR:
        SendMessage(hwnd, WM_CHAR, wParam, lParam);
        break;

    case IF_KEYDOWN:
        SendMessage(hwnd, WM_KEYDOWN, wParam, lParam);
        break;

    case WM_KEYDOWN:
    case WM_CHAR:
        return 0;       // eat it (see notes at top of file)

    default:

        // Check registered message
        if (pThis && uMsg == pThis->m_uMsgItemActivate)
        {
            TraceMsg(TF_IFORMS, "IForms: Received AM_ITEMACTIVATE(WM_APP+2)");
            pThis->SetText((LPCWSTR)lParam);
            pThis->CheckAutoFillPassword((LPCWSTR)lParam);

            return 0;
        }

        return DefWindowProc(hwnd, uMsg, wParam, lParam);
    }

    return 1;
}

CIntelliForms::CAutoSuggest::CEnumString::CEnumString()
{
//  TraceMsg(TF_IFORMS, "CIntelliForms::CAutoSuggest::CEnumString::CEnumString");
    DllAddRef();

    InitializeCriticalSection(&m_crit);

    m_cRef = 1;
}

CIntelliForms::CAutoSuggest::CEnumString::~CEnumString()
{
//  TraceMsg(TF_IFORMS, "CIntelliForms::CAutoSuggest::CEnumString::~CEnumString");
    if (m_pslMain)
    {
        delete m_pslMain;
    }
    SysFreeString(m_bstrName);
    if (m_pszOpsValue)
    {
        CoTaskMemFree(m_pszOpsValue);
    }

    DeleteCriticalSection(&m_crit);

    DllRelease();
}

HRESULT CIntelliForms::CAutoSuggest::CEnumString::Init(IHTMLInputTextElement *pInputEle, CIntelliForms *pIntelliForms)
{
    if (m_fInit ||              // Can only init once
        !pInputEle || !pIntelliForms)       // Need both pointers
    {
        return E_FAIL;
    }

    m_fInit=TRUE;
    m_pIntelliForms = pIntelliForms;

    // Take care of things that must be done on the main thread. Autocomplete will
    //  call us on a secondary thread to do the enumeration.
    CIntelliForms::GetName(pInputEle, &m_bstrName);

    if (m_bstrName && m_bstrName[0])
    {
        // See if this specifies the "vcard." format
        if (IsEnabledInCPL() &&
            !StrCmpNICW(m_bstrName, c_wszVCardPrefix, ARRAYSIZE(c_wszVCardPrefix)-1))
        {
            // It does. Retrieve string from the profile assistant store.
            IHTMLWindow2     *pWin2 = NULL;
            IServiceProvider *pQS   = NULL;

            // QS up to get the shdocvw IHTMLWindow2 instead of NF trident's
            pInputEle->QueryInterface(IID_IServiceProvider, (void **)&pQS);

            if (pQS)
            {
                pQS->QueryService(IID_IHTMLWindow2, IID_IHTMLWindow2, (void **)&pWin2);
                pQS->Release();
            }

            if (pWin2)
            {
                IOmNavigator *pNav=NULL;
                pWin2->get_navigator(&pNav);
                if (pNav)
                {
                    IHTMLOpsProfile *pProfile=NULL;
                    pNav->get_userProfile(&pProfile);
                    if (pProfile)
                    {
                        IOpsProfileSimple *pSimple=NULL;
                        pProfile->QueryInterface(IID_IOpsProfileSimple, (void **)&pSimple);
                        if (pSimple)
                        {
                            pSimple->ReadProperties(1, &m_bstrName, &m_pszOpsValue);
                            pSimple->Release();
                        }
                        pProfile->Release();
                    }
                    pNav->Release();
                }
                pWin2->Release();
            }
        }
    }

    return S_OK;
}

void CIntelliForms::CAutoSuggest::CEnumString::UnInit()
{
    EnterCriticalSection(&m_crit);

    m_pIntelliForms = NULL;

    LeaveCriticalSection(&m_crit);
}

HRESULT CIntelliForms::CAutoSuggest::CEnumString::ResetEnum()
{
    EnterCriticalSection(&m_crit);

    if (m_pslMain)
    {
        delete m_pslMain;
        m_pslMain = NULL;
    }

    m_fFilledStrings = FALSE;

    LeaveCriticalSection(&m_crit);

    return S_OK;
}

STDMETHODIMP CIntelliForms::CAutoSuggest::CEnumString::QueryInterface(REFIID riid, void **ppv)
{
    *ppv = NULL;

    if ((IID_IEnumString == riid) ||
        (IID_IUnknown == riid))
    {
        *ppv = (IEnumString *)this;
    }

    if (NULL != *ppv)
    {
        ((IUnknown *)*ppv)->AddRef();
        return S_OK;
    }

    return E_NOINTERFACE;
}

STDMETHODIMP_(ULONG) CIntelliForms::CAutoSuggest::CEnumString::AddRef(void)
{
    return InterlockedIncrement(&m_cRef);
}

STDMETHODIMP_(ULONG) CIntelliForms::CAutoSuggest::CEnumString::Release(void)
{
    if (InterlockedDecrement(&m_cRef) != 0)
    {
        return 1;
    }

    delete this;
    return 0;
}

STDMETHODIMP CIntelliForms::CAutoSuggest::CEnumString::Next(ULONG celt, LPOLESTR *rgelt, ULONG *pceltFetched)
{
    EnterCriticalSection(&m_crit);

    if (!m_fFilledStrings)
    {
        FillEnumerator();
    }

    if (m_pslMain)
    {
        int iNewPtr = m_iPtr + celt;

        if (iNewPtr > m_pslMain->NumStrings())
        {
            iNewPtr = m_pslMain->NumStrings();
        }

        *pceltFetched = iNewPtr - m_iPtr;

        LPOLESTR lpstr;

        for (; m_iPtr < iNewPtr; m_iPtr ++)
        {
             m_pslMain->GetTaskAllocString(m_iPtr, &lpstr);

             if (!lpstr) break;

             *(rgelt ++) = lpstr;
        }

        if (m_iPtr < iNewPtr)
        {
            *pceltFetched += (m_iPtr - iNewPtr);
        }
    }

    LeaveCriticalSection(&m_crit);

    if (!m_pslMain)
    {
        return E_FAIL;
    }

    return (*pceltFetched) ? S_OK : S_FALSE;
}

STDMETHODIMP CIntelliForms::CAutoSuggest::CEnumString::Reset()
{
    EnterCriticalSection(&m_crit);

    m_iPtr = 0;

    LeaveCriticalSection(&m_crit);

    return S_OK;
}

HRESULT CIntelliForms::CAutoSuggest::CEnumString::FillEnumerator()
{
    // Already in critical section
    ASSERT(!m_pslMain);

    if (m_fFilledStrings)
    {
        return S_FALSE;
    }

    if (!m_bstrName || !m_bstrName[0] || !m_pIntelliForms)
    {
        return E_FAIL;
    }

    m_fFilledStrings = TRUE;

    m_iPtr = 0;

    // Fill the enumerator based on our name
    TraceMsg(TF_IFORMS, "IForms: Intelliforms filling enumerator");

    // Open any previously saved strings
    if (!m_pIntelliForms->IsRestricted() &&
        IsEnabledInCPL() &&
        m_pIntelliForms->IsEnabledForPage())
    {
        m_pIntelliForms->ReadFromStore(m_bstrName, &m_pslMain);

        // Add in profile assistant value, if any
        if (m_pszOpsValue && m_pszOpsValue[0])
        {
            if (!m_pslMain)
            {
                CStringList_New(&m_pslMain);
            }
            else
            {
                // don't risk a scavenge (perf)
                m_pslMain->SetMaxStrings(CStringList::MAX_STRINGS+4);
            }

            if (m_pslMain)
            {
                m_pslMain->AddString(m_pszOpsValue);
            }
        }
    }

    // Next fill with any usernames that have saved passwords
    CStringList *pslPasswords;

    if (!m_pIntelliForms->IsRestrictedPW() &&
        CIntelliForms::IsEnabledRestorePW() &&
        SUCCEEDED(m_pIntelliForms->GetPasswordStringList(&pslPasswords)))
    {
        ASSERT(!(pslPasswords->NumStrings() & 1));

        FILETIME ft;

        if (pslPasswords->NumStrings() > 0)
        {
            if (!m_pslMain)
            {
                CStringList_New(&m_pslMain);
            }
            else
            {
                // avoid expensive scavenging while adding usernames to string list
                m_pslMain->SetMaxStrings(m_pslMain->GetMaxStrings() + pslPasswords->NumStrings()/2);
            }

            if (m_pslMain)
            {
                for (int i=0; i<pslPasswords->NumStrings(); i+=2)
                {
                    if (SUCCEEDED(pslPasswords->GetStringTime(i, &ft)) &&
                        FILETIME_TO_INT(ft) != 0)
                    {
                        // We have a saved password for this username. Add username to enumerator.
                        m_pslMain->AddString(pslPasswords->GetString(i));
                    }
                }
            }
        }

        // do not delete pslPasswords
    }

    return (m_pslMain) ? ((m_pslMain->NumStrings()) ? S_OK : S_FALSE) : E_FAIL;
}

// Static helper. Pretty basic.
HRESULT CStringList_New(CStringList **ppNew, BOOL fAutoDelete/*=TRUE*/)
{
    *ppNew = new CStringList();

    if (*ppNew)
    {
        (*ppNew)->SetAutoScavenge(fAutoDelete);
    }

    return (*ppNew) ? S_OK : E_OUTOFMEMORY;
}

CStringList::CStringList()
{
    TraceMsg(TF_IFORMS, "IForms: CStringList::CStringList");
    m_fAutoScavenge = TRUE;
    m_dwMaxStrings = MAX_STRINGS;
}

CStringList::~CStringList()
{
    TraceMsg(TF_IFORMS, "IForms: CStringList::~CStringList");
    CleanUp();
}

void CStringList::CleanUp()
{
    if (m_psiIndex)
    {
        LocalFree(m_psiIndex);
        m_psiIndex = NULL;
    }
    if (m_pBuffer)
    {
        LocalFree(m_pBuffer);
        m_pBuffer = NULL;
    }
    m_dwIndexSize = 0;
    m_dwBufEnd = m_dwBufSize = 0;
}

HRESULT CStringList::WriteToBlobs(LPBYTE *ppBlob1, DWORD *pcbBlob1, LPBYTE *ppBlob2, DWORD *pcbBlob2)
{
    HRESULT hr = E_FAIL;
    TraceMsg(TF_IFORMS, "+WriteToBlobs");

    if (SUCCEEDED(Validate()))
    {
        DWORD dwIndexSize;

        dwIndexSize = INDEX_SIZE(m_psiIndex->dwNumStrings);
        ASSERT(dwIndexSize <= m_dwIndexSize);

        *ppBlob1 = (LPBYTE) LocalAlloc(LMEM_FIXED, dwIndexSize);
        if (*ppBlob1)
        {
            *ppBlob2 = (LPBYTE) LocalAlloc(LMEM_FIXED, m_dwBufEnd);

            if (*ppBlob2)
            {
                memcpy(*ppBlob1, m_psiIndex, dwIndexSize);
                *pcbBlob1=dwIndexSize;

                memcpy(*ppBlob2, m_pBuffer, m_dwBufEnd);
                *pcbBlob2=m_dwBufEnd;

                hr = S_OK;
            }
        }
    }
    else
    {
        // Validate failed.
        TraceMsg(TF_ERROR | TF_IFORMS, "Validate FAILED in WriteToBlobs");
        *ppBlob1=NULL;
        *ppBlob2=NULL;
    }

    if (FAILED(hr))
    {
        if (*ppBlob1)
        {
            LocalFree(*ppBlob1);
            *ppBlob1=NULL;
        }
        if (*ppBlob2)
        {
            LocalFree(*ppBlob2);
            *ppBlob2=NULL;
        }
        *pcbBlob1=0;
        *pcbBlob2=0;
    }

    TraceMsg(TF_IFORMS, "-WriteToBlobs");

    return hr;
}

// Take the blobs and use as our buffer
HRESULT CStringList::ReadFromBlobs(LPBYTE *ppBlob1, DWORD cbBlob1, LPBYTE *ppBlob2, DWORD cbBlob2)
{
    HRESULT hr = E_FAIL;

    TraceMsg(TF_IFORMS, "+ReadFromBlobs");

    if (m_psiIndex)
    {
        TraceMsg(TF_IFORMS, "IForms: CStringList::ReadFromRegistry called with initialized instance.");
        CleanUp();
    }

    // Allocate our buffers.
    m_psiIndex = (StringIndex *) (*ppBlob1);
    m_pBuffer = (LPBYTE) (*ppBlob2);

    *ppBlob1 = NULL;
    *ppBlob2 = NULL;

    if (!m_psiIndex || !m_pBuffer || !cbBlob1 || !cbBlob2)
    {
        // Nothing to do
        CleanUp();

        return S_FALSE;
    }

    // Validate our string index.
    if ((m_psiIndex->dwSignature == INDEX_SIGNATURE) &&
        (m_psiIndex->cbSize == STRINGINDEX_CBSIZE) &&
        (m_psiIndex->dwNumStrings <= MAX_STRINGS))
    {
        m_dwBufEnd = m_dwBufSize = cbBlob2;
        m_dwIndexSize = cbBlob1;

        if (SUCCEEDED(Validate()))
        {
            // Everything worked. Amazing.
            hr = S_OK;
        }
    }

    if (FAILED(hr))
    {
        // Release buffers if necessary.
        CleanUp();
    }

    TraceMsg(TF_IFORMS, "-ReadFromBlobs");

    return hr;
}

// static
HRESULT CStringList::GetFlagsFromIndex(LPBYTE pBlob1, INT64 *piFlags)
{
    StringIndex *psiIndex = (StringIndex *)pBlob1;

    if ((psiIndex->dwSignature == INDEX_SIGNATURE) &&
        (psiIndex->cbSize == STRINGINDEX_CBSIZE))
    {
        *piFlags = psiIndex->iData;

        return S_OK;
    }

    return E_FAIL;
}

HRESULT CStringList::Validate()
{
    TraceMsg(TF_IFORMS, "+CStringList::Validate");
    if (!m_psiIndex || !m_pBuffer)
    {
        return E_FAIL;
    }

    for (DWORD dw=0; dw < m_psiIndex->dwNumStrings; dw++)
    {
        DWORD dwPtr  = m_psiIndex->StringEntry[dw].dwStringPtr;
        DWORD dwSize = (GetStringLen(dw)+1) * sizeof(WCHAR);

        if (dwPtr + dwSize > m_dwBufSize)
        {
            return E_FAIL;
        }
    }

    TraceMsg(TF_IFORMS, "-CStringList::Validate");
    return S_OK;
}

HRESULT CStringList::Init(DWORD dwBufSize /* =0 */)
{
    DWORD dwMaxStrings=0;
    DWORD dwIndexSize=0;

    if (m_psiIndex)
    {
        TraceMsg(TF_IFORMS, "IForms: CStringList::Init called when already initialized");
        CleanUp();
    }

    if (dwBufSize == 0)
    {
        dwBufSize = INIT_BUF_SIZE;
    }

    dwMaxStrings = dwBufSize >> 5;  // this is relatively arbitrary but doesn't matter much

    if (dwMaxStrings == 0)
        dwMaxStrings = 1;

    dwIndexSize = INDEX_SIZE(dwMaxStrings);

    m_pBuffer = (LPBYTE)LocalAlloc(LMEM_FIXED, dwBufSize);
    m_psiIndex = (StringIndex *)LocalAlloc(LMEM_FIXED, dwIndexSize);

    if ((NULL == m_psiIndex) ||
        (NULL == m_pBuffer))
    {
        TraceMsg(TF_IFORMS, "IForms: CStringList::Init memory allocation failed");

        CleanUp();
        return E_OUTOFMEMORY;
    }

    *((WCHAR *)m_pBuffer) = L'\0';

    m_dwBufSize = dwBufSize;
    m_dwBufEnd = 0;

    m_psiIndex->dwSignature = INDEX_SIGNATURE;
    m_psiIndex->cbSize = STRINGINDEX_CBSIZE;
    m_psiIndex->dwNumStrings = 0;
    m_psiIndex->iData = 0;
    m_dwIndexSize = dwIndexSize;

    TraceMsg(TF_IFORMS, "IForms: CStringList::Init succeeded");

    return S_OK;
}

HRESULT CStringList::GetBSTR(int iIndex, BSTR *pbstrRet)
{
    LPCWSTR lpwstr = GetString(iIndex);

    if (!lpwstr)
    {
        *pbstrRet = NULL;
        return E_INVALIDARG;
    }

    *pbstrRet = SysAllocString(lpwstr);

    return (*pbstrRet) ? S_OK : E_OUTOFMEMORY;
}

HRESULT CStringList::GetTaskAllocString(int iIndex, LPOLESTR *pRet)
{
    LPCWSTR lpwstr = GetString(iIndex);

    if (!lpwstr)
    {
        *pRet = NULL;
        return E_INVALIDARG;
    }

    DWORD dwSize = (GetStringLen(iIndex)+1) * sizeof(WCHAR);

    *pRet = (LPOLESTR)CoTaskMemAlloc(dwSize);

    if (!*pRet)
    {
        return E_OUTOFMEMORY;
    }

    memcpy(*pRet, lpwstr, dwSize);

    return S_OK;
}

HRESULT CStringList::FindString(LPCWSTR lpwstr, int iLen, int *piNum, BOOL fCaseSensitive)
{
    if (!m_psiIndex) return E_FAIL;

    DWORD dw;

    if (!lpwstr)
    {
        return E_INVALIDARG;
    }

    if (iLen <= 0)
    {
        iLen = lstrlenW(lpwstr);
    }

    if (piNum)
    {
        *piNum = -1;
    }

    for (dw=0; dw<m_psiIndex->dwNumStrings; dw++)
    {
        if (m_psiIndex->StringEntry[dw].dwStringLen == (DWORD)iLen)
        {
            if ((fCaseSensitive && (!StrCmpW(GetString(dw), lpwstr))) ||
                (!fCaseSensitive && (!StrCmpIW(GetString(dw), lpwstr))))
            {
                // Match!
                if (piNum)
                {
                    *piNum = (int) dw;
                }

                return S_OK;
            }
        }
    }

    return E_FAIL;      // Couldn't find it
}

// CStringList is not optimized for deleting
HRESULT CStringList::DeleteString(int iIndex)
{
    TraceMsg(TF_IFORMS, "+DeleteString");
    if (!m_psiIndex)
    {
        return E_FAIL;
    }

    if ((iIndex<0) || ((DWORD)iIndex >= m_psiIndex->dwNumStrings))
    {
        return E_INVALIDARG;
    }

    if ((DWORD)iIndex == (m_psiIndex->dwNumStrings-1))
    {
        // Simple case - deleting last string
        m_dwBufEnd -= (sizeof(WCHAR) * (GetStringLen(iIndex) + 1));
        m_psiIndex->dwNumStrings --;
        return S_OK;
    }

    DWORD cbSizeDeleted;
    LPCWSTR pwszString1, pwszString2;

    pwszString1 = GetString(iIndex);
    pwszString2 = GetString(iIndex+1);

    // Size in bytes of string to be deleted including null terminator
    cbSizeDeleted = (DWORD)((DWORD_PTR)pwszString2 - (DWORD_PTR)pwszString1);

    ASSERT(cbSizeDeleted == (sizeof(WCHAR) * (lstrlenW(GetString(iIndex))+1)));

    // Delete entry in index
    memcpy(&(m_psiIndex->StringEntry[iIndex]), &(m_psiIndex->StringEntry[iIndex+1]),
                STRINGENTRY_SIZE*(m_psiIndex->dwNumStrings - iIndex - 1));
    m_psiIndex->dwNumStrings --;

    // Delete string in buffer
    memcpy((LPWSTR)pwszString1, pwszString2, m_dwBufEnd-(int)PtrDiff(pwszString2, m_pBuffer));
    m_dwBufEnd -= cbSizeDeleted;

    // Fix up pointers in index
    for (int i=iIndex; (DWORD)i < m_psiIndex->dwNumStrings; i++)
    {
        m_psiIndex->StringEntry[i].dwStringPtr -= cbSizeDeleted;
    }
    TraceMsg(TF_IFORMS, "-DeleteString");

    return S_OK;
}

HRESULT CStringList::InsertString(int iIndex, LPCWSTR lpwstr)
{
    TraceMsg(TF_IFORMS, "+InsertString");
    if (!m_psiIndex)
    {
        return E_FAIL;
    }

    if ((iIndex<0) || ((DWORD)iIndex > m_psiIndex->dwNumStrings))
    {
        return E_INVALIDARG;
    }

    if ((DWORD)iIndex == m_psiIndex->dwNumStrings)
    {
        // Simple case - inserting to end
        return _AddString(lpwstr, FALSE, NULL);
    }

    DWORD dwLen = (DWORD)lstrlenW(lpwstr);
    DWORD dwSizeInserted = sizeof(WCHAR) * (dwLen + 1);

    if (FAILED(EnsureBuffer(m_dwBufEnd + dwSizeInserted)) ||
        FAILED(EnsureIndex(m_psiIndex->dwNumStrings + 1)))
    {
        return E_OUTOFMEMORY;
    }


    // Insert into buffer
    LPWSTR pwszBufLoc = GetStringPtr(iIndex);

    memcpy((LPBYTE)pwszBufLoc + dwSizeInserted, pwszBufLoc, m_dwBufEnd - (int) PtrDiff(pwszBufLoc, m_pBuffer));
    memcpy(pwszBufLoc, lpwstr, dwSizeInserted);
    m_dwBufEnd += dwSizeInserted;

    // Insert into index
    memcpy(&(m_psiIndex->StringEntry[iIndex+1]), &(m_psiIndex->StringEntry[iIndex]),
                STRINGENTRY_SIZE*(m_psiIndex->dwNumStrings - iIndex));
    struct StringIndex::tagStringEntry *pse=&(m_psiIndex->StringEntry[iIndex]);
    pse->dwStringPtr = (DWORD)PtrDiff(pwszBufLoc, m_pBuffer);
    pse->ftLastSubmitted.dwLowDateTime = pse->ftLastSubmitted.dwHighDateTime = 0;
    pse->dwStringLen = dwLen;
    m_psiIndex->dwNumStrings ++;

    // Fix up pointers after inserted string
    for (int i=iIndex+1; (DWORD)i<m_psiIndex->dwNumStrings; i++)
    {
        m_psiIndex->StringEntry[i].dwStringPtr += dwSizeInserted;
    }
    TraceMsg(TF_IFORMS, "-InsertString");

    return S_OK;
}

HRESULT CStringList::ReplaceString(int iIndex, LPCWSTR lpwstr)
{
    TraceMsg(TF_IFORMS, "+ReplaceString");
    if (!m_psiIndex)
    {
        return E_FAIL;
    }

    if ((iIndex<0) || ((DWORD)iIndex >= m_psiIndex->dwNumStrings))
    {
        return E_INVALIDARG;
    }

    if ((DWORD)lstrlenW(lpwstr) == m_psiIndex->StringEntry[iIndex].dwStringLen)
    {
        // Simple case - strings equal length
        memcpy( GetStringPtr(iIndex),
                lpwstr,
                 (m_psiIndex->StringEntry[iIndex].dwStringLen)*sizeof(WCHAR));

        return S_OK;
    }

    // Delete old string, then insert new one
    DeleteString(iIndex);

    HRESULT hr = InsertString(iIndex, lpwstr);

    TraceMsg(TF_IFORMS, "-ReplaceString");

    return hr;
}

HRESULT CStringList::AddString(LPCWSTR lpwstr, FILETIME ft, int *piNum /*=NULL*/)
{
    int iNum;
    HRESULT hr;

    TraceMsg(TF_IFORMS, "+AddString");

    hr = _AddString(lpwstr, TRUE, &iNum);

    if (piNum)
    {
        *piNum = iNum;
    }

    if (SUCCEEDED(hr))
    {
        UpdateStringTime(iNum, ft);
    }

    TraceMsg(TF_IFORMS, "-AddString");

    return hr;
}


HRESULT CStringList::AddString(LPCWSTR lpwstr, int *piNum /*=NULL*/)
{
    return _AddString(lpwstr, TRUE, piNum);
}

HRESULT CStringList::AppendString(LPCWSTR lpwstr, int *piNum /*=NULL*/)
{
    return _AddString(lpwstr, FALSE, piNum);
}

HRESULT CStringList::AppendString(LPCWSTR lpwstr, FILETIME ft, int *piNum /*=NULL*/)
{
    int iNum;
    HRESULT hr;

    hr = _AddString(lpwstr, FALSE, &iNum);

    if (piNum)
    {
        *piNum = iNum;
    }

    if (SUCCEEDED(hr))
    {
        SetStringTime(iNum, ft);
    }

    return hr;
}

HRESULT CStringList::_AddString(LPCWSTR lpwstr, BOOL fCheckDuplicates, int *piNum)
{
    DWORD dwSize, dwLen;
    int iNum = -1;
    WCHAR wchBufTruncated[MAX_URL_STRING];
    LPCWSTR lpwstrTruncated=lpwstr;

    TraceMsg(TF_IFORMS, "+_AddString");

    if (piNum)
    {
        *piNum = -1;
    }

    if (!lpwstr)
    {
        return E_INVALIDARG;
    }

    if (!m_psiIndex)
    {
        if (FAILED(Init()))
        {
            return E_FAIL;
        }
    }

    dwLen = (DWORD) lstrlenW(lpwstr);

    // Explicitly truncate strings to MAX_URL characters. If we don't do this, browseui
    //  autocomplete code truncates it anyway and then we have problems removing
    //  duplicates and deleting these long strings. All IntelliForms code can handle
    //  arbitrary length strings.
    if (dwLen >= ARRAYSIZE(wchBufTruncated))
    {
        StrCpyNW(wchBufTruncated, lpwstr, ARRAYSIZE(wchBufTruncated));
        lpwstrTruncated = wchBufTruncated;
        dwLen = lstrlenW(wchBufTruncated);
    }

    dwSize = (dwLen+1)*sizeof(WCHAR);

    if (fCheckDuplicates && SUCCEEDED(FindString(lpwstrTruncated, (int)dwLen, &iNum, FALSE)))
    {
        if (piNum)
        {
            *piNum = iNum;
        }

        if (!StrCmpW(lpwstrTruncated, GetString(iNum)))
        {
            return S_FALSE;             // String is an exact duplicate
        }

        // String is a duplicate but has different case. Replace.
        ASSERT(m_psiIndex->StringEntry[iNum].dwStringLen == dwLen);
        memcpy(GetStringPtr(iNum), lpwstrTruncated, dwSize);

        return S_OK;                    // String was different in case
    }

    if (m_psiIndex->dwNumStrings >= m_dwMaxStrings)
    {
        if (m_fAutoScavenge)
        {
            // Remove the oldest string from our list.
            DWORD dwIndex;
            int iOldest=-1;
            FILETIME ftOldest = { 0xFFFFFFFF, 0x7FFFFFFF };
            for (dwIndex=0; dwIndex<m_psiIndex->dwNumStrings; dwIndex++)
            {
                if ((FILETIME_TO_INT(m_psiIndex->StringEntry[dwIndex].ftLastSubmitted) != 0) &&
                    (1 == CompareFileTime(&ftOldest, &m_psiIndex->StringEntry[dwIndex].ftLastSubmitted)))
                {
                    ftOldest = m_psiIndex->StringEntry[dwIndex].ftLastSubmitted;
                    iOldest = (int)dwIndex;
                }
            }

            if (iOldest != -1)
            {
                DeleteString(iOldest);
            }
            else
            {
                // User must not be setting string times.
                return E_OUTOFMEMORY;
            }
        }
        else
        {
            // Auto-scavenge is disabled.
            return E_OUTOFMEMORY;
        }
    }

    if (FAILED(EnsureBuffer(m_dwBufEnd + dwSize)) ||
        FAILED(EnsureIndex(m_psiIndex->dwNumStrings + 1)))
    {
        return E_OUTOFMEMORY;
    }

    // Our buffers are large enough. Do it.
    if (piNum)
    {
        *piNum = (int) m_psiIndex->dwNumStrings;
    }

    LPWSTR pwszNewString = (LPWSTR)(m_pBuffer + m_dwBufEnd);

    memcpy(pwszNewString, lpwstrTruncated, dwSize);
    m_dwBufEnd += dwSize;

    struct StringIndex::tagStringEntry *pse=&(m_psiIndex->StringEntry[m_psiIndex->dwNumStrings]);
    pse->dwStringPtr = (DWORD)PtrDiff(pwszNewString, m_pBuffer);
    pse->ftLastSubmitted.dwLowDateTime = pse->ftLastSubmitted.dwHighDateTime = 0;
    pse->dwStringLen = dwLen;

    m_psiIndex->dwNumStrings ++;

    TraceMsg(TF_IFORMS, "-_AddString");

    return S_OK;           // We added a new string
}

HRESULT CStringList::EnsureBuffer(DWORD dwSizeNeeded)
{
    TraceMsg(TF_IFORMS, "+EnsureBuffer");

    if (dwSizeNeeded <= m_dwBufSize)
    {
        return S_OK;        // Already big enough
    }

    if (!m_pBuffer)
    {
        return E_FAIL;
    }

    DWORD dwNewBufSize = m_dwBufSize * 2;

    // Grow buffer.
    if (dwSizeNeeded > dwNewBufSize)
    {
        TraceMsg(TF_IFORMS, "IForms: StringList special growing size (big string)");
        dwNewBufSize = dwSizeNeeded;
    }

    TraceMsg(TF_IFORMS, "IForms: CStringList growing");

    LPBYTE pBuf = (LPBYTE)LocalReAlloc(m_pBuffer, dwNewBufSize, LMEM_MOVEABLE);
    if (!pBuf)
    {
        TraceMsg(TF_IFORMS, "IForms: CStringList: ReAlloc failure");
        // Realloc failure: our old memory is still present
        return E_FAIL;
    }

    m_dwBufSize = dwNewBufSize;

    m_pBuffer = pBuf;

    TraceMsg(TF_IFORMS, "-EnsureBuffer");

    // Successfully realloced to bigger buffer
    return S_OK;
}

// grow psiIndex if needed
HRESULT CStringList::EnsureIndex(DWORD dwNumStringsNeeded)
{
    TraceMsg(TF_IFORMS, "+EnsureIndex");

    if (!m_psiIndex)
    {
        return E_FAIL;
    }

    if (INDEX_SIZE(dwNumStringsNeeded) > m_dwIndexSize)
    {
        DWORD dwNewMaxStrings = (m_psiIndex->dwNumStrings) * 2;
        DWORD dwNewIndexSize = INDEX_SIZE(dwNewMaxStrings);

        TraceMsg(TF_IFORMS, "IForms: CStringList growing max strings");

        StringIndex *psiBuf =
            (StringIndex *)LocalReAlloc(m_psiIndex, dwNewIndexSize, LMEM_MOVEABLE);

        if (!psiBuf)
        {
            // Realloc failure: Old memory still present
            TraceMsg(TF_IFORMS, "IForms: CStringList ReAlloc failure");
            return E_OUTOFMEMORY;
        }

        // Success. Don't need to fix any pointers in index (buffer is unchanged)
        m_psiIndex = psiBuf;
        m_dwIndexSize = dwNewIndexSize;
    }

    TraceMsg(TF_IFORMS, "-EnsureIndex");

    return S_OK;
}

// This dlg proc is used for password save, change, delete dialogs
INT_PTR AutoSuggestDlgProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch (msg)
    {
    case WM_INITDIALOG:
        CenterWindow(hDlg, GetParent(hDlg));

        SetWindowLongPtr(hDlg, DWLP_USER, (LONG_PTR) lParam);

        if (lParam == IDD_AUTOSUGGEST_SAVEPASSWORD)
        {
            // For "Save" password we default to no. For "Change" and "Delete" we default to yes.
            SetFocus(GetDlgItem(hDlg, IDNO));
            return FALSE;
        }
        return TRUE;

    case WM_COMMAND:
        switch (GET_WM_COMMAND_ID(wParam, lParam))
        {
            case IDCANCEL:          // close box
            case IDYES:             // yes button
            case IDNO:              // no button
                if (IDD_AUTOSUGGEST_SAVEPASSWORD == GetWindowLongPtr(hDlg, DWLP_USER))
                {
                    // Check the "don't ask me again" checkbox for the save password dlg
                    if (IsDlgButtonChecked(hDlg, IDC_AUTOSUGGEST_NEVER))
                    {
                        SHSetValue(HKEY_CURRENT_USER, c_szRegKeySMIEM, c_szRegValAskPasswords,
                                REG_SZ, c_szNo, sizeof(c_szNo));
                    }
                }

                EndDialog(hDlg, LOWORD(wParam));
                return TRUE;
        }
        break;

#ifdef CHECKBOX_HELP
    case WM_HELP:
        // Only process WM_HELP for save password dlg
        if (IDD_AUTOSUGGEST_SAVEPASSWORD == GetWindowLong(hDlg, DWL_USER))
        {
            SHWinHelpOnDemandWrap((HWND) ((LPHELPINFO) lParam)->hItemHandle, c_szHelpFile,
                HELP_WM_HELP, (DWORD_PTR)(LPTSTR) c_aIFormsHelpIds);
        }
        break;

    case WM_CONTEXTMENU:      // right mouse click
        // Only process WM_HELP for save password dlg
        if (IDD_AUTOSUGGEST_SAVEPASSWORD == GetWindowLong(hDlg, DWL_USER))
        {
            SHWinHelpOnDemandWrap((HWND) wParam, c_szHelpFile, HELP_CONTEXTMENU,
                (DWORD_PTR)(LPTSTR) c_aIFormsHelpIds);
        }
        break;
#endif
    }

    return FALSE;
}


//================================================================================

INT_PTR CALLBACK AskUserDlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg)
    {
        case WM_INITDIALOG:
        CenterWindow(hDlg, GetParent(hDlg));
        Animate_OpenEx(GetDlgItem(hDlg, IDD_ANIMATE), HINST_THISDLL, MAKEINTRESOURCE(IDA_AUTOSUGGEST));
        return TRUE;

        case WM_COMMAND:
        {
            switch (LOWORD(wParam))
            {
                case IDC_AUTOSUGGEST_HELP:
                    SHHtmlHelpOnDemandWrap(GetParent(hDlg), TEXT("iexplore.chm > iedefault"),
                        HH_DISPLAY_TOPIC, (DWORD_PTR) TEXT("autocomp.htm"), ML_CROSSCODEPAGE);
                    break;

                case IDYES:
                case IDNO:
                {
                    LPCTSTR pszData;
                    DWORD  cbData;
                    DWORD  dwData=0;

                    if (LOWORD(wParam) == IDYES)
                    {
                        pszData = c_szYes;
                        cbData = sizeof(c_szYes);
                    }
                    else
                    {
                        pszData = c_szNo;
                        cbData = sizeof(c_szNo);
                    }

                    // Write the enabled state into our CPL regkey
                    SHSetValue(HKEY_CURRENT_USER, c_szRegKeySMIEM, c_szRegValUseFormSuggest,
                        REG_SZ, pszData, cbData);

                    // Flag it as "asked user" so we don't ask them again
                    SHSetValue(HKEY_CURRENT_USER, c_szRegKeyIntelliForms, c_szRegValAskUser,
                        REG_DWORD, &dwData, sizeof(dwData));
                }

                // Fall through
                case IDCANCEL:
                {
                    EndDialog(hDlg, LOWORD(wParam));
                }
                break;
            }
        }
        return TRUE;

    case WM_DESTROY:
        break;
    }

    return FALSE;
}
