Files
openide/platform/ui.jcef/jcef/SettingsHelper.java
Vladimir Kharitonov c6ee8f8bfa IJPL-161139 JBR-6766 JCEF: revert adding --disable-gpu-compositing
GitOrigin-RevId: c3802bee8b6b7896bb75ab46a14edaa5c37fd4c7
2024-09-24 20:53:32 +00:00

384 lines
15 KiB
Java

// Copyright 2000-2024 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.execution.Platform;
import com.intellij.ide.IdeBundle;
import com.intellij.notification.Notification;
import com.intellij.notification.NotificationDisplayType;
import com.intellij.notification.NotificationGroup;
import com.intellij.notification.NotificationType;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.NotNullLazyValue;
import com.intellij.openapi.util.SystemInfoRt;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.openapi.util.registry.RegistryManager;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.ArrayUtil;
import com.intellij.util.SystemProperties;
import com.jetbrains.cef.JCefAppConfig;
import com.jetbrains.cef.JCefVersionDetails;
import org.cef.CefSettings;
import org.cef.misc.BoolRef;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Locale;
import java.util.Optional;
final class SettingsHelper {
private static final Logger LOG = Logger.getInstance(JBCefApp.class);
private static final String MISSING_LIBS_SUPPORT_URL = "https://intellij-support.jetbrains.com/hc/en-us/articles/360016421559";
static final @NotNull NotNullLazyValue<NotificationGroup> NOTIFICATION_GROUP = NotNullLazyValue.createValue(() -> {
return NotificationGroup.create("JCEF", NotificationDisplayType.BALLOON, true, null, null, null);
});
private static String ourLinuxDistribution = null;
static boolean isOffScreenRenderingModeEnabled() {
return RegistryManager.getInstance().is("ide.browser.jcef.osr.enabled");
}
static CefSettings loadSettings(@NotNull JCefAppConfig config) {
CefSettings settings = config.getCefSettings();
settings.windowless_rendering_enabled = isOffScreenRenderingModeEnabled();
settings.log_severity = getLogLevel();
settings.log_file = System.getProperty("ide.browser.jcef.log.path",
System.getProperty("user.home") + Platform.current().fileSeparator + "jcef_" + ProcessHandle.current().pid() + ".log");
if (settings.log_file.trim().isEmpty())
settings.log_file = null;
//todo[tav] IDEA-260446 & IDEA-260344 However, without proper background the CEF component flashes white in dark themes
//settings.background_color = settings.new ColorType(bg.getAlpha(), bg.getRed(), bg.getGreen(), bg.getBlue());
int debuggingPort = getRemoteDebugPort();
if (debuggingPort > 0) {
settings.remote_debugging_port = debuggingPort;
}
settings.cache_path = ApplicationManager.getApplication().getService(JBCefAppCache.class).getPath().toString();
if (Registry.is("ide.browser.jcef.sandbox.enable")) {
LOG.info("JCEF-sandbox is enabled");
settings.no_sandbox = false;
if (SystemInfoRt.isWindows) {
String sandboxPtr = System.getProperty("jcef.sandbox.ptr");
if (sandboxPtr != null && !sandboxPtr.trim().isEmpty()) {
if (isSandboxSupported() && checkWinLauncherCefVersion())
settings.browser_subprocess_path = "";
else {
LOG.info("JCEF-sandbox was disabled because current jcef version doesn't support sandbox");
settings.no_sandbox = true;
}
} else {
LOG.info("JCEF-sandbox was disabled because java-process initialized without sandbox");
settings.no_sandbox = true;
}
} else if (SystemInfoRt.isMac) {
ProcessHandle.Info i = ProcessHandle.current().info();
Optional<String> processAppPath = i.command();
String appBundlePath = getMacAppBundlePath();
if (processAppPath.isPresent() && processAppPath.get().endsWith("/bin/java")) {
// Sandbox must be disabled when user runs IDE from debugger (otherwise dlopen will fail)
LOG.warn("JCEF-sandbox was disabled (to enable you should start IDE from launcher)");
settings.no_sandbox = true;
} else if (appBundlePath == null || !SystemProperties.getJavaHome().startsWith(appBundlePath)) {
// https://youtrack.jetbrains.com/issue/JBR-6629
LOG.warn("JCEF-sandbox was disabled (jbr %s doesn't belong to the app bundle %s)".formatted(SystemProperties.getJavaHome(),
appBundlePath));
settings.no_sandbox = true;
}
} else if (SystemInfoRt.isLinux) {
String linuxDistrib = readLinuxDistribution();
if (
linuxDistrib != null &&
(linuxDistrib.contains("debian") || linuxDistrib.contains("centos"))
) {
if (Boolean.getBoolean("ide.browser.jcef.sandbox.disable_linux_os_check")) {
LOG.warn("JCEF sandbox enabled via VM-option 'disable_linux_os_check', OS: " + linuxDistrib);
} else {
LOG.info("JCEF sandbox was disabled because of unsupported OS: " + linuxDistrib
+ ". To skip this check run IDE with VM-option -Dide.browser.jcef.sandbox.disable_linux_os_check=true");
settings.no_sandbox = true;
}
}
}
}
return settings;
}
static String[] loadArgs(@NotNull JCefAppConfig config, @NotNull CefSettings settings, @Nullable BoolRef doTrackGPUCrashes) {
String[] argsFromProviders = JBCefAppRequiredArgumentsProvider
.getProviders()
.stream()
.flatMap(p -> {
LOG.debug("got options: [" + p.getOptions() + "] from:" + p.getClass().getName());
return p.getOptions().stream();
})
.distinct()
.toArray(String[]::new);
String[] args = ArrayUtil.mergeArrays(config.getAppArgs(), argsFromProviders);
JBCefProxySettings proxySettings = JBCefProxySettings.getInstance();
String[] proxyArgs = null;
if (proxySettings.USE_PROXY_PAC) {
if (proxySettings.USE_PAC_URL) {
proxyArgs = new String[] {"--proxy-pac-url=" + proxySettings.PAC_URL + ":" + proxySettings.PROXY_PORT};
}
else {
// when "Auto-detect proxy settings" proxy option is enabled in IntelliJ:
// IntelliJ's behavior: use system proxy settings or an automatically detected the proxy auto-config (PAC) file
// CEF's behavior : use system proxy settings
// When no proxy flag passes to CEF, it uses the system proxy by default and detected the proxy auto-config (PAC) file
// when "--proxy-auto-detect" flag passed.
// CEF doesn't have any proxy flag that checks both system proxy settings and automatically detects proxy auto-config,
// so we let the CEF uses the system proxy here because this is more useful for users and users can also manually
// configure the PAC file in IntelliJ setting if they need to use PAC file.
}
}
else if (proxySettings.USE_HTTP_PROXY) {
String proxyScheme;
if (proxySettings.PROXY_TYPE_IS_SOCKS) {
proxyScheme = "socks";
}
else {
proxyScheme = "http";
}
String proxyServer = "--proxy-server=" + proxyScheme + "://" + proxySettings.PROXY_HOST + ":" + proxySettings.PROXY_PORT;
if (StringUtil.isEmptyOrSpaces(proxySettings.PROXY_EXCEPTIONS)) {
proxyArgs = new String[]{proxyServer};
}
else {
String proxyBypassList = "--proxy-bypass-list=" + proxySettings.PROXY_EXCEPTIONS;
proxyArgs = new String[]{proxyServer, proxyBypassList};
}
}
else {
proxyArgs = new String[]{"--no-proxy-server"};
}
if (proxyArgs != null) args = ArrayUtil.mergeArrays(args, proxyArgs);
if (Registry.is("ide.browser.jcef.gpu.disable")) {
// Add possibility to disable GPU (see IDEA-248140)
args = ArrayUtil.mergeArrays(args, "--disable-gpu", "--disable-gpu-compositing");
}
final boolean trackGPUCrashes = Registry.is("ide.browser.jcef.gpu.infinitecrash");
if (trackGPUCrashes) {
args = ArrayUtil.mergeArrays(args, "--disable-gpu-process-crash-limit");
if (doTrackGPUCrashes != null)
doTrackGPUCrashes.set(true);
}
// Sometimes it's useful to be able to pass any additional keys (see IDEA-248140)
// NOTE: List of keys: https://peter.sh/experiments/chromium-command-line-switches/
String extraArgsProp = System.getProperty("ide.browser.jcef.extra.args", "");
if (!extraArgsProp.isEmpty()) {
String[] extraArgs = extraArgsProp.split(",");
if (extraArgs.length > 0) {
LOG.debug("add extra CEF args: [" + Arrays.toString(extraArgs) + "]");
args = ArrayUtil.mergeArrays(args, extraArgs);
}
}
if (settings.remote_debugging_port > 0) {
args = ArrayUtil.mergeArrays(args, "--remote-allow-origins=*");
} else if (getRemoteDebugPort() == 0) {
args = ArrayUtil.mergeArrays(args, "--remote-debugging-port=0", "--remote-allow-origins=*");
}
args = ArrayUtil.mergeArrays(args, "--autoplay-policy=no-user-gesture-required", "--disable-component-update");
return args;
}
static void showNotificationDisableGPU() {
Notification notification = NOTIFICATION_GROUP.getValue().createNotification(
IdeBundle.message("notification.content.jcef.gpucrash.title"),
IdeBundle.message("notification.content.jcef.gpucrash.message"),
NotificationType.ERROR);
notification.addAction(new AnAction(IdeBundle.message("notification.content.jcef.gpucrash.action.restart")) {
@Override
public void actionPerformed(@NotNull AnActionEvent e) {
ApplicationManager.getApplication().restart();
}
});
if (!Registry.is("ide.browser.jcef.gpu.disable")) {
//noinspection DialogTitleCapitalization
notification.addAction(new AnAction(IdeBundle.message("notification.content.jcef.gpucrash.action.disable")) {
@Override
public void actionPerformed(@NotNull AnActionEvent e) {
Registry.get("ide.browser.jcef.gpu.disable").setValue(true);
ApplicationManager.getApplication().restart();
}
});
}
notification.notify(null);
}
private static CefSettings.LogSeverity getLogLevel() {
String level = System.getProperty("ide.browser.jcef.log.level", "disable").toLowerCase(Locale.ENGLISH);
return switch (level) {
case "disable" -> CefSettings.LogSeverity.LOGSEVERITY_DISABLE;
case "verbose" -> CefSettings.LogSeverity.LOGSEVERITY_VERBOSE;
case "info" -> CefSettings.LogSeverity.LOGSEVERITY_INFO;
case "warning" -> CefSettings.LogSeverity.LOGSEVERITY_WARNING;
case "error" -> CefSettings.LogSeverity.LOGSEVERITY_ERROR;
case "fatal" -> CefSettings.LogSeverity.LOGSEVERITY_FATAL;
default -> CefSettings.LogSeverity.LOGSEVERITY_DEFAULT;
};
}
private static @Nullable String readLinuxDistributionFromOsRelease() {
String fileName = "/etc/os-release";
File f = new File(fileName);
if (!f.exists()) return null;
try {
BufferedReader br = new BufferedReader(new FileReader(fileName, Charset.defaultCharset()));
String line;
while ((line = br.readLine()) != null) {
if (line.startsWith("NAME="))
return line.replace("NAME=", "").replace("\"", "").toLowerCase(Locale.US);
}
} catch (IOException e) {
LOG.error(e);
}
return null;
}
private static @Nullable String readLinuxDistributionFromLsbRelease() {
String fileName = "/etc/lsb-release";
File f = new File(fileName);
if (!f.exists()) return null;
try {
BufferedReader br = new BufferedReader(new FileReader(fileName, Charset.defaultCharset()));
String line;
while ((line = br.readLine()) != null) {
if (line.startsWith("DISTRIB_DESCRIPTION"))
return line.replace("DISTRIB_DESCRIPTION=", "").replace("\"", "").toLowerCase(Locale.US);
}
} catch (IOException e) {
LOG.error(e);
}
return null;
}
private static String readLinuxDistribution() {
if (ourLinuxDistribution == null) {
if (SystemInfoRt.isLinux) {
String readResult = readLinuxDistributionFromLsbRelease();
if (readResult == null)
readResult = readLinuxDistributionFromOsRelease();
ourLinuxDistribution = readResult == null ? "linux" : readResult;
} else {
ourLinuxDistribution = "";
}
}
return ourLinuxDistribution;
}
private static boolean isSandboxSupported() {
JCefVersionDetails version;
try {
version = JCefAppConfig.getVersionDetails();
}
catch (Throwable e) {
LOG.error("JCEF runtime version is not supported");
return false;
}
return version.cefVersion.major >= 104 && version.apiVersion.minor >= 9;
}
private static boolean checkWinLauncherCefVersion() {
// a string like "119.4.7+g55e15c8+chromium-119.0.6045.199"
String launcherCefVersion = System.getProperty("jcef.sandbox.cefVersion");
if (launcherCefVersion == null) {
LOG.error("The launcher cef version is unknown");
return false;
}
String cefVersion;
try {
JCefVersionDetails version = JCefAppConfig.getVersionDetails();
cefVersion = "%d.%d.%d+g%s+chromium-%d.%d.%d.%d".formatted(
version.cefVersion.major,
version.cefVersion.api,
version.cefVersion.patch,
version.cefVersion.commitHash,
version.chromiumVersion.major,
version.chromiumVersion.minor,
version.chromiumVersion.build,
version.chromiumVersion.patch
);
}
catch (Throwable e) {
LOG.error("JCEF runtime version is not available");
return false;
}
if (!cefVersion.equals(launcherCefVersion)) {
LOG.warn("CEF version " + cefVersion + " doesn't match the launcher version " + launcherCefVersion);
return false;
}
return true;
}
private static String getMacAppBundlePath() {
String command = ProcessHandle.current().info().command().orElse(null);
if (command == null) {
return null;
}
Path p = Path.of(command).toAbsolutePath().normalize();
while (p != null) {
File infoPlist = Path.of(p.toString(), "Info.plist").toFile();
if (infoPlist.exists() && infoPlist.isFile() && Path.of("Contents").equals(p.getFileName())) {
p = p.getParent();
break;
}
p = p.getParent();
}
return p == null ? null : p.toString();
}
/**
* Returns the DevTools debug port.
* Possible values:
* -1 - remote DevTools are disabled
* 0 - allocate a random port
* > 0 - the port number
* <p>
* 'ide.browser.jcef.debug.port' has priority over 'ide.browser.jcef.debug.port.random.enabled'.
* It means that if 'ide.browser.jcef.debug.port' value >= 0, 'ide.browser.jcef.debug.port.random.enabled' is ignored.
*/
private static int getRemoteDebugPort() {
int result = Registry.intValue("ide.browser.jcef.debug.port", -1);
if (result >= 0) {
return result;
}
if (Registry.is("ide.browser.jcef.debug.port.random.enabled", false)) {
return 0;
}
return -1;
}
}