/*******************************************************************************
 * Copyright (c) 2010, 2017 IBM Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.swt.browser;


import java.io.*;
import java.net.*;
import java.nio.charset.*;
import java.util.*;

import org.eclipse.swt.*;
import org.eclipse.swt.graphics.*;
import org.eclipse.swt.internal.*;
import org.eclipse.swt.internal.ole.win32.*;
import org.eclipse.swt.internal.webkit.*;
import org.eclipse.swt.internal.win32.*;
import org.eclipse.swt.widgets.*;

class WebKit extends WebBrowser {
	IWebView webView;
	long /*int*/ webViewWindowHandle, webViewData;
	int refCount = 0;
	int lastKeyCode, lastCharCode;

	WebDownloadDelegate webDownloadDelegate;
	WebFrameLoadDelegate webFrameLoadDelegate;
	WebPolicyDelegate webPolicyDelegate;
	WebResourceLoadDelegate webResourceLoadDelegate;
	WebUIDelegate webUIDelegate;

	boolean ignoreDispose;
	boolean loadingText = false;
	boolean traverseNext = true;
	boolean traverseOut = false;
	boolean untrustedText;
	String lastNavigateURL;
	BrowserFunction eventFunction;

	static int prefsIdentifier;
	static long /*int*/ ExternalClass;
	static boolean LibraryLoaded = false;
	static String LibraryLoadError;
	static Callback JSObjectHasPropertyProc;
	static Callback JSObjectGetPropertyProc;
	static Callback JSObjectCallAsFunctionProc;
	static final int MAX_PROGRESS = 100;
	static final String ABOUT_BLANK = "about:blank"; //$NON-NLS-1$
	static final String CLASSNAME_EXTERNAL = "External"; //$NON-NLS-1$
	static final String EMPTY_STRING = ""; //$NON-NLS-1$
	static final String FUNCTIONNAME_CALLJAVA = "callJava"; //$NON-NLS-1$
	static final String HEADER_SETCOOKIE = "Set-Cookie"; //$NON-NLS-1$
	static final String POST = "POST"; //$NON-NLS-1$
	static final String PROPERTY_LENGTH = "length"; //$NON-NLS-1$
	static final String PROTOCOL_HTTPS = "https://"; //$NON-NLS-1$
	static final String PROTOCOL_FILE = "file://"; //$NON-NLS-1$
	static final String PROTOCOL_HTTP = "http://"; //$NON-NLS-1$
	static final String USER_AGENT = "user-agent"; //$NON-NLS-1$
	static final String URI_FILEROOT = "file:///"; //$NON-NLS-1$

	/* event strings */
	static final String DOMEVENT_DRAGSTART = "dragstart"; //$NON-NLS-1$
	static final String DOMEVENT_KEYDOWN = "keydown"; //$NON-NLS-1$
	static final String DOMEVENT_KEYPRESS = "keypress"; //$NON-NLS-1$
	static final String DOMEVENT_KEYUP = "keyup"; //$NON-NLS-1$
	static final String DOMEVENT_MOUSEDOWN = "mousedown"; //$NON-NLS-1$
	static final String DOMEVENT_MOUSEUP = "mouseup"; //$NON-NLS-1$
	static final String DOMEVENT_MOUSEMOVE = "mousemove"; //$NON-NLS-1$
	static final String DOMEVENT_MOUSEOUT = "mouseout"; //$NON-NLS-1$
	static final String DOMEVENT_MOUSEOVER = "mouseover"; //$NON-NLS-1$
	static final String DOMEVENT_MOUSEWHEEL = "mousewheel"; //$NON-NLS-1$

static {
	/*
	* Attempt to load the swt-webkit library.  This will only succeed if the Apple
	* Application Support package is on the user's Windows Path environment variable.
	*/
	try {
		Library.loadLibrary ("swt-webkit"); // $NON-NLS-1$
		LibraryLoaded = true;
	} catch (Throwable e) {
	}

	/*
	* If needed, add the Apple Application Support package's directory to the library
	* lookup path and try to load the swt-webkit library again.
	*/
	if (!LibraryLoaded) {
		/*
		 * Locate the Apple Application Support directory (if installed) and add its path to the library lookup path.
		 *
		 * As of Safari 5.1.4, the Apple Application Support directory is in the Safari installation directory,
		 * which is pointed to by registry entry "HKEY_LOCAL_MACHINE\SOFTWARE\Apple Computer, Inc.\Safari".
		 *
		 * With earlier versions of Safari the Apple Application Support is installed in a stand-alone location, which
		 * is pointed to by registry entry "HKEY_LOCAL_MACHINE\SOFTWARE\Apple Inc.\Apple Application Support\InstallDir".
		 */

		String AASDirectory = readInstallDir ("SOFTWARE\\Apple Computer, Inc.\\Safari"); //$NON-NLS-1$
		if (AASDirectory != null) {
			AASDirectory += "\\Apple Application Support"; //$NON-NLS-1$
			if (!new File(AASDirectory).exists()) {
				AASDirectory = null;
			}
		}

		if (AASDirectory == null) {
			AASDirectory = readInstallDir ("SOFTWARE\\Apple Inc.\\Apple Application Support"); //$NON-NLS-1$
		}

		if (AASDirectory != null) {
			TCHAR buffer = new TCHAR (0, AASDirectory, true);
			boolean success = OS.SetDllDirectory (buffer); /* should succeed on XP+SP1 and newer */
			if (success) {
				try {
					Library.loadLibrary ("swt-webkit"); //$NON-NLS-1$
					LibraryLoaded = true;
				} catch (Throwable e) {
					LibraryLoadError = "Failed to load the swt-webkit library"; //$NON-NLS-1$
					if (Device.DEBUG) System.out.println ("Failed to load swt-webkit library. Apple Application Support directory path: " + AASDirectory); //$NON-NLS-1$
				}
			} else {
				LibraryLoadError = "Failed to add the Apple Application Support package to the library lookup path.  "; //$NON-NLS-1$
				LibraryLoadError += "To use a SWT.WEBKIT-style Browser prepend " + AASDirectory + " to your Windows 'Path' environment variable and restart."; //$NON-NLS-1$ //$NON-NLS-2$
			}
		} else {
			LibraryLoadError = "Safari must be installed to use a SWT.WEBKIT-style Browser"; //$NON-NLS-1$
		}
	}

	if (LibraryLoaded) {
		JSObjectHasPropertyProc = new Callback (WebKit.class, "JSObjectHasPropertyProc", 3); //$NON-NLS-1$
		if (JSObjectHasPropertyProc.getAddress () == 0) SWT.error (SWT.ERROR_NO_MORE_CALLBACKS);
		JSObjectGetPropertyProc = new Callback (WebKit.class, "JSObjectGetPropertyProc", 4); //$NON-NLS-1$
		if (JSObjectGetPropertyProc.getAddress () == 0) SWT.error (SWT.ERROR_NO_MORE_CALLBACKS);
		JSObjectCallAsFunctionProc = new Callback (WebKit.class, "JSObjectCallAsFunctionProc", 6); //$NON-NLS-1$
		if (JSObjectCallAsFunctionProc.getAddress () == 0) SWT.error (SWT.ERROR_NO_MORE_CALLBACKS);

		NativeClearSessions = () -> {
			long /*int*/[] result = new long /*int*/[1];
			int hr = WebKit_win32.WebKitCreateInstance (WebKit_win32.CLSID_WebCookieManager, 0, WebKit_win32.IID_IWebCookieManager, result);
			if (hr != COM.S_OK || result[0] == 0) {
				return;
			}
			IWebCookieManager cookieManager = new IWebCookieManager (result[0]);
			long /*int*/[] storage = new long /*int*/[1];
			hr = cookieManager.cookieStorage (storage);
			cookieManager.Release ();
			if (hr != COM.S_OK || storage[0] == 0) {
				return;
			}
			long /*int*/ cookies = WebKit_win32.CFHTTPCookieStorageCopyCookies (storage[0]);
			if (cookies != 0) {
				int count = WebKit_win32.CFArrayGetCount (cookies);
				for (int i = 0; i < count; i++) {
					long /*int*/ cookie = WebKit_win32.CFArrayGetValueAtIndex (cookies, i);
					long /*int*/ flags = WebKit_win32.CFHTTPCookieGetFlags (cookie);
					if ((flags & WebKit_win32.CFHTTPCookieSessionOnlyFlag) != 0) {
						WebKit_win32.CFHTTPCookieStorageDeleteCookie (storage[0], cookie);
					}
				}
				WebKit_win32.CFRelease (cookies);
			}
			// WebKit_win32.CFRelease (storage[0]);	//intentionally commented, causes crash
		};

		NativeGetCookie = () -> {
			long /*int*/[] result = new long /*int*/[1];
			int hr = WebKit_win32.WebKitCreateInstance (WebKit_win32.CLSID_WebCookieManager, 0, WebKit_win32.IID_IWebCookieManager, result);
			if (hr != COM.S_OK || result[0] == 0) {
				return;
			}
			IWebCookieManager cookieManager = new IWebCookieManager (result[0]);
			long /*int*/[] storage = new long /*int*/[1];
			hr = cookieManager.cookieStorage (storage);
			cookieManager.Release ();
			if (hr != COM.S_OK || storage[0] == 0) {
				return;
			}
			char[] chars = CookieUrl.toCharArray ();
			long /*int*/ string = WebKit_win32.CFStringCreateWithCharacters (0, chars, chars.length);
			if (string != 0) {
				long /*int*/ cfUrl = WebKit_win32.CFURLCreateWithString (0, string, 0);
				if (cfUrl != 0) {
					boolean secure = CookieUrl.startsWith (PROTOCOL_HTTPS);
					long /*int*/ cookiesArray = WebKit_win32.CFHTTPCookieStorageCopyCookiesForURL (storage[0], cfUrl, secure);
					if (cookiesArray != 0) {
						int count = WebKit_win32.CFArrayGetCount (cookiesArray);
						for (int i = 0; i < count; i++) {
							long /*int*/ cookie = WebKit_win32.CFArrayGetValueAtIndex (cookiesArray, i);
							if (cookie != 0) {
								long /*int*/ cookieName = WebKit_win32.CFHTTPCookieGetName (cookie);
								if (cookieName != 0) {
									String name = stringFromCFString (cookieName);
									if (CookieName.equals (name)) {
										long /*int*/ value = WebKit_win32.CFHTTPCookieGetValue (cookie);
										if (value != 0) CookieValue = stringFromCFString (value);
										break;
									}
								}
							}
						}
						WebKit_win32.CFRelease (cookiesArray);
					}
					WebKit_win32.CFRelease (cfUrl);
				}
				WebKit_win32.CFRelease (string);
			}
			// WebKit_win32.CFRelease (storage[0]);	//intentionally commented, causes crash
		};

		NativeSetCookie = () -> {
			long /*int*/[] result = new long /*int*/[1];
			int hr = WebKit_win32.WebKitCreateInstance (WebKit_win32.CLSID_WebCookieManager, 0, WebKit_win32.IID_IWebCookieManager, result);
			if (hr != COM.S_OK || result[0] == 0) {
				return;
			}
			IWebCookieManager cookieManager = new IWebCookieManager (result[0]);
			long /*int*/[] storage = new long /*int*/[1];
			hr = cookieManager.cookieStorage (storage);
			cookieManager.Release ();
			if (hr != COM.S_OK || storage[0] == 0) {
				return;
			}

			char[] chars = CookieUrl.toCharArray ();
			long /*int*/ string = WebKit_win32.CFStringCreateWithCharacters (0, chars, chars.length);
			if (string != 0) {
				long /*int*/ cfUrl = WebKit_win32.CFURLCreateWithString (0, string, 0);
				if (cfUrl != 0) {
					chars = CookieValue.toCharArray ();
					long /*int*/ value = WebKit_win32.CFStringCreateWithCharacters (0, chars, chars.length);
					if (value != 0) {
						chars = HEADER_SETCOOKIE.toCharArray ();
						long /*int*/ key = WebKit_win32.CFStringCreateWithCharacters (0, chars, chars.length);
						if (key != 0) {
							long /*int*/ headers = WebKit_win32.CFDictionaryCreate (0, new long /*int*/[] {key}, new long /*int*/[] {value}, 1, WebKit_win32.kCFCopyStringDictionaryKeyCallBacks (), WebKit_win32.kCFTypeDictionaryValueCallBacks ());
							if (headers != 0) {
								long /*int*/ cookies = WebKit_win32.CFHTTPCookieCreateWithResponseHeaderFields (0, headers, cfUrl);
								if (cookies != 0) {
									long /*int*/ cookie = WebKit_win32.CFArrayGetValueAtIndex (cookies, 0);
									if (cookie != 0) {
										WebKit_win32.CFHTTPCookieStorageSetCookie (storage[0], cookie);
										CookieResult = true;
									}
									WebKit_win32.CFRelease (cookies);
								}
								WebKit_win32.CFRelease (headers);
							}
							WebKit_win32.CFRelease (key);
						}
						WebKit_win32.CFRelease (value);
					}
					WebKit_win32.CFRelease (cfUrl);
				}
				WebKit_win32.CFRelease (string);
			}
			// WebKit_win32.CFRelease (storage[0]);	//intentionally commented, causes crash
		};

		if (NativePendingCookies != null) {
			SetPendingCookies (NativePendingCookies);
		}
		NativePendingCookies = null;
	}
}

static long /*int*/ createBSTR (String string) {
	char[] data = (string + '\0').toCharArray ();
	return COM.SysAllocString (data);
}

static String error (int code) {
	throw new SWTError ("WebKit error " + code); //$NON-NLS-1$
}

static String extractBSTR (long /*int*/ bstrString) {
	int size = COM.SysStringByteLen (bstrString);
	if (size == 0) return EMPTY_STRING;
	char[] buffer = new char[(size + 1) / 2]; // add one to avoid rounding errors
	OS.MoveMemory (buffer, bstrString, size);
	return new String (buffer);
}

static Browser findBrowser (long /*int*/ webView) {
	if (webView == 0) return null;
	IWebView iwebView = new IWebView (webView);
	long /*int*/[] result = new long /*int*/[1];
	int hr = iwebView.hostWindow (result);
	if (hr == COM.S_OK && result[0] != 0) {
		Widget widget = Display.getCurrent ().findWidget (result[0]);
		if (widget != null && widget instanceof Browser) return (Browser)widget;
	}
	return null;
}

static long /*int*/ JSObjectCallAsFunctionProc (long /*int*/ ctx, long /*int*/ function, long /*int*/ thisObject, long /*int*/ argumentCount, long /*int*/ arguments, long /*int*/ exception) {
	WebKit_win32.JSGlobalContextRetain (ctx);
	if (WebKit_win32.JSValueIsObjectOfClass (ctx, thisObject, ExternalClass) == 0) {
		return WebKit_win32.JSValueMakeUndefined (ctx);
	}
	long /*int*/ ptr = WebKit_win32.JSObjectGetPrivate (thisObject);
	long /*int*/[] handle = new long /*int*/[1];
	C.memmove (handle, ptr, C.PTR_SIZEOF);
	Browser browser = findBrowser (handle[0]);
	if (browser == null) return WebKit_win32.JSValueMakeUndefined (ctx);
	WebKit webkit = (WebKit)browser.webBrowser;
	return webkit.callJava (ctx, function, thisObject, argumentCount, arguments, exception);
}

static long /*int*/ JSObjectGetPropertyProc (long /*int*/ ctx, long /*int*/ object, long /*int*/ propertyName, long /*int*/ exception) {
	byte[] bytes = (FUNCTIONNAME_CALLJAVA + '\0').getBytes (StandardCharsets.UTF_8);
	long /*int*/ name = WebKit_win32.JSStringCreateWithUTF8CString (bytes);
	long /*int*/ addr = WebKit_win32.JSObjectCallAsFunctionProc_CALLBACK (WebKit.JSObjectCallAsFunctionProc.getAddress ());
	long /*int*/ function = WebKit_win32.JSObjectMakeFunctionWithCallback (ctx, name, addr);
	WebKit_win32.JSStringRelease (name);
	return function;
}

static long /*int*/ JSObjectHasPropertyProc (long /*int*/ ctx, long /*int*/ object, long /*int*/ propertyName) {
	byte[] bytes = (FUNCTIONNAME_CALLJAVA + '\0').getBytes (StandardCharsets.UTF_8);
	return WebKit_win32.JSStringIsEqualToUTF8CString (propertyName, bytes);
}

static String readInstallDir (String keyString) {
	long /*int*/[] phkResult = new long /*int*/[1];
	TCHAR key = new TCHAR (0, keyString, true);
	if (OS.RegOpenKeyEx (OS.HKEY_LOCAL_MACHINE, key, 0, OS.KEY_READ, phkResult) == 0) {
		int[] lpcbData = new int[1];
		TCHAR buffer = new TCHAR (0, "InstallDir", true); //$NON-NLS-1$
		int result = OS.RegQueryValueEx (phkResult[0], buffer, 0, null, (TCHAR)null, lpcbData);
		if (result == 0) {
			TCHAR lpData = new TCHAR (0, lpcbData[0] / TCHAR.sizeof);
			result = OS.RegQueryValueEx (phkResult[0], buffer, 0, null, lpData, lpcbData);
			if (result == 0) {
				OS.RegCloseKey (phkResult[0]);
				return lpData.toString (0, lpData.strlen ());
			}
		}
		OS.RegCloseKey (phkResult[0]);
    }
    return null;
}

static String stringFromCFString (long /*int*/ cfString) {
	if (cfString == 0) return null;
	int length = WebKit_win32.CFStringGetLength (cfString);
	long /*int*/ ptr = WebKit_win32.CFStringGetCharactersPtr (cfString);
	char[] chars = new char[length];
	if (ptr != 0) {
		OS.MoveMemory (chars, ptr, length);
	} else {
		for (int j = 0; j < length; j++) {
			chars[j] = WebKit_win32.CFStringGetCharacterAtIndex (cfString, j);
		}
	}
	return new String (chars);
}

static String stringFromJSString (long /*int*/ jsString) {
	if (jsString == 0) return null;
	int length = WebKit_win32.JSStringGetLength (jsString);
	byte[] bytes = new byte[length + 1];
	WebKit_win32.JSStringGetUTF8CString (jsString, bytes, length + 1);
	return new String (bytes);
}

@Override
public boolean back () {
	int[] result = new int[1];
	webView.goBack (result);
	return result[0] != 0;
}

long /*int*/ callJava (long /*int*/ ctx, long /*int*/ func, long /*int*/ thisObject, long /*int*/ argumentCount, long /*int*/ arguments, long /*int*/ exception) {
	Object returnValue = null;
	if (argumentCount == 3) {
		long /*int*/[] result = new long /*int*/[1];
		C.memmove (result, arguments, C.PTR_SIZEOF);
		int type = WebKit_win32.JSValueGetType (ctx, result[0]);
		if (type == WebKit_win32.kJSTypeNumber) {
			int index = ((Double)convertToJava (ctx, result[0])).intValue ();
			result[0] = 0;
			if (index > 0) {
				Object key = new Integer (index);
				C.memmove (result, arguments + C.PTR_SIZEOF, C.PTR_SIZEOF);
				type = WebKit_win32.JSValueGetType (ctx, result[0]);
				if (type == WebKit_win32.kJSTypeString) {
					String token = (String)convertToJava (ctx, result[0]);
					BrowserFunction function = (BrowserFunction)functions.get (key);
					if (function != null && token.equals (function.token)) {
						try {
							C.memmove (result, arguments + 2 * C.PTR_SIZEOF, C.PTR_SIZEOF);
							Object temp = convertToJava (ctx, result[0]);
							if (temp instanceof Object[]) {
								Object[] args = (Object[])temp;
								try {
									returnValue = function.function (args);
								} catch (Exception e) {
									/* exception during function invocation */
									returnValue = WebBrowser.CreateErrorString (e.getLocalizedMessage ());
								}
							}
						} catch (IllegalArgumentException e) {
							/* invalid argument value type */
							if (function.isEvaluate) {
								/* notify the function so that a java exception can be thrown */
								function.function (new String[] {WebBrowser.CreateErrorString (new SWTException (SWT.ERROR_INVALID_RETURN_VALUE).getLocalizedMessage ())});
							}
							returnValue = WebBrowser.CreateErrorString (e.getLocalizedMessage ());
						}
					}
				}
			}
		}
	}
	return convertToJS (ctx, returnValue);
}

@Override
public boolean close () {
	return shouldClose ();
}

Object convertToJava (long /*int*/ ctx, long /*int*/ value) {
	int type = WebKit_win32.JSValueGetType (ctx, value);
	switch (type) {
		case WebKit_win32.kJSTypeBoolean: {
			int result = (int)WebKit_win32.JSValueToNumber (ctx, value, null);
			return new Boolean (result != 0);
		}
		case WebKit_win32.kJSTypeNumber: {
			double result = WebKit_win32.JSValueToNumber (ctx, value, null);
			return new Double (result);
		}
		case WebKit_win32.kJSTypeString: {
			long /*int*/ string = WebKit_win32.JSValueToStringCopy (ctx, value, null);
			if (string == 0) return ""; //$NON-NLS-1$
			long /*int*/ length = WebKit_win32.JSStringGetMaximumUTF8CStringSize (string);
			byte[] bytes = new byte[(int)/*64*/length];
			length = WebKit_win32.JSStringGetUTF8CString (string, bytes, length);
			WebKit_win32.JSStringRelease (string);
			/* length-1 is needed below to exclude the terminator character */
			return new String (bytes, 0, (int)/*64*/length - 1, StandardCharsets.UTF_8);
		}
		case WebKit_win32.kJSTypeNull:
			// FALL THROUGH
		case WebKit_win32.kJSTypeUndefined: return null;
		case WebKit_win32.kJSTypeObject: {
			byte[] bytes = (PROPERTY_LENGTH + '\0').getBytes (StandardCharsets.UTF_8);
			long /*int*/ propertyName = WebKit_win32.JSStringCreateWithUTF8CString (bytes);
			long /*int*/ valuePtr = WebKit_win32.JSObjectGetProperty (ctx, value, propertyName, null);
			WebKit_win32.JSStringRelease (propertyName);
			type = WebKit_win32.JSValueGetType (ctx, valuePtr);
			if (type == WebKit_win32.kJSTypeNumber) {
				int length = (int)WebKit_win32.JSValueToNumber (ctx, valuePtr, null);
				Object[] result = new Object[length];
				for (int i = 0; i < length; i++) {
					long /*int*/ current = WebKit_win32.JSObjectGetPropertyAtIndex (ctx, value, i, null);
					if (current != 0) {
						result[i] = convertToJava (ctx, current);
					}
				}
				return result;
			}
		}
	}
	SWT.error (SWT.ERROR_INVALID_ARGUMENT);
	return null;
}

long /*int*/ convertToJS (long /*int*/ ctx, Object value) {
	if (value == null) {
		return WebKit_win32.JSValueMakeNull (ctx);
	}
	if (value instanceof String) {
		byte[] bytes = ((String)value + '\0').getBytes (StandardCharsets.UTF_8);
		long /*int*/ stringRef = WebKit_win32.JSStringCreateWithUTF8CString (bytes);
		long /*int*/ result = WebKit_win32.JSValueMakeString (ctx, stringRef);
		WebKit_win32.JSStringRelease (stringRef);
		return result;
	}
	if (value instanceof Boolean) {
		return WebKit_win32.JSValueMakeBoolean (ctx, ((Boolean)value).booleanValue () ? 1 : 0);
	}
	if (value instanceof Number) {
		return WebKit_win32.JSValueMakeNumber (ctx, ((Number)value).doubleValue ());
	}
	if (value instanceof Object[]) {
		Object[] arrayValue = (Object[]) value;
		int length = arrayValue.length;
		long /*int*/[] arguments = new long /*int*/[length];
		for (int i = 0; i < length; i++) {
			Object javaObject = arrayValue[i];
			long /*int*/ jsObject = convertToJS (ctx, javaObject);
			arguments[i] = jsObject;
		}
		return WebKit_win32.JSObjectMakeArray (ctx, length, arguments, null);
	}
	SWT.error (SWT.ERROR_INVALID_RETURN_VALUE);
	return 0;
}

@Override
public void create (Composite parent, int style) {
	if (!LibraryLoaded) {
		browser.dispose ();
		SWT.error (SWT.ERROR_NO_HANDLES, null, LibraryLoadError == null ? null : " [" + LibraryLoadError + ']'); //$NON-NLS-1$
	}

	if (ExternalClass == 0) {
		JSClassDefinition jsClassDefinition = new JSClassDefinition ();
		byte[] bytes = (CLASSNAME_EXTERNAL + '\0').getBytes ();
		jsClassDefinition.className = C.malloc (bytes.length);
		C.memmove (jsClassDefinition.className, bytes, bytes.length);

		/* custom callbacks for hasProperty, getProperty and callAsFunction */
		long /*int*/ addr = WebKit_win32.JSObjectHasPropertyProc_CALLBACK (JSObjectHasPropertyProc.getAddress ());
		jsClassDefinition.hasProperty = addr;
		addr = WebKit_win32.JSObjectGetPropertyProc_CALLBACK (JSObjectGetPropertyProc.getAddress ());
		jsClassDefinition.getProperty = addr;

		long /*int*/ classDefinitionPtr = C.malloc (JSClassDefinition.sizeof);
		WebKit_win32.memmove (classDefinitionPtr, jsClassDefinition, JSClassDefinition.sizeof);
		ExternalClass = WebKit_win32.JSClassCreate (classDefinitionPtr);
		WebKit_win32.JSClassRetain (ExternalClass);
	}

	long /*int*/[] result = new long /*int*/[1];
	int hr = WebKit_win32.WebKitCreateInstance (WebKit_win32.CLSID_WebView, 0, WebKit_win32.IID_IWebView, result);
	if (hr != COM.S_OK || result[0] == 0) {
		browser.dispose ();
		error (hr);
	}
	webView = new IWebView (result[0]);
	webViewData = C.malloc (C.PTR_SIZEOF);
	C.memmove (webViewData, new long /*int*/[] {webView.getAddress ()}, C.PTR_SIZEOF);
	hr = webView.setHostWindow (browser.handle);
	if (hr != COM.S_OK) {
		browser.dispose ();
		error (hr);
	}
	hr = webView.initWithFrame (new RECT (), 0, 0);
	if (hr != COM.S_OK) {
		browser.dispose ();
		error (hr);
	}
	result[0] = 0;
	hr = webView.QueryInterface (WebKit_win32.IID_IWebViewPrivate, result);
	if (hr != COM.S_OK || result[0] == 0) {
		browser.dispose ();
		error (hr);
	}
	IWebViewPrivate webViewPrivate = new IWebViewPrivate (result[0]);
	result[0] = 0;
	hr = webViewPrivate.viewWindow (result);
	if (hr != COM.S_OK || result[0] == 0) {
		browser.dispose ();
		error (hr);
	}
	webViewPrivate.Release ();
	webViewWindowHandle = result[0];

	webFrameLoadDelegate = new WebFrameLoadDelegate (browser);
	hr = webView.setFrameLoadDelegate (webFrameLoadDelegate.getAddress ());
	if (hr != COM.S_OK) {
		browser.dispose ();
		error (hr);
	}
	webUIDelegate = new WebUIDelegate (browser);
	hr = webView.setUIDelegate (webUIDelegate.getAddress ());
	if (hr != COM.S_OK) {
		browser.dispose ();
		error (hr);
	}

	webResourceLoadDelegate = new WebResourceLoadDelegate (browser);
	hr = webView.setResourceLoadDelegate (webResourceLoadDelegate.getAddress ());
	if (hr != COM.S_OK) {
		browser.dispose ();
		error (hr);
	}

	webDownloadDelegate = new WebDownloadDelegate (browser);
	hr = webView.setDownloadDelegate (webDownloadDelegate.getAddress ());
	if (hr != COM.S_OK) {
		browser.dispose ();
		error (hr);
	}

	webPolicyDelegate = new WebPolicyDelegate (browser);
	hr = webView.setPolicyDelegate (webPolicyDelegate.getAddress ());
	if (hr != COM.S_OK) {
		browser.dispose ();
		error (hr);
	}

	initializeWebViewPreferences ();

	Listener listener = e -> {
		switch (e.type) {
			case SWT.Dispose: {
				/* make this handler run after other dispose listeners */
				if (ignoreDispose) {
					ignoreDispose = false;
					break;
				}
				ignoreDispose = true;
				browser.notifyListeners (e.type, e);
				e.type = SWT.NONE;
				onDispose ();
				break;
			}
			case SWT.FocusIn: {
				OS.SetFocus (webViewWindowHandle);
				break;
			}
			case SWT.Resize: {
				Rectangle bounds = DPIUtil.autoScaleUp(browser.getClientArea ()); // To Pixels
				OS.SetWindowPos (webViewWindowHandle, 0, bounds.x, bounds.y, bounds.width, bounds.height, OS.SWP_DRAWFRAME);
				break;
			}
			case SWT.Traverse: {
				if (traverseOut) {
					e.doit = true;
					traverseOut = false;
				} else {
					e.doit = false;
				}
				break;
			}
		}
	};
	browser.addListener (SWT.Dispose, listener);
	browser.addListener (SWT.KeyDown, listener); /* needed for tabbing into the Browser */
	browser.addListener (SWT.FocusIn, listener);
	browser.addListener (SWT.Resize, listener);
	browser.addListener (SWT.Traverse, listener);

	eventFunction = new BrowserFunction (browser, "HandleWebKitEvent") { //$NON-NLS-1$
		@Override
		public Object function (Object[] arguments) {
			return handleEvent (arguments) ? Boolean.TRUE : Boolean.FALSE;
		};
	};
}

@Override
public boolean execute (String script) {
	long /*int*/[] result = new long /*int*/[1];
	int hr = webView.mainFrame (result);
	if (hr != COM.S_OK || result[0] == 0) {
		return false;
	}
	IWebFrame frame = new IWebFrame (result[0]);
	long /*int*/ context = frame.globalContext ();
	frame.Release ();
	if (context == 0) {
		return false;
	}
	byte[] bytes = (script + '\0').getBytes (StandardCharsets.UTF_8); //$NON-NLS-1$
	long /*int*/ scriptString = WebKit_win32.JSStringCreateWithUTF8CString (bytes);
	if (scriptString == 0) return false;
	bytes = (getUrl () + '\0').getBytes (StandardCharsets.UTF_8); //$NON-NLS-1$
	long /*int*/ urlString = WebKit_win32.JSStringCreateWithUTF8CString (bytes);
	if (urlString == 0) {
		WebKit_win32.JSStringRelease (scriptString);
		return false;
	}
	long /*int*/ evalResult = WebKit_win32.JSEvaluateScript (context, scriptString, 0, urlString, 0, null);
	WebKit_win32.JSStringRelease (urlString);
	WebKit_win32.JSStringRelease (scriptString);
	return evalResult != 0;
}

@Override
public boolean forward () {
	int[] result = new int[1];
	webView.goForward (result);
	return result[0] != 0;
}

@Override
public String getBrowserType () {
	return "webkit"; //$NON-NLS-1$
}

@Override
public String getText () {
	long /*int*/[] result = new long /*int*/[1];
	int hr = webView.mainFrame (result);
	if (hr != COM.S_OK || result[0] == 0) {
		return EMPTY_STRING;
	}
	IWebFrame mainFrame = new IWebFrame (result[0]);
	result[0] = 0;
	hr = mainFrame.dataSource (result);
	mainFrame.Release ();
	if (hr != COM.S_OK || result[0] == 0) {
		return EMPTY_STRING;
	}
	IWebDataSource dataSource = new IWebDataSource (result[0]);
	result[0] = 0;
	hr = dataSource.representation (result);
	dataSource.Release ();
	if (hr != COM.S_OK || result[0] == 0) {
		return EMPTY_STRING;
	}
	IWebDocumentRepresentation representation = new IWebDocumentRepresentation (result[0]);
	result[0] = 0;
	hr = representation.documentSource (result);
	representation.Release ();
	if (hr != COM.S_OK || result[0] == 0) {
		return EMPTY_STRING;
	}
	String source = extractBSTR (result[0]);
	COM.SysFreeString (result[0]);
	return source;
}

@Override
public String getUrl () {
	return webFrameLoadDelegate.getUrl ();
}

boolean handleEvent (Object[] arguments) {

	/*
	* DOM events are currently received by hooking DOM listeners
	* in javascript that invoke this method via a BrowserFunction.
	* Document.addListener is not implemented on WebKit on windows.
	* The argument lists received here are:
	*
	* For key events:
	* 	argument 0: type (String)
	* 	argument 1: keyCode (Double)
	* 	argument 2: charCode (Double)
	* 	argument 3: altKey (Boolean)
	* 	argument 4: ctrlKey (Boolean)
	* 	argument 5: shiftKey (Boolean)
	* 	argument 6: metaKey (Boolean)
	* 	returns doit
	*
	* For mouse events
	* 	argument 0: type (String)
	* 	argument 1: screenX (Double)
	* 	argument 2: screenY (Double)
	* 	argument 3: detail (Double)
	* 	argument 4: button (Double)
	* 	argument 5: altKey (Boolean)
	* 	argument 6: ctrlKey (Boolean)
	* 	argument 7: shiftKey (Boolean)
	* 	argument 8: metaKey (Boolean)
	* 	argument 9: hasRelatedTarget (Boolean)
	* 	returns doit
	*/

	String type = (String)arguments[0];
	if (type.equals (DOMEVENT_KEYDOWN)) {
		int keyCode = translateKey (((Double)arguments[1]).intValue ());
		lastKeyCode = keyCode;
		switch (keyCode) {
			case SWT.SHIFT:
			case SWT.CONTROL:
			case SWT.ALT:
			case SWT.CAPS_LOCK:
			case SWT.NUM_LOCK:
			case SWT.SCROLL_LOCK:
			case SWT.COMMAND:
//			case SWT.ESC:
			case SWT.TAB:
			case SWT.PAUSE:
//			case SWT.BS:
			case SWT.INSERT:
			case SWT.DEL:
			case SWT.HOME:
			case SWT.END:
			case SWT.PAGE_UP:
			case SWT.PAGE_DOWN:
			case SWT.ARROW_DOWN:
			case SWT.ARROW_UP:
			case SWT.ARROW_LEFT:
			case SWT.ARROW_RIGHT:
			case SWT.F1:
			case SWT.F2:
			case SWT.F3:
			case SWT.F4:
			case SWT.F5:
			case SWT.F6:
			case SWT.F7:
			case SWT.F8:
			case SWT.F9:
			case SWT.F10:
			case SWT.F11:
			case SWT.F12: {
				/* keypress events will not be received for these keys, so send KeyDowns for them now */

				Event keyEvent = new Event ();
				keyEvent.widget = browser;
				keyEvent.type = type.equals (DOMEVENT_KEYDOWN) ? SWT.KeyDown : SWT.KeyUp;
				keyEvent.keyCode = keyCode;
				switch (keyCode) {
					case SWT.BS: keyEvent.character = SWT.BS; break;
					case SWT.DEL: keyEvent.character = SWT.DEL; break;
					case SWT.ESC: keyEvent.character = SWT.ESC; break;
					case SWT.TAB: keyEvent.character = SWT.TAB; break;
				}
				lastCharCode = keyEvent.character;
				keyEvent.stateMask =
					(((Boolean)arguments[3]).booleanValue () ? SWT.ALT : 0) |
					(((Boolean)arguments[4]).booleanValue () ? SWT.CTRL : 0) |
					(((Boolean)arguments[5]).booleanValue () ? SWT.SHIFT : 0) |
					(((Boolean)arguments[6]).booleanValue () ? SWT.COMMAND : 0);
				keyEvent.stateMask &= ~keyCode;		/* remove current keydown if it's a state key */
				if (!sendKeyEvent (keyEvent) || browser.isDisposed ()) return false;
				break;
			}
		}
		return true;
	}

	if (type.equals (DOMEVENT_KEYPRESS)) {
		/*
		* if keydown could not determine a keycode for this key then it's a
		* key for which key events are not sent (eg.- the Windows key)
		*/
		if (lastKeyCode == 0) return true;

		lastCharCode = ((Double)arguments[2]).intValue ();
		if (((Boolean)arguments[4]).booleanValue () && (0 <= lastCharCode && lastCharCode <= 0x7F)) {
			if ('a' <= lastCharCode && lastCharCode <= 'z') lastCharCode -= 'a' - 'A';
			if (64 <= lastCharCode && lastCharCode <= 95) lastCharCode -= 64;
		}

		Event keyEvent = new Event ();
		keyEvent.widget = browser;
		keyEvent.type = SWT.KeyDown;
		keyEvent.keyCode = lastKeyCode;
		keyEvent.character = (char)lastCharCode;
		keyEvent.stateMask =
			(((Boolean)arguments[3]).booleanValue () ? SWT.ALT : 0) |
			(((Boolean)arguments[4]).booleanValue () ? SWT.CTRL : 0) |
			(((Boolean)arguments[5]).booleanValue () ? SWT.SHIFT : 0) |
			(((Boolean)arguments[6]).booleanValue () ? SWT.COMMAND : 0);
		return sendKeyEvent (keyEvent) && !browser.isDisposed ();
	}

	if (type.equals (DOMEVENT_KEYUP)) {
		int keyCode = translateKey (((Double)arguments[1]).intValue ());
		if (keyCode == 0) {
			/* indicates a key for which key events are not sent */
			return true;
		}
		if (keyCode != lastKeyCode) {
			/* keyup does not correspond to the last keydown */
			lastKeyCode = keyCode;
			lastCharCode = 0;
		}

		Event keyEvent = new Event ();
		keyEvent.widget = browser;
		keyEvent.type = SWT.KeyUp;
		keyEvent.keyCode = lastKeyCode;
		keyEvent.character = (char)lastCharCode;
		keyEvent.stateMask =
			(((Boolean)arguments[3]).booleanValue () ? SWT.ALT : 0) |
			(((Boolean)arguments[4]).booleanValue () ? SWT.CTRL : 0) |
			(((Boolean)arguments[5]).booleanValue () ? SWT.SHIFT : 0) |
			(((Boolean)arguments[6]).booleanValue () ? SWT.COMMAND : 0);
		switch (lastKeyCode) {
			case SWT.SHIFT:
			case SWT.CONTROL:
			case SWT.ALT:
			case SWT.COMMAND: {
				keyEvent.stateMask |= lastKeyCode;
			}
		}
		browser.notifyListeners (keyEvent.type, keyEvent);
		lastKeyCode = lastCharCode = 0;
		return keyEvent.doit && !browser.isDisposed ();
	}

	/* mouse events */

	/*
	 * MouseOver and MouseOut events are fired any time the mouse enters or exits
	 * any element within the Browser.  To ensure that SWT events are only
	 * fired for mouse movements into or out of the Browser, do not fire an
	 * event if there is a related target element.
	 */
	if (type.equals (DOMEVENT_MOUSEOVER) || type.equals (DOMEVENT_MOUSEOUT)) {
		if (((Boolean)arguments[9]).booleanValue ()) return true;
	}

	/*
	 * The position of mouse events is received in screen-relative coordinates
	 * in order to handle pages with frames, since frames express their event
	 * coordinates relative to themselves rather than relative to their top-
	 * level page.  Convert screen-relative coordinates to be browser-relative.
	 */
	Point position = new Point (((Double)arguments[1]).intValue (), ((Double)arguments[2]).intValue ());// Points or Pixles ?
	position = browser.getDisplay ().map (null, browser, position);

	Event mouseEvent = new Event ();
	mouseEvent.widget = browser;
	mouseEvent.x = position.x;
	mouseEvent.y = position.y;
	int mask =
		(((Boolean)arguments[5]).booleanValue () ? SWT.ALT : 0) |
		(((Boolean)arguments[6]).booleanValue () ? SWT.CTRL : 0) |
		(((Boolean)arguments[7]).booleanValue () ? SWT.SHIFT : 0);
	mouseEvent.stateMask = mask;

	if (type.equals (DOMEVENT_MOUSEDOWN)) {
		mouseEvent.type = SWT.MouseDown;
		mouseEvent.count = ((Double)arguments[3]).intValue ();
		mouseEvent.button = ((Double)arguments[4]).intValue ();
		browser.notifyListeners (mouseEvent.type, mouseEvent);
		if (browser.isDisposed ()) return true;
		if (((Double)arguments[3]).intValue () == 2) {
			mouseEvent = new Event ();
			mouseEvent.type = SWT.MouseDoubleClick;
			mouseEvent.widget = browser;
			mouseEvent.x = position.x;
			mouseEvent.y = position.y;
			mouseEvent.stateMask = mask;
			mouseEvent.count = ((Double)arguments[3]).intValue ();
			mouseEvent.button = ((Double)arguments[4]).intValue ();
			browser.notifyListeners (mouseEvent.type, mouseEvent);
		}
		return true;
	}

	if (type.equals (DOMEVENT_MOUSEUP)) {
		mouseEvent.type = SWT.MouseUp;
		mouseEvent.count = ((Double)arguments[3]).intValue ();
		mouseEvent.button = ((Double)arguments[4]).intValue ();
		switch (mouseEvent.button) {
			case 1: mouseEvent.stateMask |= SWT.BUTTON1; break;
			case 2: mouseEvent.stateMask |= SWT.BUTTON2; break;
			case 3: mouseEvent.stateMask |= SWT.BUTTON3; break;
			case 4: mouseEvent.stateMask |= SWT.BUTTON4; break;
			case 5: mouseEvent.stateMask |= SWT.BUTTON5; break;
		}
	} else if (type.equals (DOMEVENT_MOUSEMOVE)) {
		mouseEvent.type = SWT.MouseMove;
	} else if (type.equals (DOMEVENT_MOUSEWHEEL)) {
		mouseEvent.type = SWT.MouseWheel;
		mouseEvent.count = ((Double)arguments[3]).intValue ();
	} else if (type.equals (DOMEVENT_MOUSEOVER)) {
		mouseEvent.type = SWT.MouseEnter;
	} else if (type.equals (DOMEVENT_MOUSEOUT)) {
		mouseEvent.type = SWT.MouseExit;
		if (mouseEvent.x < 0) mouseEvent.x = -1;
		if (mouseEvent.y < 0) mouseEvent.y = -1;
	} else if (type.equals (DOMEVENT_DRAGSTART)) {
		mouseEvent.type = SWT.DragDetect;
		mouseEvent.button = ((Double)arguments[4]).intValue () + 1;
		switch (mouseEvent.button) {
			case 1: mouseEvent.stateMask |= SWT.BUTTON1; break;
			case 2: mouseEvent.stateMask |= SWT.BUTTON2; break;
			case 3: mouseEvent.stateMask |= SWT.BUTTON3; break;
			case 4: mouseEvent.stateMask |= SWT.BUTTON4; break;
			case 5: mouseEvent.stateMask |= SWT.BUTTON5; break;
		}
	}

	browser.notifyListeners (mouseEvent.type, mouseEvent);
	return true;
}

@Override
public boolean isBackEnabled () {
	long /*int*/[] address = new long /*int*/[1];
	int hr = webView.QueryInterface (WebKit_win32.IID_IWebIBActions, address);
	if (hr != COM.S_OK || address[0] == 0) {
		return false;
	}
	IWebIBActions webIBActions = new IWebIBActions (address[0]);
	int[] result = new int[1];
	webIBActions.canGoBack (webView.getAddress (), result);
	webIBActions.Release ();
	return result[0] != 0;
}

@Override
public boolean isFocusControl () {
	long /*int*/ hwndFocus = OS.GetFocus ();
	return hwndFocus != 0 && hwndFocus == webViewWindowHandle;
}

@Override
public boolean isForwardEnabled () {
	long /*int*/[] address = new long /*int*/[1];
	int hr = webView.QueryInterface (WebKit_win32.IID_IWebIBActions, address);
	if (hr != COM.S_OK || address[0] == 0) {
		return false;
	}
	IWebIBActions webIBActions = new IWebIBActions (address[0]);
	int[] result = new int[1];
	webIBActions.canGoForward (webView.getAddress (), result);
	webIBActions.Release ();
	return result[0] != 0;
}

void onDispose () {
	/* Browser could have been disposed by one of the Dispose listeners */
	if (!browser.isDisposed ()) {
		/* invoke onbeforeunload handlers but don't prompt with message box */
		if (!browser.isClosing) {
			webUIDelegate.prompt = false;
			shouldClose ();
			webUIDelegate.prompt = true;
		}
	}

	Iterator<BrowserFunction> elements = functions.values().iterator ();
	while (elements.hasNext ()) {
		elements.next ().dispose (false);
	}
	functions = null;

	eventFunction.dispose();
	eventFunction = null;
	C.free (webViewData);

	webView.setPreferences (0);
	webView.setHostWindow (0);
	webView.setFrameLoadDelegate (0);
	webView.setResourceLoadDelegate (0);
	webView.setUIDelegate (0);
	webView.setPolicyDelegate (0);
	webView.setDownloadDelegate (0);
	webView.Release();
	webView = null;
	webDownloadDelegate = null;
	webFrameLoadDelegate = null;
	webPolicyDelegate = null;
	webResourceLoadDelegate = null;
	webUIDelegate = null;
	lastNavigateURL = null;
}

@Override
public void refresh () {
	webFrameLoadDelegate.html = null;
	long /*int*/[] result = new long /*int*/[1];
	int hr = webView.QueryInterface (WebKit_win32.IID_IWebIBActions, result);
	if (hr != COM.S_OK || result[0] == 0) {
		return;
	}
	IWebIBActions webIBActions = new IWebIBActions (result[0]);
	webIBActions.reload (webView.getAddress ());
	webIBActions.Release ();
}

@Override
boolean sendKeyEvent (Event event) {
	/*
	 * browser.traverse() is called through dislay.translateTraversal() for all
	 * traversal types except SWT.TRAVERSE_MNEMONIC. So, override
	 * WebBrowser.sendKeyEvent() so that when it is called from handleEvent(),
	 * browser.traverse() is not called again.
	 */
	boolean doit = true;
	switch (event.keyCode) {
		case SWT.ESC:
		case SWT.CR:
		case SWT.ARROW_DOWN:
		case SWT.ARROW_RIGHT:
		case SWT.ARROW_UP:
		case SWT.ARROW_LEFT:
		case SWT.TAB:
		case SWT.PAGE_DOWN:
		case SWT.PAGE_UP:
			break;
		default: {
			if (translateMnemonics ()) {
				if (event.character != 0 && (event.stateMask & (SWT.ALT | SWT.CTRL)) == SWT.ALT) {
					int traversal = SWT.TRAVERSE_MNEMONIC;
					boolean oldEventDoit = event.doit;
					event.doit = true;
					doit = !browser.traverse (traversal, event);
					event.doit = oldEventDoit;
				}
			}
			break;
		}
	}
	if (doit) {
		browser.notifyListeners (event.type, event);
		doit = event.doit;
	}
	return doit;
}

@Override
public boolean setText (String html, boolean trusted) {
	/*
	* If this.html is not null then the about:blank page is already being loaded,
	* so no navigate is required.  Just set the html that is to be shown.
	*/
	boolean blankLoading = webFrameLoadDelegate.html != null;
	webFrameLoadDelegate.html = html;
	untrustedText = !trusted;
	if (blankLoading) return true;

	long /*int*/[] result = new long /*int*/[1];
	int hr = webView.mainFrame (result);
	if (hr != COM.S_OK || result[0] == 0) {
		return false;
	}
	IWebFrame frame = new IWebFrame (result[0]);

	result[0] = 0;
	hr = WebKit_win32.WebKitCreateInstance (WebKit_win32.CLSID_WebMutableURLRequest, 0, WebKit_win32.IID_IWebMutableURLRequest, result);
	if (hr != COM.S_OK || result[0] == 0) {
		frame.Release ();
		return false;
	}
	IWebMutableURLRequest request = new IWebMutableURLRequest (result[0]);

	long /*int*/ urlString = createBSTR (ABOUT_BLANK);
	hr = request.setURL (urlString);
	COM.SysFreeString (urlString);

	if (hr == COM.S_OK) {
		hr = frame.loadRequest (request.getAddress ());
	}
	frame.Release ();
	request.Release ();
	return hr == COM.S_OK;
}

@Override
public boolean setUrl (String url, String postData, String[] headers) {
	if (url.length () == 0) return false;
	/*
	* WebKit attempts to open the exact url string that is passed to it and
	* will not infer a protocol if it's not specified.  Detect the case of an
	* invalid URL string and try to fix it by prepending an appropriate protocol.
	*/
	try {
		new URL (url);
	} catch (MalformedURLException e) {
		String testUrl = null;
		if (new File (url).isAbsolute ()) {
			/* appears to be a local file */
			testUrl = PROTOCOL_FILE + url;
		} else {
			testUrl = PROTOCOL_HTTP + url;
		}
		try {
			new URL (testUrl);
			url = testUrl;		/* adding the protocol made the url valid */
		} catch (MalformedURLException e2) {
			/* adding the protocol did not make the url valid, so do nothing */
		}
	}
	webFrameLoadDelegate.html = null;
	lastNavigateURL = url;
	long /*int*/[] result = new long /*int*/[1];
	int hr = webView.mainFrame (result);
	if (hr != COM.S_OK || result[0] == 0) {
		return false;
	}
	IWebFrame frame = new IWebFrame (result[0]);

	result[0] = 0;
	hr = WebKit_win32.WebKitCreateInstance (WebKit_win32.CLSID_WebMutableURLRequest, 0, WebKit_win32.IID_IWebMutableURLRequest, result);
	if (hr != COM.S_OK || result[0] == 0) {
		frame.Release ();
		return false;
	}
	IWebMutableURLRequest request = new IWebMutableURLRequest (result[0]);

	if (postData != null) { //TODO: POST
//    	webResourceLoadDelegate.postData = postData;
//    	long /*int*/ postString = createBSTR (POST);
//		hr = request.setHTTPMethod (postString);
//		COM.SysFreeString (postString);
//
//		result[0] = 0;
//		hr = request.QueryInterface (WebKit_win32.IID_IWebMutableURLRequestPrivate, result);
//		if (hr == COM.S_OK && result[0] != 0) {
//			IWebMutableURLRequestPrivate requestPrivate = new IWebMutableURLRequestPrivate(result[0]);
//			int cfRequest = requestPrivate.cfRequest();
//			byte[] bytes = postData.getBytes();
//			long /*int*/ data = WebKit_win32.CFDataCreate(0, bytes, bytes.length);
//			if (data != 0)WebKit_win32.CFURLRequestSetHTTPRequestBody(cfRequest, data);
//
//			long /*int*/ dataGet = WebKit_win32.CFURLRequestCopyHTTPRequestBody(cfRequest);
//			int length = WebKit_win32.CFDataGetLength(dataGet);
//			long /*int*/ bytePtr = WebKit_win32.CFDataGetBytePtr(dataGet);
//		}
	}
	hr = COM.S_OK;	//TODO: once post code is completed, remove this line if not required
	if (headers != null) {
		for (int i = 0; i < headers.length; i++) {
			String current = headers[i];
			if (current != null) {
				int index = current.indexOf (':');
				if (index != -1) {
					String key = current.substring (0, index).trim ();
					String value = current.substring (index + 1).trim ();
					if (key.length () > 0 && value.length () > 0) {
						long /*int*/ valueString = createBSTR (value);
						if (key.equalsIgnoreCase (USER_AGENT)) {
							/*
							* Feature of WebKit.  The user-agent header value cannot be overridden
							* here.  The workaround is to temporarily set the value on the WebView
							* and then remove it after the loading of the request has begun.
							*/
							hr = webView.setCustomUserAgent (valueString);
						} else {
							long /*int*/ keyString = createBSTR (key);
							hr = request.setValue (valueString, keyString);
							COM.SysFreeString (keyString);
						}
						COM.SysFreeString (valueString);
					}
				}
			}
		}
	}
	if (hr == COM.S_OK) {
		long /*int*/ urlString = createBSTR (url);
		hr = request.setURL (urlString);
		COM.SysFreeString (urlString);
		if (hr == COM.S_OK) {
			hr = frame.loadRequest (request.getAddress ());
		}
		webView.setCustomUserAgent (0);
	}
	frame.Release ();
	request.Release ();
	return hr == COM.S_OK;
}

boolean shouldClose () {
	if (!jsEnabled) return true;

	long /*int*/[] address = new long /*int*/[1];
	int hr = webView.QueryInterface (WebKit_win32.IID_IWebViewPrivate, address);
	if (hr != COM.S_OK || address[0] == 0) {
		return false;
	}
	IWebViewPrivate webViewPrivate = new IWebViewPrivate (address[0]);
	int[] result = new int[1];
	/* This function will fire the before unload handler for a page */
	webViewPrivate.shouldClose (result);
	webViewPrivate.Release ();
	return result[0] != 0;
}

@Override
public void stop () {
	webFrameLoadDelegate.html = null;
	long /*int*/[] result = new long /*int*/[1];
	int hr = webView.QueryInterface (WebKit_win32.IID_IWebIBActions, result);
	if (hr != COM.S_OK || result[0] == 0) {
		return;
	}
	IWebIBActions webIBActions = new IWebIBActions (result[0]);
	webIBActions.stopLoading (webView.getAddress ());
	webIBActions.Release ();
}

void initializeWebViewPreferences () {
	/*
	 * Try to create separate preferences for each webview using different identifier for each webview.
	 * Otherwise all the webviews use the shared preferences.
	 */
	long /*int*/[] result = new long /*int*/[1];
	int hr = WebKit_win32.WebKitCreateInstance (WebKit_win32.CLSID_WebPreferences, 0, WebKit_win32.IID_IWebPreferences, result);
	if (hr == COM.S_OK && result[0] != 0) {
		IWebPreferences preferences = new IWebPreferences (result[0]);
		result[0] = 0;
		hr = preferences.initWithIdentifier (createBSTR (String.valueOf (prefsIdentifier++)), result);
		preferences.Release ();
		if (hr == COM.S_OK && result[0] != 0) {
			preferences = new IWebPreferences (result[0]);
			webView.setPreferences (preferences.getAddress());
			preferences.Release ();
		}
	}

	result[0] = 0;
	hr = webView.preferences (result);
	if (hr == COM.S_OK && result[0] != 0) {
		IWebPreferences preferences = new IWebPreferences (result[0]);
		preferences.setJavaScriptEnabled (1);
		preferences.setJavaScriptCanOpenWindowsAutomatically (1);
		preferences.setJavaEnabled (0);	/* disable applets */
		preferences.setTabsToLinks (1);
		preferences.setFontSmoothing (WebKit_win32.FontSmoothingTypeWindows);
		preferences.Release ();
	}
}

}
