Skip to main content

Creating IntelliJ plugin with WebView

Rafał Mucha

Dec 21, 2020|7 min read
Creating IntelliJ plugin with WebView
1<idea-plugin>
2<id>catviewer</id>
3<name>CatViewer</name>
4<version>1.0</version>
5
6<description><![CDATA[
7Your employee track every minute spent in the browser? Use CatViewer in your IDE, and browse funny cats along your code!
8]]></description>
9
10<idea-version since-build="192.5118"/>
11
12<depends>com.intellij.modules.platform</depends>
13
14<extensions defaultExtensionNs="com.intellij">
15 <toolWindow id="CatViewer" anchor="right" factoryClass="com.catviewer.WindowFactory"/>
16</extensions>
17
18</idea-plugin>
Image Alt

1<toolWindow id="CatViewer" anchor="right" factoryClass="com.catviewer.WindowFactory"/>
1package com.catviewer
2
3import com.intellij.openapi.components.ServiceManager
4import com.intellij.openapi.project.Project
5import com.intellij.openapi.wm.{ ToolWindow, ToolWindowFactory }
6
7class WindowFactory extends ToolWindowFactory {
8 override def createToolWindowContent(project: Project, toolWindow: ToolWindow): Unit = {
9 val catViewerWindow = ServiceManager.getService(project, classOf[CatViewerWindowService]).catViewerWindow
10 val component = toolWindow.getComponent
11 component.getParent.add(catViewerWindow.content)
12 }
13}
1package com.catviewer
2
3import com.intellij.openapi.project.Project
4
5class CatViewerWindowService(val project: Project) {
6 val catViewerWindow = CatViewerWindow(project)
7}
1<extensions defaultExtensionNs="com.intellij">
2 <toolWindow id="CatViewer" anchor="right" factoryClass="com.catviewer.WindowFactory"/>
3 <projectService id="CatViewerWindowService" serviceImplementation="com.catviewer.CatViewerWindowService"/>
4</extensions>
1package com.catviewer
2
3import com.intellij.openapi.project.Project
4import com.intellij.openapi.util.Disposer
5import com.intellij.ui.jcef.JBCefBrowser
6import javax.swing.JComponent
7import org.cef.CefApp
8
9case class CatViewerWindow(project: Project) {
10 private lazy val webView: JBCefBrowser = {
11 val browser = new JBCefBrowser()
12 registerAppSchemeHandler()
13 browser.loadURL("http://myapp/index.html")
14 Disposer.register(project, browser)
15 browser
16 }
17
18 def content: JComponent = webView.getComponent
19
20 private def registerAppSchemeHandler(): Unit = {
21 CefApp
22 .getInstance()
23 .registerSchemeHandlerFactory(
24 "http",
25 "myapp",
26 new CustomSchemeHandlerFactory
27 )
28 }
29}
1package com.catviewer
2
3import java.io.{IOException, InputStream}
4import java.net.URLConnection
5import org.cef.callback.CefCallback
6import org.cef.handler.{CefLoadHandler, CefResourceHandler}
7import org.cef.misc.{IntRef, StringRef}
8import org.cef.network.{CefRequest, CefResponse}
9
10class CustomResourceHandler extends CefResourceHandler {
11 private var state: ResourceHandlerState = ClosedConnection
12 override def processRequest(
13 cefRequest: CefRequest,
14 cefCallback: CefCallback
15 ): Boolean = {
16 val urlOption = Option(cefRequest.getURL)
17 urlOption match {
18 case Some(processedUrl) =>
19 val pathToResource = processedUrl.replace("http://myapp", "webview/")
20 val newUrl = getClass.getClassLoader.getResource(pathToResource)
21 state = OpenedConnection(
22 newUrl.openConnection()
23 )
24 cefCallback.Continue()
25 true
26 case None => false
27 }
28 }
29
30 override def getResponseHeaders(
31 cefResponse: CefResponse,
32 responseLength: IntRef,
33 redirectUrl: StringRef
34 ): Unit = {
35 state.getResponseHeaders(cefResponse, responseLength, redirectUrl)
36 }
37
38 override def readResponse(
39 dataOut: Array[Byte],
40 designedBytesToRead: Int,
41 bytesRead: IntRef,
42 callback: CefCallback
43 ): Boolean = {
44 state.readResponse(dataOut, designedBytesToRead, bytesRead, callback)
45 }
46
47 override def cancel(): Unit = {
48 state.close()
49 state = ClosedConnection
50 }
51}
52
53sealed trait ResourceHandlerState {
54 def getResponseHeaders(
55 cefResponse: CefResponse,
56 responseLength: IntRef,
57 redirectUrl: StringRef
58 ): Unit
59
60 def readResponse(
61 dataOut: Array[Byte],
62 designedBytesToRead: Int,
63 bytesRead: IntRef,
64 callback: CefCallback
65 ): Boolean
66
67 def close(): Unit = {}
68}
69
70case class OpenedConnection(connection: URLConnection)
71 extends ResourceHandlerState {
72 private lazy val inputStream: InputStream = connection.getInputStream
73 override def getResponseHeaders(
74 cefResponse: CefResponse,
75 responseLength: IntRef,
76 redirectUrl: StringRef
77 ): Unit = {
78 try {
79 val url = connection.getURL.toString
80 url match {
81 case x if x.contains("css") => cefResponse.setMimeType("text/css")
82 case x if x.contains("js") =>
83 cefResponse.setMimeType("text/javascript")
84 case x if x.contains("html") => cefResponse.setMimeType("text/html")
85 case _ => cefResponse.setMimeType(connection.getContentType)
86 }
87 responseLength.set(inputStream.available())
88 cefResponse.setStatus(200)
89 } catch {
90 case e: IOException =>
91 cefResponse.setError(CefLoadHandler.ErrorCode.ERR_FILE_NOT_FOUND)
92 cefResponse.setStatusText(e.getLocalizedMessage)
93 cefResponse.setStatus(404)
94 }
95 }
96
97 override def readResponse(
98 dataOut: Array[Byte],
99 designedBytesToRead: Int,
100 bytesRead: IntRef,
101 callback: CefCallback
102 ): Boolean = {
103 val availableSize = inputStream.available()
104 if (availableSize > 0) {
105 val maxBytesToRead = Math.min(availableSize, designedBytesToRead)
106 val realNumberOfReadBytes =
107 inputStream.read(dataOut, 0, maxBytesToRead)
108 bytesRead.set(realNumberOfReadBytes)
109 true
110 } else {
111 inputStream.close()
112 false
113 }
114 }
115
116 override def close(): Unit = {
117 inputStream.close()
118 }
119}
120
121case object ClosedConnection extends ResourceHandlerState {
122 override def getResponseHeaders(
123 cefResponse: CefResponse,
124 responseLength: IntRef,
125 redirectUrl: StringRef
126 ): Unit = {
127 cefResponse.setStatus(404)
128 }
129
130 override def readResponse(
131 dataOut: Array[Byte],
132 designedBytesToRead: Int,
133 bytesRead: IntRef,
134 callback: CefCallback
135 ): Boolean = {
136 false
137 }
138}
1package com.catviewer
2
3import org.cef.browser.{ CefBrowser, CefFrame }
4import org.cef.callback.CefSchemeHandlerFactory
5import org.cef.handler.CefResourceHandler
6import org.cef.network.CefRequest
7
8class CustomSchemeHandlerFactory extends CefSchemeHandlerFactory {
9 override def create(
10 cefBrowser: CefBrowser,
11 cefFrame: CefFrame,
12 s: String,
13 cefRequest: CefRequest
14 ): CefResourceHandler = {
15 new CustomResourceHandler()
16 }
17}
1<html>
2<body>
3 <div>
4 <img src="cat.png"/>
5 </div>
6</body>
7</html>
1buildscript {
2 repositories {
3 mavenCentral()
4 maven { url 'https://plugins.gradle.org/m2/' }
5 maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' }
6 }
7 dependencies {
8 classpath "org.jetbrains.intellij.plugins:gradle-intellij-plugin:0.6.5"
9 classpath "cz.alenkacz:gradle-scalafmt:1.13.0"
10 }
11}
12
13allprojects {
14 repositories {
15 mavenCentral()
16 maven { url 'https://plugins.gradle.org/m2/' }
17 maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' }
18 }
19}
20
21apply plugin: 'idea'
22apply plugin: 'scala'
23apply plugin: 'org.jetbrains.intellij'
24apply plugin: 'scalafmt'
25
26ext {
27 scalaVersion = "2.13.3"
28}
29
30sourceSets {
31 main.scala.srcDirs = ['src/main/scala']
32 main.java.srcDirs = []
33}
34
35dependencies {
36 implementation group: "org.scala-lang", name: "scala-library", version: scalaVersion
37 implementation group: 'org.scala-lang', name: 'scala-reflect', version: scalaVersion
38}
39
40sourceCompatibility = 8
41targetCompatibility = 8
Image Alt

Subscribe to our newsletter and never miss an article