Move Webview HTML generation out of interface-utils.ts

This moves the Webview HTML generation used by `AbstractWebview` out of
`interface-utils.ts` and into a new file `webview-html.ts` in the
`common/vscode` directory.
This commit is contained in:
Koen Vlaswinkel
2023-06-06 11:12:05 +02:00
parent a7a24fc0e9
commit 461ff9bd21
3 changed files with 102 additions and 107 deletions

View File

@@ -11,11 +11,7 @@ import { join } from "path";
import { DisposableObject, DisposeHandler } from "../../pure/disposable-object";
import { tmpDir } from "../../helpers";
import {
getHtmlForWebview,
WebviewMessage,
WebviewView,
} from "../../interface-utils";
import { getHtmlForWebview, WebviewMessage, WebviewView } from "./webview-html";
export type WebviewPanelConfig = {
viewId: string;

View File

@@ -0,0 +1,101 @@
import { ExtensionContext, Uri, Webview } from "vscode";
import { randomBytes } from "crypto";
import { EOL } from "os";
export type WebviewView =
| "results"
| "compare"
| "variant-analysis"
| "data-flow-paths"
| "data-extensions-editor";
export interface WebviewMessage {
t: string;
}
/**
* Returns HTML to populate the given webview.
* Uses a content security policy that only loads the given script.
*/
export function getHtmlForWebview(
ctx: ExtensionContext,
webview: Webview,
view: WebviewView,
{
allowInlineStyles,
allowWasmEval,
}: {
allowInlineStyles?: boolean;
allowWasmEval?: boolean;
} = {
allowInlineStyles: false,
allowWasmEval: false,
},
): string {
const scriptUriOnDisk = Uri.file(ctx.asAbsolutePath("out/webview.js"));
const stylesheetUrisOnDisk = [
Uri.file(ctx.asAbsolutePath("out/webview.css")),
];
// Convert the on-disk URIs into webview URIs.
const scriptWebviewUri = webview.asWebviewUri(scriptUriOnDisk);
const stylesheetWebviewUris = stylesheetUrisOnDisk.map(
(stylesheetUriOnDisk) => webview.asWebviewUri(stylesheetUriOnDisk),
);
// Use a nonce in the content security policy to uniquely identify the above resources.
const nonce = getNonce();
const stylesheetsHtmlLines = allowInlineStyles
? stylesheetWebviewUris.map((uri) => createStylesLinkWithoutNonce(uri))
: stylesheetWebviewUris.map((uri) => createStylesLinkWithNonce(nonce, uri));
const styleSrc = allowInlineStyles
? `${webview.cspSource} vscode-file: 'unsafe-inline'`
: `'nonce-${nonce}'`;
const fontSrc = webview.cspSource;
/*
* Content security policy:
* default-src: allow nothing by default.
* script-src:
* - allow the given script, using the nonce.
* - 'wasm-unsafe-eval: allow loading WebAssembly modules if necessary.
* style-src: allow only the given stylesheet, using the nonce.
* connect-src: only allow fetch calls to webview resource URIs
* (this is used to load BQRS result files).
*/
return `
<html>
<head>
<meta http-equiv="Content-Security-Policy"
content="default-src 'none'; script-src 'nonce-${nonce}'${
allowWasmEval ? " 'wasm-unsafe-eval'" : ""
}; font-src ${fontSrc}; style-src ${styleSrc}; connect-src ${
webview.cspSource
};">
${stylesheetsHtmlLines.join(` ${EOL}`)}
</head>
<body>
<div id=root data-view="${view}">
</div>
<script nonce="${nonce}" src="${scriptWebviewUri}">
</script>
</body>
</html>`;
}
/** Gets a nonce string created with 128 bits of entropy. */
function getNonce(): string {
return randomBytes(16).toString("base64");
}
function createStylesLinkWithNonce(nonce: string, uri: Uri): string {
return `<link nonce="${nonce}" rel="stylesheet" href="${uri}">`;
}
function createStylesLinkWithoutNonce(uri: Uri): string {
return `<link rel="stylesheet" href="${uri}">`;
}

View File

@@ -1,12 +1,8 @@
import { randomBytes } from "crypto";
import { EOL } from "os";
import {
Uri,
Location,
Range,
ExtensionContext,
WebviewPanel,
Webview,
workspace,
window as Window,
ViewColumn,
@@ -30,11 +26,6 @@ import {
* interface.ts and compare-interface.ts.
*/
/** Gets a nonce string created with 128 bits of entropy. */
export function getNonce(): string {
return randomBytes(16).toString("base64");
}
/**
* Whether to force webview to reveal
*/
@@ -109,91 +100,6 @@ export function tryResolveLocation(
}
}
export type WebviewView =
| "results"
| "compare"
| "variant-analysis"
| "data-flow-paths"
| "data-extensions-editor";
export interface WebviewMessage {
t: string;
}
/**
* Returns HTML to populate the given webview.
* Uses a content security policy that only loads the given script.
*/
export function getHtmlForWebview(
ctx: ExtensionContext,
webview: Webview,
view: WebviewView,
{
allowInlineStyles,
allowWasmEval,
}: {
allowInlineStyles?: boolean;
allowWasmEval?: boolean;
} = {
allowInlineStyles: false,
allowWasmEval: false,
},
): string {
const scriptUriOnDisk = Uri.file(ctx.asAbsolutePath("out/webview.js"));
const stylesheetUrisOnDisk = [
Uri.file(ctx.asAbsolutePath("out/webview.css")),
];
// Convert the on-disk URIs into webview URIs.
const scriptWebviewUri = webview.asWebviewUri(scriptUriOnDisk);
const stylesheetWebviewUris = stylesheetUrisOnDisk.map(
(stylesheetUriOnDisk) => webview.asWebviewUri(stylesheetUriOnDisk),
);
// Use a nonce in the content security policy to uniquely identify the above resources.
const nonce = getNonce();
const stylesheetsHtmlLines = allowInlineStyles
? stylesheetWebviewUris.map((uri) => createStylesLinkWithoutNonce(uri))
: stylesheetWebviewUris.map((uri) => createStylesLinkWithNonce(nonce, uri));
const styleSrc = allowInlineStyles
? `${webview.cspSource} vscode-file: 'unsafe-inline'`
: `'nonce-${nonce}'`;
const fontSrc = webview.cspSource;
/*
* Content security policy:
* default-src: allow nothing by default.
* script-src:
* - allow the given script, using the nonce.
* - 'wasm-unsafe-eval: allow loading WebAssembly modules if necessary.
* style-src: allow only the given stylesheet, using the nonce.
* connect-src: only allow fetch calls to webview resource URIs
* (this is used to load BQRS result files).
*/
return `
<html>
<head>
<meta http-equiv="Content-Security-Policy"
content="default-src 'none'; script-src 'nonce-${nonce}'${
allowWasmEval ? " 'wasm-unsafe-eval'" : ""
}; font-src ${fontSrc}; style-src ${styleSrc}; connect-src ${
webview.cspSource
};">
${stylesheetsHtmlLines.join(` ${EOL}`)}
</head>
<body>
<div id=root data-view="${view}">
</div>
<script nonce="${nonce}" src="${scriptWebviewUri}">
</script>
</body>
</html>`;
}
export async function showResolvableLocation(
loc: ResolvableLocationValue,
databaseItem: DatabaseItem,
@@ -279,11 +185,3 @@ export async function jumpToLocation(
}
}
}
function createStylesLinkWithNonce(nonce: string, uri: Uri): string {
return `<link nonce="${nonce}" rel="stylesheet" href="${uri}">`;
}
function createStylesLinkWithoutNonce(uri: Uri): string {
return `<link rel="stylesheet" href="${uri}">`;
}