Skip to content

Commit

Permalink
fix: dataflow links work across files [IDE-391] (#550)
Browse files Browse the repository at this point in the history
* fix: dataflow links work across files

* refactor: pre-compute virtual files outside of UI thread
  • Loading branch information
teodora-sandu authored Jun 17, 2024
1 parent 80f8911 commit 2581e2d
Show file tree
Hide file tree
Showing 6 changed files with 85 additions and 52 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## [2.8.6]
### Fixed
- automatically migrate old-format endpoint to https://api.xxx.snyk.io endpoint and save it in settings. Also, add tooltip to custom endpoint field explaining the format.
- fix multi-file links in the DataFlow HTML panel

## [2.8.5]
### Fixed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,29 +3,33 @@ package io.snyk.plugin.ui.jcef
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.editor.colors.ColorKey
import com.intellij.openapi.editor.colors.EditorColorsManager
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.ui.JBColor
import com.intellij.ui.jcef.JBCefBrowserBase
import com.intellij.ui.jcef.JBCefJSQuery
import com.intellij.util.ui.UIUtil
import io.snyk.plugin.getDocument
import io.snyk.plugin.navigateToSource
import io.snyk.plugin.SnykFile
import org.cef.browser.CefBrowser
import org.cef.browser.CefFrame
import org.cef.handler.CefLoadHandlerAdapter
import java.awt.Color

class OpenFileLoadHandlerGenerator(snykFile: SnykFile) {
private val project = snykFile.project
private val virtualFile = snykFile.virtualFile

class OpenFileLoadHandlerGenerator(
private val project: Project,
private val virtualFiles: LinkedHashMap<String, VirtualFile?>,
) {
fun openFile(value: String): JBCefJSQuery.Response {
val values = value.replace("\n", "").split(":")
val filePath = values[0]
val startLine = values[1].toInt()
val endLine = values[2].toInt()
val startCharacter = values[3].toInt()
val endCharacter = values[4].toInt()

val virtualFile = virtualFiles[filePath] ?: return JBCefJSQuery.Response("success")

ApplicationManager.getApplication().invokeLater {
val document = virtualFile.getDocument()
val startLineStartOffset = document?.getLineStartOffset(startLine) ?: 0
Expand All @@ -43,35 +47,48 @@ class OpenFileLoadHandlerGenerator(snykFile: SnykFile) {
return "#%02x%02x%02x".format(color.red, color.green, color.blue)
}

fun shift(colorComponent: Int, d: Double): Int {
fun shift(
colorComponent: Int,
d: Double,
): Int {
val n = (colorComponent * d).toInt()
return n.coerceIn(0, 255)
}

fun getCodeDiffColors(baseColor: Color, isHighContrast: Boolean): Pair<Color, Color> {
val addedColor = if (isHighContrast) {
Color(28, 68, 40) // high contrast green
} else {
Color(shift(baseColor.red, 0.75), baseColor.green, shift(baseColor.blue, 0.75))
}
fun getCodeDiffColors(
baseColor: Color,
isHighContrast: Boolean,
): Pair<Color, Color> {
val addedColor =
if (isHighContrast) {
Color(28, 68, 40) // high contrast green
} else {
Color(shift(baseColor.red, 0.75), baseColor.green, shift(baseColor.blue, 0.75))
}

val removedColor = if (isHighContrast) {
Color(84, 36, 38) // high contrast red
} else {
Color(shift(baseColor.red, 1.25), shift(baseColor.green, 0.85), shift(baseColor.blue, 0.85))
}
val removedColor =
if (isHighContrast) {
Color(84, 36, 38) // high contrast red
} else {
Color(shift(baseColor.red, 1.25), shift(baseColor.green, 0.85), shift(baseColor.blue, 0.85))
}
return Pair(addedColor, removedColor)
}

fun generate(jbCefBrowser: JBCefBrowserBase): CefLoadHandlerAdapter {
val openFileQuery = JBCefJSQuery.create(jbCefBrowser)
val isDarkTheme = EditorColorsManager.getInstance().isDarkEditor
val isHighContrast = EditorColorsManager.getInstance().globalScheme.name.contains("High contrast", ignoreCase = true)
val isHighContrast =
EditorColorsManager.getInstance().globalScheme.name.contains("High contrast", ignoreCase = true)

openFileQuery.addHandler { openFile(it) }

return object : CefLoadHandlerAdapter() {
override fun onLoadEnd(browser: CefBrowser, frame: CefFrame, httpStatusCode: Int) {
override fun onLoadEnd(
browser: CefBrowser,
frame: CefFrame,
httpStatusCode: Int,
) {
if (frame.isMain) {
val script = """
(function() {
Expand All @@ -89,14 +106,12 @@ class OpenFileLoadHandlerGenerator(snykFile: SnykFile) {
target.getAttribute("end-character"));
}
// Attach a single event listener to the document
document.addEventListener('click', function(e) {
// Find the nearest ancestor
var target = e.target.closest('.data-flow-clickable-row');
if (target) {
navigateToIssue(e, target);
}
});
const dataFlows = document.getElementsByClassName('data-flow-clickable-row');
for (let i = 0; i < dataFlows.length; i++) {
dataFlows[i].addEventListener('click', (e) => {
navigateToIssue(e, dataFlows[i]);
});
}
document.getElementById('position-line').addEventListener('click', (e) => {
// Find the first
var target = document.getElementsByClassName('data-flow-clickable-row')[0];
Expand All @@ -116,8 +131,10 @@ class OpenFileLoadHandlerGenerator(snykFile: SnykFile) {
val dataFlowColor = toCssHex(baseColor)

val globalScheme = EditorColorsManager.getInstance().globalScheme
val tearLineColor = globalScheme.getColor(ColorKey.find("TEARLINE_COLOR")) // The closest color to target_rgb = (198, 198, 200)
val tabItemHoverColor = globalScheme.getColor(ColorKey.find("INDENT_GUIDE")) // The closest color to target_rgb = RGB (235, 236, 240)
val tearLineColor =
globalScheme.getColor(ColorKey.find("TEARLINE_COLOR")) // The closest color to target_rgb = (198, 198, 200)
val tabItemHoverColor =
globalScheme.getColor(ColorKey.find("INDENT_GUIDE")) // The closest color to target_rgb = RGB (235, 236, 240)

val themeScript = """
(function(){
Expand All @@ -142,8 +159,8 @@ class OpenFileLoadHandlerGenerator(snykFile: SnykFile) {
}
// Add theme class to body
const isDarkTheme = ${isDarkTheme};
const isHighContrast = ${isHighContrast};
const isDarkTheme = $isDarkTheme;
const isHighContrast = $isHighContrast;
document.body.classList.add(isHighContrast ? 'high-contrast' : (isDarkTheme ? 'dark' : 'light'));
})();
"""
Expand All @@ -153,4 +170,3 @@ class OpenFileLoadHandlerGenerator(snykFile: SnykFile) {
}
}
}

14 changes: 7 additions & 7 deletions src/main/kotlin/io/snyk/plugin/ui/jcef/Utils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,19 @@ import com.intellij.ui.jcef.JBCefBrowserBuilder
import org.cef.handler.CefLoadHandlerAdapter
import java.awt.Component


object JCEFUtils {
fun getJBCefBrowserComponentIfSupported (html: String, loadHandlerGenerator: (jbCefBrowser: JBCefBrowser) -> CefLoadHandlerAdapter): Component? {
fun getJBCefBrowserComponentIfSupported(
html: String,
loadHandlerGenerator: (jbCefBrowser: JBCefBrowser) -> CefLoadHandlerAdapter,
): Component? {
if (!JBCefApp.isSupported()) {
return null
}
val cefClient = JBCefApp.getInstance().createClient()
cefClient.setProperty("JS_QUERY_POOL_SIZE", 1)
val jbCefBrowser = JBCefBrowserBuilder().
setClient(cefClient).
setEnableOpenDevToolsMenuItem(false).
setMouseWheelEventEnable(true).
build()
val jbCefBrowser =
JBCefBrowserBuilder().setClient(cefClient).setEnableOpenDevToolsMenuItem(true)
.setMouseWheelEventEnable(true).build()
jbCefBrowser.setOpenLinksInExternalBrowser(true)

val loadHandler = loadHandlerGenerator(jbCefBrowser)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package io.snyk.plugin.ui.toolwindow.panels

import com.intellij.openapi.vfs.VirtualFile
import com.intellij.openapi.vfs.VirtualFileManager
import com.intellij.uiDesigner.core.GridLayoutManager
import com.intellij.util.ui.JBUI
import io.snyk.plugin.SnykFile
Expand All @@ -18,6 +20,7 @@ import snyk.common.lsp.ScanIssue
import stylesheets.SnykStylesheets
import java.awt.BorderLayout
import java.awt.Font
import java.nio.file.Paths
import javax.swing.JLabel
import javax.swing.JPanel

Expand All @@ -37,7 +40,13 @@ class SuggestionDescriptionPanelFromLS(
pluginSettings().isGlobalIgnoresFeatureEnabled &&
issue.canLoadSuggestionPanelFromHTML()
) {
val openFileLoadHandlerGenerator = OpenFileLoadHandlerGenerator(snykFile)
val virtualFiles = LinkedHashMap<String, VirtualFile?>()
for (dataFlow in issue.additionalData.dataFlow) {
virtualFiles[dataFlow.filePath] =
VirtualFileManager.getInstance().findFileByNioPath(Paths.get(dataFlow.filePath))
}

val openFileLoadHandlerGenerator = OpenFileLoadHandlerGenerator(snykFile.project, virtualFiles)
val html = this.getStyledHTML()
val jbCefBrowserComponent =
JCEFUtils.getJBCefBrowserComponentIfSupported(html) {
Expand Down Expand Up @@ -127,13 +136,17 @@ class SuggestionDescriptionPanelFromLS(
ideStyle = SnykStylesheets.SnykCodeSuggestion
}
html = html.replace("\${ideStyle}", "<style nonce=\${nonce}>$ideStyle</style>")
html = html.replace("\${ideScript}", "<script nonce=\${nonce}>" +
" // Ensure the document is fully loaded before executing script to manipulate DOM.\n" +
" document.addEventListener('DOMContentLoaded', () => {\n" +
" document.getElementById(\"ai-fix-wrapper\").classList.add(\"hidden\");\n" +
" document.getElementById(\"no-ai-fix-wrapper\").classList.remove(\"hidden\");\n" +
" })" +
"</script>")
html =
html.replace(
"\${ideScript}",
"<script nonce=\${nonce}>" +
" // Ensure the document is fully loaded before executing script to manipulate DOM.\n" +
" document.addEventListener('DOMContentLoaded', () => {\n" +
" document.getElementById(\"ai-fix-wrapper\").classList.add(\"hidden\");\n" +
" document.getElementById(\"no-ai-fix-wrapper\").classList.remove(\"hidden\");\n" +
" })" +
"</script>",
)

val nonce = getNonce()
html = html.replace("\${nonce}", nonce)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,15 @@ import com.intellij.psi.PsiFile
import com.intellij.testFramework.fixtures.BasePlatformTestCase
import io.mockk.unmockkAll
import io.snyk.plugin.resetSettings
import io.snyk.plugin.SnykFile
import org.junit.Test
import snyk.code.annotator.SnykCodeAnnotator
import java.nio.file.Paths

class OpenFileLoadHandlerGeneratorTest : BasePlatformTestCase(){
class OpenFileLoadHandlerGeneratorTest : BasePlatformTestCase() {
private lateinit var generator: OpenFileLoadHandlerGenerator
private val fileName = "app.js"
private lateinit var file: VirtualFile
private lateinit var psiFile: PsiFile
private lateinit var snykFile: SnykFile

override fun getTestDataPath(): String {
val resource = SnykCodeAnnotator::class.java.getResource("/test-fixtures/code/annotator")
Expand All @@ -31,9 +29,11 @@ class OpenFileLoadHandlerGeneratorTest : BasePlatformTestCase(){

file = myFixture.copyFileToProject(fileName)
psiFile = WriteAction.computeAndWait<PsiFile, Throwable> { psiManager.findFile(file)!! }
snykFile = SnykFile(psiFile.project, psiFile.virtualFile)

generator = OpenFileLoadHandlerGenerator(snykFile)
val virtualFiles = LinkedHashMap<String, VirtualFile?>()
virtualFiles[fileName] = psiFile.virtualFile

generator = OpenFileLoadHandlerGenerator(psiFile.project, virtualFiles)
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,9 @@ class SuggestionDescriptionPanelFromLSOSSTest : BasePlatformTestCase() {
every { issue.additionalData.fixedIn } returns listOf("fixedIn")
every { issue.additionalData.exploit } returns "exploit"
every { issue.additionalData.description } returns "description"
every {
issue.additionalData.dataFlow
} returns emptyList()
}

@Test
Expand Down

0 comments on commit 2581e2d

Please sign in to comment.