From d1938d59f743411cc9f903b6ec974ee6e72b01bc Mon Sep 17 00:00:00 2001 From: Thomas Schouten <15669080+PHPirates@users.noreply.github.com> Date: Tue, 30 Jun 2026 21:25:46 +0200 Subject: [PATCH] Wait for browser to load before sending messages --- CHANGELOG.md | 1 + gradle.properties | 2 +- .../intellij/pdf/viewer/BrowserMessages.kt | 3 + .../pdf/viewer/jcef/JcefBrowserMessagePipe.kt | 56 ++++++++++++++++--- .../intellij/pdf/viewer/jcef/JcefUtils.kt | 40 ------------- .../editor/view/PdfJcefPreviewController.kt | 7 +-- .../viewer/application/BrowserMessagePipe.kt | 3 + 7 files changed, 58 insertions(+), 54 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cd024311..5f0033d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Unreleased +- Wait for browser to load before sending messages - Add retries to forward search ## 0.18.2 diff --git a/gradle.properties b/gradle.properties index 9f15ffea..85fe7e6a 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,6 @@ pluginName = PDF Viewer group = com.firsttimeinforever.intellij.pdf.viewer -version = 0.18.3-alpha.1 +version = 0.18.3-alpha.2 # To run with AS 2021.3.1 Canary 5 #platformVersion = 213.6777.52 diff --git a/model/src/commonMain/kotlin/com/firsttimeinforever/intellij/pdf/viewer/BrowserMessages.kt b/model/src/commonMain/kotlin/com/firsttimeinforever/intellij/pdf/viewer/BrowserMessages.kt index 5e45e325..9b5fcdaa 100644 --- a/model/src/commonMain/kotlin/com/firsttimeinforever/intellij/pdf/viewer/BrowserMessages.kt +++ b/model/src/commonMain/kotlin/com/firsttimeinforever/intellij/pdf/viewer/BrowserMessages.kt @@ -5,6 +5,9 @@ import com.firsttimeinforever.intellij.pdf.viewer.tex.SynctexViewCoordinates import kotlinx.serialization.Serializable object BrowserMessages { + @Serializable + class BrowserReady + @Serializable data class InitialViewProperties(val properties: ViewProperties) diff --git a/plugin/src/main/kotlin/com/firsttimeinforever/intellij/pdf/viewer/jcef/JcefBrowserMessagePipe.kt b/plugin/src/main/kotlin/com/firsttimeinforever/intellij/pdf/viewer/jcef/JcefBrowserMessagePipe.kt index 096a6128..00877244 100644 --- a/plugin/src/main/kotlin/com/firsttimeinforever/intellij/pdf/viewer/jcef/JcefBrowserMessagePipe.kt +++ b/plugin/src/main/kotlin/com/firsttimeinforever/intellij/pdf/viewer/jcef/JcefBrowserMessagePipe.kt @@ -1,6 +1,7 @@ package com.firsttimeinforever.intellij.pdf.viewer.jcef -import com.firsttimeinforever.intellij.pdf.viewer.jcef.JcefUtils.addLoadEndHandler +import com.firsttimeinforever.intellij.pdf.viewer.BrowserMessages +import com.firsttimeinforever.intellij.pdf.viewer.jcef.JcefUtils.addLoadHandler import com.firsttimeinforever.intellij.pdf.viewer.jcef.JcefUtils.addUnitHandler import com.firsttimeinforever.intellij.pdf.viewer.jcef.JcefUtils.executeJavaScript import com.firsttimeinforever.intellij.pdf.viewer.mpi.MessagePipe @@ -12,27 +13,59 @@ import com.intellij.openapi.diagnostic.logger import com.intellij.ui.jcef.JBCefBrowser import com.intellij.ui.jcef.JBCefBrowserBase import com.intellij.ui.jcef.JBCefJSQuery +import org.cef.browser.CefBrowser +import org.cef.browser.CefFrame +import org.cef.handler.CefLoadHandlerAdapter +import org.cef.network.CefRequest class JcefBrowserMessagePipe(private val browser: JBCefBrowser) : MessagePipe { private val query = checkNotNull(JBCefJSQuery.create(browser as JBCefBrowserBase)) private val receiveSubscribers = hashMapOf>() + private val pendingMessages = ArrayDeque() + private val lock = Any() + @Volatile + private var isBrowserReady = false init { query.addUnitHandler(::receiveHandler) - browser.addLoadEndHandler { - // FIXME: Implement generic way of packing/unpacking messages into strings - val code = query.inject("raw") - browser.executeJavaScript("window['$ideSendFunctionName'] = raw => $code;") - browser.executeJavaScript("window.dispatchEvent(new Event('IdeReady'));") - } + browser.addLoadHandler(object : CefLoadHandlerAdapter() { + override fun onLoadStart(browser: CefBrowser, frame: CefFrame, transitionType: CefRequest.TransitionType) { + if (!frame.isMain) return + synchronized(lock) { + isBrowserReady = false + } + } + + override fun onLoadEnd(browser: CefBrowser, frame: CefFrame, httpStatusCode: Int) { + if (!frame.isMain) return + val code = query.inject("raw") + this@JcefBrowserMessagePipe.browser.executeJavaScript("window['$ideSendFunctionName'] = raw => $code;") + this@JcefBrowserMessagePipe.browser.executeJavaScript("window.dispatchEvent(new Event('IdeReady'));") + } + }) } private fun receiveHandler(raw: String) { logger.debug(raw) val (type, data) = MessagePipeSupport.MessagePacker.unpack(raw) + if (type == BrowserMessages.BrowserReady::class.simpleName) { + onBrowserReady() + return + } callSubscribers(type, data) } + private fun onBrowserReady() { + val messagesToFlush = mutableListOf() + synchronized(lock) { + isBrowserReady = true + while (pendingMessages.isNotEmpty()) { + messagesToFlush += pendingMessages.removeFirst() + } + } + messagesToFlush.forEach { browser.executeJavaScript(it) } + } + private fun callSubscribers(type: String, data: String) { when (val subscribers = receiveSubscribers[type]) { null -> logger.warn("No subscribers for $type!\nAttached data: $data") @@ -43,7 +76,14 @@ class JcefBrowserMessagePipe(private val browser: JBCefBrowser) : MessagePipe { override fun send(type: String, data: String) { val raw = MessagePipeSupport.MessagePacker.pack(type, data) logger.debug("Sending message: $raw") - browser.executeJavaScript("$browserSendFunctionName('$raw')") + val messageCall = "$browserSendFunctionName('$raw')" + synchronized(lock) { + if (!isBrowserReady) { + pendingMessages.addLast(messageCall) + return + } + } + browser.executeJavaScript(messageCall) } override fun subscribe(type: String, handler: MessageReceivedHandler) { diff --git a/plugin/src/main/kotlin/com/firsttimeinforever/intellij/pdf/viewer/jcef/JcefUtils.kt b/plugin/src/main/kotlin/com/firsttimeinforever/intellij/pdf/viewer/jcef/JcefUtils.kt index 492c82f8..e95dd301 100644 --- a/plugin/src/main/kotlin/com/firsttimeinforever/intellij/pdf/viewer/jcef/JcefUtils.kt +++ b/plugin/src/main/kotlin/com/firsttimeinforever/intellij/pdf/viewer/jcef/JcefUtils.kt @@ -1,9 +1,7 @@ package com.firsttimeinforever.intellij.pdf.viewer.jcef -import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.diagnostic.Logger import com.intellij.openapi.diagnostic.thisLogger -import com.intellij.openapi.util.registry.Registry import com.intellij.ui.jcef.JBCefBrowser import com.intellij.ui.jcef.JBCefJSQuery import org.cef.CefSettings @@ -12,8 +10,6 @@ import org.cef.browser.CefFrame import org.cef.handler.CefDisplayHandlerAdapter import org.cef.handler.CefLoadHandler import org.cef.handler.CefLoadHandlerAdapter -import java.util.concurrent.CountDownLatch -import java.util.concurrent.TimeUnit internal object JcefUtils { @Suppress("NOTHING_TO_INLINE") @@ -30,39 +26,6 @@ internal object JcefUtils { }) } - private class LatchedLoadEndHandler(private val latch: CountDownLatch): CefLoadHandlerAdapter() { - override fun onLoadError( - browser: CefBrowser?, - frame: CefFrame?, - errorCode: CefLoadHandler.ErrorCode?, - errorText: String?, - failedUrl: String? - ) { - logger.warn("Failed to load page.\n\tUrl: $failedUrl\n\tError text: $errorText") - latch.countDown() - } - - override fun onLoadEnd(browser: CefBrowser?, frame: CefFrame?, httpStatusCode: Int) { - latch.countDown() - } - } - - fun JBCefBrowser.invokeAndWaitForLoadEnd(block: () -> Unit) { - ApplicationManager.getApplication().invokeAndWait { - val latch = CountDownLatch(1) - val handler = LatchedLoadEndHandler(latch) - block() - try { - latch.countDown() - latch.await(loadLatchTimeout, TimeUnit.MILLISECONDS) - } catch (exception: Throwable) { - logger.error(exception) - } finally { - jbCefClient.removeLoadHandler(handler, cefBrowser) - } - } - } - @Suppress("NOTHING_TO_INLINE") inline fun CefBrowser.executeJavaScript(code: String) { executeJavaScript(code, null, 0) @@ -132,8 +95,5 @@ internal object JcefUtils { } } - private val loadLatchTimeout: Long - get() = Registry.intValue("pdf.viewer.jcefLatchTimeout", 10000).toLong() - private val logger = thisLogger() } diff --git a/plugin/src/main/kotlin/com/firsttimeinforever/intellij/pdf/viewer/ui/editor/view/PdfJcefPreviewController.kt b/plugin/src/main/kotlin/com/firsttimeinforever/intellij/pdf/viewer/ui/editor/view/PdfJcefPreviewController.kt index 327db96a..dcfd43c8 100644 --- a/plugin/src/main/kotlin/com/firsttimeinforever/intellij/pdf/viewer/ui/editor/view/PdfJcefPreviewController.kt +++ b/plugin/src/main/kotlin/com/firsttimeinforever/intellij/pdf/viewer/ui/editor/view/PdfJcefPreviewController.kt @@ -5,7 +5,6 @@ import com.firsttimeinforever.intellij.pdf.viewer.IdeMessages import com.firsttimeinforever.intellij.pdf.viewer.jcef.JcefBrowserMessagePipe import com.firsttimeinforever.intellij.pdf.viewer.jcef.JcefUtils.addConsoleMessageListener import com.firsttimeinforever.intellij.pdf.viewer.jcef.JcefUtils.createDefaultConsoleMessageListener -import com.firsttimeinforever.intellij.pdf.viewer.jcef.JcefUtils.invokeAndWaitForLoadEnd import com.firsttimeinforever.intellij.pdf.viewer.jcef.PdfStaticServer import com.firsttimeinforever.intellij.pdf.viewer.model.* import com.firsttimeinforever.intellij.pdf.viewer.model.ViewThemeUtils.create @@ -176,10 +175,8 @@ class PdfJcefPreviewController(val project: Project, val virtualFile: VirtualFil tryToPreserveState -> buildUrlWithState(base, viewState) else -> "$base#${createPageModeParameter(PdfViewerSettings.instance.defaultSidebarViewMode)}" } - browser.invokeAndWaitForLoadEnd { - logger.debug("Loading url $url") - browser.loadURL(url) - } + logger.debug("Loading url $url") + browser.loadURL(url) } catch(exception: Throwable) { logger.error(exception) } finally { diff --git a/web-view/viewer/src/jsMain/kotlin/com/firsttimeinforever/intellij/pdf/viewer/application/BrowserMessagePipe.kt b/web-view/viewer/src/jsMain/kotlin/com/firsttimeinforever/intellij/pdf/viewer/application/BrowserMessagePipe.kt index 5f373250..a67596b7 100644 --- a/web-view/viewer/src/jsMain/kotlin/com/firsttimeinforever/intellij/pdf/viewer/application/BrowserMessagePipe.kt +++ b/web-view/viewer/src/jsMain/kotlin/com/firsttimeinforever/intellij/pdf/viewer/application/BrowserMessagePipe.kt @@ -1,9 +1,11 @@ package com.firsttimeinforever.intellij.pdf.viewer.application +import com.firsttimeinforever.intellij.pdf.viewer.BrowserMessages import com.firsttimeinforever.intellij.pdf.viewer.mpi.MessagePipe import com.firsttimeinforever.intellij.pdf.viewer.mpi.MessagePipe.Companion.browserSendFunctionName import com.firsttimeinforever.intellij.pdf.viewer.mpi.MessagePipe.Companion.ideSendFunctionName import com.firsttimeinforever.intellij.pdf.viewer.mpi.MessagePipeSupport +import com.firsttimeinforever.intellij.pdf.viewer.mpi.MessagePipeSupport.send import com.firsttimeinforever.intellij.pdf.viewer.mpi.MessageReceivedHandler import kotlinx.browser.window import org.w3c.dom.get @@ -18,6 +20,7 @@ class BrowserMessagePipe : MessagePipe { console.log() callSubscribers(type, data) } + send(BrowserMessages.BrowserReady()) } private fun callSubscribers(type: String, data: String) {