diff --git a/images/src/org/intellij/images/editor/impl/jcef/CefLocalRequestHandler.kt b/images/src/org/intellij/images/editor/impl/jcef/CefLocalRequestHandler.kt index 2e8453695cd8..4a04b1ce0770 100644 --- a/images/src/org/intellij/images/editor/impl/jcef/CefLocalRequestHandler.kt +++ b/images/src/org/intellij/images/editor/impl/jcef/CefLocalRequestHandler.kt @@ -1,55 +1,12 @@ // Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package org.intellij.images.editor.impl.jcef -import org.cef.browser.CefBrowser -import org.cef.browser.CefFrame -import org.cef.callback.CefCallback -import org.cef.handler.* -import org.cef.misc.BoolRef -import org.cef.network.CefRequest -import java.net.URL - -private typealias CefResourceProvider = () -> CefResourceHandler? +import com.intellij.ui.jcef.utils.JBCefLocalRequestHandler +@Deprecated(replaceWith = ReplaceWith("com.intellij.ui.jcef.utils.JBCefStreamResourceHandler"), + message = "Use JBCefStreamResourceHandler instead", + level = DeprecationLevel.WARNING) class CefLocalRequestHandler( - private val myProtocol: String, - private val myAuthority: String -) : CefRequestHandlerAdapter() { - private val myResources: MutableMap = HashMap() - - private val REJECTING_RESOURCE_HANDLER: CefResourceHandler = object : CefResourceHandlerAdapter() { - override fun processRequest(request: CefRequest, callback: CefCallback): Boolean { - callback.cancel() - return false - } - } - - private val RESOURCE_REQUEST_HANDLER = object : CefResourceRequestHandlerAdapter() { - override fun getResourceHandler(browser: CefBrowser?, frame: CefFrame?, request: CefRequest): CefResourceHandler { - val url = URL(request.url) - url.protocol - if (!url.protocol.equals(myProtocol) || !url.authority.equals(myAuthority)) { - return REJECTING_RESOURCE_HANDLER - } - return try { - myResources[url.path]?.let { it() } ?: REJECTING_RESOURCE_HANDLER - } catch (e: RuntimeException) { - REJECTING_RESOURCE_HANDLER - } - } - } - - fun addResource(resourcePath: String, resourceProvider: CefResourceProvider) { - myResources[resourcePath] = resourceProvider - } - - override fun getResourceRequestHandler(browser: CefBrowser?, - frame: CefFrame?, - request: CefRequest?, - isNavigation: Boolean, - isDownload: Boolean, - requestInitiator: String?, - disableDefaultHandling: BoolRef?): CefResourceRequestHandler { - return RESOURCE_REQUEST_HANDLER - } -} + myProtocol: String, + myAuthority: String, +) : JBCefLocalRequestHandler(myProtocol, myAuthority) diff --git a/images/src/org/intellij/images/editor/impl/jcef/CefStreamResourceHandler.kt b/images/src/org/intellij/images/editor/impl/jcef/CefStreamResourceHandler.kt index 32e87c3304ea..2075888be4ac 100644 --- a/images/src/org/intellij/images/editor/impl/jcef/CefStreamResourceHandler.kt +++ b/images/src/org/intellij/images/editor/impl/jcef/CefStreamResourceHandler.kt @@ -2,63 +2,15 @@ package org.intellij.images.editor.impl.jcef import com.intellij.openapi.Disposable -import com.intellij.openapi.diagnostic.Logger -import com.intellij.openapi.util.Disposer -import org.cef.callback.CefCallback -import org.cef.handler.CefResourceHandler -import org.cef.misc.IntRef -import org.cef.misc.StringRef -import org.cef.network.CefRequest -import org.cef.network.CefResponse -import java.io.IOException +import com.intellij.ui.jcef.utils.JBCefStreamResourceHandler import java.io.InputStream -class CefStreamResourceHandler(private val myStream: InputStream, - private val myMimeType: String, - parent: Disposable, - private val headers: Map = mapOf()) : CefResourceHandler, Disposable { - init { - Disposer.register(parent, this) - } - - override fun processRequest(request: CefRequest, callback: CefCallback): Boolean { - callback.Continue() - return true - } - - override fun getResponseHeaders(response: CefResponse, responseLength: IntRef, redirectUrl: StringRef) { - response.mimeType = myMimeType - response.status = 200 - for (header in headers) { - response.setHeaderByName(header.key, header.value, true /* overwrite */) - } - } - - override fun readResponse(dataOut: ByteArray, bytesToRead: Int, bytesRead: IntRef, callback: CefCallback): Boolean { - try { - bytesRead.set(myStream.read(dataOut, 0, bytesToRead)) - if (bytesRead.get() != -1) { - return true - } - } - catch (e: IOException) { - callback.cancel() - } - bytesRead.set(0) - Disposer.dispose(this) - return false - } - - override fun cancel() { - Disposer.dispose(this) - } - - override fun dispose() { - try { - myStream.close() - } - catch (e: IOException) { - Logger.getInstance(CefStreamResourceHandler::class.java).warn("Failed to close the stream", e) - } - } -} \ No newline at end of file +@Deprecated(replaceWith = ReplaceWith("com.intellij.ui.jcef.utils.JBCefStreamResourceHandler"), + message = "Use JBCefStreamResourceHandler instead", + level = DeprecationLevel.WARNING) +class CefStreamResourceHandler( + myStream: InputStream, + myMimeType: String, + parent: Disposable, + headers: Map = mapOf(), +) : JBCefStreamResourceHandler(myStream, myMimeType, parent, headers) diff --git a/images/src/org/intellij/images/editor/impl/jcef/JCefImageViewer.kt b/images/src/org/intellij/images/editor/impl/jcef/JCefImageViewer.kt index e63ed75476fc..51a636475d19 100644 --- a/images/src/org/intellij/images/editor/impl/jcef/JCefImageViewer.kt +++ b/images/src/org/intellij/images/editor/impl/jcef/JCefImageViewer.kt @@ -20,6 +20,8 @@ import com.intellij.openapi.util.text.StringUtil import com.intellij.openapi.vfs.VirtualFile import com.intellij.openapi.vfs.isTooLarge import com.intellij.ui.jcef.* +import com.intellij.ui.jcef.utils.JBCefLocalRequestHandler +import com.intellij.ui.jcef.utils.JBCefStreamResourceHandler import com.intellij.util.IncorrectOperationException import kotlinx.serialization.Serializable import kotlinx.serialization.json.Json @@ -180,32 +182,32 @@ class JCefImageViewer(private val myFile: VirtualFile, init { myDocument.addDocumentListener(this) - myRequestHandler = CefLocalRequestHandler(PROTOCOL, HOST_NAME) + myRequestHandler = JBCefLocalRequestHandler(PROTOCOL, HOST_NAME) myRequestHandler.addResource(VIEWER_PATH) { javaClass.getResourceAsStream("resources/image_viewer.html")?.let { - CefStreamResourceHandler(it, "text/html", this@JCefImageViewer) + JBCefStreamResourceHandler(it, "text/html", this@JCefImageViewer) } } myRequestHandler.addResource(OVERLAY_SCROLLBARS_CSS_PATH) { - CefStreamResourceHandler(ByteArrayInputStream(JBCefScrollbarsHelper.getOverlayScrollbarsSourceCSS().toByteArray(StandardCharsets.UTF_8)), "text/css", this) + JBCefStreamResourceHandler(ByteArrayInputStream(JBCefScrollbarsHelper.getOverlayScrollbarsSourceCSS().toByteArray(StandardCharsets.UTF_8)), "text/css", this) } myRequestHandler.addResource(OVERLAY_SCROLLBARS_JS_PATH) { - CefStreamResourceHandler(ByteArrayInputStream(JBCefScrollbarsHelper.getOverlayScrollbarsSourceJS().toByteArray(StandardCharsets.UTF_8)), "text/css", this) + JBCefStreamResourceHandler(ByteArrayInputStream(JBCefScrollbarsHelper.getOverlayScrollbarsSourceJS().toByteArray(StandardCharsets.UTF_8)), "text/css", this) } myRequestHandler.addResource(SCROLLBARS_CSS_PATH) { - CefStreamResourceHandler(ByteArrayInputStream(JBCefScrollbarsHelper.getOverlayScrollbarStyle().toByteArray(StandardCharsets.UTF_8)), "text/css", this) + JBCefStreamResourceHandler(ByteArrayInputStream(JBCefScrollbarsHelper.getOverlayScrollbarStyle().toByteArray(StandardCharsets.UTF_8)), "text/css", this) } myRequestHandler.addResource(CHESSBOARD_CSS_PATH) { - CefStreamResourceHandler(ByteArrayInputStream(buildChessboardStyle().toByteArray(StandardCharsets.UTF_8)), "text/css", this) + JBCefStreamResourceHandler(ByteArrayInputStream(buildChessboardStyle().toByteArray(StandardCharsets.UTF_8)), "text/css", this) } myRequestHandler.addResource(GRID_CSS_PATH) { - CefStreamResourceHandler(ByteArrayInputStream(buildGridStyle().toByteArray(StandardCharsets.UTF_8)), "text/css", this) + JBCefStreamResourceHandler(ByteArrayInputStream(buildGridStyle().toByteArray(StandardCharsets.UTF_8)), "text/css", this) } myRequestHandler.addResource(IMAGE_PATH) { @@ -218,11 +220,11 @@ class JCefImageViewer(private val myFile: VirtualFile, Logger.getInstance(JCefImageViewer::class.java).warn("Failed to read the file", e) } - var resourceHandler: CefStreamResourceHandler? = null + var resourceHandler: JBCefStreamResourceHandler? = null stream?.let { try { - resourceHandler = CefStreamResourceHandler(it, mimeType, this@JCefImageViewer, - mapOf("Content-Security-Policy" to "script-src 'none'")) + resourceHandler = JBCefStreamResourceHandler(it, mimeType, this@JCefImageViewer, + mapOf("Content-Security-Policy" to "script-src 'none'")) } catch (_: IncorrectOperationException) { // The viewer has been disposed just return null that will reject all requests } diff --git a/platform/ui.jcef/api-dump-unreviewed.txt b/platform/ui.jcef/api-dump-unreviewed.txt index c10602c06906..e53f3898b111 100644 --- a/platform/ui.jcef/api-dump-unreviewed.txt +++ b/platform/ui.jcef/api-dump-unreviewed.txt @@ -271,3 +271,19 @@ f:com.intellij.ui.jcef.JcefShortcutProvider - ():V - s:getActions():java.util.List - s:registerShortcuts(javax.swing.JComponent,com.intellij.ui.jcef.JBCefBrowser):V +c:com.intellij.ui.jcef.utils.JBCefLocalRequestHandler +- org.cef.handler.CefRequestHandlerAdapter +- (java.lang.String,java.lang.String):V +- f:addResource(java.lang.String,kotlin.jvm.functions.Function0):V +- f:createResource(java.lang.String,kotlin.jvm.functions.Function0):java.lang.String +- getResourceRequestHandler(org.cef.browser.CefBrowser,org.cef.browser.CefFrame,org.cef.network.CefRequest,Z,Z,java.lang.String,org.cef.misc.BoolRef):org.cef.handler.CefResourceRequestHandler +c:com.intellij.ui.jcef.utils.JBCefStreamResourceHandler +- com.intellij.openapi.Disposable +- org.cef.handler.CefResourceHandler +- (java.io.InputStream,java.lang.String,com.intellij.openapi.Disposable,java.util.Map):V +- b:(java.io.InputStream,java.lang.String,com.intellij.openapi.Disposable,java.util.Map,I,kotlin.jvm.internal.DefaultConstructorMarker):V +- cancel():V +- dispose():V +- getResponseHeaders(org.cef.network.CefResponse,org.cef.misc.IntRef,org.cef.misc.StringRef):V +- processRequest(org.cef.network.CefRequest,org.cef.callback.CefCallback):Z +- readResponse(B[],I,org.cef.misc.IntRef,org.cef.callback.CefCallback):Z diff --git a/platform/ui.jcef/exposed-third-party-api.txt b/platform/ui.jcef/exposed-third-party-api.txt index d3872b213680..b246fe128bd2 100644 --- a/platform/ui.jcef/exposed-third-party-api.txt +++ b/platform/ui.jcef/exposed-third-party-api.txt @@ -1,9 +1,11 @@ +kotlin/jvm/internal/DefaultConstructorMarker org/cef/CefClient org/cef/CefSettings org/cef/browser/CefBrowser org/cef/browser/CefFrame org/cef/browser/CefMessageRouter org/cef/browser/CefMessageRouter$CefMessageRouterConfig +org/cef/callback/CefCallback org/cef/callback/CefContextMenuParams org/cef/callback/CefMenuModel org/cef/callback/CefSchemeHandlerFactory @@ -23,5 +25,13 @@ org/cef/handler/CefLoadHandler$ErrorCode org/cef/handler/CefPermissionHandler org/cef/handler/CefRenderHandler org/cef/handler/CefRequestHandler +org/cef/handler/CefRequestHandlerAdapter +org/cef/handler/CefResourceHandler +org/cef/handler/CefResourceRequestHandler +org/cef/misc/BoolRef +org/cef/misc/IntRef +org/cef/misc/StringRef org/cef/network/CefCookie org/cef/network/CefCookieManager +org/cef/network/CefRequest +org/cef/network/CefResponse diff --git a/platform/ui.jcef/jcef/utils/JBCefLocalRequestHandler.kt b/platform/ui.jcef/jcef/utils/JBCefLocalRequestHandler.kt new file mode 100644 index 000000000000..e9553701f768 --- /dev/null +++ b/platform/ui.jcef/jcef/utils/JBCefLocalRequestHandler.kt @@ -0,0 +1,72 @@ +// 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.utils + +import org.cef.browser.CefBrowser +import org.cef.browser.CefFrame +import org.cef.callback.CefCallback +import org.cef.handler.* +import org.cef.misc.BoolRef +import org.cef.network.CefRequest +import java.net.URL + +/** + * Handles local protocol-specific CEF resource requests for a defined `protocol` and `authority`. + * + * This class implements a mechanism to serve protocol-specific resources based on mappings provided + * through the `addResource` function. Only requests matching the configured protocol and authority are processed, + * while others are rejected. + * + * @param myProtocol The protocol to handle (e.g., "http", "file"). + * @param myAuthority The authority of the requests (e.g., "localhost", "mydomain"). + */ +open class JBCefLocalRequestHandler( + private val myProtocol: String, + private val myAuthority: String +) : CefRequestHandlerAdapter() { + private val myResources: MutableMap CefResourceHandler?> = HashMap() + + private val REJECTING_RESOURCE_HANDLER: CefResourceHandler = object : CefResourceHandlerAdapter() { + override fun processRequest(request: CefRequest, callback: CefCallback): Boolean { + callback.cancel() + return false + } + } + + private val RESOURCE_REQUEST_HANDLER = object : CefResourceRequestHandlerAdapter() { + override fun getResourceHandler(browser: CefBrowser?, frame: CefFrame?, request: CefRequest): CefResourceHandler { + val url = URL(request.url) + url.protocol + if (!url.protocol.equals(myProtocol) || !url.authority.equals(myAuthority)) { + return REJECTING_RESOURCE_HANDLER + } + return try { + val path = url.path.trim('/') + myResources[path]?.let { it() } ?: REJECTING_RESOURCE_HANDLER + } catch (e: RuntimeException) { + println(e.message) + REJECTING_RESOURCE_HANDLER + } + } + } + + fun addResource(resourcePath: String, resourceProvider: () -> CefResourceHandler?) { + val normalisedPath = resourcePath.trim('/') + myResources[normalisedPath] = resourceProvider + } + + fun createResource(resourcePath: String, resourceProvider: () -> CefResourceHandler?): String { + val normalisedPath = resourcePath.trim('/') + myResources[normalisedPath] = resourceProvider + return "$myProtocol://$myAuthority/$normalisedPath" + } + + override fun getResourceRequestHandler(browser: CefBrowser?, + frame: CefFrame?, + request: CefRequest?, + isNavigation: Boolean, + isDownload: Boolean, + requestInitiator: String?, + disableDefaultHandling: BoolRef?): CefResourceRequestHandler { + return RESOURCE_REQUEST_HANDLER + } +} diff --git a/platform/ui.jcef/jcef/utils/JBCefStreamResourceHandler.kt b/platform/ui.jcef/jcef/utils/JBCefStreamResourceHandler.kt new file mode 100644 index 000000000000..48d83d92cb2b --- /dev/null +++ b/platform/ui.jcef/jcef/utils/JBCefStreamResourceHandler.kt @@ -0,0 +1,77 @@ +// 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.utils + +import com.intellij.openapi.Disposable +import com.intellij.openapi.diagnostic.Logger +import com.intellij.openapi.util.Disposer +import org.cef.callback.CefCallback +import org.cef.handler.CefResourceHandler +import org.cef.misc.IntRef +import org.cef.misc.StringRef +import org.cef.network.CefRequest +import org.cef.network.CefResponse +import java.io.IOException +import java.io.InputStream + +/** + * A handler for managing custom resource requests in JCEF. + * This class implements the `CefResourceHandler` interface, enabling the customization of resource + * loading from a provided input stream. It supports setting MIME types and headers for responses + * and ensures proper disposal of resources. + * + * @param myStream The input stream from which the resource will be read. + * @param myMimeType The MIME type of the response to be sent to the client. + * @param parent A `Disposable` object to which this handler is registered for automatic disposal. + * @param headers Optional headers that will be included in the response. + */ +open class JBCefStreamResourceHandler( + private val myStream: InputStream, + private val myMimeType: String, + parent: Disposable, + private val headers: Map = mapOf(), +) : CefResourceHandler, Disposable { + init { + Disposer.register(parent, this) + } + + override fun processRequest(request: CefRequest, callback: CefCallback): Boolean { + callback.Continue() + return true + } + + override fun getResponseHeaders(response: CefResponse, responseLength: IntRef, redirectUrl: StringRef) { + response.mimeType = myMimeType + response.status = 200 + for (header in headers) { + response.setHeaderByName(header.key, header.value, true /* overwrite */) + } + } + + override fun readResponse(dataOut: ByteArray, bytesToRead: Int, bytesRead: IntRef, callback: CefCallback): Boolean { + try { + bytesRead.set(myStream.read(dataOut, 0, bytesToRead)) + if (bytesRead.get() != -1) { + return true + } + } + catch (_: IOException) { + callback.cancel() + } + bytesRead.set(0) + Disposer.dispose(this) + return false + } + + override fun cancel() { + Disposer.dispose(this) + } + + override fun dispose() { + try { + myStream.close() + } + catch (e: IOException) { + Logger.getInstance(JBCefStreamResourceHandler::class.java).warn("Failed to close the stream", e) + } + } +} \ No newline at end of file