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; } } }