|
|
@@ -67,20 +67,23 @@ class WebViewManager(var project: Project) : Disposable, ThemeChangeListener {
|
|
|
// Latest created WebView instance
|
|
|
@Volatile
|
|
|
private var latestWebView: WebViewInstance? = null
|
|
|
-
|
|
|
+
|
|
|
// Store WebView creation callbacks
|
|
|
private val creationCallbacks = mutableListOf<WebViewCreationCallback>()
|
|
|
|
|
|
// Resource root directory path
|
|
|
@Volatile
|
|
|
private var resourceRootDir: Path? = null
|
|
|
-
|
|
|
+
|
|
|
// Current theme configuration
|
|
|
private var currentThemeConfig: JsonObject? = null
|
|
|
-
|
|
|
+
|
|
|
// Current theme type
|
|
|
private var isDarkTheme: Boolean = true
|
|
|
-
|
|
|
+
|
|
|
+ // Current body theme class
|
|
|
+ private var bodyThemeClass: String = "vscode-dark"
|
|
|
+
|
|
|
// Prevent repeated dispose
|
|
|
private var isDisposed = false
|
|
|
private var themeInitialized = false
|
|
|
@@ -91,41 +94,57 @@ class WebViewManager(var project: Project) : Disposable, ThemeChangeListener {
|
|
|
*/
|
|
|
fun initializeThemeManager(resourceRoot: String) {
|
|
|
if (isDisposed or themeInitialized) return
|
|
|
-
|
|
|
+
|
|
|
logger.info("Initialize theme manager")
|
|
|
val themeManager = ThemeManager.getInstance()
|
|
|
themeManager.initialize(resourceRoot)
|
|
|
themeManager.addThemeChangeListener(this)
|
|
|
themeInitialized = true
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
/**
|
|
|
* Implement ThemeChangeListener interface, handle theme change events
|
|
|
*/
|
|
|
override fun onThemeChanged(themeConfig: JsonObject, isDarkTheme: Boolean) {
|
|
|
logger.info("Received theme change event, isDarkTheme: $isDarkTheme, config: ${themeConfig.size()}")
|
|
|
this.currentThemeConfig = themeConfig
|
|
|
+ this.bodyThemeClass = if (isDarkTheme) "vscode-dark" else "vscode-light"
|
|
|
this.isDarkTheme = isDarkTheme
|
|
|
-
|
|
|
+
|
|
|
// Send theme config to all WebView instances
|
|
|
sendThemeConfigToWebViews(themeConfig)
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
/**
|
|
|
* Send theme config to all WebView instances
|
|
|
*/
|
|
|
private fun sendThemeConfigToWebViews(themeConfig: JsonObject) {
|
|
|
logger.info("Send theme config to WebView")
|
|
|
-
|
|
|
+
|
|
|
// getAllWebViews().forEach { webView ->
|
|
|
try {
|
|
|
- getLatestWebView()?.sendThemeConfigToWebView(themeConfig)
|
|
|
+ getLatestWebView()?.sendThemeConfigToWebView(themeConfig, this.bodyThemeClass)
|
|
|
} catch (e: Exception) {
|
|
|
logger.error("Failed to send theme config to WebView", e)
|
|
|
}
|
|
|
// }
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Dispose the latest WebView instance
|
|
|
+ */
|
|
|
+ private fun disposeLatestWebView() {
|
|
|
+ latestWebView?.let { webView ->
|
|
|
+ try {
|
|
|
+ logger.info("Disposing latest WebView instance: ${webView.viewType}/${webView.viewId}")
|
|
|
+ webView.dispose()
|
|
|
+ } catch (e: Exception) {
|
|
|
+ logger.error("Failed to dispose latest WebView", e)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ latestWebView = null
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* Save HTML content to resource directory
|
|
|
* @param html HTML content
|
|
|
@@ -137,9 +156,9 @@ class WebViewManager(var project: Project) : Disposable, ThemeChangeListener {
|
|
|
logger.warn("Resource root directory does not exist, cannot save HTML content")
|
|
|
throw IOException("Resource root directory does not exist")
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
val filePath = resourceRootDir?.resolve(filename)
|
|
|
-
|
|
|
+
|
|
|
try {
|
|
|
if (filePath != null) {
|
|
|
logger.info("HTML content saved to: $filePath")
|
|
|
@@ -152,7 +171,7 @@ class WebViewManager(var project: Project) : Disposable, ThemeChangeListener {
|
|
|
throw e
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
/**
|
|
|
* Register WebView creation callback
|
|
|
* @param callback Callback object
|
|
|
@@ -161,7 +180,7 @@ class WebViewManager(var project: Project) : Disposable, ThemeChangeListener {
|
|
|
fun addCreationCallback(callback: WebViewCreationCallback, disposable: Disposable? = null) {
|
|
|
synchronized(creationCallbacks) {
|
|
|
creationCallbacks.add(callback)
|
|
|
-
|
|
|
+
|
|
|
// If Disposable is provided, automatically remove callback when disposed
|
|
|
if (disposable != null) {
|
|
|
Disposer.register(disposable, Disposable {
|
|
|
@@ -169,7 +188,7 @@ class WebViewManager(var project: Project) : Disposable, ThemeChangeListener {
|
|
|
})
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// If there is already a latest created WebView, notify immediately
|
|
|
latestWebView?.let { webview ->
|
|
|
ApplicationManager.getApplication().invokeLater {
|
|
|
@@ -177,7 +196,7 @@ class WebViewManager(var project: Project) : Disposable, ThemeChangeListener {
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
/**
|
|
|
* Remove WebView creation callback
|
|
|
* @param callback Callback object to remove
|
|
|
@@ -187,7 +206,7 @@ class WebViewManager(var project: Project) : Disposable, ThemeChangeListener {
|
|
|
creationCallbacks.remove(callback)
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
/**
|
|
|
* Notify all callbacks that WebView has been created
|
|
|
* @param instance Created WebView instance
|
|
|
@@ -196,7 +215,7 @@ class WebViewManager(var project: Project) : Disposable, ThemeChangeListener {
|
|
|
val callbacks = synchronized(creationCallbacks) {
|
|
|
creationCallbacks.toList() // Create a copy to avoid concurrent modification
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// Safely call callbacks in UI thread
|
|
|
ApplicationManager.getApplication().invokeLater {
|
|
|
callbacks.forEach { callback ->
|
|
|
@@ -208,33 +227,36 @@ class WebViewManager(var project: Project) : Disposable, ThemeChangeListener {
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
/**
|
|
|
* Register WebView provider and create WebView instance
|
|
|
*/
|
|
|
fun registerProvider(data: WebviewViewProviderData) {
|
|
|
- logger.info("Register WebView provider and create WebView instance: ${data.viewType}")
|
|
|
+ logger.info("Register WebView provider and create WebView instance: ${data.viewType} for project: ${project.name}")
|
|
|
val extension = data.extension
|
|
|
-
|
|
|
+
|
|
|
+ // Clean up any existing WebView for this project before creating a new one
|
|
|
+ disposeLatestWebView()
|
|
|
+
|
|
|
// Get location info from extension and set resource root directory
|
|
|
try {
|
|
|
@Suppress("UNCHECKED_CAST")
|
|
|
val location = extension?.get("location") as? Map<String, Any?>
|
|
|
val fsPath = location?.get("fsPath") as? String
|
|
|
-
|
|
|
+
|
|
|
if (fsPath != null) {
|
|
|
// Set resource root directory
|
|
|
val path = Paths.get(fsPath)
|
|
|
logger.info("Get resource directory path from extension: $path")
|
|
|
-
|
|
|
+
|
|
|
// Ensure the resource directory exists
|
|
|
if (!path.exists()) {
|
|
|
path.createDirectories()
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// Update resource root directory
|
|
|
resourceRootDir = path
|
|
|
-
|
|
|
+
|
|
|
// Initialize theme manager
|
|
|
initializeThemeManager(fsPath)
|
|
|
|
|
|
@@ -253,7 +275,7 @@ class WebViewManager(var project: Project) : Disposable, ThemeChangeListener {
|
|
|
|
|
|
val title = data.options["title"] as? String ?: data.viewType
|
|
|
val state = data.options["state"] as? Map<String, Any?> ?: emptyMap()
|
|
|
-
|
|
|
+
|
|
|
val webview = WebViewInstance(data.viewType, viewId, title, state,project,data.extension)
|
|
|
// DEBUG HERE!
|
|
|
// webview.showDebugWindow()
|
|
|
@@ -264,13 +286,13 @@ class WebViewManager(var project: Project) : Disposable, ThemeChangeListener {
|
|
|
|
|
|
// Set as the latest created WebView
|
|
|
latestWebView = webview
|
|
|
-
|
|
|
- logger.info("Create WebView instance: viewType=${data.viewType}, viewId=$viewId")
|
|
|
+
|
|
|
+ logger.info("Create WebView instance: viewType=${data.viewType}, viewId=$viewId for project: ${project.name}")
|
|
|
|
|
|
// Notify callback
|
|
|
notifyWebViewCreated(webview)
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
/**
|
|
|
* Get the latest created WebView instance
|
|
|
*/
|
|
|
@@ -297,13 +319,13 @@ class WebViewManager(var project: Project) : Disposable, ThemeChangeListener {
|
|
|
const msgStr = JSON.stringify(message);
|
|
|
${getLatestWebView()?.jsQuery?.inject("msgStr")}
|
|
|
};
|
|
|
-
|
|
|
+
|
|
|
// Inject VSCode API mock
|
|
|
globalThis.acquireVsCodeApi = (function() {
|
|
|
let acquired = false;
|
|
|
-
|
|
|
+
|
|
|
let state = JSON.parse('${encodedState}');
|
|
|
-
|
|
|
+
|
|
|
if (typeof window !== "undefined" && !window.receiveMessageFromPlugin) {
|
|
|
console.log("VSCodeAPIWrapper: Setting up receiveMessageFromPlugin for IDEA plugin compatibility");
|
|
|
window.receiveMessageFromPlugin = (message) => {
|
|
|
@@ -315,7 +337,7 @@ class WebViewManager(var project: Project) : Disposable, ThemeChangeListener {
|
|
|
window.dispatchEvent(event);
|
|
|
};
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
return () => {
|
|
|
if (acquired) {
|
|
|
throw new Error('An instance of the VS Code API has already been acquired');
|
|
|
@@ -337,21 +359,21 @@ class WebViewManager(var project: Project) : Disposable, ThemeChangeListener {
|
|
|
});
|
|
|
};
|
|
|
})();
|
|
|
-
|
|
|
+
|
|
|
// Clean up references to window parent for security
|
|
|
delete window.parent;
|
|
|
delete window.top;
|
|
|
delete window.frameElement;
|
|
|
-
|
|
|
+
|
|
|
console.log("VSCode API mock injected");
|
|
|
""")
|
|
|
|
|
|
|
|
|
|
|
|
logger.info("Received HTML update event: handle=${data.handle}, html length: ${data.htmlContent.length}")
|
|
|
-
|
|
|
+
|
|
|
val webView = getLatestWebView()
|
|
|
-
|
|
|
+
|
|
|
if (webView != null) {
|
|
|
try {
|
|
|
// If HTTP server is running
|
|
|
@@ -380,7 +402,7 @@ class WebViewManager(var project: Project) : Disposable, ThemeChangeListener {
|
|
|
// Delay sending theme config to ensure HTML is loaded
|
|
|
ApplicationManager.getApplication().invokeLater {
|
|
|
try {
|
|
|
- webView.sendThemeConfigToWebView(currentThemeConfig!!)
|
|
|
+ webView.sendThemeConfigToWebView(currentThemeConfig!!, this.bodyThemeClass)
|
|
|
} catch (e: Exception) {
|
|
|
logger.error("Failed to send theme config to WebView", e)
|
|
|
}
|
|
|
@@ -396,23 +418,46 @@ class WebViewManager(var project: Project) : Disposable, ThemeChangeListener {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Handle project switching by cleaning up current state
|
|
|
+ */
|
|
|
+ fun onProjectSwitch() {
|
|
|
+ logger.info("Handling project switch for WebViewManager")
|
|
|
+
|
|
|
+ // Dispose current WebView
|
|
|
+ disposeLatestWebView()
|
|
|
+
|
|
|
+ // Reset theme initialization flag to allow re-initialization
|
|
|
+ themeInitialized = false
|
|
|
+
|
|
|
+ // Clear theme data
|
|
|
+ currentThemeConfig = null
|
|
|
+
|
|
|
+ // Clear resource directory reference
|
|
|
+ resourceRootDir = null
|
|
|
+
|
|
|
+ logger.info("Project switch handled, WebViewManager state reset")
|
|
|
+ }
|
|
|
+
|
|
|
override fun dispose() {
|
|
|
if (isDisposed) {
|
|
|
logger.info("WebViewManager has already been disposed, ignoring repeated call")
|
|
|
return
|
|
|
}
|
|
|
isDisposed = true
|
|
|
-
|
|
|
- logger.info("Releasing WebViewManager resources...")
|
|
|
+
|
|
|
+ logger.info("Releasing WebViewManager resources for project: ${project.name}")
|
|
|
|
|
|
// Remove listener from theme manager
|
|
|
try {
|
|
|
- ThemeManager.getInstance().removeThemeChangeListener(this)
|
|
|
+ if (themeInitialized) {
|
|
|
+ ThemeManager.getInstance().removeThemeChangeListener(this)
|
|
|
+ }
|
|
|
} catch (e: Exception) {
|
|
|
logger.error("Failed to remove listener from theme manager", e)
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// Clean up resource directory
|
|
|
try {
|
|
|
// Only delete index.html file, keep other files
|
|
|
@@ -434,21 +479,18 @@ class WebViewManager(var project: Project) : Disposable, ThemeChangeListener {
|
|
|
logger.error("Failed to clean up index.html file", e)
|
|
|
}
|
|
|
|
|
|
- try {
|
|
|
- latestWebView?.dispose()
|
|
|
- } catch (e: Exception) {
|
|
|
- logger.error("Failed to release WebView resources", e)
|
|
|
- }
|
|
|
-
|
|
|
+ // Dispose WebView
|
|
|
+ disposeLatestWebView()
|
|
|
+
|
|
|
// Reset theme data
|
|
|
currentThemeConfig = null
|
|
|
-
|
|
|
+
|
|
|
// Clear callback list
|
|
|
synchronized(creationCallbacks) {
|
|
|
creationCallbacks.clear()
|
|
|
}
|
|
|
-
|
|
|
- logger.info("WebViewManager released")
|
|
|
+
|
|
|
+ logger.info("WebViewManager released for project: ${project.name}")
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -466,10 +508,10 @@ class WebViewInstance(
|
|
|
val extension: Map<String, Any?>
|
|
|
) : Disposable {
|
|
|
private val logger = Logger.getInstance(WebViewInstance::class.java)
|
|
|
-
|
|
|
+
|
|
|
// JCEF browser instance
|
|
|
val browser = JBCefBrowser.createBuilder().setOffScreenRendering(true).build()
|
|
|
-
|
|
|
+
|
|
|
// WebView state
|
|
|
private var isDisposed = false
|
|
|
|
|
|
@@ -479,16 +521,20 @@ class WebViewInstance(
|
|
|
// JSON serialization
|
|
|
private val gson = Gson()
|
|
|
|
|
|
+ // Body theme class (e.g., "vscode-dark" or "vscode-light")
|
|
|
+ private var bodyThemeClass: String = "vscode-dark"
|
|
|
+
|
|
|
// Coroutine scope
|
|
|
private val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
|
|
|
|
|
|
private var isPageLoaded = false
|
|
|
+ private var isInitialPageLoad = true
|
|
|
|
|
|
private var currentThemeConfig: JsonObject? = null
|
|
|
-
|
|
|
+
|
|
|
// Callback for page load completion
|
|
|
private var pageLoadCallback: (() -> Unit)? = null
|
|
|
-
|
|
|
+
|
|
|
init {
|
|
|
setupJSBridge()
|
|
|
// Enable resource loading interception
|
|
|
@@ -498,8 +544,9 @@ class WebViewInstance(
|
|
|
/**
|
|
|
* Send theme config to the specified WebView instance
|
|
|
*/
|
|
|
- fun sendThemeConfigToWebView(themeConfig: JsonObject) {
|
|
|
+ fun sendThemeConfigToWebView(themeConfig: JsonObject, bodyThemeClass: String) {
|
|
|
currentThemeConfig = themeConfig
|
|
|
+ this.bodyThemeClass = bodyThemeClass
|
|
|
if(isDisposed or !isPageLoaded) {
|
|
|
logger.warn("WebView has been disposed or not loaded, cannot send theme config:${isDisposed},${isPageLoaded}")
|
|
|
return
|
|
|
@@ -514,7 +561,7 @@ class WebViewInstance(
|
|
|
fun isPageLoaded(): Boolean {
|
|
|
return isPageLoaded
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
/**
|
|
|
* Set callback for page load completion
|
|
|
* @param callback Callback function to be called when page is loaded
|
|
|
@@ -522,7 +569,7 @@ class WebViewInstance(
|
|
|
fun setPageLoadCallback(callback: (() -> Unit)?) {
|
|
|
pageLoadCallback = callback
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
private fun injectTheme() {
|
|
|
if(currentThemeConfig == null) {
|
|
|
return
|
|
|
@@ -542,15 +589,17 @@ class WebViewInstance(
|
|
|
if (cssContent != null) {
|
|
|
val injectThemeScript = """
|
|
|
(function() {
|
|
|
- console.log("Ready to inject CSS variables into WebView")
|
|
|
function injectCSSVariables() {
|
|
|
+ if (window.__cssVariablesInjected) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
if(document.documentElement) {
|
|
|
// Convert cssContent to style attribute of html tag
|
|
|
try {
|
|
|
// Extract CSS variables (format: --name:value;)
|
|
|
const cssLines = `$cssContent`.split('\n');
|
|
|
const cssVariables = [];
|
|
|
-
|
|
|
+
|
|
|
// Process each line, extract CSS variable declarations
|
|
|
for (const line of cssLines) {
|
|
|
const trimmedLine = line.trim();
|
|
|
@@ -563,17 +612,25 @@ class WebViewInstance(
|
|
|
cssVariables.push(trimmedLine);
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// Merge extracted CSS variables into style attribute string
|
|
|
const styleAttrValue = cssVariables.join(' ');
|
|
|
-
|
|
|
+
|
|
|
// Set as style attribute of html tag
|
|
|
document.documentElement.setAttribute('style', styleAttrValue);
|
|
|
console.log("CSS variables set as style attribute of HTML tag");
|
|
|
+
|
|
|
+ // Add theme class to body element for styled-components compatibility
|
|
|
+ // Remove existing theme classes
|
|
|
+ document.body.classList.remove('vscode-dark', 'vscode-light');
|
|
|
+
|
|
|
+ // Add appropriate theme class based on current theme
|
|
|
+ document.body.classList.add('$bodyThemeClass');
|
|
|
+ console.log("Added theme class to body: $bodyThemeClass");
|
|
|
} catch (error) {
|
|
|
- console.error("Error processing CSS variables:", error);
|
|
|
+ console.error("Error processing CSS variables and theme classes:", error);
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// Keep original default style injection logic
|
|
|
if(document.head) {
|
|
|
// Inject default theme style into head, use id="_defaultStyles"
|
|
|
@@ -583,14 +640,14 @@ class WebViewInstance(
|
|
|
defaultStylesElement.id = '_defaultStyles';
|
|
|
document.head.appendChild(defaultStylesElement);
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// Add default_themes.css content
|
|
|
defaultStylesElement.textContent = `
|
|
|
html {
|
|
|
background: var(--vscode-sideBar-background);
|
|
|
scrollbar-color: var(--vscode-scrollbarSlider-background) var(--vscode-sideBar-background);
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
body {
|
|
|
overscroll-behavior-x: none;
|
|
|
background-color: transparent;
|
|
|
@@ -601,24 +658,24 @@ class WebViewInstance(
|
|
|
margin: 0;
|
|
|
padding: 0 20px;
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
img, video {
|
|
|
max-width: 100%;
|
|
|
max-height: 100%;
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
a, a code {
|
|
|
color: var(--vscode-textLink-foreground);
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
p > a {
|
|
|
text-decoration: var(--text-link-decoration);
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
a:hover {
|
|
|
color: var(--vscode-textLink-activeForeground);
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
a:focus,
|
|
|
input:focus,
|
|
|
select:focus,
|
|
|
@@ -626,7 +683,7 @@ class WebViewInstance(
|
|
|
outline: 1px solid -webkit-focus-ring-color;
|
|
|
outline-offset: -1px;
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
code {
|
|
|
font-family: var(--monaco-monospace-font);
|
|
|
color: var(--vscode-textPreformat-foreground);
|
|
|
@@ -634,16 +691,16 @@ class WebViewInstance(
|
|
|
padding: 1px 3px;
|
|
|
border-radius: 4px;
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
pre code {
|
|
|
padding: 0;
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
blockquote {
|
|
|
background: var(--vscode-textBlockQuote-background);
|
|
|
border-color: var(--vscode-textBlockQuote-border);
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
kbd {
|
|
|
background-color: var(--vscode-keybindingLabel-background);
|
|
|
color: var(--vscode-keybindingLabel-foreground);
|
|
|
@@ -656,16 +713,16 @@ class WebViewInstance(
|
|
|
vertical-align: middle;
|
|
|
padding: 1px 3px;
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
::-webkit-scrollbar {
|
|
|
width: 10px;
|
|
|
height: 10px;
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
::-webkit-scrollbar-corner {
|
|
|
background-color: var(--vscode-editor-background);
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
::-webkit-scrollbar-thumb {
|
|
|
background-color: var(--vscode-scrollbarSlider-background);
|
|
|
}
|
|
|
@@ -683,6 +740,7 @@ class WebViewInstance(
|
|
|
}
|
|
|
`;
|
|
|
console.log("Default style injected to id=_defaultStyles");
|
|
|
+ window.__cssVariablesInjected = true;
|
|
|
}
|
|
|
} else {
|
|
|
// If html tag does not exist yet, wait for DOM to load and try again
|
|
|
@@ -801,7 +859,7 @@ class WebViewInstance(
|
|
|
return true
|
|
|
}
|
|
|
}, browser.cefBrowser)
|
|
|
-
|
|
|
+
|
|
|
// Register load handler
|
|
|
client.addLoadHandler(object : CefLoadHandlerAdapter() {
|
|
|
override fun onLoadingStateChange(
|
|
|
@@ -812,7 +870,7 @@ class WebViewInstance(
|
|
|
) {
|
|
|
logger.info("WebView loading state changed: isLoading=$isLoading, canGoBack=$canGoBack, canGoForward=$canGoForward")
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
override fun onLoadStart(
|
|
|
browser: CefBrowser?,
|
|
|
frame: CefFrame?,
|
|
|
@@ -820,8 +878,9 @@ class WebViewInstance(
|
|
|
) {
|
|
|
logger.info("WebView started loading: ${frame?.url}, transition type: $transitionType")
|
|
|
isPageLoaded = false
|
|
|
+ isInitialPageLoad = true
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
override fun onLoadEnd(
|
|
|
browser: CefBrowser?,
|
|
|
frame: CefFrame?,
|
|
|
@@ -829,11 +888,14 @@ class WebViewInstance(
|
|
|
) {
|
|
|
logger.info("WebView finished loading: ${frame?.url}, status code: $httpStatusCode")
|
|
|
isPageLoaded = true
|
|
|
- injectTheme()
|
|
|
- // Notify page load completion
|
|
|
- pageLoadCallback?.invoke()
|
|
|
+
|
|
|
+ if (isInitialPageLoad) {
|
|
|
+ injectTheme()
|
|
|
+ pageLoadCallback?.invoke()
|
|
|
+ isInitialPageLoad = false
|
|
|
+ }
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
override fun onLoadError(
|
|
|
browser: CefBrowser?,
|
|
|
frame: CefFrame?,
|
|
|
@@ -887,7 +949,7 @@ class WebViewInstance(
|
|
|
logger.error("Failed to enable WebView resource interception", e)
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
/**
|
|
|
* Load URL
|
|
|
*/
|
|
|
@@ -897,7 +959,7 @@ class WebViewInstance(
|
|
|
browser.loadURL(url)
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
/**
|
|
|
* Load HTML content
|
|
|
*/
|
|
|
@@ -911,7 +973,7 @@ class WebViewInstance(
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
/**
|
|
|
* Execute JavaScript
|
|
|
*/
|
|
|
@@ -921,7 +983,7 @@ class WebViewInstance(
|
|
|
browser.cefBrowser.executeJavaScript(script, browser.cefBrowser.url, 0)
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
/**
|
|
|
* Open developer tools
|
|
|
*/
|
|
|
@@ -939,7 +1001,7 @@ class WebViewInstance(
|
|
|
frame.add(browser.component)
|
|
|
frame.setSize(800, 600)
|
|
|
frame.isVisible = true
|
|
|
-
|
|
|
+
|
|
|
// Optional: Add dev tools button
|
|
|
val toolbar = JPanel()
|
|
|
val devToolsButton = JButton("Open DevTools")
|
|
|
@@ -949,7 +1011,7 @@ class WebViewInstance(
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
override fun dispose() {
|
|
|
if (!isDisposed) {
|
|
|
browser.dispose()
|
|
|
@@ -957,4 +1019,4 @@ class WebViewInstance(
|
|
|
logger.info("WebView instance released: $viewType/$viewId")
|
|
|
}
|
|
|
}
|
|
|
-}
|
|
|
+}
|