io.webfolder.ui4j.webkit.WebKitIsolatedCookieHandler Maven / Gradle / Ivy
package io.webfolder.ui4j.webkit;
import static java.util.Collections.synchronizedMap;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;
import java.net.CookieHandler;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import javafx.scene.web.WebView;
import com.sun.webkit.WebPage;
import com.sun.webkit.WebPageClient;
import com.sun.webkit.network.CookieManager;
/**
* Custom CookieHandler that help to isolate CookieStore per WebView.
*
* Warning! This class is not ready to use for the production environments.
* Implementation is tested for the server side cookies but broken for the client side cookies.
*/
public class WebKitIsolatedCookieHandler extends CookieHandler {
private static final Map threadWebViewMappings =
synchronizedMap(new WeakHashMap<>());
private static final Map cookieManagers =
synchronizedMap(new WeakHashMap<>());
static {
Class> klassNetworkContext = null;
Class> klassUrlLoaderFactory = null;
try {
klassNetworkContext = WebKitIsolatedCookieHandler.class.getClassLoader().loadClass("com.sun.webkit.network.NetworkContext");
klassUrlLoaderFactory = WebKitIsolatedCookieHandler.class.getClassLoader().loadClass("com.sun.webkit.network.NetworkContext$URLLoaderThreadFactory");
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
Constructor> constructor;
ThreadFactory urlThreadFactory = null;
try {
constructor = klassUrlLoaderFactory.getDeclaredConstructor();
constructor.setAccessible(true);
urlThreadFactory = (ThreadFactory) constructor.newInstance();
} catch (NoSuchMethodException | SecurityException | InstantiationException
| IllegalAccessException | IllegalArgumentException
| InvocationTargetException e) {
throw new RuntimeException(e);
}
try {
Field fieldPool = klassNetworkContext.getDeclaredField("threadPool");
fieldPool.setAccessible(true);
Field keepAlive = klassNetworkContext.getDeclaredField("THREAD_POOL_KEEP_ALIVE_TIME");
keepAlive.setAccessible(true);
Field poolSize = klassNetworkContext.getDeclaredField("THREAD_POOL_SIZE");
poolSize.setAccessible(true);
int THREAD_POOL_SIZE = poolSize.getInt(null);
long THREAD_POOL_KEEP_ALIVE_TIME = keepAlive.getLong(null);
WebKitThreadPoolExecutor threadPool = new WebKitThreadPoolExecutor(
THREAD_POOL_SIZE,
THREAD_POOL_SIZE,
THREAD_POOL_KEEP_ALIVE_TIME,
TimeUnit.MILLISECONDS,
new LinkedBlockingQueue(),
urlThreadFactory);
threadPool.allowCoreThreadTimeOut(true);
setFinalStatic(fieldPool, threadPool);
} catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) {
throw new RuntimeException(e);
}
}
private static class WebKitThreadPoolExecutor extends ThreadPoolExecutor {
public WebKitThreadPoolExecutor(int corePoolSize, int maximumPoolSize,
long keepAliveTime, TimeUnit unit,
BlockingQueue workQueue, ThreadFactory threadFactory) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
threadFactory);
}
@Override
protected void beforeExecute(Thread t, Runnable r) {
WebPage webPage = null;
try {
Field fieldCallable = r.getClass().getDeclaredField("callable");
fieldCallable.setAccessible(true);
Object adapter = fieldCallable.get(r);
Field fieldTask = adapter.getClass().getDeclaredField("task");
fieldTask.setAccessible(true);
Object urlLoader = fieldTask.get(adapter);
Field fieldWebPage = urlLoader.getClass().getDeclaredField("webPage");
fieldWebPage.setAccessible(true);
webPage = (WebPage) fieldWebPage.get(urlLoader);
} catch (NoSuchFieldException | SecurityException |
IllegalArgumentException | IllegalAccessException e) {
throw new RuntimeException(e);
}
try {
} catch (IllegalArgumentException e) {
throw new RuntimeException(e);
}
@SuppressWarnings("unchecked")
WebPageClient client = webPage.getPageClient();
WebView webView = client.getContainer();
threadWebViewMappings.put(t.getId(), webView);
}
}
@Override
public Map> get(URI uri, Map> requestHeaders) throws IOException {
long id = Thread.currentThread().getId();
WebView webView = threadWebViewMappings.get(id);
CookieManager cookieManager = cookieManagers.get(webView);
if (cookieManager == null) {
cookieManager = new CookieManager();
cookieManagers.put(webView, cookieManager);
}
return cookieManager.get(uri, requestHeaders);
}
@Override
public void put(URI uri, Map> responseHeaders) throws IOException {
long id = Thread.currentThread().getId();
WebView webView = threadWebViewMappings.get(id);
CookieManager cookieManager = cookieManagers.get(webView);
if (cookieManager == null) {
cookieManager = new CookieManager();
cookieManagers.put(webView, cookieManager);
}
cookieManager.put(uri, responseHeaders);
}
private static void setFinalStatic(Field field, Object newValue) {
try {
field.setAccessible(true);
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
field.set(null, newValue);
} catch (NoSuchFieldException | SecurityException |
IllegalArgumentException | IllegalAccessException e) {
throw new RuntimeException(e);
}
}
public void clear() {
threadWebViewMappings.clear();
cookieManagers.clear();
}
public void remove(WebView webView) {
cookieManagers.remove(webView);
List list = new ArrayList<>();
for (Map.Entry entry : threadWebViewMappings.entrySet()) {
if (entry.getValue().equals(webView)) {
list.add(entry.getKey());
}
}
list.forEach(id -> threadWebViewMappings.remove(id));
}
}