From 5edadae11c61b05449bb673b80b04c7434ae4af9 Mon Sep 17 00:00:00 2001
From: Asaf Gabai <77976014+asafgabai@users.noreply.github.com>
Date: Mon, 13 Mar 2023 10:57:21 +0200
Subject: [PATCH] Handle JCEF is not supported error (#309)
* Changed message in case JCEF is not supported.
* Created an empty state for view load error.
* Fixed a bug regarding opening links inside the webview, instead of in a new browser window.
---
.../ide/idea/ui/JFrogLocalToolWindow.java | 148 +++++++++++-------
.../jfrog/ide/idea/ui/JFrogToolWindow.java | 1 +
.../idea/ui/jcef/message/MessagePacker.java | 15 +-
.../ide/idea/ui/utils/ComponentUtils.java | 24 +--
.../ide/idea/ui/webview/WebviewManager.java | 75 +++++++++
5 files changed, 184 insertions(+), 79 deletions(-)
create mode 100644 src/main/java/com/jfrog/ide/idea/ui/webview/WebviewManager.java
diff --git a/src/main/java/com/jfrog/ide/idea/ui/JFrogLocalToolWindow.java b/src/main/java/com/jfrog/ide/idea/ui/JFrogLocalToolWindow.java
index 3d6c87a1..462762a9 100644
--- a/src/main/java/com/jfrog/ide/idea/ui/JFrogLocalToolWindow.java
+++ b/src/main/java/com/jfrog/ide/idea/ui/JFrogLocalToolWindow.java
@@ -5,6 +5,8 @@
import com.intellij.openapi.actionSystem.DefaultActionGroup;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.project.Project;
+import com.intellij.openapi.projectRoots.impl.jdkDownloader.RuntimeChooserUtil;
+import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
@@ -13,10 +15,9 @@
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiManager;
import com.intellij.ui.*;
+import com.intellij.ui.components.JBLabel;
import com.intellij.ui.components.JBPanel;
import com.intellij.ui.jcef.JBCefApp;
-import com.intellij.ui.jcef.JBCefBrowser;
-import com.intellij.ui.jcef.JBCefBrowserBase;
import com.intellij.util.ui.UIUtil;
import com.jfrog.ide.common.nodes.ApplicableIssueNode;
import com.jfrog.ide.common.nodes.IssueNode;
@@ -28,15 +29,13 @@
import com.jfrog.ide.idea.events.ApplicationEvents;
import com.jfrog.ide.idea.log.Logger;
import com.jfrog.ide.idea.scan.ScanManager;
-import com.jfrog.ide.idea.ui.jcef.message.MessagePacker;
import com.jfrog.ide.idea.ui.utils.ComponentUtils;
+import com.jfrog.ide.idea.ui.webview.WebviewManager;
import com.jfrog.ide.idea.ui.webview.WebviewObjectConverter;
import com.jfrog.ide.idea.utils.Utils;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.cef.browser.CefBrowser;
-import org.cef.browser.CefFrame;
-import org.cef.handler.CefLoadHandlerAdapter;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
@@ -55,14 +54,13 @@
*/
public class JFrogLocalToolWindow extends AbstractJFrogToolWindow {
private final LocalComponentsTree componentsTree;
- private final JBCefBrowserBase browser;
private final OnePixelSplitter verticalSplit;
- private final JPanel leftPanelContent;
- private final JComponent compTreeView;
- private final MessagePacker messagePacker;
- private final boolean isInitialized;
+ private JPanel leftPanelContent;
+ private JComponent compTreeView;
+ private boolean isInitialized;
private IssueNode selectedIssue;
private Path tempDirPath;
+ private WebviewManager webviewManager;
/**
* @param project - Currently opened IntelliJ project
@@ -70,25 +68,34 @@ public class JFrogLocalToolWindow extends AbstractJFrogToolWindow {
public JFrogLocalToolWindow(@NotNull Project project) {
super(project);
componentsTree = LocalComponentsTree.getInstance(project);
- browser = new JBCefBrowser();
+ JPanel leftPanel = new JBPanel<>(new BorderLayout());
+ verticalSplit = new OnePixelSplitter(false, 0.4f);
+ verticalSplit.setFirstComponent(leftPanel);
+ setContent(verticalSplit);
+ if (!JBCefApp.isSupported()) {
+ leftPanel.add(createJcefNotSupportedView(), 0);
+ return;
+ }
- messagePacker = new MessagePacker(browser);
- initVulnerabilityInfoBrowser();
+ JComponent browserComponent;
+ try {
+ browserComponent = initVulnerabilityInfoBrowser();
+ } catch (IOException | URISyntaxException e) {
+ Logger.getInstance().error("Local view couldn't be initialized.", e);
+ leftPanel.removeAll();
+ leftPanel.add(createLoadErrorView(), 0);
+ return;
+ }
JPanel toolbar = createActionToolbar();
toolbar.setBorder(IdeBorderFactory.createBorder(SideBorder.BOTTOM));
- JPanel leftPanel = new JBPanel<>(new BorderLayout());
leftPanel.add(toolbar, BorderLayout.PAGE_START);
leftPanelContent = new JBPanel<>(new BorderLayout());
leftPanel.add(leftPanelContent);
compTreeView = createComponentsTreeView();
- verticalSplit = new OnePixelSplitter(false, 0.4f);
- verticalSplit.setFirstComponent(leftPanel);
- setContent(verticalSplit);
-
refreshView();
- registerListeners();
+ registerListeners(browserComponent);
isInitialized = true;
}
@@ -102,7 +109,7 @@ public JPanel createActionToolbar() {
/**
* Register the issues tree listeners.
*/
- public void registerListeners() {
+ public void registerListeners(JComponent browserComponent) {
// Xray credentials were set listener
appBusConnection.subscribe(ApplicationEvents.ON_CONFIGURATION_DETAILS_CHANGE, () -> ApplicationManager.getApplication().invokeLater(this::onConfigurationChange));
@@ -115,7 +122,7 @@ public void registerListeners() {
selectedIssue = (IssueNode) e.getNewLeadSelectionPath().getLastPathComponent();
updateIssueOrLicenseInWebview(selectedIssue);
- verticalSplit.setSecondComponent(browser.getComponent());
+ verticalSplit.setSecondComponent(browserComponent);
});
projectBusConnection.subscribe(ApplicationEvents.ON_SCAN_LOCAL_STARTED, () -> {
setLeftPanelContent(compTreeView);
@@ -138,56 +145,84 @@ private void refreshView() {
@SuppressWarnings("UnstableApiUsage")
private JComponent createReadyEnvView() {
- JPanel noCredentialsPanel = new JBPanel<>();
- noCredentialsPanel.setLayout(new BoxLayout(noCredentialsPanel, BoxLayout.PAGE_AXIS));
+ JPanel readyEnvPanel = new JBPanel<>();
+ readyEnvPanel.setLayout(new BoxLayout(readyEnvPanel, BoxLayout.PAGE_AXIS));
// "We're all set!"
- HyperlinkLabel allSetLabel = new HyperlinkLabel();
+ JBLabel allSetLabel = new JBLabel();
allSetLabel.setText("We're all set.");
- ComponentUtils.addCenteredHyperlinkLabel(noCredentialsPanel, allSetLabel);
+ ComponentUtils.addCenteredComponent(readyEnvPanel, allSetLabel);
// "Scan your project"
HyperlinkLabel scanLink = new HyperlinkLabel();
scanLink.setTextWithHyperlink("Scan your project");
scanLink.addHyperlinkListener(e -> ScanManager.getInstance(project).startScan());
- ComponentUtils.addCenteredHyperlinkLabel(noCredentialsPanel, scanLink);
+ ComponentUtils.addCenteredComponent(readyEnvPanel, scanLink);
- return ComponentUtils.createUnsupportedPanel(noCredentialsPanel);
+ return ComponentUtils.createUnsupportedPanel(readyEnvPanel);
}
- private void setLeftPanelContent(JComponent component) {
- leftPanelContent.removeAll();
- leftPanelContent.add(component, 0);
+ @SuppressWarnings("UnstableApiUsage")
+ private JComponent createJcefNotSupportedView() {
+ JPanel jcefNotSupportedPanel = new JBPanel<>();
+ jcefNotSupportedPanel.setLayout(new BoxLayout(jcefNotSupportedPanel, BoxLayout.PAGE_AXIS));
+
+ // "Thank you for installing the JFrog IDEA Plugin!"
+ JBLabel thanksLabel = new JBLabel();
+ thanksLabel.setText("Thank you for installing the JFrog IntelliJ IDEA Plugin!");
+ ComponentUtils.addCenteredComponent(jcefNotSupportedPanel, thanksLabel);
+
+ // "The plugin uses a component named JCEF that seem to be missing in your IDE."
+ JBLabel pluginNeedsJcefLabel = new JBLabel();
+ pluginNeedsJcefLabel.setText("The plugin uses a component named JCEF that seem to be missing in your IDE.");
+ ComponentUtils.addCenteredComponent(jcefNotSupportedPanel, pluginNeedsJcefLabel);
+
+ // "To make JCEF available in your IDE, you’ll need to have the IDE use a different boot runtime."
+ JBLabel replaceBootRuntimeLabel = new JBLabel();
+ replaceBootRuntimeLabel.setText("To make JCEF available in your IDE, you’ll need to have the IDE use a different boot runtime.");
+ ComponentUtils.addCenteredComponent(jcefNotSupportedPanel, replaceBootRuntimeLabel);
+
+ // "Click here, choose a boot runtime with JCEF, restart the IDE and come back."
+ HyperlinkLabel scanLink = new HyperlinkLabel();
+ scanLink.setTextWithHyperlink("Click here, choose a boot runtime with JCEF, restart the IDE and come back.");
+ scanLink.addHyperlinkListener(e -> RuntimeChooserUtil.INSTANCE.showRuntimeChooserPopup());
+ ComponentUtils.addCenteredComponent(jcefNotSupportedPanel, scanLink);
+
+ return ComponentUtils.createUnsupportedPanel(jcefNotSupportedPanel);
}
- private void initVulnerabilityInfoBrowser() {
- if (!JBCefApp.isSupported()) {
- Logger.getInstance().error("Could not open the issue details view - JCEF is not supported");
- return;
- }
+ private JComponent createLoadErrorView() {
+ JPanel loadErrorPanel = new JBPanel<>();
+ loadErrorPanel.setLayout(new BoxLayout(loadErrorPanel, BoxLayout.PAGE_AXIS));
- try {
- tempDirPath = Files.createTempDirectory("jfrog-idea-plugin");
- Utils.extractFromResources("/jfrog-ide-webview", tempDirPath);
- } catch (IOException | URISyntaxException e) {
- Logger.getInstance().error(e.getMessage());
- return;
- }
+ // "The view couldn't be loaded."
+ JBLabel viewNotLoadedLabel = new JBLabel();
+ viewNotLoadedLabel.setText("The view couldn't be loaded.");
+ ComponentUtils.addCenteredComponent(loadErrorPanel, viewNotLoadedLabel);
- browser.getJBCefClient().addLoadHandler(new CefLoadHandlerAdapter() {
- @Override
- public void onLoadEnd(CefBrowser browser, CefFrame frame, int httpStatusCode) {
- updateIssueOrLicenseInWebview(selectedIssue);
- }
+ // "Check the Notifications / Event Log for more information."
+ JBLabel checkLogsLabel = new JBLabel();
+ checkLogsLabel.setText("Check the Notifications / Event Log for more information.");
+ ComponentUtils.addCenteredComponent(loadErrorPanel, checkLogsLabel);
- @Override
- public void onLoadError(CefBrowser browser, CefFrame frame, ErrorCode errorCode, String errorText, String failedUrl) {
- Logger.getInstance().error("An error occurred while opening the issue details view: " + errorText);
- }
- }, browser.getCefBrowser());
+ return ComponentUtils.createUnsupportedPanel(loadErrorPanel);
+ }
+
+ private void setLeftPanelContent(JComponent component) {
+ leftPanelContent.removeAll();
+ leftPanelContent.add(component, 0);
+ }
+
+ private JComponent initVulnerabilityInfoBrowser() throws IOException, URISyntaxException {
+ tempDirPath = Files.createTempDirectory("jfrog-idea-plugin");
+ Utils.extractFromResources("/jfrog-ide-webview", tempDirPath);
String pageUri = tempDirPath.resolve("index.html").toFile().toURI().toString();
- browser.loadURL(pageUri);
+ webviewManager = new WebviewManager();
+ Disposer.register(this, webviewManager);
+
+ CefBrowser browser = webviewManager.createBrowser(pageUri, () -> updateIssueOrLicenseInWebview(selectedIssue));
+ return (JComponent) browser.getUIComponent();
}
/**
@@ -209,14 +244,14 @@ private JComponent createComponentsTreeView() {
private void updateIssueOrLicenseInWebview(IssueNode vulnerabilityOrViolation) {
if (vulnerabilityOrViolation instanceof VulnerabilityNode) {
VulnerabilityNode issue = (VulnerabilityNode) vulnerabilityOrViolation;
- messagePacker.send(WebviewObjectConverter.convertIssueToDepPage(issue));
+ webviewManager.sendMessage(WebviewObjectConverter.convertIssueToDepPage(issue));
} else if (vulnerabilityOrViolation instanceof ApplicableIssueNode) {
ApplicableIssueNode node = (ApplicableIssueNode) vulnerabilityOrViolation;
- messagePacker.send(WebviewObjectConverter.convertIssueToDepPage(node.getIssue()));
+ webviewManager.sendMessage(WebviewObjectConverter.convertIssueToDepPage(node.getIssue()));
navigateToFile(node);
} else if (vulnerabilityOrViolation instanceof LicenseViolationNode) {
LicenseViolationNode license = (LicenseViolationNode) vulnerabilityOrViolation;
- messagePacker.send(WebviewObjectConverter.convertLicenseToDepPage(license));
+ webviewManager.sendMessage(WebviewObjectConverter.convertLicenseToDepPage(license));
}
}
@@ -259,7 +294,6 @@ public void onConfigurationChange() {
@Override
public void dispose() {
super.dispose();
- browser.dispose();
try {
FileUtils.deleteDirectory(tempDirPath.toFile());
} catch (IOException e) {
diff --git a/src/main/java/com/jfrog/ide/idea/ui/JFrogToolWindow.java b/src/main/java/com/jfrog/ide/idea/ui/JFrogToolWindow.java
index 39f151d7..ada9b4f9 100644
--- a/src/main/java/com/jfrog/ide/idea/ui/JFrogToolWindow.java
+++ b/src/main/java/com/jfrog/ide/idea/ui/JFrogToolWindow.java
@@ -5,6 +5,7 @@
import com.intellij.ui.content.Content;
import com.intellij.ui.content.ContentFactory;
import com.intellij.ui.content.ContentManager;
+import com.jfrog.ide.idea.log.Logger;
import org.jetbrains.annotations.NotNull;
diff --git a/src/main/java/com/jfrog/ide/idea/ui/jcef/message/MessagePacker.java b/src/main/java/com/jfrog/ide/idea/ui/jcef/message/MessagePacker.java
index 2d7bec2c..c11f7f9a 100644
--- a/src/main/java/com/jfrog/ide/idea/ui/jcef/message/MessagePacker.java
+++ b/src/main/java/com/jfrog/ide/idea/ui/jcef/message/MessagePacker.java
@@ -1,15 +1,13 @@
package com.jfrog.ide.idea.ui.jcef.message;
import com.fasterxml.jackson.core.JsonProcessingException;
-import com.intellij.ui.jcef.JBCefBrowserBase;
import com.jfrog.ide.idea.log.Logger;
-
-import static com.jfrog.ide.idea.ui.jcef.message.PackedMessage.IDE_SEND_FUNCTION_NAME;
+import org.cef.browser.CefBrowser;
public class MessagePacker implements MessagePipe {
- JBCefBrowserBase browser;
+ CefBrowser browser;
- public MessagePacker(JBCefBrowserBase browser) {
+ public MessagePacker(CefBrowser browser) {
this.browser = browser;
}
@@ -24,11 +22,8 @@ public void send(Object data) {
}
private void send(String raw) {
- browser.getCefBrowser().executeJavaScript(
- "window." + IDE_SEND_FUNCTION_NAME + "=" + raw,
- browser.getCefBrowser().getURL(), 0);
- browser.getCefBrowser().executeJavaScript(
+ browser.executeJavaScript(
"window.postMessage(" + raw + ")",
- browser.getCefBrowser().getURL(), 0);
+ browser.getURL(), 0);
}
}
diff --git a/src/main/java/com/jfrog/ide/idea/ui/utils/ComponentUtils.java b/src/main/java/com/jfrog/ide/idea/ui/utils/ComponentUtils.java
index c7100bc7..336c026e 100644
--- a/src/main/java/com/jfrog/ide/idea/ui/utils/ComponentUtils.java
+++ b/src/main/java/com/jfrog/ide/idea/ui/utils/ComponentUtils.java
@@ -41,41 +41,41 @@ public static JComponent createNoCredentialsView() {
noCredentialsPanel.setLayout(new BoxLayout(noCredentialsPanel, BoxLayout.PAGE_AXIS));
// "Thank you for installing the JFrog plugin"
- HyperlinkLabel thanksLabel = new HyperlinkLabel();
+ JBLabel thanksLabel = new JBLabel();
thanksLabel.setText("Thank you for installing the JFrog IntelliJ IDEA Plugin!");
- addCenteredHyperlinkLabel(noCredentialsPanel, thanksLabel);
+ addCenteredComponent(noCredentialsPanel, thanksLabel);
// "If you already have a JFrog environment, configure its connection details."
HyperlinkLabel configLink = new HyperlinkLabel();
configLink.setTextWithHyperlink("If you already have a JFrog environment,configure its connection details.");
configLink.addHyperlinkListener(e -> ShowSettingsUtil.getInstance().showSettingsDialog(null, JFrogGlobalConfiguration.class));
- addCenteredHyperlinkLabel(noCredentialsPanel, configLink);
+ addCenteredComponent(noCredentialsPanel, configLink);
// "Don't have a JFrog environment? Get one for FREE!"
HyperlinkLabel getFreeLink = new HyperlinkLabel();
getFreeLink.setTextWithHyperlink("Don't have a JFrog environment?Get one for FREE!");
getFreeLink.addHyperlinkListener(e -> BrowserUtil.browse("https://github.com/jfrog/jfrog-idea-plugin#set-up-a-free-jfrog-environment-in-the-cloud"));
- addCenteredHyperlinkLabel(noCredentialsPanel, getFreeLink);
+ addCenteredComponent(noCredentialsPanel, getFreeLink);
// "Read more about the plugin."
HyperlinkLabel readMoreLink = new HyperlinkLabel();
readMoreLink.setTextWithHyperlink("Read more about the plugin.");
readMoreLink.addHyperlinkListener(e -> BrowserUtil.browse("https://github.com/jfrog/jfrog-idea-plugin#readme"));
- addCenteredHyperlinkLabel(noCredentialsPanel, readMoreLink);
+ addCenteredComponent(noCredentialsPanel, readMoreLink);
return createUnsupportedPanel(noCredentialsPanel);
}
/**
- * Add centered HyperlinkLabel to the input panel.
+ * Add centered {@link JComponent} to the input panel.
*
- * @param panel - The input panel
- * @param hyperlinkLabel - The hyperlink label
+ * @param panel - The input panel
+ * @param component - The component to add
*/
- public static void addCenteredHyperlinkLabel(JPanel panel, HyperlinkLabel hyperlinkLabel) {
- hyperlinkLabel.setMaximumSize(hyperlinkLabel.getPreferredSize());
- hyperlinkLabel.setAlignmentX(Component.CENTER_ALIGNMENT);
- panel.add(hyperlinkLabel);
+ public static void addCenteredComponent(JPanel panel, JComponent component) {
+ component.setMaximumSize(component.getPreferredSize());
+ component.setAlignmentX(Component.CENTER_ALIGNMENT);
+ panel.add(component);
}
@SuppressWarnings("UnstableApiUsage")
diff --git a/src/main/java/com/jfrog/ide/idea/ui/webview/WebviewManager.java b/src/main/java/com/jfrog/ide/idea/ui/webview/WebviewManager.java
new file mode 100644
index 00000000..b877001c
--- /dev/null
+++ b/src/main/java/com/jfrog/ide/idea/ui/webview/WebviewManager.java
@@ -0,0 +1,75 @@
+package com.jfrog.ide.idea.ui.webview;
+
+import com.intellij.ide.BrowserUtil;
+import com.intellij.openapi.Disposable;
+import com.intellij.openapi.util.Disposer;
+import com.intellij.ui.jcef.JBCefBrowser;
+import com.intellij.ui.jcef.JBCefBrowserBase;
+import com.intellij.ui.jcef.JBCefJSQuery;
+import com.jfrog.ide.idea.log.Logger;
+import com.jfrog.ide.idea.ui.jcef.message.MessagePacker;
+import org.cef.browser.CefBrowser;
+import org.cef.browser.CefFrame;
+import org.cef.handler.CefLoadHandlerAdapter;
+
+public class WebviewManager implements Disposable {
+ private JBCefBrowser jbCefBrowser;
+ private MessagePacker messagePacker;
+
+ public CefBrowser createBrowser(String pageUri, Runnable onLoadEnd) {
+ jbCefBrowser = new JBCefBrowser(pageUri);
+ Disposer.register(this, jbCefBrowser);
+ CefBrowser cefBrowser = jbCefBrowser.getCefBrowser();
+ handleLoadEvent(onLoadEnd);
+ messagePacker = new MessagePacker(cefBrowser);
+ return cefBrowser;
+ }
+
+ private void handleLoadEvent(Runnable onLoadEnd) {
+ CefBrowser cefBrowser = jbCefBrowser.getCefBrowser();
+ JBCefJSQuery openInBrowserQuery = JBCefJSQuery.create((JBCefBrowserBase) jbCefBrowser);
+ Disposer.register(this, openInBrowserQuery);
+ openInBrowserQuery.addHandler((link) -> {
+ BrowserUtil.browse(link);
+ return null;
+ });
+
+ String injectedCall = openInBrowserQuery.inject("link");
+ jbCefBrowser.getJBCefClient().addLoadHandler(new CefLoadHandlerAdapter() {
+ @Override
+ public void onLoadEnd(CefBrowser browser, CefFrame frame, int httpStatusCode) {
+ super.onLoadEnd(browser, frame, httpStatusCode);
+ if (onLoadEnd != null) {
+ onLoadEnd.run();
+ }
+
+ // Add a click event listener to the whole page. Any click on an element (even if it wasn't created
+ // yet), will trigger this function which sends the link address to openInBrowserQuery.
+ cefBrowser.executeJavaScript(
+ "function callback(e) {\n" +
+ " if (e.target.tagName !== 'A')\n" +
+ " return;\n" +
+ " let link = e.target.href;\n" +
+ injectedCall +
+ " e.preventDefault();\n" +
+ "}\n" +
+ "document.addEventListener('click', callback, false);",
+ cefBrowser.getURL(), 0);
+ }
+
+ @Override
+ public void onLoadError(CefBrowser browser, CefFrame frame, ErrorCode errorCode, String errorText, String failedUrl) {
+ super.onLoadError(browser, frame, errorCode, errorText, failedUrl);
+ Logger.getInstance().error("An error occurred while opening the issue details view: " + errorText);
+ }
+ }, cefBrowser);
+ }
+
+ public void sendMessage(Object data) {
+ messagePacker.send(data);
+ }
+
+ @Override
+ public void dispose() {
+ }
+}