Add vscode.env.webviewResourceRoot api
Fixes #72155 Adds a constant to the api that tracks the root path for resources inside of webviews. This is required because we will not be able to use `vscode-resource:` uris on the web. Our current approach is to rewrite the html we are given but there are almost certainly going to be cases where we don't get this quite right. Adopts the new api for the markdown preview
This commit is contained in:
parent
6520c22b83
commit
7f3d3d835f
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -4,7 +4,8 @@
|
|||
"description": "%description%",
|
||||
"version": "1.0.0",
|
||||
"icon": "icon.png",
|
||||
"publisher": "vscode",
|
||||
"publisher": "vscode",
|
||||
"enableProposedApi": true,
|
||||
"license": "MIT",
|
||||
"aiKey": "AIF-d9b70cd4-b9f9-4d70-929b-a071c400b217",
|
||||
"engines": {
|
||||
|
|
|
@ -19,7 +19,7 @@ const settings = getSettings();
|
|||
const vscode = acquireVsCodeApi();
|
||||
|
||||
// Set VS Code state
|
||||
let state = getData('data-state');
|
||||
let state = getData<{ line: number }>('data-state');
|
||||
vscode.setState(state);
|
||||
|
||||
const messaging = createPosterForVsCode(vscode);
|
||||
|
@ -131,8 +131,8 @@ document.addEventListener('click', event => {
|
|||
if (node.getAttribute('href').startsWith('#')) {
|
||||
break;
|
||||
}
|
||||
if (node.href.startsWith('file://') || node.href.startsWith('vscode-resource:')) {
|
||||
const [path, fragment] = node.href.replace(/^(file:\/\/|vscode-resource:)/i, '').split('#');
|
||||
if (node.href.startsWith('file://') || node.href.startsWith('vscode-resource:') || node.href.startsWith(settings.webviewResourceRoot)) {
|
||||
const [path, fragment] = node.href.replace(/^(file:\/\/|vscode-resource:)/i, '').replace(new RegExp(`^${escapeRegExp(settings.webviewResourceRoot)}`)).split('#');
|
||||
messaging.postMessage('clickLink', { path, fragment });
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
@ -157,4 +157,8 @@ if (settings.scrollEditorWithPreview) {
|
|||
}
|
||||
}
|
||||
}, 50));
|
||||
}
|
||||
|
||||
function escapeRegExp(text: string) {
|
||||
return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
|
||||
}
|
|
@ -4,18 +4,19 @@
|
|||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
export interface PreviewSettings {
|
||||
source: string;
|
||||
line: number;
|
||||
lineCount: number;
|
||||
scrollPreviewWithEditor?: boolean;
|
||||
scrollEditorWithPreview: boolean;
|
||||
disableSecurityWarnings: boolean;
|
||||
doubleClickToSwitchToEditor: boolean;
|
||||
readonly source: string;
|
||||
readonly line: number;
|
||||
readonly lineCount: number;
|
||||
readonly scrollPreviewWithEditor?: boolean;
|
||||
readonly scrollEditorWithPreview: boolean;
|
||||
readonly disableSecurityWarnings: boolean;
|
||||
readonly doubleClickToSwitchToEditor: boolean;
|
||||
readonly webviewResourceRoot: string;
|
||||
}
|
||||
|
||||
let cachedSettings: PreviewSettings | undefined = undefined;
|
||||
|
||||
export function getData(key: string): PreviewSettings {
|
||||
export function getData<T = {}>(key: string): T {
|
||||
const element = document.getElementById('vscode-markdown-preview-data');
|
||||
if (element) {
|
||||
const data = element.getAttribute(key);
|
||||
|
|
|
@ -14,6 +14,7 @@ import { Logger } from '../logger';
|
|||
import { ContentSecurityPolicyArbiter, MarkdownPreviewSecurityLevel } from '../security';
|
||||
import { MarkdownPreviewConfigurationManager, MarkdownPreviewConfiguration } from './previewConfig';
|
||||
import { MarkdownContributionProvider } from '../markdownExtensions';
|
||||
import { toResoruceUri } from '../util/resources';
|
||||
|
||||
/**
|
||||
* Strings used inside the markdown preview.
|
||||
|
@ -63,7 +64,8 @@ export class MarkdownContentProvider {
|
|||
scrollPreviewWithEditor: config.scrollPreviewWithEditor,
|
||||
scrollEditorWithPreview: config.scrollEditorWithPreview,
|
||||
doubleClickToSwitchToEditor: config.doubleClickToSwitchToEditor,
|
||||
disableSecurityWarnings: this.cspArbiter.shouldDisableSecurityWarnings()
|
||||
disableSecurityWarnings: this.cspArbiter.shouldDisableSecurityWarnings(),
|
||||
webviewResourceRoot: vscode.env.webviewResourceRoot,
|
||||
};
|
||||
|
||||
this.logger.log('provideTextDocumentContent', initialData);
|
||||
|
@ -84,7 +86,7 @@ export class MarkdownContentProvider {
|
|||
data-state="${escapeAttribute(JSON.stringify(state || {}))}">
|
||||
<script src="${this.extensionResourcePath('pre.js')}" nonce="${nonce}"></script>
|
||||
${this.getStyles(sourceUri, nonce, config, state)}
|
||||
<base href="${markdownDocument.uri.with({ scheme: 'vscode-resource' }).toString(true)}">
|
||||
<base href="${toResoruceUri(markdownDocument.uri)}">
|
||||
</head>
|
||||
<body class="vscode-body ${config.scrollBeyondLastLine ? 'scrollBeyondLastLine' : ''} ${config.wordWrap ? 'wordWrap' : ''} ${config.markEditorSelection ? 'showEditorSelection' : ''}">
|
||||
${body}
|
||||
|
@ -108,8 +110,7 @@ export class MarkdownContentProvider {
|
|||
}
|
||||
|
||||
private extensionResourcePath(mediaFile: string): string {
|
||||
return vscode.Uri.file(this.context.asAbsolutePath(path.join('media', mediaFile)))
|
||||
.with({ scheme: 'vscode-resource' })
|
||||
return toResoruceUri(vscode.Uri.file(this.context.asAbsolutePath(path.join('media', mediaFile))))
|
||||
.toString();
|
||||
}
|
||||
|
||||
|
@ -124,23 +125,17 @@ export class MarkdownContentProvider {
|
|||
|
||||
// Assume it must be a local file
|
||||
if (path.isAbsolute(href)) {
|
||||
return vscode.Uri.file(href)
|
||||
.with({ scheme: 'vscode-resource' })
|
||||
.toString();
|
||||
return toResoruceUri(vscode.Uri.file(href)).toString();
|
||||
}
|
||||
|
||||
// Use a workspace relative path if there is a workspace
|
||||
const root = vscode.workspace.getWorkspaceFolder(resource);
|
||||
if (root) {
|
||||
return vscode.Uri.file(path.join(root.uri.fsPath, href))
|
||||
.with({ scheme: 'vscode-resource' })
|
||||
.toString();
|
||||
return toResoruceUri(vscode.Uri.file(path.join(root.uri.fsPath, href))).toString();
|
||||
}
|
||||
|
||||
// Otherwise look relative to the markdown file
|
||||
return vscode.Uri.file(path.join(path.dirname(resource.fsPath), href))
|
||||
.with({ scheme: 'vscode-resource' })
|
||||
.toString();
|
||||
return toResoruceUri(vscode.Uri.file(path.join(path.dirname(resource.fsPath), href))).toString();
|
||||
}
|
||||
|
||||
private computeCustomStyleSheetIncludes(resource: vscode.Uri, config: MarkdownPreviewConfiguration): string {
|
||||
|
@ -197,17 +192,17 @@ export class MarkdownContentProvider {
|
|||
private getCspForResource(resource: vscode.Uri, nonce: string): string {
|
||||
switch (this.cspArbiter.getSecurityLevelForResource(resource)) {
|
||||
case MarkdownPreviewSecurityLevel.AllowInsecureContent:
|
||||
return `<meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src 'self' vscode-resource: http: https: data:; media-src 'self' vscode-resource: http: https: data:; script-src 'nonce-${nonce}'; style-src 'self' vscode-resource: 'unsafe-inline' http: https: data:; font-src 'self' vscode-resource: http: https: data:;">`;
|
||||
return `<meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src 'self' ${vscode.env.webviewResourceRoot} http: https: data:; media-src 'self' ${vscode.env.webviewResourceRoot} http: https: data:; script-src 'nonce-${nonce}'; style-src 'self' ${vscode.env.webviewResourceRoot} 'unsafe-inline' http: https: data:; font-src 'self' ${vscode.env.webviewResourceRoot} http: https: data:;">`;
|
||||
|
||||
case MarkdownPreviewSecurityLevel.AllowInsecureLocalContent:
|
||||
return `<meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src 'self' vscode-resource: https: data: http://localhost:* http://127.0.0.1:*; media-src 'self' vscode-resource: https: data: http://localhost:* http://127.0.0.1:*; script-src 'nonce-${nonce}'; style-src 'self' vscode-resource: 'unsafe-inline' https: data: http://localhost:* http://127.0.0.1:*; font-src 'self' vscode-resource: https: data: http://localhost:* http://127.0.0.1:*;">`;
|
||||
return `<meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src 'self' ${vscode.env.webviewResourceRoot} https: data: http://localhost:* http://127.0.0.1:*; media-src 'self' ${vscode.env.webviewResourceRoot} https: data: http://localhost:* http://127.0.0.1:*; script-src 'nonce-${nonce}'; style-src 'self' ${vscode.env.webviewResourceRoot} 'unsafe-inline' https: data: http://localhost:* http://127.0.0.1:*; font-src 'self' ${vscode.env.webviewResourceRoot} https: data: http://localhost:* http://127.0.0.1:*;">`;
|
||||
|
||||
case MarkdownPreviewSecurityLevel.AllowScriptsAndAllContent:
|
||||
return '';
|
||||
|
||||
case MarkdownPreviewSecurityLevel.Strict:
|
||||
default:
|
||||
return `<meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src 'self' vscode-resource: https: data:; media-src 'self' vscode-resource: https: data:; script-src 'nonce-${nonce}'; style-src 'self' vscode-resource: 'unsafe-inline' https: data:; font-src 'self' vscode-resource: https: data:;">`;
|
||||
return `<meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src 'self' ${vscode.env.webviewResourceRoot} https: data:; media-src 'self' ${vscode.env.webviewResourceRoot} https: data:; script-src 'nonce-${nonce}'; style-src 'self' ${vscode.env.webviewResourceRoot} 'unsafe-inline' https: data:; font-src 'self' ${vscode.env.webviewResourceRoot} https: data:;">`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,10 +7,10 @@ import * as vscode from 'vscode';
|
|||
import * as path from 'path';
|
||||
import { Disposable } from './util/dispose';
|
||||
import * as arrays from './util/arrays';
|
||||
import { toResoruceUri } from './util/resources';
|
||||
|
||||
const resolveExtensionResource = (extension: vscode.Extension<any>, resourcePath: string): vscode.Uri => {
|
||||
return vscode.Uri.file(path.join(extension.extensionPath, resourcePath))
|
||||
.with({ scheme: 'vscode-resource' });
|
||||
return toResoruceUri(vscode.Uri.file(path.join(extension.extensionPath, resourcePath)));
|
||||
};
|
||||
|
||||
const resolveExtensionResources = (extension: vscode.Extension<any>, resourcePaths: unknown): vscode.Uri[] => {
|
||||
|
|
16
extensions/markdown-language-features/src/util/resources.ts
Normal file
16
extensions/markdown-language-features/src/util/resources.ts
Normal file
|
@ -0,0 +1,16 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
const rootUri = vscode.Uri.parse(vscode.env.webviewResourceRoot);
|
||||
|
||||
export function toResoruceUri(uri: vscode.Uri): vscode.Uri {
|
||||
return rootUri.with({
|
||||
path: rootUri.path + uri.path,
|
||||
query: uri.query,
|
||||
fragment: uri.fragment,
|
||||
});
|
||||
}
|
|
@ -251,7 +251,7 @@ suite('Webview tests', () => {
|
|||
});
|
||||
</script>`);
|
||||
|
||||
const workspaceRootUri = vscode.Uri.file(vscode.workspace.rootPath!).with({ scheme: 'vscode-resource' });
|
||||
const workspaceRootUri = vscode.env.webviewResourceRoot + vscode.Uri.file(vscode.workspace.rootPath!).path;
|
||||
|
||||
{
|
||||
const imagePath = workspaceRootUri.toString() + '/image.png';
|
||||
|
|
|
@ -153,4 +153,5 @@ export interface IEnvironmentService {
|
|||
driverVerbose: boolean;
|
||||
|
||||
webviewEndpoint?: string;
|
||||
readonly webviewResourceRoot: string;
|
||||
}
|
||||
|
|
|
@ -266,6 +266,10 @@ export class EnvironmentService implements IEnvironmentService {
|
|||
get driverHandle(): string | undefined { return this._args['driver']; }
|
||||
get driverVerbose(): boolean { return !!this._args['driver-verbose']; }
|
||||
|
||||
get webviewResourceRoot(): string {
|
||||
return 'vscode-resource:';
|
||||
}
|
||||
|
||||
constructor(private _args: ParsedArgs, private _execPath: string) {
|
||||
if (!process.env['VSCODE_LOGS']) {
|
||||
const key = toLocalISOString(new Date()).replace(/-|:|\.\d+Z$/g, '');
|
||||
|
|
14
src/vs/vscode.proposed.d.ts
vendored
14
src/vs/vscode.proposed.d.ts
vendored
|
@ -1464,4 +1464,18 @@ declare module 'vscode' {
|
|||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region Webview Resource Roots
|
||||
|
||||
export namespace env {
|
||||
/**
|
||||
* Root url from which local resources are loaded inside of webviews.
|
||||
*
|
||||
* This is `vscode-resource:` when vscode is run on the desktop. When vscode is run
|
||||
* on the web, this points to a server endpoint.
|
||||
*/
|
||||
export const webviewResourceRoot: string;
|
||||
}
|
||||
|
||||
//#endregion
|
||||
}
|
||||
|
|
|
@ -58,6 +58,7 @@ export interface IEnvironment {
|
|||
extensionTestsLocationURI?: URI;
|
||||
globalStorageHome: URI;
|
||||
userHome: URI;
|
||||
webviewResourceRoot: string;
|
||||
}
|
||||
|
||||
export interface IStaticWorkspaceData {
|
||||
|
|
|
@ -259,7 +259,11 @@ export function createApiFactory(
|
|||
},
|
||||
openExternal(uri: URI) {
|
||||
return extHostWindow.openUri(uri, { allowTunneling: !!initData.remoteAuthority });
|
||||
}
|
||||
},
|
||||
get webviewResourceRoot() {
|
||||
checkProposedApiEnabled(extension);
|
||||
return initData.environment.webviewResourceRoot;
|
||||
},
|
||||
};
|
||||
if (!initData.environment.extensionTestsLocationURI) {
|
||||
// allow to patch env-function when running tests
|
||||
|
|
|
@ -133,4 +133,8 @@ export class BrowserWorkbenchEnvironmentService implements IEnvironmentService {
|
|||
driverHandle?: string;
|
||||
driverVerbose: boolean;
|
||||
webviewEndpoint?: string;
|
||||
|
||||
get webviewResourceRoot(): string {
|
||||
return this.webviewEndpoint ? this.webviewEndpoint + '/vscode-resource' : 'vscode-resource:';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -189,7 +189,8 @@ export class RemoteExtensionHostClient extends Disposable implements IExtensionH
|
|||
extensionDevelopmentLocationURI: this._environmentService.extensionDevelopmentLocationURI,
|
||||
extensionTestsLocationURI: this._environmentService.extensionTestsLocationURI,
|
||||
globalStorageHome: remoteExtensionHostData.globalStorageHome,
|
||||
userHome: remoteExtensionHostData.userHome
|
||||
userHome: remoteExtensionHostData.userHome,
|
||||
webviewResourceRoot: this._environmentService.webviewResourceRoot,
|
||||
},
|
||||
workspace: this._contextService.getWorkbenchState() === WorkbenchState.EMPTY ? null : {
|
||||
configuration: workspace.configuration,
|
||||
|
|
|
@ -394,7 +394,8 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter {
|
|||
extensionDevelopmentLocationURI: this._environmentService.extensionDevelopmentLocationURI,
|
||||
extensionTestsLocationURI: this._environmentService.extensionTestsLocationURI,
|
||||
globalStorageHome: URI.file(this._environmentService.globalStorageHome),
|
||||
userHome: URI.file(this._environmentService.userHome)
|
||||
userHome: URI.file(this._environmentService.userHome),
|
||||
webviewResourceRoot: this._environmentService.webviewResourceRoot,
|
||||
},
|
||||
workspace: this._contextService.getWorkbenchState() === WorkbenchState.EMPTY ? undefined : {
|
||||
configuration: withNullAsUndefined(workspace.configuration),
|
||||
|
|
Loading…
Reference in a new issue