Skip to content

Commit

Permalink
Merge pull request #75741 from microsoft/dev/mjbvz/webviewResourceRoot
Browse files Browse the repository at this point in the history
Add vscode.env.webviewResourceRoot API
  • Loading branch information
mjbvz authored Jun 19, 2019
2 parents 3c5977d + 7f3d3d8 commit 535c9d5
Show file tree
Hide file tree
Showing 17 changed files with 88 additions and 38 deletions.
9 changes: 6 additions & 3 deletions extensions/markdown-language-features/media/index.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion extensions/markdown-language-features/media/pre.js

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion extensions/markdown-language-features/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
10 changes: 7 additions & 3 deletions extensions/markdown-language-features/preview-src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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();
Expand All @@ -157,4 +157,8 @@ if (settings.scrollEditorWithPreview) {
}
}
}, 50));
}

function escapeRegExp(text: string) {
return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
}
17 changes: 9 additions & 8 deletions extensions/markdown-language-features/preview-src/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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);
Expand All @@ -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}
Expand All @@ -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();
}

Expand All @@ -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 {
Expand Down Expand Up @@ -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:;">`;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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[] => {
Expand Down
16 changes: 16 additions & 0 deletions extensions/markdown-language-features/src/util/resources.ts
Original file line number Diff line number Diff line change
@@ -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,
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
1 change: 1 addition & 0 deletions src/vs/platform/environment/common/environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,4 +154,5 @@ export interface IEnvironmentService {
driverVerbose: boolean;

webviewEndpoint?: string;
readonly webviewResourceRoot: string;
}
4 changes: 4 additions & 0 deletions src/vs/platform/environment/node/environmentService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,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, '');
Expand Down
14 changes: 14 additions & 0 deletions src/vs/vscode.proposed.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
1 change: 1 addition & 0 deletions src/vs/workbench/api/common/extHost.protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ export interface IEnvironment {
extensionTestsLocationURI?: URI;
globalStorageHome: URI;
userHome: URI;
webviewResourceRoot: string;
}

export interface IStaticWorkspaceData {
Expand Down
6 changes: 5 additions & 1 deletion src/vs/workbench/api/node/extHost.api.impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,4 +135,8 @@ export class BrowserWorkbenchEnvironmentService implements IEnvironmentService {
driverHandle?: string;
driverVerbose: boolean;
webviewEndpoint?: string;

get webviewResourceRoot(): string {
return this.webviewEndpoint ? this.webviewEndpoint + '/vscode-resource' : 'vscode-resource:';
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down

0 comments on commit 535c9d5

Please sign in to comment.