From af86838d786073db07d19a0bd3db95187becb1b4 Mon Sep 17 00:00:00 2001 From: Vladimir Kharitonov Date: Thu, 15 Jan 2026 15:42:23 +0100 Subject: [PATCH] IJPL-224692 [jcef] support JCEF a standalone bundle GitOrigin-RevId: 9d42cd3b2e920e1c07f1891d2cdd39c0bdc280b7 --- .../src/META-INF/PlatformExtensionPoints.xml | 2 + .../src/META-INF/PlatformExtensions.xml | 1 + platform/ui.jcef/api-dump.txt | 3 + platform/ui.jcef/jcef/JBCefApp.java | 105 ++++++++++-------- platform/ui.jcef/jcef/JBCefBrowserBase.java | 2 +- .../ui.jcef/jcef/JBCefBrowserBuilder.java | 5 +- .../JBCefDefaultNativeBundleProvider.java | 20 ++++ .../jcef/JBCefNativeBundleProvider.java | 12 ++ .../ui.jcef/jcef/JBCefOSRHandlerFactory.java | 2 +- 9 files changed, 104 insertions(+), 48 deletions(-) create mode 100644 platform/ui.jcef/jcef/JBCefDefaultNativeBundleProvider.java create mode 100644 platform/ui.jcef/jcef/JBCefNativeBundleProvider.java diff --git a/platform/platform-resources/src/META-INF/PlatformExtensionPoints.xml b/platform/platform-resources/src/META-INF/PlatformExtensionPoints.xml index e6f3e0373478..aa73823ae846 100644 --- a/platform/platform-resources/src/META-INF/PlatformExtensionPoints.xml +++ b/platform/platform-resources/src/META-INF/PlatformExtensionPoints.xml @@ -621,6 +621,8 @@ + + diff --git a/platform/platform-resources/src/META-INF/PlatformExtensions.xml b/platform/platform-resources/src/META-INF/PlatformExtensions.xml index cbee66afdbb7..af3591c96f8d 100644 --- a/platform/platform-resources/src/META-INF/PlatformExtensions.xml +++ b/platform/platform-resources/src/META-INF/PlatformExtensions.xml @@ -221,6 +221,7 @@ + diff --git a/platform/ui.jcef/api-dump.txt b/platform/ui.jcef/api-dump.txt index d2ee5be6ea31..9d0abcbe0c3c 100644 --- a/platform/ui.jcef/api-dump.txt +++ b/platform/ui.jcef/api-dump.txt @@ -4,6 +4,9 @@ a:com.intellij.ui.jcef.JBCefBrowserBase com.intellij.ui.jcef.JBCefBrowserBase$ErrorPage - sf:DEFAULT:com.intellij.ui.jcef.JBCefBrowserBase$ErrorPage - a:create(org.cef.handler.CefLoadHandler$ErrorCode,java.lang.String,java.lang.String):java.lang.String +c:com.intellij.ui.jcef.JBCefDefaultNativeBundleProvider +- getNativeBundlePath():java.lang.String +- isAvailable():Z com.intellij.ui.jcef.JBCefOSRHandlerFactory - sf:DEFAULT:com.intellij.ui.jcef.JBCefOSRHandlerFactory - createCefRenderHandler(javax.swing.JComponent):org.cef.handler.CefRenderHandler diff --git a/platform/ui.jcef/jcef/JBCefApp.java b/platform/ui.jcef/jcef/JBCefApp.java index f9b775e1f48f..55bac427b3d6 100644 --- a/platform/ui.jcef/jcef/JBCefApp.java +++ b/platform/ui.jcef/jcef/JBCefApp.java @@ -21,9 +21,7 @@ import com.intellij.ui.scale.ScaleContext; import com.intellij.util.ArrayUtil; import com.jetbrains.cef.JCefAppConfig; import com.jetbrains.cef.JCefVersionDetails; -import org.cef.CefApp; -import org.cef.CefClient; -import org.cef.CefSettings; +import org.cef.*; import org.cef.browser.CefMessageRouter; import org.cef.browser.CefRendering; import org.cef.callback.CefSchemeHandlerFactory; @@ -32,12 +30,12 @@ import org.cef.handler.CefAppHandlerAdapter; import org.cef.handler.CefRenderHandler; import org.cef.misc.BoolRef; import org.cef.misc.CefLog; -import org.cef.misc.Utils; import org.jdom.IllegalDataException; import org.jetbrains.annotations.*; import javax.swing.*; import java.awt.GraphicsEnvironment; +import java.io.File; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; @@ -67,7 +65,6 @@ public final class JBCefApp { private static final Logger LOG = Logger.getInstance(JBCefApp.class); private static final boolean SKIP_VERSION_CHECK = Boolean.getBoolean("ide.browser.jcef.skip_version_check"); private static final boolean SKIP_MODULE_CHECK = Boolean.getBoolean("ide.browser.jcef.skip_module_check"); - private static final boolean IS_REMOTE_ENABLED; private static final String REGISTRY_REMOTE_KEY = "ide.browser.jcef.out-of-process.enabled"; private static final int MIN_SUPPORTED_CEF_MAJOR_VERSION = 119; @@ -83,6 +80,8 @@ public final class JBCefApp { private String @Nullable [] myCefArgs; private final @Nullable CefSettings myCefSettings; private final @NotNull CompletableFuture myDebuggingPort = new CompletableFuture<>(); + private final boolean myIsRemoteEnabled; + private final @Nullable File myServerExe; private final @NotNull Disposable myDisposable = new Disposable() { @Override @@ -126,34 +125,41 @@ public final class JBCefApp { System.setProperty(PROPERTY_NAME, "true"); } } - - IS_REMOTE_ENABLED = CefApp.isRemoteEnabled(); - - if (IS_REMOTE_ENABLED) { - final Supplier defaultRenderingFactory = () -> { - JBCefOSRHandlerFactory osrHandlerFactory = JBCefOSRHandlerFactory.getInstance(); - JComponent component = osrHandlerFactory.createComponent(true); - CefRenderHandler handler = osrHandlerFactory.createCefRenderHandler(component); - return new CefRendering.CefRenderingWithHandler(handler, component); - }; - CefApp.setDefaultRenderingFactory(defaultRenderingFactory); - } } private JBCefApp(@NotNull JCefAppConfig config) throws IllegalStateException { myDelegate = getActiveDelegate(); + myIsRemoteEnabled = myDelegate == null && config.isRemoteEnabled(); + myServerExe = config.getServerExe(); + SystemBootstrap.setLoader(config.getLoader()); + if (myDelegate != null) { myCefSettings = null; myCefApp = null; myDebuggingPort.completeExceptionally(new UnsupportedOperationException()); } else { + CefApp.setIsRemoteEnabled(myIsRemoteEnabled); + if (myIsRemoteEnabled) { + final Supplier defaultRenderingFactory = () -> { + JBCefOSRHandlerFactory osrHandlerFactory = JBCefOSRHandlerFactory.getInstance(); + JComponent component = osrHandlerFactory.createComponent(true); + CefRenderHandler handler = osrHandlerFactory.createCefRenderHandler(component); + return new CefRendering.CefRenderingWithHandler(handler, component); + }; + CefApp.setDefaultRenderingFactory(defaultRenderingFactory); + } + CefSettings settings = Cancellation.forceNonCancellableSectionInClassInitializer(() -> SettingsHelper.loadSettings(config)); final String logPath = SettingsHelper.getLogPath(); CefLog.init(logPath, settings.log_severity); JBCefHealthMonitor.getInstance().performHealthCheckAsync(settings, () -> { - CefApp.startup(ArrayUtil.EMPTY_STRING_ARRAY); + if (OS.isMacintosh() && config.getCefFrameworkPathOSX() != null) { + CefApp.startupAsync(config.getCefFrameworkPathOSX()); + } else { + CefApp.startup(ArrayUtil.EMPTY_STRING_ARRAY); + } }); BoolRef trackGPUCrashes = new BoolRef(false); @@ -171,7 +177,7 @@ public final class JBCefApp { myCefArgs = args; CefApp.addAppHandler(new MyCefAppHandler(args, trackGPUCrashes.get())); myCefSettings = settings; - myCefApp = CefApp.getInstance(settings); + myCefApp = CefApp.getInstance(null, settings, myServerExe); CEFAPP_INSTANCE_COUNT.intValue(); if (myCefSettings.remote_debugging_port > 0) { @@ -188,7 +194,7 @@ public final class JBCefApp { }); } - if (IS_REMOTE_ENABLED) { + if (myIsRemoteEnabled) { StartupTest.checkBrowserCreation(myCefApp, () -> restartJCEF(true, true)); if (ApplicationManager.getApplication().isInternal()) { //noinspection UnresolvedPluginConfigReference @@ -215,7 +221,7 @@ public final class JBCefApp { } private boolean restartJCEF(boolean withVerboseLogging, boolean withNewCachePath) { - if (!IS_REMOTE_ENABLED) { + if (!myIsRemoteEnabled) { return false; } @@ -242,7 +248,7 @@ public final class JBCefApp { } CefApp.addAppHandler(new MyCefAppHandler(myCefArgs, true)); - final CefApp newInstance = CefApp.getInstance(myCefArgs, myCefSettings); + final CefApp newInstance = CefApp.getInstance(myCefArgs, myCefSettings, myServerExe); if (newInstance == null) { LOG.error("JCEF wasn't restarted (new instance is null)."); return false; @@ -307,7 +313,12 @@ public final class JBCefApp { if (!JreHiDpiUtil.isJreHiDPIEnabled()) { System.setProperty("jcef.forceDeviceScaleFactor", String.valueOf(getForceDeviceScaleFactor())); } - config = JCefAppConfig.getInstance(); + String nativeBundlePath = getNativeBundlePath(); + if (nativeBundlePath != null && !isJcefFromJbr()) { + config = JCefAppConfig.getInstance(nativeBundlePath); + } else { + config = JCefAppConfig.getInstance(); + } } catch (Exception e) { LOG.error(e); @@ -395,28 +406,17 @@ public final class JBCefApp { } } - final String altFramework = Utils.getString("ALT_CEF_FRAMEWORK_DIR"); - final String altPipe = Utils.getString("ALT_CEF_SERVER_PIPE"); - final String altPort = Utils.getString("ALT_CEF_SERVER_PORT"); - final boolean isAltCefPathUsed = (altFramework != null && !altFramework.isEmpty()) - || (altPipe != null && !altPipe.isEmpty()) - || (altPort != null && !altPort.isEmpty()); + return isJcefFromJbr() || getNativeBundlePath() != null; + } - final boolean skipModuleCheck = isAltCefPathUsed || SKIP_MODULE_CHECK; - if (!skipModuleCheck) { - URL url = JCefAppConfig.class.getResource("JCefAppConfig.class"); - if (url == null) { - return unsupported.apply("JCefAppConfig.class not found"); - } - String path = url.toString(); - String name = JCefAppConfig.class.getName().replace('.', '/'); - boolean isJbrModule = path != null && path.contains("/jcef/" + name); - if (!isJbrModule) { - return unsupported.apply("JCefAppConfig.class is not from a JBR module, url: " + path); - } + private static boolean isJcefFromJbr() { + URL url = JCefAppConfig.class.getResource("JCefAppConfig.class"); + if (url == null) { + LOG.error("JCefAppConfig.class not found"); + return false; } - return true; + return url.getProtocol().equals("jrt"); } /** @@ -638,8 +638,8 @@ public final class JBCefApp { return JreHiDpiUtil.isJreHiDPIEnabled() ? scaledSize : ROUND.round(scaledSize / getForceDeviceScaleFactor()); } - static boolean isRemoteEnabled() { - return IS_REMOTE_ENABLED; + boolean isRemoteEnabled() { + return myIsRemoteEnabled; } private static int readDebugPortFile(@NotNull Path filePath) throws IOException { @@ -666,6 +666,23 @@ public final class JBCefApp { return CefDelegate.EP.findFirstSafe(CefDelegate::isActive); } + private static @Nullable String getNativeBundlePath() { + // the native bundle provider is used only if there is no JCEF in JBR + if (isJcefFromJbr()) { + return null; + } + + @Nullable JBCefNativeBundleProvider provider = null; + if (!isJcefFromJbr()) { + provider = JBCefNativeBundleProvider.EP.findFirstSafe(JBCefNativeBundleProvider::isAvailable); + } + if (provider == null) { + return null; + } + + return provider.getNativeBundlePath(); + } + private static boolean isLinuxLibcSupported() { String libcVersionString; try { diff --git a/platform/ui.jcef/jcef/JBCefBrowserBase.java b/platform/ui.jcef/jcef/JBCefBrowserBase.java index 390adda69b4a..ae56333d1a0a 100644 --- a/platform/ui.jcef/jcef/JBCefBrowserBase.java +++ b/platform/ui.jcef/jcef/JBCefBrowserBase.java @@ -900,7 +900,7 @@ public abstract class JBCefBrowserBase implements JBCefDisposable { boolean isMouseWheelEventEnabled, CefBrowserSettings settings) { final String validUrl = ObjectUtils.notNull(url, ""); - if (JBCefApp.isRemoteEnabled()) { + if (JBCefApp.getInstance().isRemoteEnabled()) { Supplier renderingSupplier = () -> createCefRenderingWithHandler(osrHandlerFactory, isMouseWheelEventEnabled); CefBrowser browser = client.createBrowser(validUrl, renderingSupplier, true, context, settings); diff --git a/platform/ui.jcef/jcef/JBCefBrowserBuilder.java b/platform/ui.jcef/jcef/JBCefBrowserBuilder.java index 302ef12d3bc1..957744be1fc6 100644 --- a/platform/ui.jcef/jcef/JBCefBrowserBuilder.java +++ b/platform/ui.jcef/jcef/JBCefBrowserBuilder.java @@ -18,7 +18,8 @@ public class JBCefBrowserBuilder { @Nullable String myUrl; @Nullable CefBrowser myCefBrowser; @Nullable JBCefOSRHandlerFactory myOSRHandlerFactory; - boolean myIsOffScreenRendering = RegistryManager.getInstance().is("ide.browser.jcef.osr.enabled") || JBCefApp.isRemoteEnabled(); + boolean myIsOffScreenRendering = RegistryManager.getInstance().is("ide.browser.jcef.osr.enabled") || JBCefApp.getInstance() + .isRemoteEnabled(); boolean myCreateImmediately; boolean myEnableOpenDevToolsMenuItem; boolean myMouseWheelEventEnable = true; @@ -36,7 +37,7 @@ public class JBCefBrowserBuilder { */ public @NotNull JBCefBrowserBuilder setOffScreenRendering(boolean isOffScreenRendering) { if (!isOffScreenRendering) { - if (JBCefApp.isRemoteEnabled()) { + if (JBCefApp.getInstance().isRemoteEnabled()) { Logger.getInstance(JBCefBrowserBuilder.class).warn("Trying to create windowed browser when remote-mode is enabled. Settings isOffScreenRendering=false will be ignored."); myIsOffScreenRendering = true; return this; diff --git a/platform/ui.jcef/jcef/JBCefDefaultNativeBundleProvider.java b/platform/ui.jcef/jcef/JBCefDefaultNativeBundleProvider.java new file mode 100644 index 000000000000..65b32e01e1f8 --- /dev/null +++ b/platform/ui.jcef/jcef/JBCefDefaultNativeBundleProvider.java @@ -0,0 +1,20 @@ +// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package com.intellij.ui.jcef; + +// this extension point implementation is used for demoing and debugging JCEF from a standalone bundle +public class JBCefDefaultNativeBundleProvider implements JBCefNativeBundleProvider { + private final String path; + JBCefDefaultNativeBundleProvider() { + path = System.getProperty("jcef.native.bundle.path", null); + } + + @Override + public String getNativeBundlePath() { + return path; + } + + @Override + public boolean isAvailable() { + return path != null; + } +} diff --git a/platform/ui.jcef/jcef/JBCefNativeBundleProvider.java b/platform/ui.jcef/jcef/JBCefNativeBundleProvider.java new file mode 100644 index 000000000000..fe1f0e50dd25 --- /dev/null +++ b/platform/ui.jcef/jcef/JBCefNativeBundleProvider.java @@ -0,0 +1,12 @@ +// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package com.intellij.ui.jcef; + +import com.intellij.openapi.extensions.ExtensionPointName; +import org.jetbrains.annotations.ApiStatus; + +@ApiStatus.Internal +public interface JBCefNativeBundleProvider { + ExtensionPointName EP = ExtensionPointName.create("com.intellij.cefNativeBundleProvider"); + String getNativeBundlePath(); + boolean isAvailable(); +} diff --git a/platform/ui.jcef/jcef/JBCefOSRHandlerFactory.java b/platform/ui.jcef/jcef/JBCefOSRHandlerFactory.java index 6972828fd5ef..87bd27810340 100644 --- a/platform/ui.jcef/jcef/JBCefOSRHandlerFactory.java +++ b/platform/ui.jcef/jcef/JBCefOSRHandlerFactory.java @@ -43,7 +43,7 @@ public interface JBCefOSRHandlerFactory { assert component instanceof JBCefOsrComponent; JBCefOsrComponent osrComponent = (JBCefOsrComponent)component; var screenBoundsProvider = createScreenBoundsProvider(); - JBCefOsrHandler handler = JBCefApp.isRemoteEnabled() ? + JBCefOsrHandler handler = JBCefApp.getInstance().isRemoteEnabled() ? new JBCefNativeOsrHandler(osrComponent, screenBoundsProvider) : new JBCefOsrHandler(osrComponent, screenBoundsProvider); osrComponent.setRenderHandler(handler);