diff --git a/extensions/markdown-language-features/src/features/preview.ts b/extensions/markdown-language-features/src/features/preview.ts index 0bb67dccd4c..5f4a03275ff 100644 --- a/extensions/markdown-language-features/src/features/preview.ts +++ b/extensions/markdown-language-features/src/features/preview.ts @@ -412,7 +412,7 @@ export class MarkdownPreview extends Disposable { this.currentVersion = pendingVersion; if (this._resource === resource) { - const content = await this._contentProvider.provideTextDocumentContent(document, this._previewConfigurations, this.line, this.state); + const content = await this._contentProvider.provideTextDocumentContent(document, await this.editor.webview.resourceRoot, this._previewConfigurations, this.line, this.state); // Another call to `doUpdate` may have happened. // Make sure we are still updating for the correct document if (this.currentVersion && this.currentVersion.equals(pendingVersion)) { diff --git a/extensions/markdown-language-features/src/features/previewContentProvider.ts b/extensions/markdown-language-features/src/features/previewContentProvider.ts index 57edf1bd26d..00bcf723aab 100644 --- a/extensions/markdown-language-features/src/features/previewContentProvider.ts +++ b/extensions/markdown-language-features/src/features/previewContentProvider.ts @@ -51,6 +51,7 @@ export class MarkdownContentProvider { public async provideTextDocumentContent( markdownDocument: vscode.TextDocument, + webviewResourceRoot: string, previewConfigurations: MarkdownPreviewConfigurationManager, initialLine: number | undefined = undefined, state?: any @@ -65,14 +66,14 @@ export class MarkdownContentProvider { scrollEditorWithPreview: config.scrollEditorWithPreview, doubleClickToSwitchToEditor: config.doubleClickToSwitchToEditor, disableSecurityWarnings: this.cspArbiter.shouldDisableSecurityWarnings(), - webviewResourceRoot: vscode.env.webviewResourceRoot, + webviewResourceRoot: webviewResourceRoot, }; this.logger.log('provideTextDocumentContent', initialData); // Content Security Policy const nonce = new Date().getTime() + '' + new Date().getMilliseconds(); - const csp = this.getCspForResource(sourceUri, nonce); + const csp = this.getCspForResource(webviewResourceRoot, sourceUri, nonce); const body = await this.engine.render(markdownDocument); return ` @@ -84,14 +85,14 @@ export class MarkdownContentProvider { data-settings="${escapeAttribute(JSON.stringify(initialData))}" data-strings="${escapeAttribute(JSON.stringify(previewStrings))}" data-state="${escapeAttribute(JSON.stringify(state || {}))}"> - - ${this.getStyles(sourceUri, nonce, config, state)} - + + ${this.getStyles(webviewResourceRoot, sourceUri, nonce, config, state)} + ${body}
- ${this.getScripts(nonce)} + ${this.getScripts(webviewResourceRoot, nonce)} `; } @@ -109,12 +110,12 @@ export class MarkdownContentProvider { `; } - private extensionResourcePath(mediaFile: string): string { - return toResoruceUri(vscode.Uri.file(this.context.asAbsolutePath(path.join('media', mediaFile)))) + private extensionResourcePath(webviewResourceRoot: string, mediaFile: string): string { + return toResoruceUri(webviewResourceRoot, vscode.Uri.file(this.context.asAbsolutePath(path.join('media', mediaFile)))) .toString(); } - private fixHref(resource: vscode.Uri, href: string): string { + private fixHref(webviewResourceRoot: string, resource: vscode.Uri, href: string): string { if (!href) { return href; } @@ -125,23 +126,23 @@ export class MarkdownContentProvider { // Assume it must be a local file if (path.isAbsolute(href)) { - return toResoruceUri(vscode.Uri.file(href)).toString(); + return toResoruceUri(webviewResourceRoot, vscode.Uri.file(href)).toString(); } // Use a workspace relative path if there is a workspace const root = vscode.workspace.getWorkspaceFolder(resource); if (root) { - return toResoruceUri(vscode.Uri.file(path.join(root.uri.fsPath, href))).toString(); + return toResoruceUri(webviewResourceRoot, vscode.Uri.file(path.join(root.uri.fsPath, href))).toString(); } // Otherwise look relative to the markdown file - return toResoruceUri(vscode.Uri.file(path.join(path.dirname(resource.fsPath), href))).toString(); + return toResoruceUri(webviewResourceRoot, vscode.Uri.file(path.join(path.dirname(resource.fsPath), href))).toString(); } - private computeCustomStyleSheetIncludes(resource: vscode.Uri, config: MarkdownPreviewConfiguration): string { + private computeCustomStyleSheetIncludes(webviewResourceRoot: string, resource: vscode.Uri, config: MarkdownPreviewConfiguration): string { if (Array.isArray(config.styles)) { return config.styles.map(style => { - return ``; + return ``; }).join('\n'); } return ''; @@ -172,37 +173,41 @@ export class MarkdownContentProvider { return ret; } - private getStyles(resource: vscode.Uri, nonce: string, config: MarkdownPreviewConfiguration, state?: any): string { + private getStyles(webviewResourceRoot: string, resource: vscode.Uri, nonce: string, config: MarkdownPreviewConfiguration, state?: any): string { const baseStyles = this.contributionProvider.contributions.previewStyles - .map(resource => ``) + .map(resource => ``) .join('\n'); return `${baseStyles} ${this.getSettingsOverrideStyles(nonce, config)} - ${this.computeCustomStyleSheetIncludes(resource, config)} + ${this.computeCustomStyleSheetIncludes(webviewResourceRoot, resource, config)} ${this.getImageStabilizerStyles(state)}`; } - private getScripts(nonce: string): string { + private getScripts(resourceRoot: string, nonce: string): string { return this.contributionProvider.contributions.previewScripts - .map(resource => ``) + .map(resource => ``) .join('\n'); } - private getCspForResource(resource: vscode.Uri, nonce: string): string { + private getCspForResource( + webviewResourceRoot: string, + resource: vscode.Uri, + nonce: string + ): string { switch (this.cspArbiter.getSecurityLevelForResource(resource)) { case MarkdownPreviewSecurityLevel.AllowInsecureContent: - return ``; + return ``; case MarkdownPreviewSecurityLevel.AllowInsecureLocalContent: - return ``; + return ``; case MarkdownPreviewSecurityLevel.AllowScriptsAndAllContent: return ''; case MarkdownPreviewSecurityLevel.Strict: default: - return ``; + return ``; } } } diff --git a/extensions/markdown-language-features/src/markdownExtensions.ts b/extensions/markdown-language-features/src/markdownExtensions.ts index ffbdc3054b8..b03c5df80bd 100644 --- a/extensions/markdown-language-features/src/markdownExtensions.ts +++ b/extensions/markdown-language-features/src/markdownExtensions.ts @@ -7,10 +7,9 @@ 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, resourcePath: string): vscode.Uri => { - return toResoruceUri(vscode.Uri.file(path.join(extension.extensionPath, resourcePath))); + return vscode.Uri.file(path.join(extension.extensionPath, resourcePath)); }; const resolveExtensionResources = (extension: vscode.Extension, resourcePaths: unknown): vscode.Uri[] => { diff --git a/extensions/markdown-language-features/src/util/resources.ts b/extensions/markdown-language-features/src/util/resources.ts index f7133f5a0c4..cb9b8cb56d6 100644 --- a/extensions/markdown-language-features/src/util/resources.ts +++ b/extensions/markdown-language-features/src/util/resources.ts @@ -5,9 +5,9 @@ import * as vscode from 'vscode'; -const rootUri = vscode.Uri.parse(vscode.env.webviewResourceRoot); -export function toResoruceUri(uri: vscode.Uri): vscode.Uri { +export function toResoruceUri(webviewResourceRoot: string, uri: vscode.Uri): vscode.Uri { + const rootUri = vscode.Uri.parse(webviewResourceRoot); return rootUri.with({ path: rootUri.path + uri.path, query: uri.query, diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/webview.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/webview.test.ts index 01e8b460efa..8ef8cec74d6 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/webview.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/webview.test.ts @@ -251,7 +251,7 @@ suite('Webview tests', () => { }); `); - const workspaceRootUri = vscode.env.webviewResourceRoot + vscode.Uri.file(vscode.workspace.rootPath!).path; + const workspaceRootUri = webview.webview.resourceRoot + vscode.Uri.file(vscode.workspace.rootPath!).path; { const imagePath = workspaceRootUri.toString() + '/image.png'; diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 86d4ae370b6..f1849eb35e9 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -1461,14 +1461,14 @@ declare module 'vscode' { //#region Webview Resource Roots - export namespace env { + export interface Webview { /** * 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; + readonly resourceRoot: Thenable; } //#endregion diff --git a/src/vs/workbench/api/browser/mainThreadCodeInsets.ts b/src/vs/workbench/api/browser/mainThreadCodeInsets.ts index 378fb000ddb..e06a5dc3ea9 100644 --- a/src/vs/workbench/api/browser/mainThreadCodeInsets.ts +++ b/src/vs/workbench/api/browser/mainThreadCodeInsets.ts @@ -12,6 +12,7 @@ import { IWebviewService, Webview } from 'vs/workbench/contrib/webview/common/we import { DisposableStore } from 'vs/base/common/lifecycle'; import { IActiveCodeEditor, IViewZone } from 'vs/editor/browser/editorBrowser'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; // todo@joh move these things back into something like contrib/insets class EditorWebviewZone implements IViewZone { @@ -59,6 +60,7 @@ export class MainThreadEditorInsets implements MainThreadEditorInsetsShape { constructor( context: IExtHostContext, + @IEnvironmentService private readonly _environmentService: IEnvironmentService, @ICodeEditorService private readonly _editorService: ICodeEditorService, @IWebviewService private readonly _webviewService: IWebviewService, ) { @@ -144,4 +146,8 @@ export class MainThreadEditorInsets implements MainThreadEditorInsetsShape { } return Promise.resolve(false); } + + async $getResourceRoot(_handle: number): Promise { + return this._environmentService.webviewResourceRoot; + } } diff --git a/src/vs/workbench/api/browser/mainThreadWebview.ts b/src/vs/workbench/api/browser/mainThreadWebview.ts index 83b6403e4f5..5dd98e6e0d6 100644 --- a/src/vs/workbench/api/browser/mainThreadWebview.ts +++ b/src/vs/workbench/api/browser/mainThreadWebview.ts @@ -23,6 +23,7 @@ import { ACTIVE_GROUP, IEditorService } from 'vs/workbench/services/editor/commo import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { extHostNamedCustomer } from '../common/extHostCustomers'; import { IProductService } from 'vs/platform/product/common/product'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; @extHostNamedCustomer(MainContext.MainThreadWebviews) export class MainThreadWebviews extends Disposable implements MainThreadWebviewsShape { @@ -54,6 +55,7 @@ export class MainThreadWebviews extends Disposable implements MainThreadWebviews @IOpenerService private readonly _openerService: IOpenerService, @ITelemetryService private readonly _telemetryService: ITelemetryService, @IProductService private readonly _productService: IProductService, + @IEnvironmentService private readonly _environmentService: IEnvironmentService, ) { super(); @@ -139,6 +141,10 @@ export class MainThreadWebviews extends Disposable implements MainThreadWebviews webview.setOptions(reviveWebviewOptions(options as any /*todo@mat */)); } + async $getResourceRoot(_handle: WebviewPanelHandle): Promise { + return this._environmentService.webviewResourceRoot; + } + public $reveal(handle: WebviewPanelHandle, showOptions: WebviewPanelShowOptions): void { const webview = this.getWebview(handle); if (webview.isDisposed()) { diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index f40201b71c3..2de33214423 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -59,7 +59,6 @@ export interface IEnvironment { extensionTestsLocationURI?: URI; globalStorageHome: URI; userHome: URI; - webviewResourceRoot: string; } export interface IStaticWorkspaceData { @@ -519,6 +518,7 @@ export interface MainThreadEditorInsetsShape extends IDisposable { $setHtml(handle: number, value: string): void; $setOptions(handle: number, options: modes.IWebviewOptions): void; $postMessage(handle: number, value: any): Promise; + $getResourceRoot(handle: number): Promise; } export interface ExtHostEditorInsetsShape { @@ -543,6 +543,7 @@ export interface MainThreadWebviewsShape extends IDisposable { $setHtml(handle: WebviewPanelHandle, value: string): void; $setOptions(handle: WebviewPanelHandle, options: modes.IWebviewOptions): void; $postMessage(handle: WebviewPanelHandle, value: any): Promise; + $getResourceRoot(handle: WebviewPanelHandle): Promise; $registerSerializer(viewType: string): void; $unregisterSerializer(viewType: string): void; diff --git a/src/vs/workbench/api/common/extHostCodeInsets.ts b/src/vs/workbench/api/common/extHostCodeInsets.ts index 5a6773d4438..5b40af3d04d 100644 --- a/src/vs/workbench/api/common/extHostCodeInsets.ts +++ b/src/vs/workbench/api/common/extHostCodeInsets.ts @@ -61,6 +61,10 @@ export class ExtHostEditorInsets implements ExtHostEditorInsetsShape { private _html: string = ''; private _options: vscode.WebviewOptions; + get resourceRoot(): Promise { + return that._proxy.$getResourceRoot(handle); + } + set options(value: vscode.WebviewOptions) { this._options = value; that._proxy.$setOptions(handle, value); diff --git a/src/vs/workbench/api/common/extHostWebview.ts b/src/vs/workbench/api/common/extHostWebview.ts index b6ad9c09ee2..98d6631c2a0 100644 --- a/src/vs/workbench/api/common/extHostWebview.ts +++ b/src/vs/workbench/api/common/extHostWebview.ts @@ -39,6 +39,10 @@ export class ExtHostWebview implements vscode.Webview { this._onMessageEmitter.dispose(); } + public get resourceRoot(): Promise { + return this._proxy.$getResourceRoot(this._handle); + } + public get html(): string { this.assertNotDisposed(); return this._html; diff --git a/src/vs/workbench/api/node/extHost.api.impl.ts b/src/vs/workbench/api/node/extHost.api.impl.ts index 9cff724bc75..0548192c903 100644 --- a/src/vs/workbench/api/node/extHost.api.impl.ts +++ b/src/vs/workbench/api/node/extHost.api.impl.ts @@ -262,10 +262,6 @@ export function createApiFactory( openExternal(uri: URI) { return extHostWindow.openUri(uri, { allowTunneling: !!initData.remote.isRemote }); }, - get webviewResourceRoot() { - checkProposedApiEnabled(extension); - return initData.environment.webviewResourceRoot; - }, get remoteName() { if (!initData.remote.authority) { return undefined; diff --git a/src/vs/workbench/services/extensions/common/remoteExtensionHostClient.ts b/src/vs/workbench/services/extensions/common/remoteExtensionHostClient.ts index 5ab1b30e06c..f43a59b9663 100644 --- a/src/vs/workbench/services/extensions/common/remoteExtensionHostClient.ts +++ b/src/vs/workbench/services/extensions/common/remoteExtensionHostClient.ts @@ -190,7 +190,6 @@ export class RemoteExtensionHostClient extends Disposable implements IExtensionH extensionTestsLocationURI: this._environmentService.extensionTestsLocationURI, globalStorageHome: remoteExtensionHostData.globalStorageHome, userHome: remoteExtensionHostData.userHome, - webviewResourceRoot: this._environmentService.webviewResourceRoot, }, workspace: this._contextService.getWorkbenchState() === WorkbenchState.EMPTY ? null : { configuration: workspace.configuration, diff --git a/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts b/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts index 860845137fc..3d324296b01 100644 --- a/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts +++ b/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts @@ -400,7 +400,6 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter { extensionTestsLocationURI: this._environmentService.extensionTestsLocationURI, globalStorageHome: URI.file(this._environmentService.globalStorageHome), userHome: URI.file(this._environmentService.userHome), - webviewResourceRoot: this._environmentService.webviewResourceRoot, }, workspace: this._contextService.getWorkbenchState() === WorkbenchState.EMPTY ? undefined : { configuration: withNullAsUndefined(workspace.configuration),