-
Notifications
You must be signed in to change notification settings - Fork 2.6k
Revert "♻️ refactor(webview): move webview HTML generation to WebviewHTMLManager" #2502
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -28,7 +28,7 @@ import { supportPrompt } from "../../shared/support-prompt" | |||||
| import { GlobalFileNames } from "../../shared/globalFileNames" | ||||||
| import { HistoryItem } from "../../shared/HistoryItem" | ||||||
| import { ExtensionMessage } from "../../shared/ExtensionMessage" | ||||||
| import { Mode, PromptComponent, defaultModeSlug } from "../../shared/modes" | ||||||
| import { Mode, PromptComponent, defaultModeSlug, getModeBySlug, getGroupName } from "../../shared/modes" | ||||||
| import { experimentDefault } from "../../shared/experiments" | ||||||
| import { formatLanguage } from "../../shared/language" | ||||||
| import { Terminal, TERMINAL_SHELL_INTEGRATION_TIMEOUT } from "../../integrations/terminal/Terminal" | ||||||
|
|
@@ -47,7 +47,8 @@ import { CustomModesManager } from "../config/CustomModesManager" | |||||
| import { buildApiHandler } from "../../api" | ||||||
| import { ACTION_NAMES } from "../CodeActionProvider" | ||||||
| import { Cline, ClineOptions } from "../Cline" | ||||||
| import { WebviewHTMLManager } from "./WebviewHTMLManager" | ||||||
| import { getNonce } from "./getNonce" | ||||||
| import { getUri } from "./getUri" | ||||||
| import { telemetryService } from "../../services/telemetry/TelemetryService" | ||||||
| import { getWorkspacePath } from "../../utils/path" | ||||||
| import { webviewMessageHandler } from "./webviewMessageHandler" | ||||||
|
|
@@ -81,7 +82,6 @@ export class ClineProvider extends EventEmitter<ClineProviderEvents> implements | |||||
| public readonly contextProxy: ContextProxy | ||||||
| public readonly providerSettingsManager: ProviderSettingsManager | ||||||
| public readonly customModesManager: CustomModesManager | ||||||
| private readonly webviewHTMLManager: WebviewHTMLManager | ||||||
|
|
||||||
| constructor( | ||||||
| readonly context: vscode.ExtensionContext, | ||||||
|
|
@@ -92,7 +92,6 @@ export class ClineProvider extends EventEmitter<ClineProviderEvents> implements | |||||
|
|
||||||
| this.log("ClineProvider instantiated") | ||||||
| this.contextProxy = new ContextProxy(context) | ||||||
| this.webviewHTMLManager = new WebviewHTMLManager(this.contextProxy) | ||||||
| ClineProvider.activeInstances.add(this) | ||||||
|
|
||||||
| // Register this provider with the telemetry service to enable it to add | ||||||
|
|
@@ -394,8 +393,8 @@ export class ClineProvider extends EventEmitter<ClineProviderEvents> implements | |||||
|
|
||||||
| webviewView.webview.html = | ||||||
| this.contextProxy.extensionMode === vscode.ExtensionMode.Development | ||||||
| ? await this.webviewHTMLManager.getHMRHtmlContent(webviewView.webview) | ||||||
| : this.webviewHTMLManager.getHtmlContent(webviewView.webview) | ||||||
| ? await this.getHMRHtmlContent(webviewView.webview) | ||||||
| : this.getHtmlContent(webviewView.webview) | ||||||
|
|
||||||
| // Sets up an event listener to listen for messages passed from the webview view context | ||||||
| // and executes code based on the message that is recieved | ||||||
|
|
@@ -598,6 +597,187 @@ export class ClineProvider extends EventEmitter<ClineProviderEvents> implements | |||||
| await this.view?.webview.postMessage(message) | ||||||
| } | ||||||
|
|
||||||
| private async getHMRHtmlContent(webview: vscode.Webview): Promise<string> { | ||||||
| // Try to read the port from the file | ||||||
| let localPort = "5173" // Default fallback | ||||||
| try { | ||||||
| const fs = require("fs") | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consider replacing the synchronous |
||||||
| const path = require("path") | ||||||
| const portFilePath = path.resolve(__dirname, "../.vite-port") | ||||||
|
|
||||||
| if (fs.existsSync(portFilePath)) { | ||||||
| localPort = fs.readFileSync(portFilePath, "utf8").trim() | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Validate and sanitize the value read from the
Suggested change
|
||||||
| console.log(`[ClineProvider:Vite] Using Vite server port from ${portFilePath}: ${localPort}`) | ||||||
| } else { | ||||||
| console.log( | ||||||
| `[ClineProvider:Vite] Port file not found at ${portFilePath}, using default port: ${localPort}`, | ||||||
| ) | ||||||
| } | ||||||
| } catch (err) { | ||||||
| console.error("[ClineProvider:Vite] Failed to read Vite port file:", err) | ||||||
| // Continue with default port if file reading fails | ||||||
| } | ||||||
|
|
||||||
| const localServerUrl = `localhost:${localPort}` | ||||||
|
|
||||||
| // Check if local dev server is running. | ||||||
| try { | ||||||
| await axios.get(`http://${localServerUrl}`) | ||||||
| } catch (error) { | ||||||
| vscode.window.showErrorMessage(t("common:errors.hmr_not_running")) | ||||||
|
|
||||||
| return this.getHtmlContent(webview) | ||||||
| } | ||||||
|
|
||||||
| const nonce = getNonce() | ||||||
|
|
||||||
| const stylesUri = getUri(webview, this.contextProxy.extensionUri, [ | ||||||
| "webview-ui", | ||||||
| "build", | ||||||
| "assets", | ||||||
| "index.css", | ||||||
| ]) | ||||||
|
|
||||||
| const codiconsUri = getUri(webview, this.contextProxy.extensionUri, [ | ||||||
| "node_modules", | ||||||
| "@vscode", | ||||||
| "codicons", | ||||||
| "dist", | ||||||
| "codicon.css", | ||||||
| ]) | ||||||
|
|
||||||
| const imagesUri = getUri(webview, this.contextProxy.extensionUri, ["assets", "images"]) | ||||||
|
|
||||||
| const file = "src/index.tsx" | ||||||
| const scriptUri = `http://${localServerUrl}/${file}` | ||||||
|
|
||||||
| const reactRefresh = /*html*/ ` | ||||||
| <script nonce="${nonce}" type="module"> | ||||||
| import RefreshRuntime from "http://localhost:${localPort}/@react-refresh" | ||||||
| RefreshRuntime.injectIntoGlobalHook(window) | ||||||
| window.$RefreshReg$ = () => {} | ||||||
| window.$RefreshSig$ = () => (type) => type | ||||||
| window.__vite_plugin_react_preamble_installed__ = true | ||||||
| </script> | ||||||
| ` | ||||||
|
|
||||||
| const csp = [ | ||||||
| "default-src 'none'", | ||||||
| `font-src ${webview.cspSource}`, | ||||||
| `style-src ${webview.cspSource} 'unsafe-inline' https://* http://${localServerUrl} http://0.0.0.0:${localPort}`, | ||||||
| `img-src ${webview.cspSource} data:`, | ||||||
| `script-src 'unsafe-eval' ${webview.cspSource} https://* https://*.posthog.com http://${localServerUrl} http://0.0.0.0:${localPort} 'nonce-${nonce}'`, | ||||||
| `connect-src https://* https://*.posthog.com ws://${localServerUrl} ws://0.0.0.0:${localPort} http://${localServerUrl} http://0.0.0.0:${localPort}`, | ||||||
| ] | ||||||
|
|
||||||
| return /*html*/ ` | ||||||
| <!DOCTYPE html> | ||||||
| <html lang="en"> | ||||||
| <head> | ||||||
| <meta charset="utf-8"> | ||||||
| <meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no"> | ||||||
| <meta http-equiv="Content-Security-Policy" content="${csp.join("; ")}"> | ||||||
| <link rel="stylesheet" type="text/css" href="${stylesUri}"> | ||||||
| <link href="${codiconsUri}" rel="stylesheet" /> | ||||||
| <script nonce="${nonce}"> | ||||||
| window.IMAGES_BASE_URI = "${imagesUri}" | ||||||
| </script> | ||||||
| <title>Roo Code</title> | ||||||
| </head> | ||||||
| <body> | ||||||
| <div id="root"></div> | ||||||
| ${reactRefresh} | ||||||
| <script type="module" src="${scriptUri}"></script> | ||||||
| </body> | ||||||
| </html> | ||||||
| ` | ||||||
| } | ||||||
|
|
||||||
| /** | ||||||
| * Defines and returns the HTML that should be rendered within the webview panel. | ||||||
| * | ||||||
| * @remarks This is also the place where references to the React webview build files | ||||||
| * are created and inserted into the webview HTML. | ||||||
| * | ||||||
| * @param webview A reference to the extension webview | ||||||
| * @param extensionUri The URI of the directory containing the extension | ||||||
| * @returns A template string literal containing the HTML that should be | ||||||
| * rendered within the webview panel | ||||||
| */ | ||||||
| private getHtmlContent(webview: vscode.Webview): string { | ||||||
| // Get the local path to main script run in the webview, | ||||||
| // then convert it to a uri we can use in the webview. | ||||||
|
|
||||||
| // The CSS file from the React build output | ||||||
| const stylesUri = getUri(webview, this.contextProxy.extensionUri, [ | ||||||
| "webview-ui", | ||||||
| "build", | ||||||
| "assets", | ||||||
| "index.css", | ||||||
| ]) | ||||||
| // The JS file from the React build output | ||||||
| const scriptUri = getUri(webview, this.contextProxy.extensionUri, ["webview-ui", "build", "assets", "index.js"]) | ||||||
|
|
||||||
| // The codicon font from the React build output | ||||||
| // https://github.com/microsoft/vscode-extension-samples/blob/main/webview-codicons-sample/src/extension.ts | ||||||
| // we installed this package in the extension so that we can access it how its intended from the extension (the font file is likely bundled in vscode), and we just import the css fileinto our react app we don't have access to it | ||||||
| // don't forget to add font-src ${webview.cspSource}; | ||||||
| const codiconsUri = getUri(webview, this.contextProxy.extensionUri, [ | ||||||
| "node_modules", | ||||||
| "@vscode", | ||||||
| "codicons", | ||||||
| "dist", | ||||||
| "codicon.css", | ||||||
| ]) | ||||||
|
|
||||||
| const imagesUri = getUri(webview, this.contextProxy.extensionUri, ["assets", "images"]) | ||||||
|
|
||||||
| // const scriptUri = webview.asWebviewUri(vscode.Uri.joinPath(this._extensionUri, "assets", "main.js")) | ||||||
|
|
||||||
| // const styleResetUri = webview.asWebviewUri(vscode.Uri.joinPath(this._extensionUri, "assets", "reset.css")) | ||||||
| // const styleVSCodeUri = webview.asWebviewUri(vscode.Uri.joinPath(this._extensionUri, "assets", "vscode.css")) | ||||||
|
|
||||||
| // // Same for stylesheet | ||||||
| // const stylesheetUri = webview.asWebviewUri(vscode.Uri.joinPath(this._extensionUri, "assets", "main.css")) | ||||||
|
|
||||||
| // Use a nonce to only allow a specific script to be run. | ||||||
| /* | ||||||
| content security policy of your webview to only allow scripts that have a specific nonce | ||||||
| create a content security policy meta tag so that only loading scripts with a nonce is allowed | ||||||
| As your extension grows you will likely want to add custom styles, fonts, and/or images to your webview. If you do, you will need to update the content security policy meta tag to explicity allow for these resources. E.g. | ||||||
| <meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src ${webview.cspSource}; font-src ${webview.cspSource}; img-src ${webview.cspSource} https:; script-src 'nonce-${nonce}';"> | ||||||
| - 'unsafe-inline' is required for styles due to vscode-webview-toolkit's dynamic style injection | ||||||
| - since we pass base64 images to the webview, we need to specify img-src ${webview.cspSource} data:; | ||||||
|
|
||||||
| in meta tag we add nonce attribute: A cryptographic nonce (only used once) to allow scripts. The server must generate a unique nonce value each time it transmits a policy. It is critical to provide a nonce that cannot be guessed as bypassing a resource's policy is otherwise trivial. | ||||||
| */ | ||||||
| const nonce = getNonce() | ||||||
|
|
||||||
| // Tip: Install the es6-string-html VS Code extension to enable code highlighting below | ||||||
| return /*html*/ ` | ||||||
| <!DOCTYPE html> | ||||||
| <html lang="en"> | ||||||
| <head> | ||||||
| <meta charset="utf-8"> | ||||||
| <meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no"> | ||||||
| <meta name="theme-color" content="#000000"> | ||||||
| <meta http-equiv="Content-Security-Policy" content="default-src 'none'; font-src ${webview.cspSource}; style-src ${webview.cspSource} 'unsafe-inline'; img-src ${webview.cspSource} data:; script-src 'nonce-${nonce}' https://us-assets.i.posthog.com; connect-src https://openrouter.ai https://api.requesty.ai https://us.i.posthog.com https://us-assets.i.posthog.com;"> | ||||||
| <link rel="stylesheet" type="text/css" href="${stylesUri}"> | ||||||
| <link href="${codiconsUri}" rel="stylesheet" /> | ||||||
| <script nonce="${nonce}"> | ||||||
| window.IMAGES_BASE_URI = "${imagesUri}" | ||||||
| </script> | ||||||
| <title>Roo Code</title> | ||||||
| </head> | ||||||
| <body> | ||||||
| <noscript>You need to enable JavaScript to run this app.</noscript> | ||||||
| <div id="root"></div> | ||||||
| <script nonce="${nonce}" type="module" src="${scriptUri}"></script> | ||||||
| </body> | ||||||
| </html> | ||||||
| ` | ||||||
| } | ||||||
|
|
||||||
| /** | ||||||
| * Sets up an event listener to listen for messages passed from the webview context and | ||||||
| * executes code based on the message that is recieved. | ||||||
|
|
||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Avoid mixing import statements with inline require. You already import
fs/promisesat the top; consider using the imported module to maintain consistency.