/******************************************************************************* * 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 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 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[] result = new long[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[] storage = new long[1]; hr = cookieManager.cookieStorage (storage); cookieManager.Release (); if (hr != COM.S_OK || storage[0] == 0) { return; } long cookies = WebKit_win32.CFHTTPCookieStorageCopyCookies (storage[0]); if (cookies != 0) { int count = WebKit_win32.CFArrayGetCount (cookies); for (int i = 0; i < count; i++) { long cookie = WebKit_win32.CFArrayGetValueAtIndex (cookies, i); long 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[] result = new long[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[] storage = new long[1]; hr = cookieManager.cookieStorage (storage); cookieManager.Release (); if (hr != COM.S_OK || storage[0] == 0) { return; } char[] chars = CookieUrl.toCharArray (); long string = WebKit_win32.CFStringCreateWithCharacters (0, chars, chars.length); if (string != 0) { long cfUrl = WebKit_win32.CFURLCreateWithString (0, string, 0); if (cfUrl != 0) { boolean secure = CookieUrl.startsWith (PROTOCOL_HTTPS); long 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 cookie = WebKit_win32.CFArrayGetValueAtIndex (cookiesArray, i); if (cookie != 0) { long cookieName = WebKit_win32.CFHTTPCookieGetName (cookie); if (cookieName != 0) { String name = stringFromCFString (cookieName); if (CookieName.equals (name)) { long 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[] result = new long[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[] storage = new long[1]; hr = cookieManager.cookieStorage (storage); cookieManager.Release (); if (hr != COM.S_OK || storage[0] == 0) { return; } char[] chars = CookieUrl.toCharArray (); long string = WebKit_win32.CFStringCreateWithCharacters (0, chars, chars.length); if (string != 0) { long cfUrl = WebKit_win32.CFURLCreateWithString (0, string, 0); if (cfUrl != 0) { chars = CookieValue.toCharArray (); long value = WebKit_win32.CFStringCreateWithCharacters (0, chars, chars.length); if (value != 0) { chars = HEADER_SETCOOKIE.toCharArray (); long key = WebKit_win32.CFStringCreateWithCharacters (0, chars, chars.length); if (key != 0) { long headers = WebKit_win32.CFDictionaryCreate (0, new long[] {key}, new long[] {value}, 1, WebKit_win32.kCFCopyStringDictionaryKeyCallBacks (), WebKit_win32.kCFTypeDictionaryValueCallBacks ()); if (headers != 0) { long cookies = WebKit_win32.CFHTTPCookieCreateWithResponseHeaderFields (0, headers, cfUrl); if (cookies != 0) { long 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 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 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 webView) { if (webView == 0) return null; IWebView iwebView = new IWebView (webView); long[] result = new long[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 JSObjectCallAsFunctionProc (long ctx, long function, long thisObject, long argumentCount, long arguments, long exception) { WebKit_win32.JSGlobalContextRetain (ctx); if (WebKit_win32.JSValueIsObjectOfClass (ctx, thisObject, ExternalClass) == 0) { return WebKit_win32.JSValueMakeUndefined (ctx); } long ptr = WebKit_win32.JSObjectGetPrivate (thisObject); long[] handle = new long[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 JSObjectGetPropertyProc (long ctx, long object, long propertyName, long exception) { byte[] bytes = (FUNCTIONNAME_CALLJAVA + '\0').getBytes (StandardCharsets.UTF_8); long name = WebKit_win32.JSStringCreateWithUTF8CString (bytes); long addr = WebKit_win32.JSObjectCallAsFunctionProc_CALLBACK (WebKit.JSObjectCallAsFunctionProc.getAddress ()); long function = WebKit_win32.JSObjectMakeFunctionWithCallback (ctx, name, addr); WebKit_win32.JSStringRelease (name); return function; } static long JSObjectHasPropertyProc (long ctx, long object, long propertyName) { byte[] bytes = (FUNCTIONNAME_CALLJAVA + '\0').getBytes (StandardCharsets.UTF_8); return WebKit_win32.JSStringIsEqualToUTF8CString (propertyName, bytes); } static String readInstallDir (String keyString) { long[] phkResult = new long[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 cfString) { if (cfString == 0) return null; int length = WebKit_win32.CFStringGetLength (cfString); long 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 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 callJava (long ctx, long func, long thisObject, long argumentCount, long arguments, long exception) { Object returnValue = null; if (argumentCount == 3) { long[] result = new long[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) { 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 (index); 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 ctx, long value) { int type = WebKit_win32.JSValueGetType (ctx, value); switch (type) { case WebKit_win32.kJSTypeBoolean: { int result = (int)WebKit_win32.JSValueToNumber (ctx, value, null); return result != 0; } case WebKit_win32.kJSTypeNumber: { double result = WebKit_win32.JSValueToNumber (ctx, value, null); return result; } case WebKit_win32.kJSTypeString: { long string = WebKit_win32.JSValueToStringCopy (ctx, value, null); if (string == 0) return ""; //$NON-NLS-1$ long length = WebKit_win32.JSStringGetMaximumUTF8CStringSize (string); byte[] bytes = new byte[(int)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)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 propertyName = WebKit_win32.JSStringCreateWithUTF8CString (bytes); long 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 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 convertToJS (long 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 stringRef = WebKit_win32.JSStringCreateWithUTF8CString (bytes); long 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[] arguments = new long[length]; for (int i = 0; i < length; i++) { Object javaObject = arrayValue[i]; long 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 addr = WebKit_win32.JSObjectHasPropertyProc_CALLBACK (JSObjectHasPropertyProc.getAddress ()); jsClassDefinition.hasProperty = addr; addr = WebKit_win32.JSObjectGetPropertyProc_CALLBACK (JSObjectGetPropertyProc.getAddress ()); jsClassDefinition.getProperty = addr; long classDefinitionPtr = C.malloc (JSClassDefinition.sizeof); WebKit_win32.memmove (classDefinitionPtr, jsClassDefinition, JSClassDefinition.sizeof); ExternalClass = WebKit_win32.JSClassCreate (classDefinitionPtr); WebKit_win32.JSClassRetain (ExternalClass); } long[] result = new long[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[] {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[] result = new long[1]; int hr = webView.mainFrame (result); if (hr != COM.S_OK || result[0] == 0) { return false; } IWebFrame frame = new IWebFrame (result[0]); long context = frame.globalContext (); frame.Release (); if (context == 0) { return false; } byte[] bytes = (script + '\0').getBytes (StandardCharsets.UTF_8); //$NON-NLS-1$ long scriptString = WebKit_win32.JSStringCreateWithUTF8CString (bytes); if (scriptString == 0) return false; bytes = (getUrl () + '\0').getBytes (StandardCharsets.UTF_8); //$NON-NLS-1$ long urlString = WebKit_win32.JSStringCreateWithUTF8CString (bytes); if (urlString == 0) { WebKit_win32.JSStringRelease (scriptString); return false; } long 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[] result = new long[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[] address = new long[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 hwndFocus = OS.GetFocus (); return hwndFocus != 0 && hwndFocus == webViewWindowHandle; } @Override public boolean isForwardEnabled () { long[] address = new long[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 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[] result = new long[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[] result = new long[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 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[] result = new long[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 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 data = WebKit_win32.CFDataCreate(0, bytes, bytes.length); // if (data != 0)WebKit_win32.CFURLRequestSetHTTPRequestBody(cfRequest, data); // // long dataGet = WebKit_win32.CFURLRequestCopyHTTPRequestBody(cfRequest); // int length = WebKit_win32.CFDataGetLength(dataGet); // long 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 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 keyString = createBSTR (key); hr = request.setValue (valueString, keyString); COM.SysFreeString (keyString); } COM.SysFreeString (valueString); } } } } } if (hr == COM.S_OK) { long 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[] address = new long[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[] result = new long[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[] result = new long[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 (); } } }