java/com/jetbrains/cef/remote/browser/RemoteClient.java (346 lines of code) (raw):
package com.jetbrains.cef.remote.browser;
import com.jetbrains.cef.remote.CefServer;
import com.jetbrains.cef.remote.MultiHandler;
import com.jetbrains.cef.remote.RpcContext;
import com.jetbrains.cef.remote.network.RemoteRequestContext;
import com.jetbrains.cef.remote.router.RemoteMessageRouter;
import org.cef.CefBrowserSettings;
import org.cef.CefClient;
import org.cef.browser.CefBrowser;
import org.cef.browser.CefMessageRouter;
import org.cef.browser.CefRendering;
import org.cef.browser.CefRequestContext;
import org.cef.handler.*;
import org.cef.misc.CefLog;
import java.awt.*;
import java.util.*;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Supplier;
public class RemoteClient {
private static Supplier<CefRendering> ourDefaultRenderingRactory;
private int myCid = -1;
private final RpcContext myRpc;
private final Map<Integer, RemoteBrowser> myNativeIdentifier2Browser = new ConcurrentHashMap<>();
private final List<RemoteBrowser> myBrowsers = Collections.synchronizedList(new ArrayList<>());
private CefContextMenuHandler contextMenuHandler_ = null;
private CefDialogHandler dialogHandler_ = null;
private CefDisplayHandler displayHandler_ = null;
private CefDownloadHandler downloadHandler_ = null;
private CefDragHandler dragHandler_ = null;
private CefFocusHandler focusHandler_ = null;
private CefPermissionHandler permissionHandler_ = null;
private CefJSDialogHandler jsDialogHandler_ = null;
private CefKeyboardHandler keyboardHandler_ = null;
private CefPrintHandler printHandler_ = null;
private CefRequestHandler requestHandler_ = null;
private CefLoadHandler loadHandler_ = null;
private final MultiHandler<CefLifeSpanHandler> hLifeSpan = new MultiHandler<>(); // always presented
private int myHandlersMask = 0;
// MessageRouter support
private Vector<RemoteMessageRouter> msgRouters = new Vector<>();
public RemoteClient(RpcContext rpcContext) {
myRpc = rpcContext;
}
public CefServer getServer() { return myRpc.server; }
public MultiHandler<CefLifeSpanHandler> getLifeSpanHandler() { return hLifeSpan; }
// Called from lifespan handler when native browser is created on server side.
public void onAfterCreated(RemoteBrowser browser, int nativeBrowserIdentifier) {
browser.setNativeBrowserCreated(nativeBrowserIdentifier);
myNativeIdentifier2Browser.put(nativeBrowserIdentifier, browser);
hLifeSpan.handle(lsh->lsh.onAfterCreated(browser));
CefLog.Debug("Browser %s was created (native server-side part).", browser);
}
// Called from lifespan handler when native browser is disposed on server side.
public void onBeforeClosed(RemoteBrowser browser) {
hLifeSpan.handle(lsh->lsh.onBeforeClose(browser));
if (!myBrowsers.remove(browser))
CefLog.Debug("Browser %s already was removed.", browser);
if (myNativeIdentifier2Browser.remove(browser.getNativeBrowserIdentifier()) == null)
CefLog.Error("Browser with native id %d already was removed.", browser.getNativeBrowserIdentifier());
browser.onBeforeClose();
CefLog.Debug("Browser %s was closed (native server-side part).", browser);
}
protected void requestCid() {
if (myCid != -1)
return;
final int hmask = myHandlersMask | RemoteClient.HandlerMasks.NativeRender.val(); // just for simplicity (we are not going to use DummyRenderHandler now)
myRpc.exec((s) -> {
myCid = s.Client_Create(hmask);
});
myRpc.server.cid2Client.put(myCid, this);
CefLog.Debug("Registered RemoteClient with cid=%d with handlers: %s", myCid, RemoteClient.HandlerMasks.toString(hmask));
}
//
// Handler getters.
//
public CefContextMenuHandler getContextMenuHandler() {
return contextMenuHandler_;
}
public CefDialogHandler getDialogHandler() {
return dialogHandler_;
}
public CefDisplayHandler getDisplayHandler() {
return displayHandler_;
}
public CefDownloadHandler getDownloadHandler() {
return downloadHandler_;
}
public CefDragHandler getDragHandler() {
return dragHandler_;
}
public CefFocusHandler getFocusHandler() {
return focusHandler_;
}
public CefPermissionHandler getPermissionHandler() {
return permissionHandler_;
}
public CefJSDialogHandler getJSDialogHandler() {
return jsDialogHandler_;
}
public CefKeyboardHandler getKeyboardHandler() {
return keyboardHandler_;
}
public CefPrintHandler getPrintHandler() {
return printHandler_;
}
public CefRequestHandler getRequestHandler() {
return requestHandler_;
}
public CefLoadHandler getLoadHandler() { return loadHandler_; }
//
// Public API
//
public RemoteBrowser getRemoteBrowser(int nativeIdentifier) { return myNativeIdentifier2Browser.get(nativeIdentifier); }
public RemoteBrowser[] getAllBrowsers() {
// returns only active browsers (with created native part and not closed)
return myNativeIdentifier2Browser.values().toArray(new RemoteBrowser[]{});
}
public int getCid() { return myCid; }
public int getHandlersMask() { return myHandlersMask; }
//
// CefClient
//
public RemoteBrowser createBrowser(String url, CefRequestContext context, CefClient client, CefNativeRenderHandler renderHandler, Component component, CefBrowserSettings settings) {
RemoteRequestContext ctx = null;
if (context instanceof RemoteRequestContext)
ctx = (RemoteRequestContext)context;
else if (context != null)
CefLog.Error("Unsupported class %s, will be used default (global) request context. Please use RemoteRequestContext.", context.getClass());
RemoteBrowser browser = new RemoteBrowser(myRpc, this, client, url, ctx, settings);
browser.setComponent(component, renderHandler);
myBrowsers.add(browser);
return browser;
}
public RemoteBrowser createBrowser(String url, CefRequestContext context, CefClient client, CefRendering rendering, CefBrowserSettings settings) {
String errDesc = null;
if (rendering instanceof CefRendering.CefRenderingWithHandler) {
CefRendering.CefRenderingWithHandler rh = (CefRendering.CefRenderingWithHandler) rendering;
if (rh.getRenderHandler() instanceof CefNativeRenderHandler) {
return createBrowser(url, context, client, (CefNativeRenderHandler)rh.getRenderHandler(), rh.getComponent(), settings);
}
errDesc = "Can't create remote browser with render-handler: " + rh.getRenderHandler() + ", please implement CefNativeRenderHandler interface.";
} else
errDesc = "Can't create remote browser with rendering: " + rendering;
CefLog.Error(errDesc);
if (ourDefaultRenderingRactory != null) {
CefLog.Warn("Default rendering factory will be used.");
return createBrowser(url, context, client, ourDefaultRenderingRactory, settings);
}
throw new IllegalStateException(errDesc);
}
public RemoteBrowser createBrowser(String url, CefRequestContext context, CefClient client, Supplier<CefRendering> renderingFactory, CefBrowserSettings settings) {
RemoteBrowser browser = createBrowser(url, context, client, renderingFactory.get(), settings);
return browser;
}
public static void setDefaultRenderingFactory(Supplier<CefRendering> factory) {
ourDefaultRenderingRactory = factory;
}
// Handlers management
private void _updateMask(Object handler, int handlerMask) {
if (handler != null) {
myHandlersMask |= handlerMask;
if (myCid != -1)
myRpc.exec((s) -> { s.Client_AddHandlers(myCid, handlerMask);});
} else {
myHandlersMask &= ~handlerMask;
if (myCid != -1)
myRpc.exec((s) -> { s.Client_RemoveHandlers(myCid, handlerMask);});
}
}
public void addLifeSpanHandler(CefLifeSpanHandler handler) {
hLifeSpan.addHandler(handler);
}
public void removeAllLifeSpanHandlers() {
hLifeSpan.removeAllHandlers();
}
public void addLoadHandler(CefLoadHandler loadHandler) {
if (loadHandler_ != null && !Objects.equals(loadHandler_, loadHandler))
CefLog.Warn("loadHandler_ will be replaced.");
loadHandler_ = loadHandler;
_updateMask(loadHandler_, HandlerMasks.Load.val());
}
public void removeLoadHandler() {
loadHandler_ = null;
_updateMask(loadHandler_, HandlerMasks.Load.val());
}
public void addDisplayHandler(CefDisplayHandler displayHandler) {
if (displayHandler_ != null && !Objects.equals(displayHandler_, displayHandler))
CefLog.Warn("displayHandler_ will be replaced.");
displayHandler_ = displayHandler;
_updateMask(displayHandler_, HandlerMasks.Display.val());
}
public void removeDisplayHandler() {
displayHandler_ = null;
_updateMask(displayHandler_, HandlerMasks.Display.val());
}
public void addRequestHandler(CefRequestHandler requestHandler) {
if (requestHandler_ != null && !Objects.equals(requestHandler_, requestHandler))
CefLog.Warn("requestHandler_ will be replaced.");
requestHandler_ = requestHandler;
_updateMask(requestHandler_, HandlerMasks.Request.val());
}
public void removeRequestHandler() {
requestHandler_ = null;
_updateMask(requestHandler_, HandlerMasks.Request.val());
}
public void addContextMenuHandler(CefContextMenuHandler handler) {
if (contextMenuHandler_ != null && !Objects.equals(contextMenuHandler_, handler))
CefLog.Warn("contextMenuHandler_ will be replaced.");
contextMenuHandler_ = handler;
_updateMask(contextMenuHandler_, HandlerMasks.ContextMenu.val());
}
public void removeContextMenuHandler() {
contextMenuHandler_ = null;
_updateMask(contextMenuHandler_, HandlerMasks.ContextMenu.val());
}
public void addDialogHandler(CefDialogHandler handler) {
if (dialogHandler_ != null && !Objects.equals(dialogHandler_, handler))
CefLog.Warn("dialogHandler_ will be replaced.");
dialogHandler_ = handler;
_updateMask(dialogHandler_, HandlerMasks.Dialog.val());
}
public void removeDialogHandler() {
dialogHandler_ = null;
_updateMask(dialogHandler_, HandlerMasks.Dialog.val());
}
public void addDownloadHandler(CefDownloadHandler handler) {
if (downloadHandler_ != null && !Objects.equals(downloadHandler_, handler))
CefLog.Warn("downloadHandler_ will be replaced.");
downloadHandler_ = handler;
_updateMask(downloadHandler_, HandlerMasks.Download.val());
}
public void removeDownloadHandler() {
downloadHandler_ = null;
_updateMask(downloadHandler_, HandlerMasks.Download.val());
}
public void addDragHandler(CefDragHandler handler) {
if (dragHandler_ != null && !Objects.equals(dragHandler_, handler))
CefLog.Warn("dragHandler_ will be replaced.");
dragHandler_ = handler;
_updateMask(dragHandler_, HandlerMasks.Drag.val());
}
public void removeDragHandler() {
dragHandler_ = null;
_updateMask(dragHandler_, HandlerMasks.Drag.val());
}
public void addFocusHandler(CefFocusHandler handler) {
if (focusHandler_ != null && !Objects.equals(focusHandler_, handler))
CefLog.Warn("focusHandler_ will be replaced.");
focusHandler_ = handler;
_updateMask(focusHandler_, HandlerMasks.Focus.val());
}
public void removeFocusHandler() {
focusHandler_ = null;
_updateMask(focusHandler_, HandlerMasks.Focus.val());
}
public void addPermissionHandler(CefPermissionHandler handler) {
if (permissionHandler_ != null && !Objects.equals(permissionHandler_, handler))
CefLog.Warn("permissionHandler_ will be replaced.");
permissionHandler_ = handler;
_updateMask(permissionHandler_, HandlerMasks.Permission.val());
}
public void removePermissionHandler() {
permissionHandler_ = null;
_updateMask(permissionHandler_, HandlerMasks.Permission.val());
}
public void addJSDialogHandler(CefJSDialogHandler handler) {
if (jsDialogHandler_ != null && !Objects.equals(jsDialogHandler_, handler))
CefLog.Warn("jsDialogHandler_ will be replaced.");
jsDialogHandler_ = handler;
_updateMask(jsDialogHandler_, HandlerMasks.JSDialog.val());
}
public void removeJSDialogHandler() {
jsDialogHandler_ = null;
_updateMask(jsDialogHandler_, HandlerMasks.JSDialog.val());
}
public void addKeyboardHandler(CefKeyboardHandler handler) {
if (keyboardHandler_ != null && !Objects.equals(keyboardHandler_, handler))
CefLog.Warn("keyboardHandler_ will be replaced.");
keyboardHandler_ = handler;
_updateMask(keyboardHandler_, HandlerMasks.Keyboard.val());
}
public void removeKeyboardHandler() {
keyboardHandler_ = null;
_updateMask(keyboardHandler_, HandlerMasks.Keyboard.val());
}
public void addPrintHandler(CefPrintHandler handler) {
if (printHandler_ != null && !Objects.equals(printHandler_, handler))
CefLog.Warn("printHandler_ will be replaced.");
printHandler_ = handler;
_updateMask(printHandler_, HandlerMasks.Print.val());
}
public void removePrintHandler() {
printHandler_ = null;
_updateMask(printHandler_, HandlerMasks.Print.val());
}
//
// CefMessageRouter
//
public void addMessageRouter(CefMessageRouter messageRouter) {
// NOTE: we create RemoteMessageRouter via static factory method and then configure it
// with java handlers (internally will remote wrappers over java objects). CefMessageRouter is used only to
// add/remove handlers. So we can't create remote wrapper over "java" CefMessageRouter here.
RemoteMessageRouter router = (RemoteMessageRouter)messageRouter;
msgRouters.add(router);
myRpc.server.onConnected(()->{
requestCid();
router.addToClient(myCid);
}, "addMessageRouter", false);
}
public void removeMessageRouter(CefMessageRouter messageRouter) {
RemoteMessageRouter router = (RemoteMessageRouter)messageRouter;
msgRouters.remove(router);
myRpc.server.onConnected(()->{
requestCid();
router.removeFromClient(myCid);
}, "removeMessageRouter", false);
}
public void dispose() {
CefLog.Debug("RemoteClient: dispose cid=%d", myCid);
// 1. Cleanup routers
// NOTE: CefClientHandler implementation disposes all managed routers. But it seems to be
// incorrect: router is created outside of client and one router can be used by several clients. So it's better
// to dispose router as usual (inside finalize or manually (via dispose)) => just clean list here.
msgRouters.clear();
// 2. Close browsers
myBrowsers.forEach(rb -> {
if (rb != null && !rb.isClosed())
rb.close(true);
});
myBrowsers.clear();
if (myCid != -1) {
myRpc.invokeLater((s) -> {
s.Client_Dispose(myCid);
});
myCid = -1;
myRpc.server.cid2Client.remove(myCid);
}
}
public String toString() {
return "RemoteClient_" + myCid;
}
enum HandlerMasks {
Request(1 << 0),
NativeRender(1 << 1),
Load(1 << 2),
ContextMenu(1 << 4),
Dialog(1 << 5),
Display(1 << 6),
Focus(1 << 7),
Permission(1 << 8),
JSDialog(1 << 9),
Keyboard(1 << 10),
Print(1 << 11),
Download(1 << 12),
Drag(1 << 13);
private final int maskVal;
HandlerMasks(int maskVal) { this.maskVal = maskVal; }
int val() { return maskVal; }
static String toString(int mask) {
String result = "Lifespan";
for (HandlerMasks m : HandlerMasks.values()) {
if ((m.val() & mask) == 0)
continue;
result += ", " + m.name();
}
return result;
}
}
}