/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is mozilla.org code.
 *
 * The Initial Developer of the Original Code is
 * Netscape Communications Corporation.
 * Portions created by the Initial Developer are Copyright (C) 1998
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

#include "stdafx.h"
#include <string.h>
#include <string>
#include <objidl.h>
//#include <comdef.h>
#include <shlobj.h>

// commdlg.h is needed to build with WIN32_LEAN_AND_MEAN
#include <commdlg.h>

#include "MozillaControl.h"
#include "MozillaBrowser.h"
#include "IEHtmlDocument.h"
#include "PropertyDlg.h"
#include "PageSetupDlg.h"
#include "PromptService.h"
#include "HelperAppDlg.h"
#include "WindowCreator.h"

#include "nsNetUtil.h"
#include "nsCWebBrowser.h"
#include "nsIAtom.h"
#include "nsILocalFile.h"
#include "nsIWebBrowserPersist.h"
#include "nsIClipboardCommands.h"
#include "nsIProfile.h"
#include "nsIWidget.h"
#include "nsIWebBrowserFocus.h"
#include "nsAppDirectoryServiceDefs.h"
#include "nsIComponentRegistrar.h"

#ifdef NS_PRINTING
#include "nsIPrintOptions.h"
#include "nsIWebBrowserPrint.h"
#endif

#include "nsIDOMWindow.h"
#include "nsIDOMHTMLAnchorElement.h"
#include "nsIDOMNSDocument.h"

#include "nsEmbedAPI.h"
#include "nsEmbedCID.h"

#define HACK_NON_REENTRANCY
#ifdef HACK_NON_REENTRANCY
static HANDLE s_hHackedNonReentrancy = NULL;
#endif

#define NS_PROMPTSERVICE_CID \
  {0xa2112d6a, 0x0e28, 0x421f, {0xb4, 0x6a, 0x25, 0xc0, 0xb3, 0x8, 0xcb, 0xd0}}

#define NS_HELPERAPPLAUNCHERDIALOG_CID \
    {0xf68578eb, 0x6ec2, 0x4169, {0xae, 0x19, 0x8c, 0x62, 0x43, 0xf0, 0xab, 0xe1}}

static NS_DEFINE_CID(kPromptServiceCID, NS_PROMPTSERVICE_CID);
static NS_DEFINE_CID(kHelperAppLauncherDialogCID, NS_HELPERAPPLAUNCHERDIALOG_CID);

#ifdef NS_PRINTING
class PrintListener : public nsIWebProgressListener
{
    PRBool mComplete;
public:
    NS_DECL_ISUPPORTS
    NS_DECL_NSIWEBPROGRESSLISTENER

    PrintListener();
    virtual ~PrintListener();

    void WaitForComplete();
};
#endif

class SimpleDirectoryProvider :
    public nsIDirectoryServiceProvider
{
public:
    SimpleDirectoryProvider();
    BOOL IsValid() const;


    NS_DECL_ISUPPORTS
    NS_DECL_NSIDIRECTORYSERVICEPROVIDER

protected:
    virtual ~SimpleDirectoryProvider();

    nsCOMPtr<nsILocalFile> mApplicationRegistryDir;
    nsCOMPtr<nsILocalFile> mApplicationRegistryFile;
    nsCOMPtr<nsILocalFile> mUserProfileDir;
};

// Default page in design mode. The data protocol may or may not be supported
// so the scheme is checked before the page is loaded.

static const char kDesignModeScheme[] = "data";
static const OLECHAR kDesignModeURL[] =
    L"data:text/html,<html><body bgcolor=\"#00FF00\"><p>Mozilla Control</p></body></html>";

// Registry keys and values

static const TCHAR kBrowserHelperObjectRegKey[] =
    _T("Software\\Mozilla\\ActiveX Control\\Browser Helper Objects");


// Some recent SDKs define these IOleCommandTarget groups, so they're
// postfixed with _Moz to prevent linker errors.

GUID CGID_IWebBrowser_Moz =
    { 0xED016940L, 0xBD5B, 0x11cf, {0xBA, 0x4E, 0x00, 0xC0, 0x4F, 0xD7, 0x08, 0x16} };

GUID CGID_MSHTML_Moz =
    { 0xED016940L, 0xBD5B, 0x11cf, {0xBA, 0x4E, 0x00, 0xC0, 0x4F, 0xD7, 0x08, 0x16} };

/////////////////////////////////////////////////////////////////////////////
// CMozillaBrowser


nsTArray<CMozillaBrowser*> CMozillaBrowser::sBrowserList;

//
// Constructor
//
CMozillaBrowser::CMozillaBrowser()
{
    NG_TRACE_METHOD(CMozillaBrowser::CMozillaBrowser);

    // ATL flags ensures the control opens with a window
    m_bWindowOnly = TRUE;
    m_bWndLess = FALSE;

    // Initialize layout interfaces
    mWebBrowserAsWin = nsnull;
    mValidBrowserFlag = FALSE;

    // Create the container that handles some things for us
    mWebBrowserContainer = NULL;

    // Control starts off in non-edit mode
    mEditModeFlag = FALSE;

    // Control starts off without being a drop target
    mHaveDropTargetFlag = FALSE;

     // the IHTMLDocument, lazy allocation.
     mIERootDocument = NULL;

    // Browser helpers
    mBrowserHelperList = NULL;
    mBrowserHelperListCount = 0;

    // Name of the default profile to use
    mProfileName.Assign(NS_LITERAL_STRING("MozillaControl"));

    // Initialise the web browser
    Initialize();
}


//
// Destructor
//
CMozillaBrowser::~CMozillaBrowser()
{
    NG_TRACE_METHOD(CMozillaBrowser::~CMozillaBrowser);
    
    // Close the web browser
    Terminate();
}

// See bug 127982:
//
// Microsoft's InlineIsEqualGUID global function is multiply defined
// in ATL and/or SDKs with varying namespace requirements. To save the control
// from future grief, this method is used instead. 
static inline BOOL _IsEqualGUID(REFGUID rguid1, REFGUID rguid2)
{
   return (
      ((PLONG) &rguid1)[0] == ((PLONG) &rguid2)[0] &&
      ((PLONG) &rguid1)[1] == ((PLONG) &rguid2)[1] &&
      ((PLONG) &rguid1)[2] == ((PLONG) &rguid2)[2] &&
      ((PLONG) &rguid1)[3] == ((PLONG) &rguid2)[3]);
}

STDMETHODIMP CMozillaBrowser::InterfaceSupportsErrorInfo(REFIID riid)
{
    static const IID* arr[] = 
    {
        &IID_IWebBrowser,
        &IID_IWebBrowser2,
        &IID_IWebBrowserApp
    };
    for (int i = 0; i < (sizeof(arr) / sizeof(arr[0])); i++)
    {
        if (_IsEqualGUID(*arr[i], riid))
            return S_OK;
    }
    return S_FALSE;
}

//
// ShowContextMenu
//
void CMozillaBrowser::ShowContextMenu(PRUint32 aContextFlags, nsIDOMEvent *aEvent, nsIDOMNode *aNode)
{
    POINT pt;
    GetCursorPos(&pt);

    // Give the client application the chance to show its own menu
    // in place of the one the control is about to show.

    CIPtr(IDocHostUIHandler) spIDocHostUIHandler = m_spClientSite;
    if (spIDocHostUIHandler)
    {
        enum IE4MenuContexts
        {
            ctxMenuDefault = 0,
            ctxMenuImage,
            ctxMenuControl,
            ctxMenuTable,
            ctxMenuDebug,
            ctxMenu1DSelect,
            ctxMenuAnchor,
            ctxMenuImgDynSrc
        };

        DWORD dwID = ctxMenuDefault;
        if (aContextFlags & nsIContextMenuListener::CONTEXT_DOCUMENT)
        {
            dwID = ctxMenuDefault;
        }
        else if (aContextFlags & nsIContextMenuListener::CONTEXT_LINK)
        {
            dwID = ctxMenuAnchor;
        }
        else if (aContextFlags & nsIContextMenuListener::CONTEXT_IMAGE)
        {
            dwID = ctxMenuImage;
        }
        else if (aContextFlags & nsIContextMenuListener::CONTEXT_TEXT)
        {
            dwID = ctxMenu1DSelect;
        }
        else
        {
            dwID = ctxMenuDefault;
        }

        HRESULT hr = spIDocHostUIHandler->ShowContextMenu(dwID, &pt, NULL, NULL);
        if (hr == S_OK)
        {
            // Client handled menu
            return;
        }
    }

    LPTSTR pszMenuResource = NULL;
    if (aContextFlags & nsIContextMenuListener::CONTEXT_DOCUMENT)
    {
        pszMenuResource = MAKEINTRESOURCE(IDR_POPUP_DOCUMENT);
    }
    else if (aContextFlags & nsIContextMenuListener::CONTEXT_LINK)
    {
        pszMenuResource = MAKEINTRESOURCE(IDR_POPUP_LINK);
    }
    else if (aContextFlags & nsIContextMenuListener::CONTEXT_IMAGE)
    {
        pszMenuResource = MAKEINTRESOURCE(IDR_POPUP_IMAGE);
    }
    else if (aContextFlags & nsIContextMenuListener::CONTEXT_TEXT)
    {
        pszMenuResource = MAKEINTRESOURCE(IDR_POPUP_TEXT);
    }
    else
    {
        pszMenuResource = MAKEINTRESOURCE(IDR_POPUP_DOCUMENT);
    }

    if (pszMenuResource)
    {
        HMENU hMenu = LoadMenu(_Module.m_hInstResource, pszMenuResource);
        HMENU hPopupMenu = GetSubMenu(hMenu, 0);
        mContextNode = do_QueryInterface(aNode);
        UINT nCmd = TrackPopupMenu(hPopupMenu, TPM_NONOTIFY | TPM_RETURNCMD, pt.x, pt.y, 0, m_hWnd, NULL);
        DestroyMenu(hMenu);
        if (nCmd != 0)
        {
            SendMessage(WM_COMMAND, nCmd);
        }
        mContextNode = nsnull;
    }
}


//
// ShowURIPropertyDlg
//
void CMozillaBrowser::ShowURIPropertyDlg(const nsAString &aURI, const nsAString &aContentType)
{
    CPropertyDlg dlg;
    CPPageDlg linkDlg;
    dlg.AddPage(&linkDlg);

    if (!aURI.IsEmpty())
    {
        linkDlg.mType = aContentType;
        linkDlg.mURL = aURI;
    }

    dlg.DoModal();
}


//
// Displays a message box to the user. If the container provides
// a IDocHostShowUI interface we use that to display messages, otherwise
// a simple message box is shown.
//
int CMozillaBrowser::MessageBox(LPCTSTR lpszText, LPCTSTR lpszCaption, UINT nType)
{
    // Let the doc host display its own message box if it can
    CIPtr(IDocHostShowUI) spIDocHostShowUI = m_spClientSite;
    if (spIDocHostShowUI)
    {
        USES_CONVERSION;
        LRESULT lResult = 0;
        HRESULT hr = spIDocHostShowUI->ShowMessage(m_hWnd,
                        T2OLE(lpszText), T2OLE(lpszCaption), nType, NULL, 0, &lResult);
        if (hr == S_OK)
        {
            return lResult;
        }
    }

    // Do the default message box
    return CWindow::MessageBox(lpszText, lpszCaption, nType);
}


//
// Sets the startup error message from a resource string
//
HRESULT CMozillaBrowser::SetStartupErrorMessage(UINT nStringID)
{
    TCHAR szMsg[1024];
    ::LoadString(_Module.m_hInstResource, nStringID, szMsg, sizeof(szMsg) / sizeof(szMsg[0]));
    mStartupErrorMessage = szMsg;
    return S_OK;
}

//
// Tells the container to change focus to the next control in the dialog.
//
void CMozillaBrowser::NextDlgControl()
{
    HWND hwndParent = GetParent();
    if (::IsWindow(hwndParent))
    {
      ::PostMessage(hwndParent, WM_NEXTDLGCTL, 0, 0);
    }
}


//
// Tells the container to change focus to the previous control in the dialog.
//
void CMozillaBrowser::PrevDlgControl()
{
    HWND hwndParent = GetParent();
    if (::IsWindow(hwndParent))
    {
      ::PostMessage(hwndParent, WM_NEXTDLGCTL, 1, 0);
    }
}


///////////////////////////////////////////////////////////////////////////////
// Message handlers


// Handle WM_CREATE windows message
LRESULT CMozillaBrowser::OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
    NG_TRACE_METHOD(CMozillaBrowser::OnCreate);

    // Create the web browser
    CreateBrowser();

    // TODO create and register a drop target

    // Control is ready
    mBrowserReadyState = READYSTATE_COMPLETE;
    FireOnChanged(DISPID_READYSTATE);

    // Load browser helpers
    LoadBrowserHelpers();

    // Browse to a default page - if in design mode
    BOOL bUserMode = FALSE;
    if (SUCCEEDED(GetAmbientUserMode(bUserMode)))
    {
        if (!bUserMode)
        {
            // Load a page in design mode if the specified page is supported
            nsCOMPtr<nsIIOService> ios = do_GetIOService();
            if (ios)
            {
                // Ensure design page can be loaded by checking for a
                // registered protocol handler that supports the scheme
                nsCOMPtr<nsIProtocolHandler> ph;
                nsCAutoString phScheme;
                ios->GetProtocolHandler(kDesignModeScheme, getter_AddRefs(ph));
                if (ph &&
                    NS_SUCCEEDED(ph->GetScheme(phScheme)) &&
                    phScheme.Equals(NS_LITERAL_CSTRING(kDesignModeScheme)))
                {
                    Navigate(const_cast<BSTR>(kDesignModeURL), NULL, NULL, NULL, NULL);
                }
            }
        }
        else
        {
            if (mInitialSrc.Length() > 0)
            {
                Navigate(mInitialSrc, NULL, NULL, NULL, NULL);
            }
        }
    }

    // Clip the child windows out of paint operations
    SetWindowLong(GWL_STYLE, GetWindowLong(GWL_STYLE) | WS_CLIPCHILDREN | WS_TABSTOP);

    return 0;
}


// Handle WM_DESTROY window message
LRESULT CMozillaBrowser::OnDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
    NG_TRACE_METHOD(CMozillaBrowser::OnDestroy);

    // Unload browser helpers
    UnloadBrowserHelpers();

    // Clean up the browser
    DestroyBrowser();

    return 0;
}


// Handle WM_SIZE windows message
LRESULT CMozillaBrowser::OnSize(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
    NG_TRACE_METHOD(CMozillaBrowser::OnSize);

    RECT rc;
    rc.top = 0;
    rc.left = 0;
    rc.right = LOWORD(lParam);
    rc.bottom = HIWORD(lParam);
    
    AdjustWindowRectEx(&rc, GetWindowLong(GWL_STYLE), FALSE, GetWindowLong(GWL_EXSTYLE));

    rc.right -= rc.left;
    rc.bottom -= rc.top;
    rc.left = 0;
    rc.top = 0;

    // Pass resize information down to the browser...
    if (mWebBrowserAsWin)
    {
        mWebBrowserAsWin->SetPosition(rc.left, rc.top);
        mWebBrowserAsWin->SetSize(rc.right - rc.left, rc.bottom - rc.top, PR_TRUE);
    }
    return 0;
}

// Handle WM_SETFOCUS
LRESULT CMozillaBrowser::OnSetFocus(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
    ATLTRACE(_T("CMozillaBrowser::OnSetFocus()\n"));
    nsCOMPtr<nsIWebBrowserFocus> browserAsFocus = do_QueryInterface(mWebBrowser);
    if (browserAsFocus)
    {
        browserAsFocus->Activate();
    }
    CComQIPtr<IOleControlSite> controlSite = m_spClientSite;
    if (controlSite)
    {
        controlSite->OnFocus(TRUE);
    }
    return 0;
}

// Handle WM_KILLFOCUS
LRESULT CMozillaBrowser::OnKillFocus(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
    ATLTRACE(_T("CMozillaBrowser::OnKillFocus()\n"));
    nsCOMPtr<nsIWebBrowserFocus> browserAsFocus = do_QueryInterface(mWebBrowser);
    if (browserAsFocus)
    {
        browserAsFocus->Deactivate();
    }
    CComQIPtr<IOleControlSite> controlSite = m_spClientSite;
    if (controlSite)
    {
        controlSite->OnFocus(FALSE);
    }
    return 0;
}

// Handle WM_MOUSEACTIVATE messages
LRESULT CMozillaBrowser::OnMouseActivate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
    return MA_ACTIVATE;
}

// Handle WM_GETDLGCODE to receive keyboard presses
LRESULT CMozillaBrowser::OnGetDlgCode(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
    return DLGC_WANTALLKEYS; 
}

// Handle WM_PAINT windows message (and IViewObject::Draw) 
HRESULT CMozillaBrowser::OnDraw(ATL_DRAWINFO& di)
{
    NG_TRACE_METHOD(CMozillaBrowser::OnDraw);

    if (!BrowserIsValid())
    {
        RECT& rc = *(RECT*)di.prcBounds;
        DrawText(di.hdcDraw, mStartupErrorMessage.c_str(), -1, &rc, DT_TOP | DT_LEFT | DT_WORDBREAK);
    }

    return S_OK;
}

// Handle ID_PAGESETUP command
LRESULT CMozillaBrowser::OnPageSetup(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
{
    NG_TRACE_METHOD(CMozillaBrowser::OnPageSetup);


#ifdef NS_PRINTING
    nsCOMPtr<nsIWebBrowserPrint> print(do_GetInterface(mWebBrowser));
    nsCOMPtr<nsIPrintSettings> printSettings;
    if(!print ||
        NS_FAILED(print->GetGlobalPrintSettings(getter_AddRefs(printSettings))))
    {
        return 0;
    }

    // show the page setup dialog
    CPageSetupDlg dlg(printSettings);
    dlg.DoModal();
#endif

    return 0;
}

// Handle ID_PRINT command
LRESULT CMozillaBrowser::OnPrint(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
{
    NG_TRACE_METHOD(CMozillaBrowser::OnPrint);
    if (!BrowserIsValid())
    {
        return 0;
    }
    PrintDocument(TRUE);
    return 0;
}

LRESULT CMozillaBrowser::OnSaveAs(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
{
    NG_TRACE_METHOD(CMozillaBrowser::OnSaveAs);

    OPENFILENAME SaveFileName;

    char szFile[_MAX_PATH];
    char szFileTitle[256];

    //TODO:    The IE control allows you to also save as "Web Page, complete"
    //      where all of the page's images are saved in the same directory.
    //      For the moment, we're not allowing this option.

    memset(&SaveFileName, 0, sizeof(SaveFileName));
    SaveFileName.lStructSize = sizeof(SaveFileName);
    SaveFileName.hwndOwner = m_hWnd;
    SaveFileName.lpstrFilter = "Web Page, HTML Only (*.htm;*.html)\0*.htm;*.html\0Text File (*.txt)\0*.txt\0"; 
    SaveFileName.nFilterIndex = 1; 
    SaveFileName.lpstrFile = szFile; 
    SaveFileName.nMaxFile = sizeof(szFile); 
    SaveFileName.lpstrFileTitle = szFileTitle;
    SaveFileName.nMaxFileTitle = sizeof(szFileTitle); 
    SaveFileName.Flags = OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT; 
    SaveFileName.lpstrDefExt = "htm"; 

    //Get the title of the current web page to set as the default filename.
    char szTmp[_MAX_FNAME] = "untitled";
    BSTR pageName = NULL;
    get_LocationName(&pageName); // Page title
    if (pageName)
    {
        USES_CONVERSION;
        strncpy(szTmp, OLE2A(pageName), sizeof(szTmp) - 1);
        SysFreeString(pageName);
        szTmp[sizeof(szTmp) - 1] = '\0';
    }
    
    // The SaveAs dialog will fail if szFile contains any "bad" characters.
    // This hunk of code attempts to mimick the IE way of replacing "bad"
    // characters with "good" characters.
    int j = 0;
    for (int i=0; szTmp[i]!='\0'; i++)
    {        
        switch(szTmp[i])
        {
        case '\\':
        case '*':
        case '|':
        case ':':
        case '"':
        case '>':
        case '<':
        case '?':
            break;
        case '.':
            if (szTmp[i+1] != '\0')
            {
                szFile[j] = '_';
                j++;
            }
            break;
        case '/':
            szFile[j] = '-';
            j++;
            break;
        default:
            szFile[j] = szTmp[i];
            j++;
        }
    }
    szFile[j] = '\0';

    HRESULT hr = S_OK;
    if (GetSaveFileName(&SaveFileName))
    {
        nsCOMPtr<nsIWebBrowserPersist> persist(do_QueryInterface(mWebBrowser));
        USES_CONVERSION;

        char szDataFile[_MAX_PATH];
        char szDataPath[_MAX_PATH];
        char drive[_MAX_DRIVE];
        char dir[_MAX_DIR];
        char fname[_MAX_FNAME];
        char ext[_MAX_EXT];

        _splitpath(szFile, drive, dir, fname, ext);
        sprintf(szDataFile, "%s_files", fname);
        _makepath(szDataPath, drive, dir, szDataFile, "");

        nsCOMPtr<nsILocalFile> file;
        NS_NewNativeLocalFile(nsDependentCString(T2A(szFile)), TRUE, getter_AddRefs(file));

        nsCOMPtr<nsILocalFile> dataPath;
        NS_NewNativeLocalFile(nsDependentCString(szDataPath), TRUE, getter_AddRefs(dataPath));

        persist->SaveDocument(nsnull, file, dataPath, nsnull, 0, 0);
    }

    return hr;
}

LRESULT CMozillaBrowser::OnProperties(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
{
    NG_TRACE_METHOD(CMozillaBrowser::OnProperties);
    MessageBox(_T("No Properties Yet!"), _T("Control Message"), MB_OK);
    // TODO show the properties dialog
    return 0;
}

LRESULT CMozillaBrowser::OnCut(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
{
    NG_TRACE_METHOD(CMozillaBrowser::OnCut);
    nsCOMPtr<nsIClipboardCommands> clipboard(do_GetInterface(mWebBrowser));
    if (clipboard)
    {
        clipboard->CutSelection();
    }
    return 0;
}

LRESULT CMozillaBrowser::OnCopy(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
{
    NG_TRACE_METHOD(CMozillaBrowser::OnCopy);
    nsCOMPtr<nsIClipboardCommands> clipboard(do_GetInterface(mWebBrowser));
    if (clipboard)
    {
        clipboard->CopySelection();
    }
    return 0;
}

LRESULT CMozillaBrowser::OnPaste(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
{
    NG_TRACE_METHOD(CMozillaBrowser::OnPaste);
    nsCOMPtr<nsIClipboardCommands> clipboard(do_GetInterface(mWebBrowser));
    if (clipboard)
    {
        clipboard->Paste();
    }
    return 0;
}

LRESULT CMozillaBrowser::OnSelectAll(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
{
    NG_TRACE_METHOD(CMozillaBrowser::OnSelectAll);
    nsCOMPtr<nsIClipboardCommands> clipboard(do_GetInterface(mWebBrowser));
    if (clipboard)
    {
        clipboard->SelectAll();
    }
    return 0;
}

// Handle ID_VIEWSOURCE command
LRESULT CMozillaBrowser::OnViewSource(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
{
    NG_TRACE_METHOD(CMozillaBrowser::OnViewSource);

    if (!mWebBrowser)
    {
        // No webbrowser to view!
        NG_ASSERT(0);
        return 0;
    }

    nsCOMPtr<nsIWebNavigation> webNav = do_QueryInterface(mWebBrowser);
    if (!webNav)
    {
        // No webnav!
        NG_ASSERT(0);
        return 0;
    }

    nsCOMPtr<nsIURI> uri;
    webNav->GetCurrentURI(getter_AddRefs(uri));
    if (!uri)
    {
        // No URI to view!
        NG_ASSERT(0);
        return 0;
    }

    // Get the current URI
    nsCAutoString aURI;
    uri->GetSpec(aURI);

    NS_ConvertUTF8toUTF16 strURI(aURI);
    strURI.Insert(NS_LITERAL_STRING("view-source:"), 0);

    // Ask the client to create a window to view the source in
    CIPtr(IDispatch) spDispNew;
    VARIANT_BOOL bCancel = VARIANT_FALSE;
    Fire_NewWindow2(&spDispNew, &bCancel);

    // Load the view-source into a new url
    if ((bCancel == VARIANT_FALSE) && spDispNew)
    {
        CIPtr(IWebBrowser2) spOther = spDispNew;;
        if (spOther)
        {
            // tack in the viewsource command
            CComBSTR bstrURL(strURI.get());
            CComVariant vURL(bstrURL);
            VARIANT vNull;
            vNull.vt = VT_NULL;
            spOther->Navigate2(&vURL, &vNull, &vNull, &vNull, &vNull);            
        }
    }

    return 0;
}


///////////////////////////////////////////////////////////////////////////////
// Document handlers


LRESULT CMozillaBrowser::OnDocumentBack(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
{
    GoBack();
    return 0;
}


LRESULT CMozillaBrowser::OnDocumentForward(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
{
    GoForward();
    return 0;
}


LRESULT CMozillaBrowser::OnDocumentPrint(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
{
    return OnPrint(wNotifyCode, wID, hWndCtl, bHandled);
}


LRESULT CMozillaBrowser::OnDocumentRefresh(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
{
    Refresh();
    return 0;
}


LRESULT CMozillaBrowser::OnDocumentProperties(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
{
    nsCOMPtr<nsIDOMDocument> ownerDoc;
    if (mContextNode)
    {
        mContextNode->GetOwnerDocument(getter_AddRefs(ownerDoc));
    }

    // Get the document URL
    nsAutoString uri;
    nsCOMPtr<nsIDOMHTMLDocument> htmlDoc = do_QueryInterface(ownerDoc);
    if (htmlDoc)
    {
        htmlDoc->GetURL(uri);
    }
    nsAutoString contentType;
    nsCOMPtr<nsIDOMNSDocument> doc = do_QueryInterface(ownerDoc);
    if (doc)
    {
        doc->GetContentType(contentType);
    }

    ShowURIPropertyDlg(uri, contentType);

    return 0;
}


///////////////////////////////////////////////////////////////////////////////
// Link handlers


LRESULT CMozillaBrowser::OnLinkOpen(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
{
    nsAutoString uri;

    nsCOMPtr<nsIDOMHTMLAnchorElement> anchorElement = do_QueryInterface(mContextNode);
    if (anchorElement)
    {
        anchorElement->GetHref(uri);
    }

    if (!uri.IsEmpty())
    {
        CComBSTR bstrURI(uri.get());
        CComVariant vFlags(0);
        Navigate(bstrURI, &vFlags, NULL, NULL, NULL);
    }

    return 0;
}


LRESULT CMozillaBrowser::OnLinkOpenInNewWindow(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
{
    nsAutoString uri;

    nsCOMPtr<nsIDOMHTMLAnchorElement> anchorElement = do_QueryInterface(mContextNode);
    if (anchorElement)
    {
        anchorElement->GetHref(uri);
    }

    if (!uri.IsEmpty())
    {
        CComBSTR bstrURI(uri.get());
        CComVariant vFlags(navOpenInNewWindow);
        Navigate(bstrURI, &vFlags, NULL, NULL, NULL);
    }

    return 0;
}


LRESULT CMozillaBrowser::OnLinkCopyShortcut(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
{
    nsAutoString uri;

    nsCOMPtr<nsIDOMHTMLAnchorElement> anchorElement = do_QueryInterface(mContextNode);
    if (anchorElement)
    {
        anchorElement->GetHref(uri);
    }

    if (!uri.IsEmpty() && OpenClipboard())
    {
        EmptyClipboard();

        NS_ConvertUTF16toUTF8 curi(uri);
        const char *stringText;
        PRUint32 stringLen = NS_CStringGetData(curi,
                                               &stringText);

        // CF_TEXT
        HGLOBAL hmemText = GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT,
                                       stringLen + 1);
        char *pszText = (char *) GlobalLock(hmemText);
        strncpy(pszText, stringText, stringLen);
        pszText[stringLen] = '\0';
        GlobalUnlock(hmemText);
        SetClipboardData(CF_TEXT, hmemText);

        // UniformResourceLocator - CFSTR_SHELLURL
        const UINT cfShellURL = RegisterClipboardFormat(CFSTR_SHELLURL);
        HGLOBAL hmemURL = GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT,
                                      stringLen + 1);
        char *pszURL = (char *) GlobalLock(hmemURL);
        strncpy(pszText, stringText, stringLen);
        pszText[stringLen] = '\0';
        GlobalUnlock(hmemURL);
        SetClipboardData(cfShellURL, hmemURL);

        // TODO
        // FileContents - CFSTR_FILECONTENTS
        // FileGroupDescriptor - CFSTR_FILEDESCRIPTORA
        // FileGroupDescriptorW - CFSTR_FILEDESCRIPTORW

        CloseClipboard();
    }
    return 0;
}


LRESULT CMozillaBrowser::OnLinkProperties(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
{
    nsAutoString uri;
    nsAutoString type;

    nsCOMPtr<nsIDOMHTMLAnchorElement> anchorElement = do_QueryInterface(mContextNode);
    if (anchorElement)
    {
        anchorElement->GetHref(uri);
        anchorElement->GetType(type); // How many anchors implement this I wonder
    }

    ShowURIPropertyDlg(uri, type);

    return 0;
}


///////////////////////////////////////////////////////////////////////////////

// Initialises the web browser engine
HRESULT CMozillaBrowser::Initialize()
{
#ifdef HACK_NON_REENTRANCY
    // Attempt to open a named event for this process. If it's not there we
    // know this is the first time the control has run in this process, so create
    // the named event and do the initialisation. Otherwise do nothing.
    TCHAR szHackEvent[255];
    _stprintf(szHackEvent, _T("MozCtlEvent%d"), (int) GetCurrentProcessId());
    s_hHackedNonReentrancy = OpenEvent(EVENT_ALL_ACCESS, FALSE, szHackEvent);
    if (s_hHackedNonReentrancy == NULL)
    {
        s_hHackedNonReentrancy = CreateEvent(NULL, FALSE, FALSE, szHackEvent);
#endif

    // Extract the bin directory path from the control's filename
    TCHAR szMozCtlPath[MAX_PATH];
    memset(szMozCtlPath, 0, sizeof(szMozCtlPath));
    GetModuleFileName(_Module.m_hInst, szMozCtlPath, sizeof(szMozCtlPath) / sizeof(szMozCtlPath[0]));

    TCHAR szTmpDrive[_MAX_DRIVE];
    TCHAR szTmpDir[_MAX_DIR];
    TCHAR szTmpFname[_MAX_FNAME];
    TCHAR szTmpExt[_MAX_EXT];
    TCHAR szBinDirPath[MAX_PATH];

    _tsplitpath(szMozCtlPath, szTmpDrive, szTmpDir, szTmpFname, szTmpExt);
    memset(szBinDirPath, 0, sizeof(szBinDirPath));
    _tmakepath(szBinDirPath, szTmpDrive, szTmpDir, NULL, NULL);
    if (_tcslen(szBinDirPath) == 0)
    {
        return E_FAIL;
    }

    // Create a simple directory provider. If this fails because the directories are
    // invalid or whatever then the control will fallback on the default directory
    // provider.

    nsCOMPtr<nsIDirectoryServiceProvider> directoryProvider;
    SimpleDirectoryProvider *pDirectoryProvider = new SimpleDirectoryProvider;
    if (pDirectoryProvider->IsValid())
        directoryProvider = do_QueryInterface(pDirectoryProvider);

    // Create an object to represent the path
    nsresult rv;
    nsCOMPtr<nsILocalFile> binDir;
    USES_CONVERSION;
    NS_NewNativeLocalFile(nsDependentCString(T2A(szBinDirPath)), TRUE, getter_AddRefs(binDir));
    rv = NS_InitEmbedding(binDir, directoryProvider);

    // Load preferences service
    mPrefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
    if (NS_FAILED(rv))
    {
        NG_ASSERT(0);
        NG_TRACE_ALWAYS(_T("Could not create preference object rv=%08x\n"), (int) rv);
        SetStartupErrorMessage(IDS_CANNOTCREATEPREFS);
        return E_FAIL;
    }

    // Stuff in here only needs to be done once
    static BOOL bRegisterComponents = FALSE;
    if (!bRegisterComponents)
    {
        // Register our own native prompting service for message boxes, login
        // prompts etc.
        nsCOMPtr<nsIFactory> promptFactory;
        rv = NS_NewPromptServiceFactory(getter_AddRefs(promptFactory));
        if (NS_FAILED(rv)) return rv;

        nsCOMPtr<nsIComponentRegistrar> registrar;
        rv = NS_GetComponentRegistrar(getter_AddRefs(registrar));
        if (NS_FAILED(rv)) return rv;

        rv = registrar->RegisterFactory(kPromptServiceCID,
            "Prompt Service",
            NS_PROMPTSERVICE_CONTRACTID,
            promptFactory);
        if (NS_FAILED(rv)) return rv;

        // Helper app launcher dialog
        nsCOMPtr<nsIFactory> helperAppDlgFactory;
        rv = NS_NewHelperAppLauncherDlgFactory(getter_AddRefs(helperAppDlgFactory));
        if (NS_FAILED(rv)) return rv;

        rv = registrar->RegisterFactory(kHelperAppLauncherDialogCID,
            "Helper App Launcher Dialog",
            "@mozilla.org/helperapplauncherdialog;1",
            helperAppDlgFactory);
        if (NS_FAILED(rv)) return rv;

        // create our local object
        CWindowCreator *creator = new CWindowCreator();
        nsCOMPtr<nsIWindowCreator> windowCreator;
        windowCreator = static_cast<nsIWindowCreator *>(creator);

        // Attach it via the watcher service
        nsCOMPtr<nsIWindowWatcher> watcher =
            do_GetService(NS_WINDOWWATCHER_CONTRACTID);
        if (watcher)
            watcher->SetWindowCreator(windowCreator);
    }

    // Set the profile which the control will use
    nsCOMPtr<nsIProfile> profileService = 
             do_GetService(NS_PROFILE_CONTRACTID, &rv);
    if (NS_FAILED(rv))
    {
        return E_FAIL;
    }

    // Make a new default profile
    PRBool profileExists = PR_FALSE;
    rv = profileService->ProfileExists(mProfileName.get(), &profileExists);
    if (NS_FAILED(rv))
    {
        return E_FAIL;
    }
    else if (!profileExists)
    {
        rv = profileService->CreateNewProfile(mProfileName.get(), nsnull, nsnull, PR_FALSE);
        if (NS_FAILED(rv))
        {
            return E_FAIL;
        }
    }

    rv = profileService->SetCurrentProfile(mProfileName.get());
    if (NS_FAILED(rv))
    {
        return E_FAIL;
    }

#ifdef HACK_NON_REENTRANCY
    }
#endif

    return S_OK;
}

// Terminates the web browser engine
HRESULT CMozillaBrowser::Terminate()
{
#ifdef HACK_NON_REENTRANCY
    if (0)
    {
#endif

    mPrefBranch = nsnull;
    NS_TermEmbedding();

#ifdef HACK_NON_REENTRANCY
    }
#endif

    return S_OK;
}


// Create and initialise the web browser
HRESULT CMozillaBrowser::CreateBrowser() 
{    
    NG_TRACE_METHOD(CMozillaBrowser::CreateBrowser);
    
    if (mWebBrowser != nsnull)
    {
        NG_ASSERT(0);
        NG_TRACE_ALWAYS(_T("CreateBrowser() called more than once!"));
        return SetErrorInfo(E_UNEXPECTED);
    }

    RECT rcLocation;
    GetClientRect(&rcLocation);
    if (IsRectEmpty(&rcLocation))
    {
        rcLocation.bottom++;
        rcLocation.top++;
    }

    nsresult rv;

    // Create the web browser
    mWebBrowser = do_CreateInstance(NS_WEBBROWSER_CONTRACTID, &rv);
    if (NS_FAILED(rv))
    {
        NG_ASSERT(0);
        NG_TRACE_ALWAYS(_T("Could not create webbrowser object rv=%08x\n"), (int) rv);
        SetStartupErrorMessage(IDS_CANNOTCREATEPREFS);
        return rv;
    }

    // Configure what the web browser can and cannot do
    nsCOMPtr<nsIWebBrowserSetup> webBrowserAsSetup(do_QueryInterface(mWebBrowser));

    // Allow plugins?
    const PRBool kAllowPlugins = PR_TRUE;
    webBrowserAsSetup->SetProperty(nsIWebBrowserSetup::SETUP_ALLOW_PLUGINS, kAllowPlugins);

    // Host chrome or content?
    const PRBool kHostChrome = PR_FALSE; // Hardcoded for now
    webBrowserAsSetup->SetProperty(nsIWebBrowserSetup::SETUP_IS_CHROME_WRAPPER, PR_FALSE);

    // Create the webbrowser window
    mWebBrowserAsWin = do_QueryInterface(mWebBrowser);
    rv = mWebBrowserAsWin->InitWindow(nsNativeWidget(m_hWnd), nsnull,
        0, 0, rcLocation.right - rcLocation.left, rcLocation.bottom - rcLocation.top);
    rv = mWebBrowserAsWin->Create();

    // Create the container object
    mWebBrowserContainer = new CWebBrowserContainer(this);
    if (mWebBrowserContainer == NULL)
    {
        NG_ASSERT(0);
        NG_TRACE_ALWAYS(_T("Could not create webbrowsercontainer - out of memory\n"));
        return NS_ERROR_OUT_OF_MEMORY;
    }
    mWebBrowserContainer->AddRef();

    // Set up the browser with its chrome
    mWebBrowser->SetContainerWindow(static_cast<nsIWebBrowserChrome*>(mWebBrowserContainer));
    mWebBrowser->SetParentURIContentListener(mWebBrowserContainer);

    // Subscribe for progress notifications
    nsCOMPtr<nsIWeakReference> listener(
        do_GetWeakReference(static_cast<nsIWebProgressListener*>(mWebBrowserContainer)));
    mWebBrowser->AddWebBrowserListener(listener, NS_GET_IID(nsIWebProgressListener));

    // Visible
    mWebBrowserAsWin->SetVisibility(PR_TRUE);

    // Activated
    nsCOMPtr<nsIWebBrowserFocus> browserAsFocus = do_QueryInterface(mWebBrowser);
    browserAsFocus->Activate();

    // Get an editor session
    mEditingSession = do_GetInterface(mWebBrowser);
    mCommandManager = do_GetInterface(mWebBrowser);

    // Append browser to browser list
    sBrowserList.AppendElement(this);

    mValidBrowserFlag = TRUE;

    return S_OK;
}

// Clean up the browser
HRESULT CMozillaBrowser::DestroyBrowser()
{
    // TODO unregister drop target

    mValidBrowserFlag = FALSE;

    // Remove browser from browser list
    sBrowserList.RemoveElement(this);

     // Destroy the htmldoc
     if (mIERootDocument != NULL)
     {
         mIERootDocument->Release();
         mIERootDocument = NULL;
     }

    // Destroy layout...
    if (mWebBrowserAsWin)
    {
        mWebBrowserAsWin->Destroy();
        mWebBrowserAsWin = nsnull;
    }

    if (mWebBrowserContainer)
    {
        mWebBrowserContainer->Release();
        mWebBrowserContainer = NULL;
    }

    mEditingSession = nsnull;
    mCommandManager = nsnull;
    mWebBrowser = nsnull;

    return S_OK;
}


// Turns the editor mode on or off
HRESULT CMozillaBrowser::SetEditorMode(BOOL bEnabled)
{
    NG_TRACE_METHOD(CMozillaBrowser::SetEditorMode);

    if (!mEditingSession || !mCommandManager)
        return E_FAIL;
  
    nsCOMPtr<nsIDOMWindow> domWindow;
    nsresult rv = GetDOMWindow(getter_AddRefs(domWindow));
    if (NS_FAILED(rv))
        return E_FAIL;

    rv = mEditingSession->MakeWindowEditable(domWindow, "html", PR_FALSE,
                                             PR_TRUE, PR_FALSE);
 
    return S_OK;
}


HRESULT CMozillaBrowser::OnEditorCommand(DWORD nCmdID)
{
    NG_TRACE_METHOD(CMozillaBrowser::OnEditorCommand);

    nsCOMPtr<nsIDOMWindow> domWindow;
    GetDOMWindow(getter_AddRefs(domWindow));

    const char *styleCommand = nsnull;
    nsICommandParams *commandParams = nsnull;

    switch (nCmdID)
    {
    case IDM_BOLD:
        styleCommand = "cmd_bold";
        break;
    case IDM_ITALIC:
        styleCommand = "cmd_italic";
        break;
    case IDM_UNDERLINE:
        styleCommand = "cmd_underline";
        break;
    
    // TODO add the rest!
    
    default:
        // DO NOTHING
        break;
    }

    return mCommandManager ?
        mCommandManager->DoCommand(styleCommand, commandParams, domWindow) :
        NS_ERROR_FAILURE;
}


// Return the root DOM document
HRESULT CMozillaBrowser::GetDOMDocument(nsIDOMDocument **pDocument)
{
    NG_TRACE_METHOD(CMozillaBrowser::GetDOMDocument);

    HRESULT hr = E_FAIL;

    // Test for stupid args
    if (pDocument == NULL)
    {
        NG_ASSERT(0);
        return E_INVALIDARG;
    }

    *pDocument = nsnull;

    if (!BrowserIsValid())
    {
        NG_ASSERT(0);
        return E_UNEXPECTED;
    }

    // Get the DOM window from the webbrowser
    nsCOMPtr<nsIDOMWindow> window;
    mWebBrowser->GetContentDOMWindow(getter_AddRefs(window));
    if (window)
    {
        if (NS_SUCCEEDED(window->GetDocument(pDocument)) && *pDocument)
        {
            hr = S_OK;
        }
    }

    return hr;
}


// Load any browser helpers
HRESULT CMozillaBrowser::LoadBrowserHelpers()
{
    NG_TRACE_METHOD(CMozillaBrowser::LoadBrowserHelpers);

    UnloadBrowserHelpers();

    // IE loads browser helper objects from a branch of the registry
    // Search the branch looking for objects to load with the control.

    CRegKey cKey;
    if (cKey.Open(HKEY_LOCAL_MACHINE, kBrowserHelperObjectRegKey, KEY_ENUMERATE_SUB_KEYS) != ERROR_SUCCESS)
    {
        NG_TRACE(_T("No browser helper key found\n"));
        return S_FALSE;
    }

    // Count the number of browser helper object keys
    ULONG nHelperKeys = 0;
    LONG nResult = ERROR_SUCCESS;
    while (nResult == ERROR_SUCCESS)
    {
        TCHAR szCLSID[50];
        DWORD dwCLSID = sizeof(szCLSID) / sizeof(TCHAR);
        FILETIME cLastWrite;
        
        // Read next subkey
        nResult = RegEnumKeyEx(cKey, nHelperKeys, szCLSID, &dwCLSID, NULL, NULL, NULL, &cLastWrite);
        if (nResult != ERROR_SUCCESS)
        {
            break;
        }
        nHelperKeys++;
    }
    if (nHelperKeys == 0)
    {
         NG_TRACE(_T("No browser helper objects found\n"));
        return S_FALSE;
    }

    // Get the CLSID for each browser helper object
    CLSID *pClassList = new CLSID[nHelperKeys];
    DWORD nHelpers = 0;
    DWORD nKey = 0;
    nResult = ERROR_SUCCESS;
    while (nResult == ERROR_SUCCESS)
    {
        TCHAR szCLSID[50];
        DWORD dwCLSID = sizeof(szCLSID) / sizeof(TCHAR);
        FILETIME cLastWrite;
        
        // Read next subkey
        nResult = RegEnumKeyEx(cKey, nKey++, szCLSID, &dwCLSID, NULL, NULL, NULL, &cLastWrite);
        if (nResult != ERROR_SUCCESS)
        {
            break;
        }

        NG_TRACE(_T("Reading helper object entry \"%s\"\n"), szCLSID);

        // Turn the key into a CLSID
        USES_CONVERSION;
        CLSID clsid;
        if (CLSIDFromString(T2OLE(szCLSID), &clsid) != NOERROR)
        {
            continue;
        }

        pClassList[nHelpers++] = clsid;
    }

    mBrowserHelperList = new CComUnkPtr[nHelpers];

    // Create each object in turn
    for (ULONG i = 0; i < nHelpers; i++)
    {
        CLSID clsid = pClassList[i];
        HRESULT hr;
        CComQIPtr<IObjectWithSite, &IID_IObjectWithSite> cpObjectWithSite;

        hr = CoCreateInstance(clsid, NULL, CLSCTX_INPROC_SERVER, IID_IObjectWithSite, (LPVOID *) &cpObjectWithSite);
        if (FAILED(hr))
        {
            NG_TRACE(_T("Registered browser helper object cannot be created\n"));;
        }
        else
        {
            // Set the object to point at the browser
            cpObjectWithSite->SetSite((IWebBrowser2 *) this);

            // Store in the list
            CComUnkPtr cpUnk = cpObjectWithSite;
            mBrowserHelperList[mBrowserHelperListCount++] = cpUnk;
        }
    }

    delete []pClassList;
        
    return S_OK;
}

// Release browser helpers
HRESULT CMozillaBrowser::UnloadBrowserHelpers()
{
    NG_TRACE_METHOD(CMozillaBrowser::UnloadBrowserHelpers);

    if (mBrowserHelperList == NULL)
    {
        return S_OK;
    }

    // Destroy each helper    
    for (ULONG i = 0; i < mBrowserHelperListCount; i++)
    {
        CComUnkPtr cpUnk = mBrowserHelperList[i];
        if (cpUnk)
        {
            CComQIPtr<IObjectWithSite, &IID_IObjectWithSite> cpObjectWithSite = cpUnk;
            if (cpObjectWithSite)
            {
                cpObjectWithSite->SetSite(NULL);
            }
        }
    }

    // Cleanup the array
    mBrowserHelperListCount = 0;
    delete []mBrowserHelperList;
    mBrowserHelperList = NULL;

    return S_OK;
}

// Print document
HRESULT CMozillaBrowser::PrintDocument(BOOL promptUser)
{
#ifdef NS_PRINTING
    // Print the contents
    nsCOMPtr<nsIWebBrowserPrint> browserAsPrint = do_GetInterface(mWebBrowser);
    NS_ASSERTION(browserAsPrint, "No nsIWebBrowserPrint!");

    PRBool oldPrintSilent = PR_FALSE;
    nsCOMPtr<nsIPrintSettings> printSettings;
    browserAsPrint->GetGlobalPrintSettings(getter_AddRefs(printSettings));
    if (printSettings) 
    {
        printSettings->GetPrintSilent(&oldPrintSilent);
        printSettings->SetPrintSilent(promptUser ? PR_FALSE : PR_TRUE);
    }

    // Disable print progress dialog (XUL)
    PRBool oldShowPrintProgress = FALSE;
    const char *kShowPrintProgressPref = "print.show_print_progress";
    mPrefBranch->GetBoolPref(kShowPrintProgressPref, &oldShowPrintProgress);
    mPrefBranch->SetBoolPref(kShowPrintProgressPref, PR_FALSE);

    // Print
    PrintListener *listener = new PrintListener;
    nsCOMPtr<nsIWebProgressListener> printListener = do_QueryInterface(listener);
    nsresult rv = browserAsPrint->Print(printSettings, printListener);
    if (NS_SUCCEEDED(rv))
    {
        listener->WaitForComplete();
    }

    // Cleanup
    if (printSettings)
    {
        printSettings->SetPrintSilent(oldPrintSilent);
    }
    mPrefBranch->SetBoolPref(kShowPrintProgressPref, oldShowPrintProgress);
#endif
    return S_OK;
}

/////////////////////////////////////////////////////////////////////////////
// IOleObject overrides

// This is an almost verbatim copy of the standard ATL implementation of
// IOleObject::InPlaceActivate but with a few lines commented out.

HRESULT CMozillaBrowser::InPlaceActivate(LONG iVerb, const RECT* prcPosRect)
{
    NG_TRACE_METHOD(CMozillaBrowser::InPlaceActivate);

    HRESULT hr;

    if (m_spClientSite == NULL)
        return S_OK;

    CComPtr<IOleInPlaceObject> pIPO;
    ControlQueryInterface(IID_IOleInPlaceObject, (void**)&pIPO);
    _ASSERTE(pIPO != NULL);
    if (prcPosRect != NULL)
        pIPO->SetObjectRects(prcPosRect, prcPosRect);

    if (!m_bNegotiatedWnd)
    {
        if (!m_bWindowOnly)
            // Try for windowless site
            hr = m_spClientSite->QueryInterface(IID_IOleInPlaceSiteWindowless, (void **)&m_spInPlaceSite);

        if (m_spInPlaceSite)
        {
            m_bInPlaceSiteEx = TRUE;
            m_bWndLess = SUCCEEDED(m_spInPlaceSite->CanWindowlessActivate());
            m_bWasOnceWindowless = TRUE;
        }
        else
        {
            m_spClientSite->QueryInterface(IID_IOleInPlaceSiteEx, (void **)&m_spInPlaceSite);
            if (m_spInPlaceSite)
                m_bInPlaceSiteEx = TRUE;
            else
                hr = m_spClientSite->QueryInterface(IID_IOleInPlaceSite, (void **)&m_spInPlaceSite);
        }
    }

    _ASSERTE(m_spInPlaceSite);
    if (!m_spInPlaceSite)
        return E_FAIL;

    m_bNegotiatedWnd = TRUE;

    if (!m_bInPlaceActive)
    {

        BOOL bNoRedraw = FALSE;
        if (m_bWndLess)
            m_spInPlaceSite->OnInPlaceActivateEx(&bNoRedraw, ACTIVATE_WINDOWLESS);
        else
        {
            if (m_bInPlaceSiteEx)
                m_spInPlaceSite->OnInPlaceActivateEx(&bNoRedraw, 0);
            else
            {
                HRESULT hr = m_spInPlaceSite->CanInPlaceActivate();
                if (FAILED(hr))
                    return hr;
                m_spInPlaceSite->OnInPlaceActivate();
            }
        }
    }

    m_bInPlaceActive = TRUE;

    // get location in the parent window,
    // as well as some information about the parent
    //
    OLEINPLACEFRAMEINFO frameInfo;
    RECT rcPos, rcClip;
    CComPtr<IOleInPlaceFrame> spInPlaceFrame;
    CComPtr<IOleInPlaceUIWindow> spInPlaceUIWindow;
    frameInfo.cb = sizeof(OLEINPLACEFRAMEINFO);
    HWND hwndParent;
    if (m_spInPlaceSite->GetWindow(&hwndParent) == S_OK)
    {
        m_spInPlaceSite->GetWindowContext(&spInPlaceFrame,
            &spInPlaceUIWindow, &rcPos, &rcClip, &frameInfo);

        if (!m_bWndLess)
        {
            if (m_hWndCD)
            {
                ::ShowWindow(m_hWndCD, SW_SHOW);
                ::SetFocus(m_hWndCD);
            }
            else
            {
                HWND h = CreateControlWindow(hwndParent, rcPos);
                _ASSERTE(h == m_hWndCD);
            }
        }

        pIPO->SetObjectRects(&rcPos, &rcClip);
    }

    CComPtr<IOleInPlaceActiveObject> spActiveObject;
    ControlQueryInterface(IID_IOleInPlaceActiveObject, (void**)&spActiveObject);

    // Gone active by now, take care of UIACTIVATE
    if (DoesVerbUIActivate(iVerb))
    {
        if (!m_bUIActive)
        {
            m_bUIActive = TRUE;
            hr = m_spInPlaceSite->OnUIActivate();
            if (FAILED(hr))
                return hr;

            SetControlFocus(TRUE);
            // set ourselves up in the host.
            //
            if (spActiveObject)
            {
                if (spInPlaceFrame)
                    spInPlaceFrame->SetActiveObject(spActiveObject, NULL);
                if (spInPlaceUIWindow)
                    spInPlaceUIWindow->SetActiveObject(spActiveObject, NULL);
            }

// These lines are deliberately commented out to demonstrate what breaks certain
// control containers.

//            if (spInPlaceFrame)
//                spInPlaceFrame->SetBorderSpace(NULL);
//            if (spInPlaceUIWindow)
//                spInPlaceUIWindow->SetBorderSpace(NULL);
        }
    }

    m_spClientSite->ShowObject();

    return S_OK;
}


HRESULT STDMETHODCALLTYPE CMozillaBrowser::GetClientSite(IOleClientSite **ppClientSite)
{
    NG_TRACE_METHOD(CMozillaBrowser::GetClientSite);

    NG_ASSERT(ppClientSite);

    // This fixes a problem in the base class which asserts if the client
    // site has not been set when this method is called. 

    HRESULT hRes = E_POINTER;
    if (ppClientSite != NULL)
    {
        *ppClientSite = NULL;
        if (m_spClientSite == NULL)
        {
            return S_OK;
        }
    }

    return IOleObjectImpl<CMozillaBrowser>::GetClientSite(ppClientSite);
}


/////////////////////////////////////////////////////////////////////////////
// IMozControlBridge


HRESULT STDMETHODCALLTYPE CMozillaBrowser::GetWebBrowser(/* [out] */ void __RPC_FAR *__RPC_FAR *aBrowser)
{
    if (!NgIsValidAddress(aBrowser, sizeof(void *)))
    {
        NG_ASSERT(0);
        return SetErrorInfo(E_INVALIDARG);
    }

    *aBrowser = NULL;
    if (mWebBrowser)
    {
        nsIWebBrowser *browser = mWebBrowser.get();
        NS_ADDREF(browser);
        *aBrowser = (void *) browser;
    }
    return S_OK;
}

/////////////////////////////////////////////////////////////////////////////
// IWebBrowserImpl

nsresult CMozillaBrowser::GetWebNavigation(nsIWebNavigation **aWebNav)
{
    NS_ENSURE_ARG_POINTER(aWebNav);
    if (!mWebBrowser) return NS_ERROR_FAILURE;
    return mWebBrowser->QueryInterface(NS_GET_IID(nsIWebNavigation), (void **) aWebNav);
}

nsresult CMozillaBrowser::GetDOMWindow(nsIDOMWindow **aDOMWindow)
{
    NS_ENSURE_ARG_POINTER(aDOMWindow);
    if (!mWebBrowser) return NS_ERROR_FAILURE;
    return mWebBrowser->GetContentDOMWindow(aDOMWindow);
}

nsresult CMozillaBrowser::GetPrefs(nsIPrefBranch **aPrefBranch)
{
    if (mPrefBranch)
        *aPrefBranch = mPrefBranch;
    NS_IF_ADDREF(*aPrefBranch);
    return (*aPrefBranch) ? NS_OK : NS_ERROR_FAILURE;
}

PRBool CMozillaBrowser::BrowserIsValid()
{
    NG_TRACE_METHOD(CMozillaBrowser::BrowserIsValid);
    return mValidBrowserFlag ? PR_TRUE : PR_FALSE;
}

// Overrides of IWebBrowser / IWebBrowserApp / IWebBrowser2 methods

HRESULT STDMETHODCALLTYPE CMozillaBrowser::get_Parent(IDispatch __RPC_FAR *__RPC_FAR *ppDisp)
{
    NG_TRACE_METHOD(CMozillaBrowser::get_Parent);
    ENSURE_BROWSER_IS_VALID();

    if (!NgIsValidAddress(ppDisp, sizeof(IDispatch *)))
    {
        NG_ASSERT(0);
        return SetErrorInfo(E_INVALIDARG);
    }

    // Attempt to get the parent object of this control
    HRESULT hr = E_FAIL;
    if (m_spClientSite)
    {
        hr = m_spClientSite->QueryInterface(IID_IDispatch, (void **) ppDisp);
    }

    return (SUCCEEDED(hr)) ? S_OK : E_NOINTERFACE;
}

HRESULT STDMETHODCALLTYPE CMozillaBrowser::get_Document(IDispatch __RPC_FAR *__RPC_FAR *ppDisp)
{
    NG_TRACE_METHOD(CMozillaBrowser::get_Document);
    ENSURE_BROWSER_IS_VALID();

    if (!NgIsValidAddress(ppDisp, sizeof(IDispatch *)))
    {
        NG_ASSERT(0);
        return SetErrorInfo(E_UNEXPECTED);
    }

    *ppDisp = NULL;

    // Get hold of the DOM document
    nsIDOMDocument *pIDOMDocument = nsnull;
    if (FAILED(GetDOMDocument(&pIDOMDocument)) || pIDOMDocument == nsnull)
    {
        return S_OK; // return NULL pointer
    }

    if (mIERootDocument == NULL)
     {    
        nsCOMPtr <nsIDOMHTMLDocument> pIDOMHTMLDocument =
                do_QueryInterface(pIDOMDocument);

        if (!pIDOMDocument)
        {
            return SetErrorInfo(E_FAIL, L"get_Document: not HTML");
        }

         CIEHtmlDocumentInstance::CreateInstance(&mIERootDocument);
          if (mIERootDocument == NULL)
         {
            return SetErrorInfo(E_OUTOFMEMORY, L"get_Document: can't create IERootDocument");
         }
         
        // addref it so it doesn't go away on us.
         mIERootDocument->AddRef();
 
         // give it a pointer to us.  note that we shouldn't be addref'd by this call, or it would be 
         // a circular reference.
         mIERootDocument->SetParent(this);
        mIERootDocument->SetDOMNode(pIDOMDocument);
        mIERootDocument->SetNative(pIDOMHTMLDocument);
    }

    mIERootDocument->QueryInterface(IID_IDispatch, (void **) ppDisp);
    pIDOMDocument->Release();

    return S_OK;
}

HRESULT STDMETHODCALLTYPE CMozillaBrowser::get_RegisterAsDropTarget(VARIANT_BOOL __RPC_FAR *pbRegister)
{
    NG_TRACE_METHOD(CMozillaBrowser::get_RegisterAsDropTarget);
    ENSURE_BROWSER_IS_VALID();

    if (pbRegister == NULL)
    {
        NG_ASSERT(0);
        return SetErrorInfo(E_INVALIDARG);
    }

    *pbRegister = mHaveDropTargetFlag ? VARIANT_TRUE : VARIANT_FALSE;
    return S_OK;
}



static BOOL CALLBACK EnumChildProc(HWND hwnd, LPARAM lParam)
{
    ::RevokeDragDrop(hwnd);
    return TRUE;
}
 

HRESULT STDMETHODCALLTYPE CMozillaBrowser::put_RegisterAsDropTarget(VARIANT_BOOL bRegister)
{
    NG_TRACE_METHOD(CMozillaBrowser::put_RegisterAsDropTarget);
    ENSURE_BROWSER_IS_VALID();

    // Register the window as a drop target
    if (bRegister == VARIANT_TRUE)
    {
        if (!mHaveDropTargetFlag)
        {
            CDropTargetInstance *pDropTarget = NULL;
            CDropTargetInstance::CreateInstance(&pDropTarget);
            if (pDropTarget)
            {
                pDropTarget->AddRef();
                pDropTarget->SetOwner(this);


                // Ask the site if it wants to replace this drop target for another one
                CIPtr(IDropTarget) spDropTarget;
                CIPtr(IDocHostUIHandler) spDocHostUIHandler = m_spClientSite;
                if (spDocHostUIHandler)
                {
                    //if (spDocHostUIHandler->GetDropTarget(pDropTarget, &spDropTarget) != S_OK)
                    if (spDocHostUIHandler->GetDropTarget(pDropTarget, &spDropTarget) == S_OK)
                    {
                        mHaveDropTargetFlag = TRUE;
                        ::RegisterDragDrop(m_hWnd, spDropTarget);
                        //spDropTarget = pDropTarget;
                    }
                }
                else
                //if (spDropTarget)
                {
                    mHaveDropTargetFlag = TRUE;
                    ::RegisterDragDrop(m_hWnd, pDropTarget);
                    //::RegisterDragDrop(m_hWnd, spDropTarget);
                }
                pDropTarget->Release();
            }
            // Now revoke any child window drop targets and pray they aren't
            // reset by the layout engine.
            ::EnumChildWindows(m_hWnd, EnumChildProc, (LPARAM) this);
        }
    }
    else
    {
        if (mHaveDropTargetFlag)
        {
            mHaveDropTargetFlag = FALSE;
            ::RevokeDragDrop(m_hWnd);
        }
    }

    return S_OK;
}

///////////////////////////////////////////////////////////////////////////////
// Ole Command Handlers


HRESULT _stdcall CMozillaBrowser::PrintHandler(CMozillaBrowser *pThis, const GUID *pguidCmdGroup, DWORD nCmdID, DWORD nCmdexecopt, VARIANT *pvaIn, VARIANT *pvaOut)
{
    BOOL promptUser = (nCmdexecopt & OLECMDEXECOPT_DONTPROMPTUSER) ? FALSE : TRUE;
    pThis->PrintDocument(promptUser);
    return S_OK;
}


HRESULT _stdcall CMozillaBrowser::EditModeHandler(CMozillaBrowser *pThis, const GUID *pguidCmdGroup, DWORD nCmdID, DWORD nCmdexecopt, VARIANT *pvaIn, VARIANT *pvaOut)
{
    BOOL bEditorMode = (nCmdID == IDM_EDITMODE) ? TRUE : FALSE;
    pThis->SetEditorMode(bEditorMode);
    return S_OK;
}


HRESULT _stdcall CMozillaBrowser::EditCommandHandler(CMozillaBrowser *pThis, const GUID *pguidCmdGroup, DWORD nCmdID, DWORD nCmdexecopt, VARIANT *pvaIn, VARIANT *pvaOut)
{
    pThis->OnEditorCommand(nCmdID);
    return S_OK;
}


///////////////////////////////////////////////////////////////////////////////
// SimpleDirectoryProvider implementation

SimpleDirectoryProvider::SimpleDirectoryProvider()
{
    nsCOMPtr<nsILocalFile> appDataDir;

    // Attempt to fill appDataDir with a meaningful value. Any error in the process
    // will cause the constructor to return and IsValid() to return FALSE,

    CComPtr<IMalloc> shellMalloc;
    SHGetMalloc(&shellMalloc);
    if (shellMalloc)
    {
        LPITEMIDLIST pitemidList = NULL;
        SHGetSpecialFolderLocation(NULL, CSIDL_APPDATA, &pitemidList);
        if (pitemidList)
        {
            TCHAR szBuffer[MAX_PATH + 1];
            if (SUCCEEDED(SHGetPathFromIDList(pitemidList, szBuffer)))
            {
                szBuffer[MAX_PATH] = TCHAR('\0');
                USES_CONVERSION;
                NS_NewNativeLocalFile(nsDependentCString(T2A(szBuffer)), TRUE, getter_AddRefs(appDataDir));
            }
            shellMalloc->Free(pitemidList);
        }
    }
    if (!appDataDir)
    {
        return;
    }

    // Mozilla control paths are
    // App data     - {Application Data}/MozillaControl
    // App registry - {Application Data}/MozillaControl/registry.dat
    // Profiles     - {Application Data}/MozillaControl/profiles

    nsresult rv;

    // Create the root directory
    PRBool exists;
    rv = appDataDir->Exists(&exists);
    if (NS_FAILED(rv) || !exists) return;

    // MozillaControl application data
    rv = appDataDir->AppendRelativePath(NS_LITERAL_STRING("MozillaControl"));
    if (NS_FAILED(rv)) return;
    rv = appDataDir->Exists(&exists);
    if (NS_SUCCEEDED(rv) && !exists)
        rv = appDataDir->Create(nsIFile::DIRECTORY_TYPE, 0775);
    if (NS_FAILED(rv)) return;

    // Registry.dat file
    nsCOMPtr<nsIFile> appDataRegAsFile;
    rv = appDataDir->Clone(getter_AddRefs(appDataRegAsFile));
    if (NS_FAILED(rv)) return;
    nsCOMPtr<nsILocalFile> appDataRegistry = do_QueryInterface(appDataRegAsFile, &rv);
    if (NS_FAILED(rv)) return;
    appDataRegistry->AppendRelativePath(NS_LITERAL_STRING("registry.dat"));

    // Profiles directory
    nsCOMPtr<nsIFile> profileDirAsFile;
    rv = appDataDir->Clone(getter_AddRefs(profileDirAsFile));
    if (NS_FAILED(rv)) return;
    nsCOMPtr<nsILocalFile> profileDir = do_QueryInterface(profileDirAsFile, &rv);
    if (NS_FAILED(rv)) return;
    profileDir->AppendRelativePath(NS_LITERAL_STRING("profiles"));
    rv = profileDir->Exists(&exists);
    if (NS_SUCCEEDED(rv) && !exists)
        rv = profileDir->Create(nsIFile::DIRECTORY_TYPE, 0775);
    if (NS_FAILED(rv)) return;

    // Store the member values
    mApplicationRegistryDir = appDataDir;
    mApplicationRegistryFile = appDataRegistry;
    mUserProfileDir = profileDir;
}

SimpleDirectoryProvider::~SimpleDirectoryProvider()
{
}

BOOL
SimpleDirectoryProvider::IsValid() const
{
    return (mApplicationRegistryDir && mApplicationRegistryFile && mUserProfileDir) ?
        TRUE : FALSE;
}

NS_IMPL_ISUPPORTS1(SimpleDirectoryProvider, nsIDirectoryServiceProvider)

///////////////////////////////////////////////////////////////////////////////
// nsIDirectoryServiceProvider

/* nsIFile getFile (in string prop, out PRBool persistent); */
NS_IMETHODIMP SimpleDirectoryProvider::GetFile(const char *prop, PRBool *persistent, nsIFile **_retval)
{
    NS_ENSURE_ARG_POINTER(prop);
    NS_ENSURE_ARG_POINTER(persistent);
    NS_ENSURE_ARG_POINTER(_retval);

    *_retval = nsnull;
    *persistent = PR_TRUE;

    // Only need to support NS_APP_APPLICATION_REGISTRY_DIR, NS_APP_APPLICATION_REGISTRY_FILE, and
    // NS_APP_USER_PROFILES_ROOT_DIR. Unsupported keys fallback to the default service provider
    
    nsCOMPtr<nsILocalFile> localFile;
    nsresult rv = NS_ERROR_FAILURE;

    if (strcmp(prop, NS_APP_APPLICATION_REGISTRY_DIR) == 0)
    {
        localFile = mApplicationRegistryDir;
    }
    else if (strcmp(prop, NS_APP_APPLICATION_REGISTRY_FILE) == 0)
    {
        localFile = mApplicationRegistryFile;
    }
    else if (strcmp(prop, NS_APP_USER_PROFILES_ROOT_DIR) == 0)
    {
        localFile = mUserProfileDir;
    }
    
    if (localFile)
        return CallQueryInterface(localFile, _retval);
        
    return rv;
}


///////////////////////////////////////////////////////////////////////////////
// PrintListener implementation


#ifdef NS_PRINTING

NS_IMPL_ISUPPORTS1(PrintListener, nsIWebProgressListener)

PrintListener::PrintListener() : mComplete(PR_FALSE)
{
    /* member initializers and constructor code */
}

PrintListener::~PrintListener()
{
    /* destructor code */
}

void PrintListener::WaitForComplete()
{
    MSG msg;
    HANDLE hFakeEvent = ::CreateEvent(NULL, TRUE, FALSE, NULL);

    while (!mComplete)
    {
        // Process pending messages
        while (::PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE))
        {
            if (!::GetMessage(&msg, NULL, 0, 0))
            {
                ::CloseHandle(hFakeEvent);
                return;
            }

            PRBool wasHandled = PR_FALSE;
            ::NS_HandleEmbeddingEvent(msg, wasHandled);
            if (wasHandled)
                continue;

            ::TranslateMessage(&msg);
            ::DispatchMessage(&msg);
        }

        if (mComplete)
            break;
        
        // Do idle stuff
        ::MsgWaitForMultipleObjects(1, &hFakeEvent, FALSE, 500, QS_ALLEVENTS);
    }

    ::CloseHandle(hFakeEvent);
}

/* void onStateChange (in nsIWebProgress aWebProgress, in nsIRequest aRequest, in unsigned long aStateFlags, in nsresult aStatus); */
NS_IMETHODIMP PrintListener::OnStateChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, PRUint32 aStateFlags, nsresult aStatus)
{
    if (aStateFlags & nsIWebProgressListener::STATE_STOP &&
        aStateFlags & nsIWebProgressListener::STATE_IS_DOCUMENT)
    {
        mComplete = PR_TRUE;
    }
    return NS_OK;
}

/* void onProgressChange (in nsIWebProgress aWebProgress, in nsIRequest aRequest, in long aCurSelfProgress, in long aMaxSelfProgress, in long aCurTotalProgress, in long aMaxTotalProgress); */
NS_IMETHODIMP PrintListener::OnProgressChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, PRInt32 aCurSelfProgress, PRInt32 aMaxSelfProgress, PRInt32 aCurTotalProgress, PRInt32 aMaxTotalProgress)
{
    return NS_OK;
}

/* void onLocationChange (in nsIWebProgress aWebProgress, in nsIRequest aRequest, in nsIURI location); */
NS_IMETHODIMP PrintListener::OnLocationChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, nsIURI *location)
{
    return NS_OK;
}

/* void onStatusChange (in nsIWebProgress aWebProgress, in nsIRequest aRequest, in nsresult aStatus, in wstring aMessage); */
NS_IMETHODIMP PrintListener::OnStatusChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, nsresult aStatus, const PRUnichar *aMessage)
{
    return NS_OK;
}

/* void onSecurityChange (in nsIWebProgress aWebProgress, in nsIRequest aRequest, in unsigned long state); */
NS_IMETHODIMP PrintListener::OnSecurityChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, PRUint32 state)
{
    return NS_OK;
}
#endif
