/*
**	Command & Conquer Renegade(tm)
**	Copyright 2025 Electronic Arts Inc.
**
**	This program is free software: you can redistribute it and/or modify
**	it under the terms of the GNU General Public License as published by
**	the Free Software Foundation, either version 3 of the License, or
**	(at your option) any later version.
**
**	This program is distributed in the hope that it will be useful,
**	but WITHOUT ANY WARRANTY; without even the implied warranty of
**	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
**	GNU General Public License for more details.
**
**	You should have received a copy of the GNU General Public License
**	along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

/******************************************************************************
*
* NAME
*     $Archive: /Commando/Code/Commando/WebBrowser.cpp $
*
* DESCRIPTION
*
* PROGRAMMER
*     Denzil E. Long, Jr.
*     $Author: Denzil_l $
*
* VERSION INFO
*     $Revision: 24 $
*     $Modtime: 1/18/02 10:31p $
*
******************************************************************************/

#include "always.h"

#include "WebBrowser.h"
#include <WWLib\WWCOMUtil.h>
#include <WW3D2\WW3D.h>
#include <WWOnline\WOLLoginInfo.h>
#include <WWDebug\WWDebug.h>
#include "win.h"
#include "_globals.h"

WebBrowser* WebBrowser::_mInstance = NULL;

/******************************************************************************
*
* NAME
*     WebBrowser::InstallPrerequisites
*
* DESCRIPTION
*
* INPUTS
*     NONE
*
* RESULT
*     True if successful.
*
******************************************************************************/

#ifdef _DEBUG
bool WebBrowser::InstallPrerequisites(void)
	{
	// Check if the WOLBrowser component is installed by attempting to obtain
	// the WOLBrowser class object. If we can get the class object then the
	// component is already registered.
	CComPtr<IClassFactory> factory;
	HRESULT hr = CoGetClassObject(CLSID_WOLBrowser, CLSCTX_INPROC_SERVER, NULL,
			IID_IClassFactory, (void**)&factory);

	// If the component isn't registered then check for it in the run directory
	// and register it if found.
	if (FAILED(hr))
		{
		WWDEBUG_SAY(("WOLBrowser component not installed, attempting to locate in run directory\n"));

		// Attempt to find the WOLBrowser server in the run directory.
		char dllPath[512];
		DWORD length = GetCurrentDirectory(sizeof(dllPath), dllPath);

		if (length == 0)
			{
			WWDEBUG_SAY(("GetCurrentDirectory() failed!\n"));
			Print_Win32Error(GetLastError());
			return false;
			}

		WWDEBUG_SAY(("Registering WOLBrowser component\n"));
		strcat(dllPath, "\\WolBrowser.dll");
		bool success = RegisterCOMServer(dllPath);

		if (!success)
			{
			WWDEBUG_SAY(("Failed to register WOLBrowser.dll!\n"));
			::MessageBox(NULL, "WOLBrowser.dll not registered!\n\nDefaulting to external browser.",
					"Renegade Warning!", MB_ICONWARNING|MB_OK);
			return false;
			}
		}

	// Attempt to open the URL key
	HKEY key;
	LONG result = RegOpenKeyEx(HKEY_LOCAL_MACHINE, APPLICATION_SUB_KEY_NAME_URL, 0, KEY_ALL_ACCESS, &key);

	if (ERROR_SUCCESS != result)
		{
		WWDEBUG_SAY(("URL entry not in the registry\n"));
		::MessageBox(NULL, "Embedded Browser prerequisite error!\n\nURL key not found.",
				"Renegade Warning!", MB_ICONWARNING|MB_OK);

		// Attempt to create the key.
		LONG result = RegCreateKeyEx(HKEY_LOCAL_MACHINE, APPLICATION_SUB_KEY_NAME_URL, 0, NULL,
			REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, &key, NULL);

		if (ERROR_SUCCESS != result)
			{
			WWDEBUG_SAY(("Failed to create URL entry in registry\n"));
			::MessageBox(NULL, "Failed to create Embedded Browser URLS\n\nURL key not found.",
					"Renegade Warning!", MB_ICONWARNING|MB_OK);
			return false;
			}

		// Check for registry entries.
		struct URLEntry
			{
			const char* Name;
			const char* Data;
			};

		static URLEntry urls[] =
			{
			{"BattleClans", "http://renchat2.westwood.com/cgi-bin/cgiclient?ren_clan_manager&request=expand_template&Template=index.html&SKU=3072&LANGCODE=0&embedded=1"},
			{"BattleClansX", "http://renchat2.westwood.com/cgi-bin/cgiclient?ren_clan_manager&request=expand_template&Template=index.html&SKU=3072&LANGCODE=0"},
			{"Ladder", "http://renchat2.westwood.com/renegade_embedded/index.html"},
			{"LadderX", "http://renchat2.westwood.com/renegade/index.html"},
			{"NetStatus", "http://battleclans.westwood.com/cgi-bin/cgiclient?rosetta&request=do_netstatus&LANGCODE=0&SKU=3072&embedded=1"},
			{"NetStatusX", "http://battleclans.westwood.com/cgi-bin/cgiclient?rosetta&request=do_netstatus&LANGCODE=0&SKU=3072"},
			{"News", "http://battleclans.westwood.com/cgi-bin/cgiclient?rosetta&request=do_news&LANGCODE=0&SKU=3072&embedded=1"},
			{"NewsX", "http://battleclans.westwood.com/cgi-bin/cgiclient?rosetta&request=do_news&LANGCODE=0&SKU=3072"},
			{"Signup", "http://games2.westwood.com/cgi-bin/cgiclient?ren_reg2&request=expand_template&Template=newreg_menu.html&LANGCODE=0&embedded=1&SKU=3072"},
			{"SignupX", "http://games2.westwood.com/cgi-bin/cgiclient?ren_reg2&request=expand_template&Template=newreg_menu.html&LANGCODE=0"},
			{NULL, NULL}
			};

		const char* valueName = urls[0].Name;
		int index = 0;

		while (valueName)
			{
			DWORD type;
			char data[512];
			DWORD size = sizeof(data);
			result = RegQueryValueEx(key, valueName, NULL, &type, (LPBYTE)&data, &size);

			// If the URL value is not found then add it.
			const char* valueData = urls[index].Data;

			if (ERROR_SUCCESS != result || (strcmp(valueData, data) != 0))
				{
				result = RegSetValueEx(key, valueName, NULL, REG_SZ, (CONST BYTE*)valueData,
					(strlen(valueData) + 1));

				if (ERROR_SUCCESS != result)
					{
					WWDEBUG_SAY(("Failed to create URL entry '%s' in registry\n", valueName));
					char errorMsg[256];
					sprintf(errorMsg, "Embedded Browser prerequisite error!\n\nURL key '%s'", valueName);
					::MessageBox(NULL, errorMsg, "Renegade Warning!", MB_ICONWARNING|MB_OK);
					break;
					}
				}

			index++;
			valueName = urls[index].Name;
			}

		RegCloseKey(key);
		return (valueName == NULL);
		}

	RegCloseKey(key);

	return true;
	}
#endif // _DEBUG


/******************************************************************************
*
* NAME
*     WebBrowser::IsWebPageDisplayed
*
* DESCRIPTION
*     Test if a web page is currently being displayed.
*
* INPUTS
*     NONE
*
* RESULT
*     True if page is displayed; otherwise false.
*
******************************************************************************/

bool WebBrowser::IsWebPageDisplayed(void)
	{
	return (_mInstance && _mInstance->UsingEmbeddedBrowser() && _mInstance->IsVisible());
	}


/******************************************************************************
*
* NAME
*     WebBrowser::CreateInstance
*
* DESCRIPTION
*     Create an instance of the embedded browser manager.
*
* INPUTS
*     NONE
*
* RESULT
*     Browser - Pointer to instance of browser.
*
******************************************************************************/

WebBrowser* WebBrowser::CreateInstance(HWND window)
	{
	if (_mInstance)
		{
		return NULL;
		}

	WebBrowser* object = new WebBrowser;

	if (object)
		{
		bool success = object->FinalizeCreate(window);

		if (!success)
			{
			WWDEBUG_SAY(("WebBrowser: Failed to initialize!\n"));
			object->Release();
			object = NULL;
			}
		}

	return object;
	}


/******************************************************************************
*
* NAME
*     WebBrowser::WebBrowser
*
* DESCRIPTION
*     Default constructor
*
* INPUTS
*     NONE
*
* RESULT
*     NONE
*
******************************************************************************/

WebBrowser::WebBrowser() :
		mRefCount(1),
		mVisible(false),
		mSwitchedMode(false)
	{
	WWDEBUG_SAY(("WebBrowser: Instantiating\n"));
	memset(&mProcessInfo, 0, sizeof(mProcessInfo));
	_mInstance = this;
	}


/******************************************************************************
*
* NAME
*     WebBrowser::~WebBrowser
*
* DESCRIPTION
*     Destructor
*
* INPUTS
*     NONE
*
* RESULT
*     NONE
*
******************************************************************************/

WebBrowser::~WebBrowser()
	{
	WWDEBUG_SAY(("WebBrowser: Destructing\n"));

	_mInstance = NULL;

	if (mWOLBrowser)
		{
		WWDEBUG_SAY(("WebBrowser: Releasing WOLBrowser\n"));
		mWOLBrowser->RemoveObserver(static_cast<IWOLBrowserEvent*>(this));
		mWOLBrowser->Shutdown();
		}

	if (mSwitchedMode)
		{
		WW3D::Set_Device_Resolution(mRestoreWidth, mRestoreHeight, mRestoreBits, -1, true);
		}
	}


/******************************************************************************
*
* NAME
*     WebBrowser::FinalizeCreate
*
* DESCRIPTION
*     Perform post creation initialization.
*
* INPUTS
*     NONE
*
* RESULT
*     Success - True if finalization was successful; otherwise false.
*
******************************************************************************/

bool WebBrowser::FinalizeCreate(HWND window)
	{
	if (window)
		{
		RECT windowRect;
		GetClientRect(window, &windowRect);

		LONG windowWidth = (windowRect.right - windowRect.left);
		LONG windowHeight = (windowRect.bottom - windowRect.top);

		// If the display is smaller than 800 x 600 use the external browser.
		// Therefore we do NOT need to create the embedded browser component.
		if ((windowWidth >= 800) && (windowHeight >= 600))
			{
			// Create the embedded browser component.
			WWDEBUG_SAY(("WebBrowser: Creating WOLBrowser component\n"));

			HRESULT hr = CoCreateInstance(CLSID_WOLBrowser, NULL, CLSCTX_INPROC_SERVER,
					IID_IWOLBrowser, (void**)&mWOLBrowser);

			if (FAILED(hr))
				{
				WWDEBUG_SAY(("WebBrowser: Failed to create WOLBrowser component\n"));
				return false;
				}

			// If the display resolution is greater than 800 x 600 then set the
			// display mode to 800 x 600 if we are not in windowed mode.
			if ((windowWidth > 800) && (windowHeight > 600))
				{
				bool windowed = false;
				WW3D::Get_Device_Resolution(mRestoreWidth, mRestoreHeight, mRestoreBits, windowed);

				if (!windowed)
					{
					mSwitchedMode = true;
					WW3D::Set_Device_Resolution(800, 600, -1, -1, true);
					}
				}

			// Center webpage within window
			GetClientRect(window, &windowRect);
			windowWidth = (windowRect.right - windowRect.left);
			windowHeight = (windowRect.bottom - windowRect.top);

			RECT webRect;
			webRect.left = windowRect.left + ((windowWidth - 800) / 2);
			webRect.right = (webRect.left + 800);
			webRect.top = windowRect.top + ((windowHeight - 600) / 2);
			webRect.bottom = (webRect.top + 600);

			WWDEBUG_SAY(("WebBrowser: Starting up WOLBrowser\n"));
			mWOLBrowser->AddObserver(static_cast<IWOLBrowserEvent*>(this));

			// Initialize browser to cover dialog browser area
			mWOLBrowser->Startup(window, &webRect);
			mWOLBrowser->AllowPageCertification(TRUE);
			mWOLBrowser->SetUIFlags(UIFLAG_SCROLLBARS);
			}
		}

	return true;
	}


/******************************************************************************
*
* NAME
*     WebBrowser::ShowWebPage
*
* DESCRIPTION
*     Display the specified web page.
*
* INPUTS
*     Page - Web page to display.
*
* RESULT
*     NONE
*
******************************************************************************/

bool WebBrowser::ShowWebPage(char* page)
	{
	// Retrieve URL from page identifier
	char url[512];

	if (RetrievePageURL(page, url, sizeof(url)))
		{
		WWDEBUG_SAY(("WebBrowser: Showing webpage '%s' - %s\n", page, url));

		if (mWOLBrowser)
			{
			// Convert string to Unicode
			mbstowcs(mPendingURL, url, 512);

			char path[MAX_PATH];

			if (RetrieveHTMLPath(path, MAX_PATH))
				{
				wchar_t framePage[MAX_PATH];
				mbstowcs(framePage, path, MAX_PATH);
				wcscat(framePage, L"frame.htm");

				HRESULT hr = mWOLBrowser->Navigate(framePage, 0 , NULL);

				if (SUCCEEDED(hr))
					{
					Show();
					return true;
					}
				}
			}
		else
			{
			return LaunchExternal(url);
			}
		}

	return false;
	}


/******************************************************************************
*
* NAME
*     WebBrowser::Show
*
* DESCRIPTION
*     Show the web browser
*
* INPUTS
*     NONE
*
* RESULT
*     NONE
*
******************************************************************************/

void WebBrowser::Show(void)
	{
	if (mWOLBrowser)
		{
		mWOLBrowser->Show();
		mVisible = true;
		}
	}


/******************************************************************************
*
* NAME
*     WebBrowser::Hide
*
* DESCRIPTION
*     Hide the web browser
*
* INPUTS
*     NONE
*
* RESULT
*     NONE
*
******************************************************************************/

void WebBrowser::Hide(void)
	{
	if (mWOLBrowser)
		{
		mWOLBrowser->Hide();
		mVisible = false;
		}
	}


/******************************************************************************
*
* NAME
*     WebBrowser::RetrievePageURL
*
* DESCRIPTION
*     Retrieve the URL for the specified page type.
*
* INPUTS
*     Page - Page name identifier
*     URL  - Buffer to receive URL.
*     Size - Maximum size of buffer.
*
* RESULT
*     Success - True if sucessful.
*
******************************************************************************/

bool WebBrowser::RetrievePageURL(const char* page, char* url, int size)
	{
	HKEY key;
	LONG result = RegOpenKeyEx(HKEY_LOCAL_MACHINE, APPLICATION_SUB_KEY_NAME_URL, 0, KEY_READ, &key);

	if (result == ERROR_SUCCESS)
		{
		char valueName[64];
		strcpy(valueName, page);

		if (mWOLBrowser == NULL)
			{
			strcat(valueName, "X");
			}

		DWORD type;
		DWORD sizeOfBuffer = size;
		result = RegQueryValueEx(key, valueName, NULL, &type, (unsigned char*)url,
				&sizeOfBuffer);

		RegCloseKey(key);
		}

	WWASSERT(result == ERROR_SUCCESS && "RegtievePageURL() failed");

	return (result == ERROR_SUCCESS);
	}


/******************************************************************************
*
* NAME
*     WebBrowser::RetrieveHTMLPath
*
* DESCRIPTION
*     Retrieve the path string to the local HTML data.
*
* INPUTS
*     Path - Pointer to buffer to receive path.
*     Size - Size of buffer in characters.
*
* RESULT
*     Success - True if successful.
*
******************************************************************************/

bool WebBrowser::RetrieveHTMLPath(char* path, int size)
	{
	// Retrieve the full pathname for the current process.
	DWORD length = GetCurrentDirectory(size, path);

	if (length == 0)
		{
		Print_Win32Error(GetLastError());
		return false;
		}

	int dirSize = (length + strlen("\\HTML\\"));

	if (dirSize > size)
		{
		return false;
		}

	strcat(path, "\\HTML\\");

	return true;
	}


/******************************************************************************
*
* NAME
*     IUnknown::QueryInterface
*
* DESCRIPTION
*
* INPUTS
*     IID - Interface ID
*
* RESULT
*
******************************************************************************/

STDMETHODIMP WebBrowser::QueryInterface(REFIID iid, void** ppv)
	{
	*ppv = NULL;

	if ((iid == IID_IUnknown) || (iid == IID_IWOLBrowserEvent))
		{
		*ppv = static_cast<IWOLBrowserEvent*>(this);
		}
	else
		{
		return E_NOINTERFACE;
		}

	static_cast<IUnknown*>(*ppv)->AddRef();

	return S_OK;
	}


/******************************************************************************
*
* NAME
*     IUnknown::AddRef
*
* DESCRIPTION
*
* INPUTS
*     NONE
*
* RESULT
*
******************************************************************************/

ULONG STDMETHODCALLTYPE WebBrowser::AddRef(void)
	{
	return ++mRefCount;
	}


/******************************************************************************
*
* NAME
*     IUnknown::Release
*
* DESCRIPTION
*
* INPUTS
*     NONE
*
* RESULT
*
******************************************************************************/

ULONG STDMETHODCALLTYPE WebBrowser::Release(void)
	{
	WWASSERT(mRefCount > 0 && "WebBrowser: Negative reference count");
	--mRefCount;

	if (mRefCount == 0)
		{
		delete this;
		return 0;
		}

	return mRefCount;
	}


/******************************************************************************
*
* NAME
*     IWOLBrowserEvent::OnScriptQuit
*
* DESCRIPTION
*     Handle quit notification from BrowserHost.
*
* INPUTS
*     Cancel - Cancel flag
*
* RESULT
*     NONE
*
******************************************************************************/

STDMETHODIMP WebBrowser::OnScriptQuit(void)
	{
	AddRef();

	WebEvent event(WebEvent::Quit, static_cast<WebBrowser*>(this));
	NotifyObservers(event);

	Release();

	return S_OK;
	}


/******************************************************************************
*
* NAME
*     IWOLBrowserEvent::OnBeforeNavigate
*
* DESCRIPTION
*     Invoked before navigating to a url.
*
* INPUTS
*     URL         - URL navigating to.
*     TargetFrame - Name of frame that navigation is happening in.
*     Cancel      - On return; Cancel flag
*
* RESULT
*     NONE
*
******************************************************************************/

STDMETHODIMP WebBrowser::OnBeforeNavigate(const wchar_t* /* url */,
		const wchar_t* /* targetFrame */)
	{
	return S_OK;
	}


/******************************************************************************
*
* NAME
*     IWOLBrowserEvent::OnDocumentComplete
*
* DESCRIPTION
*     Invoked when the web page is loaded, once for each frame.
*
* INPUTS
*     URL      - URL navigated to.
*     TopFrame - Flag indicating if this is the top most frame.
*
* RESULT
*     NONE
*
******************************************************************************/

STDMETHODIMP WebBrowser::OnDocumentComplete(const wchar_t* url, BOOL isTopFrame)
	{
	WWDEBUG_SAY(("WebBrowser: OnDocumentComplete: %S\n", url));

	if (isTopFrame)
		{
		WWDEBUG_SAY(("WebBrowser: pending url: %S\n", mPendingURL));
		mWOLBrowser->Navigate(mPendingURL, 0 , L"Main");
		}

	return S_OK;
	}


/******************************************************************************
*
* NAME
*     IWOLBrowserEvent::OnDownloadBegin
*
* DESCRIPTION
*     Invoked at the beginning of a download (Image, Sound, etc...)
*
* INPUTS
*     NONE
*
* RESULT
*     NONE
*
******************************************************************************/

STDMETHODIMP WebBrowser::OnDownloadBegin(void)
	{
	return S_OK;
	}


/******************************************************************************
*
* NAME
*     IWOLBrowserEvent::OnProgressChange
*
* DESCRIPTION
*     Invoked as download progress changes.
*
* INPUTS
*     Progress    - Current progress.
*     MaxProgress - Maximum progress.
*
* RESULT
*     NONE
*
******************************************************************************/

STDMETHODIMP WebBrowser::OnProgressChange(long /* progress */, long /* progressMax */)
	{
	return S_OK;
	}


/******************************************************************************
*
* NAME
*     IWOLBrowserEvent::OnDownloadComplete
*
* DESCRIPTION
*     Invoked when download is completed.
*
* INPUTS
*     NONE
*
* RESULT
*     NONE
*
******************************************************************************/

STDMETHODIMP WebBrowser::OnDownloadComplete(void)
	{
	return S_OK;
	}


/******************************************************************************
*
* NAME
*     IWOLBrowserEvent::OnNavigateComplete
*
* DESCRIPTION
*     Invoked when navigation is complete.
*
* INPUTS
*     URL
*
* RESULT
*     NONE
*
******************************************************************************/

STDMETHODIMP WebBrowser::OnNavigateComplete(const wchar_t* /* url */)
	{
	return S_OK;
	}


/******************************************************************************
*
* NAME
*     IWOLBrowserEvent::OnStatusTextChange
*
* DESCRIPTION
*     Invoked when the status text changes.
*
* INPUTS
*     Status - Status text string.
*
* RESULT
*     NONE
*
******************************************************************************/

STDMETHODIMP WebBrowser::OnStatusTextChange(const wchar_t* /* statusText */)
	{
	return S_OK;
	}


/******************************************************************************
*
* NAME
*     IWOLBrowserEvent::OnTitleChange
*
* DESCRIPTION
*     Invoked when the document title changes.
*
* INPUTS
*     Title - Title string.
*
* RESULT
*     NONE
*
******************************************************************************/

STDMETHODIMP WebBrowser::OnTitleChange(const wchar_t* /* title */)
	{
	return S_OK;
	}


/******************************************************************************
*
* NAME
*     IWOLBrowserEvent::OnNewWindow
*
* DESCRIPTION
*     Invoked when requesting to open a new window for web content.
*
* INPUTS
*     Allow - On return; Flag to allow or disallow opening of new window.
*
* RESULT
*     NONE
*
******************************************************************************/

STDMETHODIMP WebBrowser::OnNewWindow(void)
	{
	return E_FAIL;
	}


/******************************************************************************
*
* NAME
*     IWOLBrowserEvent::OnShowMessage
*
* DESCRIPTION
*
* INPUTS
*
* RESULT
*
******************************************************************************/

STDMETHODIMP WebBrowser::OnShowMessage(const wchar_t* /* text */, const wchar_t* /* caption */,
		unsigned long /* type */, long* /* result */)
	{
	return S_OK;
	}


/******************************************************************************
*
* NAME
*     IWOLBrowserEvent::OnFailedPageCertification
*
* DESCRIPTION
*     Invoked when the web page fails certification.
*
* INPUTS
*     NONE
*
* RESULT
*     NONE
*
******************************************************************************/

STDMETHODIMP WebBrowser::OnFailedPageCertification(void)
	{
	WWDEBUG_SAY(("WebBrowser: !!!!! PAGE FAILED CERTIFICATION !!!!!\n"));

	AddRef();

	WebEvent event(WebEvent::CertificationFailed, static_cast<WebBrowser*>(this));
	NotifyObservers(event);

	Release();

	return S_OK;
	}


/******************************************************************************
*
* NAME
*     IWOLBrowserEvent::OnErrorMsg
*
* DESCRIPTION
*     Error message has changed.
*
* INPUTS
*     Error - Error message string.
*
* RESULT
*     NONE
*
******************************************************************************/

STDMETHODIMP WebBrowser::OnErrorMsg(const wchar_t* /* error */)
	{
	return S_OK;
	}


/******************************************************************************
*
* NAME
*     IWOLBrowserEvent::OnRegisterLogin
*
* DESCRIPTION
*     Westwood online signup account registration request.
*     This message is invoked via script in the account signup page.
*
* INPUTS
*     Nick     - User nickname.
*     Password - User password.
*
* RESULT
*     NONE
*
******************************************************************************/

STDMETHODIMP WebBrowser::OnRegisterLogin(const wchar_t* nick , const wchar_t* pass )
	{
	WWDEBUG_SAY(("WebBrowser: Register WWOnline login: '%S' - '%S'\n", nick, pass));

	RefPtr<WWOnline::LoginInfo> login = WWOnline::LoginInfo::Create(nick, pass, false);

	if (login.IsValid())
		{
		login->Remember(true);
		}
	else
		{
		WWASSERT((!"WebBrowser: Failed to create new login."));
		}

	return S_OK;
	}


/******************************************************************************
*
* NAME
*     WebBrowser::LaunchExternal
*
* DESCRIPTION
*     Launch the external browser.
*
* INPUTS
*     URL - URL to navigate to.
*
* RESULT
*     Success - True if successful
*
******************************************************************************/

bool WebBrowser::LaunchExternal(const char* url)
	{
	WWDEBUG_SAY(("WebBrowser: Launching external browser\n"));

	// Just return if no URL specified
	if (!url || (strlen(url) == 0))
		{
		return false;
		}

	// Create a temporary file with HTML content
	char tempPath[MAX_PATH];
	GetWindowsDirectory(tempPath, MAX_PATH);

	char filename[MAX_PATH];
	GetTempFileName(tempPath, "WWS", 0, filename);

	char* extPtr = strrchr(filename, '.');
	strcpy(extPtr, ".htm");

	HANDLE file = CreateFile(filename, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS,
			FILE_ATTRIBUTE_NORMAL, NULL);

	WWASSERT(INVALID_HANDLE_VALUE != file && "Failed to create temporary HTML file.");

	if (INVALID_HANDLE_VALUE == file)
		{
		return false;
		}

	// Write generic contents
	const char* contents = "<title>ViewHTML</title>";
	DWORD written;
	WriteFile(file, contents, strlen(contents), &written, NULL);
	CloseHandle(file);

	// Find the executable that can launch this file
	char exeName[MAX_PATH];
	HINSTANCE hInst = FindExecutable(filename, NULL, exeName);
	WWASSERT(((int)hInst > 32) && "Unable to find executable that will display HTML files.");

	// Delete temporary file
	DeleteFile(filename);

	if ((int)hInst <= 32)
		{
		return false;
		}

	// Launch browser with specified URL
	char commandLine[MAX_PATH];
	sprintf(commandLine, "[open] %s", url);

  STARTUPINFO startupInfo;
	memset(&startupInfo, 0, sizeof(startupInfo));
	startupInfo.cb = sizeof(startupInfo);

	memset(&mProcessInfo, 0, sizeof(mProcessInfo));

	BOOL createSuccess = CreateProcess(exeName, commandLine, NULL, NULL, FALSE,
			0, NULL, NULL, &startupInfo, &mProcessInfo);

	WWASSERT(createSuccess && "Failed to launch external WebBrowser.");

	if (createSuccess)
		{
	  WaitForInputIdle(mProcessInfo.hProcess, 5000);
		}

	return (TRUE == createSuccess);
	}


/******************************************************************************
*
* NAME
*     WebBrowser::IsExternalBrowserRunning
*
* DESCRIPTION
*     Test if the external browser is running;
*
* INPUTS
*     NONE
*
* RESULT
*     Running - True if the external browser is running.
*
******************************************************************************/

bool WebBrowser::IsExternalBrowserRunning(void) const
	{
  DWORD wait = WaitForInputIdle(mProcessInfo.hProcess, 50);

	if (WAIT_TIMEOUT == wait)
		{
		return true;
		}

	DWORD active = 0;
	BOOL success = GetExitCodeProcess(mProcessInfo.hProcess, &active);
	WWASSERT_PRINT(success, "GetExitCodeProcess() Failed");

	if (success == FALSE)
		{
		Print_Win32Error(GetLastError());
		}

	return (STILL_ACTIVE == active);
	}