mirror of
https://github.com/Microsoft/vscode
synced 2024-09-13 21:55:38 +00:00
parent
3492642650
commit
a558a9504a
|
@ -4,10 +4,10 @@
|
|||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
html, body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Ubuntu", "Droid Sans", sans-serif;
|
||||
font-size: 14px;
|
||||
font-family: var(--vscode-markdown-font-family, -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Ubuntu", "Droid Sans", sans-serif);
|
||||
font-size: var(--vscode-markdown-font-size, 14px);
|
||||
padding: 0 26px;
|
||||
line-height: 22px;
|
||||
line-height: var(--vscode-markdown-line-height, 22px);
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@ import { MarkdownPreviewConfigurationManager } from './previewConfig';
|
|||
import { MarkdownContributionProvider, MarkdownContributions } from '../markdownExtensions';
|
||||
import { isMarkdownFile } from '../util/file';
|
||||
import { resolveLinkToMarkdownFile } from '../commands/openDocumentLink';
|
||||
import { WebviewResourceProvider } from '../util/resources';
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
interface WebviewMessage {
|
||||
|
@ -388,6 +389,10 @@ export class MarkdownPreview extends Disposable {
|
|||
}
|
||||
|
||||
private async doUpdate(): Promise<void> {
|
||||
if (this._disposed) {
|
||||
return;
|
||||
}
|
||||
|
||||
const resource = this._resource;
|
||||
|
||||
clearTimeout(this.throttleTimer);
|
||||
|
@ -401,6 +406,10 @@ export class MarkdownPreview extends Disposable {
|
|||
return;
|
||||
}
|
||||
|
||||
if (this._disposed) {
|
||||
return;
|
||||
}
|
||||
|
||||
const pendingVersion = new PreviewDocumentVersion(resource, document.version);
|
||||
if (!this.forceUpdate && this.currentVersion && this.currentVersion.equals(pendingVersion)) {
|
||||
if (this.line) {
|
||||
|
@ -412,7 +421,12 @@ export class MarkdownPreview extends Disposable {
|
|||
|
||||
this.currentVersion = pendingVersion;
|
||||
if (this._resource === resource) {
|
||||
const content = await this._contentProvider.provideTextDocumentContent(document, await this.editor.webview.resourceRoot, this._previewConfigurations, this.line, this.state);
|
||||
const self = this;
|
||||
const resourceProvider: WebviewResourceProvider = {
|
||||
toWebviewResource: (resource) => this.editor.webview.toWebviewResource(resource),
|
||||
get cspRule() { return self.editor.webview.cspRule; }
|
||||
};
|
||||
const content = await this._contentProvider.provideTextDocumentContent(document, resourceProvider, 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)) {
|
||||
|
|
|
@ -3,18 +3,17 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import * as path from 'path';
|
||||
import { MarkdownEngine } from '../markdownEngine';
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import * as nls from 'vscode-nls';
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
import { Logger } from '../logger';
|
||||
import { ContentSecurityPolicyArbiter, MarkdownPreviewSecurityLevel } from '../security';
|
||||
import { MarkdownPreviewConfigurationManager, MarkdownPreviewConfiguration } from './previewConfig';
|
||||
import { MarkdownEngine } from '../markdownEngine';
|
||||
import { MarkdownContributionProvider } from '../markdownExtensions';
|
||||
import { toResoruceUri } from '../util/resources';
|
||||
import { ContentSecurityPolicyArbiter, MarkdownPreviewSecurityLevel } from '../security';
|
||||
import { WebviewResourceProvider } from '../util/resources';
|
||||
import { MarkdownPreviewConfiguration, MarkdownPreviewConfigurationManager } from './previewConfig';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
/**
|
||||
* Strings used inside the markdown preview.
|
||||
|
@ -36,8 +35,8 @@ const previewStrings = {
|
|||
'Content Disabled Security Warning')
|
||||
};
|
||||
|
||||
function escapeAttribute(value: string): string {
|
||||
return value.replace(/"/g, '"');
|
||||
function escapeAttribute(value: string | vscode.Uri): string {
|
||||
return value.toString().replace(/"/g, '"');
|
||||
}
|
||||
|
||||
export class MarkdownContentProvider {
|
||||
|
@ -51,7 +50,7 @@ export class MarkdownContentProvider {
|
|||
|
||||
public async provideTextDocumentContent(
|
||||
markdownDocument: vscode.TextDocument,
|
||||
webviewResourceRoot: string,
|
||||
resourceProvider: WebviewResourceProvider,
|
||||
previewConfigurations: MarkdownPreviewConfigurationManager,
|
||||
initialLine: number | undefined = undefined,
|
||||
state?: any
|
||||
|
@ -66,18 +65,18 @@ export class MarkdownContentProvider {
|
|||
scrollEditorWithPreview: config.scrollEditorWithPreview,
|
||||
doubleClickToSwitchToEditor: config.doubleClickToSwitchToEditor,
|
||||
disableSecurityWarnings: this.cspArbiter.shouldDisableSecurityWarnings(),
|
||||
webviewResourceRoot: webviewResourceRoot,
|
||||
webviewResourceRoot: resourceProvider.toWebviewResource(markdownDocument.uri).toString(),
|
||||
};
|
||||
|
||||
this.logger.log('provideTextDocumentContent', initialData);
|
||||
|
||||
// Content Security Policy
|
||||
const nonce = new Date().getTime() + '' + new Date().getMilliseconds();
|
||||
const csp = this.getCspForResource(webviewResourceRoot, sourceUri, nonce);
|
||||
const csp = this.getCsp(resourceProvider, sourceUri, nonce);
|
||||
|
||||
const body = await this.engine.render(markdownDocument);
|
||||
return `<!DOCTYPE html>
|
||||
<html>
|
||||
<html style="${escapeAttribute(this.getSettingsOverrideStyles(config))}">
|
||||
<head>
|
||||
<meta http-equiv="Content-type" content="text/html;charset=UTF-8">
|
||||
${csp}
|
||||
|
@ -85,14 +84,14 @@ export class MarkdownContentProvider {
|
|||
data-settings="${escapeAttribute(JSON.stringify(initialData))}"
|
||||
data-strings="${escapeAttribute(JSON.stringify(previewStrings))}"
|
||||
data-state="${escapeAttribute(JSON.stringify(state || {}))}">
|
||||
<script src="${this.extensionResourcePath(webviewResourceRoot, 'pre.js')}" nonce="${nonce}"></script>
|
||||
${this.getStyles(webviewResourceRoot, sourceUri, nonce, config, state)}
|
||||
<base href="${toResoruceUri(webviewResourceRoot, markdownDocument.uri)}">
|
||||
<script src="${this.extensionResourcePath(resourceProvider, 'pre.js')}" nonce="${nonce}"></script>
|
||||
${this.getStyles(resourceProvider, sourceUri, config, state)}
|
||||
<base href="${resourceProvider.toWebviewResource(markdownDocument.uri)}">
|
||||
</head>
|
||||
<body class="vscode-body ${config.scrollBeyondLastLine ? 'scrollBeyondLastLine' : ''} ${config.wordWrap ? 'wordWrap' : ''} ${config.markEditorSelection ? 'showEditorSelection' : ''}">
|
||||
${body}
|
||||
<div class="code-line" data-line="${markdownDocument.lineCount}"></div>
|
||||
${this.getScripts(webviewResourceRoot, nonce)}
|
||||
${this.getScripts(resourceProvider, nonce)}
|
||||
</body>
|
||||
</html>`;
|
||||
}
|
||||
|
@ -110,12 +109,13 @@ export class MarkdownContentProvider {
|
|||
</html>`;
|
||||
}
|
||||
|
||||
private extensionResourcePath(webviewResourceRoot: string, mediaFile: string): string {
|
||||
return toResoruceUri(webviewResourceRoot, vscode.Uri.file(this.context.asAbsolutePath(path.join('media', mediaFile))))
|
||||
.toString();
|
||||
private extensionResourcePath(resourceProvider: WebviewResourceProvider, mediaFile: string): string {
|
||||
const webviewResource = resourceProvider.toWebviewResource(
|
||||
vscode.Uri.file(this.context.asAbsolutePath(path.join('media', mediaFile))));
|
||||
return webviewResource.toString();
|
||||
}
|
||||
|
||||
private fixHref(webviewResourceRoot: string, resource: vscode.Uri, href: string): string {
|
||||
private fixHref(resourceProvider: WebviewResourceProvider, resource: vscode.Uri, href: string): string {
|
||||
if (!href) {
|
||||
return href;
|
||||
}
|
||||
|
@ -126,36 +126,36 @@ export class MarkdownContentProvider {
|
|||
|
||||
// Assume it must be a local file
|
||||
if (path.isAbsolute(href)) {
|
||||
return toResoruceUri(webviewResourceRoot, vscode.Uri.file(href)).toString();
|
||||
return resourceProvider.toWebviewResource(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(webviewResourceRoot, vscode.Uri.file(path.join(root.uri.fsPath, href))).toString();
|
||||
return resourceProvider.toWebviewResource(vscode.Uri.file(path.join(root.uri.fsPath, href))).toString();
|
||||
}
|
||||
|
||||
// Otherwise look relative to the markdown file
|
||||
return toResoruceUri(webviewResourceRoot, vscode.Uri.file(path.join(path.dirname(resource.fsPath), href))).toString();
|
||||
return resourceProvider.toWebviewResource(vscode.Uri.file(path.join(path.dirname(resource.fsPath), href))).toString();
|
||||
}
|
||||
|
||||
private computeCustomStyleSheetIncludes(webviewResourceRoot: string, resource: vscode.Uri, config: MarkdownPreviewConfiguration): string {
|
||||
if (Array.isArray(config.styles)) {
|
||||
return config.styles.map(style => {
|
||||
return `<link rel="stylesheet" class="code-user-style" data-source="${escapeAttribute(style)}" href="${escapeAttribute(this.fixHref(webviewResourceRoot, resource, style))}" type="text/css" media="screen">`;
|
||||
}).join('\n');
|
||||
private computeCustomStyleSheetIncludes(resourceProvider: WebviewResourceProvider, resource: vscode.Uri, config: MarkdownPreviewConfiguration): string {
|
||||
if (!Array.isArray(config.styles)) {
|
||||
return '';
|
||||
}
|
||||
return '';
|
||||
const out: string[] = [];
|
||||
for (const style of config.styles) {
|
||||
out.push(`<link rel="stylesheet" class="code-user-style" data-source="${escapeAttribute(style)}" href="${escapeAttribute(this.fixHref(resourceProvider, resource, style))}" type="text/css" media="screen">`);
|
||||
}
|
||||
return out.join('\n');
|
||||
}
|
||||
|
||||
private getSettingsOverrideStyles(nonce: string, config: MarkdownPreviewConfiguration): string {
|
||||
return `<style nonce="${nonce}">
|
||||
html, body {
|
||||
${config.fontFamily ? `font-family: ${config.fontFamily};` : ''}
|
||||
${isNaN(config.fontSize) ? '' : `font-size: ${config.fontSize}px;`}
|
||||
${isNaN(config.lineHeight) ? '' : `line-height: ${config.lineHeight};`}
|
||||
}
|
||||
</style>`;
|
||||
private getSettingsOverrideStyles(config: MarkdownPreviewConfiguration): string {
|
||||
return [
|
||||
config.fontFamily ? `--vscode-markdown-font-family: ${config.fontFamily};` : '',
|
||||
isNaN(config.fontSize) ? '' : `--vscode-markdown-font-size: ${config.fontSize}px;`,
|
||||
isNaN(config.lineHeight) ? '' : `--vscode-markdown-line-height: ${config.lineHeight};`,
|
||||
].join(' ');
|
||||
}
|
||||
|
||||
private getImageStabilizerStyles(state?: any) {
|
||||
|
@ -173,41 +173,47 @@ export class MarkdownContentProvider {
|
|||
return ret;
|
||||
}
|
||||
|
||||
private getStyles(webviewResourceRoot: string, resource: vscode.Uri, nonce: string, config: MarkdownPreviewConfiguration, state?: any): string {
|
||||
const baseStyles = this.contributionProvider.contributions.previewStyles
|
||||
.map(resource => `<link rel="stylesheet" type="text/css" href="${escapeAttribute(toResoruceUri(webviewResourceRoot, resource).toString())}">`)
|
||||
.join('\n');
|
||||
private getStyles(resourceProvider: WebviewResourceProvider, resource: vscode.Uri, config: MarkdownPreviewConfiguration, state?: any): string {
|
||||
const baseStyles: string[] = [];
|
||||
for (const resource of this.contributionProvider.contributions.previewStyles) {
|
||||
baseStyles.push(`<link rel="stylesheet" type="text/css" href="${escapeAttribute(resourceProvider.toWebviewResource(resource))}">`);
|
||||
}
|
||||
|
||||
return `${baseStyles}
|
||||
${this.getSettingsOverrideStyles(nonce, config)}
|
||||
${this.computeCustomStyleSheetIncludes(webviewResourceRoot, resource, config)}
|
||||
return `${baseStyles.join('\n')}
|
||||
${this.computeCustomStyleSheetIncludes(resourceProvider, resource, config)}
|
||||
${this.getImageStabilizerStyles(state)}`;
|
||||
}
|
||||
|
||||
private getScripts(resourceRoot: string, nonce: string): string {
|
||||
return this.contributionProvider.contributions.previewScripts
|
||||
.map(resource => `<script async src="${escapeAttribute(toResoruceUri(resourceRoot, resource).toString())}" nonce="${nonce}" charset="UTF-8"></script>`)
|
||||
.join('\n');
|
||||
private getScripts(resourceProvider: WebviewResourceProvider, nonce: string): string {
|
||||
const out: string[] = [];
|
||||
for (const resource of this.contributionProvider.contributions.previewScripts) {
|
||||
out.push(`<script async
|
||||
src="${escapeAttribute(resourceProvider.toWebviewResource(resource))}"
|
||||
nonce="${nonce}"
|
||||
charset="UTF-8"></script>`);
|
||||
}
|
||||
return out.join('\n');
|
||||
}
|
||||
|
||||
private getCspForResource(
|
||||
webviewResourceRoot: string,
|
||||
private getCsp(
|
||||
provider: WebviewResourceProvider,
|
||||
resource: vscode.Uri,
|
||||
nonce: string
|
||||
): string {
|
||||
const rule = provider.cspRule;
|
||||
switch (this.cspArbiter.getSecurityLevelForResource(resource)) {
|
||||
case MarkdownPreviewSecurityLevel.AllowInsecureContent:
|
||||
return `<meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src 'self' ${webviewResourceRoot} http: https: data:; media-src 'self' ${webviewResourceRoot} http: https: data:; script-src 'nonce-${nonce}'; style-src 'self' ${webviewResourceRoot} 'unsafe-inline' http: https: data:; font-src 'self' ${webviewResourceRoot} http: https: data:;">`;
|
||||
return `<meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src 'self' ${rule} http: https: data:; media-src 'self' ${rule} http: https: data:; script-src 'nonce-${nonce}'; style-src 'self' ${rule} 'unsafe-inline' http: https: data:; font-src 'self' ${rule} http: https: data:;">`;
|
||||
|
||||
case MarkdownPreviewSecurityLevel.AllowInsecureLocalContent:
|
||||
return `<meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src 'self' ${webviewResourceRoot} https: data: http://localhost:* http://127.0.0.1:*; media-src 'self' ${webviewResourceRoot} https: data: http://localhost:* http://127.0.0.1:*; script-src 'nonce-${nonce}'; style-src 'self' ${webviewResourceRoot} 'unsafe-inline' https: data: http://localhost:* http://127.0.0.1:*; font-src 'self' ${webviewResourceRoot} https: data: http://localhost:* http://127.0.0.1:*;">`;
|
||||
return `<meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src 'self' ${rule} https: data: http://localhost:* http://127.0.0.1:*; media-src 'self' ${rule} https: data: http://localhost:* http://127.0.0.1:*; script-src 'nonce-${nonce}'; style-src 'self' ${rule} 'unsafe-inline' https: data: http://localhost:* http://127.0.0.1:*; font-src 'self' ${rule} 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' ${webviewResourceRoot} https: data:; media-src 'self' ${webviewResourceRoot} https: data:; script-src 'nonce-${nonce}'; style-src 'self' ${webviewResourceRoot} 'unsafe-inline' https: data:; font-src 'self' ${webviewResourceRoot} https: data:;">`;
|
||||
return `<meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src 'self' ${rule} https: data:; media-src 'self' ${rule} https: data:; script-src 'nonce-${nonce}'; style-src 'self' ${rule} 'unsafe-inline' https: data:; font-src 'self' ${rule} https: data:;">`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,12 +5,8 @@
|
|||
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
export interface WebviewResourceProvider {
|
||||
toWebviewResource(resource: 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,
|
||||
fragment: uri.fragment,
|
||||
});
|
||||
}
|
||||
readonly cspRule: string;
|
||||
}
|
|
@ -251,15 +251,18 @@ suite('Webview tests', () => {
|
|||
});
|
||||
</script>`);
|
||||
|
||||
const workspaceRootUri = webview.webview.resourceRoot + vscode.Uri.file(vscode.workspace.rootPath!).path;
|
||||
async function toWebviewResource(path: string) {
|
||||
const root = await webview.webview.toWebviewResource(vscode.Uri.file(vscode.workspace.rootPath!));
|
||||
return root.toString() + path;
|
||||
}
|
||||
|
||||
{
|
||||
const imagePath = workspaceRootUri.toString() + '/image.png';
|
||||
const imagePath = await toWebviewResource('/image.png');
|
||||
const response = sendRecieveMessage(webview, { src: imagePath });
|
||||
assert.strictEqual((await response).value, true);
|
||||
}
|
||||
{
|
||||
const imagePath = workspaceRootUri.toString() + '/no-such-image.png';
|
||||
const imagePath = await toWebviewResource('/no-such-image.png');
|
||||
const response = sendRecieveMessage(webview, { src: imagePath });
|
||||
assert.strictEqual((await response).value, false);
|
||||
}
|
||||
|
|
|
@ -169,4 +169,5 @@ export interface IEnvironmentService {
|
|||
|
||||
webviewEndpoint?: string;
|
||||
readonly webviewResourceRoot: string;
|
||||
readonly webviewCspRule: string;
|
||||
}
|
||||
|
|
|
@ -278,9 +278,8 @@ 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:';
|
||||
}
|
||||
readonly webviewResourceRoot = 'vscode-resource:';
|
||||
readonly webviewCspRule = 'vscode-resource:';
|
||||
|
||||
constructor(private _args: ParsedArgs, private _execPath: string) {
|
||||
if (!process.env['VSCODE_LOGS']) {
|
||||
|
|
12
src/vs/vscode.proposed.d.ts
vendored
12
src/vs/vscode.proposed.d.ts
vendored
|
@ -1579,12 +1579,14 @@ declare module 'vscode' {
|
|||
|
||||
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.
|
||||
* Convert a uri for the local file system to one that can be used inside webviews.
|
||||
*/
|
||||
readonly resourceRoot: Thenable<string>;
|
||||
toWebviewResource(localResource: Uri): Uri;
|
||||
|
||||
/**
|
||||
* Content security policy rule for webview resources.
|
||||
*/
|
||||
readonly cspRule: string;
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
|
|
@ -12,7 +12,6 @@ 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 {
|
||||
|
@ -60,7 +59,6 @@ export class MainThreadEditorInsets implements MainThreadEditorInsetsShape {
|
|||
|
||||
constructor(
|
||||
context: IExtHostContext,
|
||||
@IEnvironmentService private readonly _environmentService: IEnvironmentService,
|
||||
@ICodeEditorService private readonly _editorService: ICodeEditorService,
|
||||
@IWebviewService private readonly _webviewService: IWebviewService,
|
||||
) {
|
||||
|
@ -147,8 +145,4 @@ export class MainThreadEditorInsets implements MainThreadEditorInsetsShape {
|
|||
}
|
||||
return inset;
|
||||
}
|
||||
|
||||
async $getResourceRoot(_handle: number): Promise<string> {
|
||||
return this._environmentService.webviewResourceRoot;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,7 +23,6 @@ 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 {
|
||||
|
@ -51,11 +50,10 @@ export class MainThreadWebviews extends Disposable implements MainThreadWebviews
|
|||
@IExtensionService extensionService: IExtensionService,
|
||||
@IEditorGroupsService private readonly _editorGroupService: IEditorGroupsService,
|
||||
@IEditorService private readonly _editorService: IEditorService,
|
||||
@IWebviewEditorService private readonly _webviewService: IWebviewEditorService,
|
||||
@IWebviewEditorService private readonly _webviewEditorService: IWebviewEditorService,
|
||||
@IOpenerService private readonly _openerService: IOpenerService,
|
||||
@ITelemetryService private readonly _telemetryService: ITelemetryService,
|
||||
@IProductService private readonly _productService: IProductService,
|
||||
@IEnvironmentService private readonly _environmentService: IEnvironmentService,
|
||||
) {
|
||||
super();
|
||||
|
||||
|
@ -65,7 +63,7 @@ export class MainThreadWebviews extends Disposable implements MainThreadWebviews
|
|||
|
||||
// This reviver's only job is to activate webview extensions
|
||||
// This should trigger the real reviver to be registered from the extension host side.
|
||||
this._register(_webviewService.registerReviver({
|
||||
this._register(_webviewEditorService.registerReviver({
|
||||
canRevive: (webview) => {
|
||||
const viewType = webview.state.viewType;
|
||||
if (viewType) {
|
||||
|
@ -97,7 +95,7 @@ export class MainThreadWebviews extends Disposable implements MainThreadWebviews
|
|||
mainThreadShowOptions.group = viewColumnToEditorGroup(this._editorGroupService, showOptions.viewColumn);
|
||||
}
|
||||
|
||||
const webview = this._webviewService.createWebview(this.getInternalWebviewId(viewType), title, mainThreadShowOptions, reviveWebviewOptions(options), {
|
||||
const webview = this._webviewEditorService.createWebview(this.getInternalWebviewId(viewType), title, mainThreadShowOptions, reviveWebviewOptions(options), {
|
||||
location: URI.revive(extensionLocation),
|
||||
id: extensionId
|
||||
}, this.createWebviewEventDelegate(handle));
|
||||
|
@ -141,10 +139,6 @@ export class MainThreadWebviews extends Disposable implements MainThreadWebviews
|
|||
webview.setOptions(reviveWebviewOptions(options as any /*todo@mat */));
|
||||
}
|
||||
|
||||
async $getResourceRoot(_handle: WebviewPanelHandle): Promise<string> {
|
||||
return this._environmentService.webviewResourceRoot;
|
||||
}
|
||||
|
||||
public $reveal(handle: WebviewPanelHandle, showOptions: WebviewPanelShowOptions): void {
|
||||
const webview = this.getWebview(handle);
|
||||
if (webview.isDisposed()) {
|
||||
|
@ -153,7 +147,7 @@ export class MainThreadWebviews extends Disposable implements MainThreadWebviews
|
|||
|
||||
const targetGroup = this._editorGroupService.getGroup(viewColumnToEditorGroup(this._editorGroupService, showOptions.viewColumn)) || this._editorGroupService.getGroup(webview.group || 0);
|
||||
if (targetGroup) {
|
||||
this._webviewService.revealWebview(webview, targetGroup, !!showOptions.preserveFocus);
|
||||
this._webviewEditorService.revealWebview(webview, targetGroup, !!showOptions.preserveFocus);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -182,7 +176,7 @@ export class MainThreadWebviews extends Disposable implements MainThreadWebviews
|
|||
throw new Error(`Reviver for ${viewType} already registered`);
|
||||
}
|
||||
|
||||
this._revivers.set(viewType, this._webviewService.registerReviver({
|
||||
this._revivers.set(viewType, this._webviewEditorService.registerReviver({
|
||||
canRevive: (webview) => {
|
||||
return webview.state && webview.state.viewType === viewType;
|
||||
},
|
||||
|
|
|
@ -59,6 +59,8 @@ export interface IEnvironment {
|
|||
extensionTestsLocationURI?: URI;
|
||||
globalStorageHome: URI;
|
||||
userHome: URI;
|
||||
webviewResourceRoot: string;
|
||||
webviewCspRule: string;
|
||||
}
|
||||
|
||||
export interface IStaticWorkspaceData {
|
||||
|
@ -533,7 +535,6 @@ export interface MainThreadEditorInsetsShape extends IDisposable {
|
|||
$setHtml(handle: number, value: string): void;
|
||||
$setOptions(handle: number, options: modes.IWebviewOptions): void;
|
||||
$postMessage(handle: number, value: any): Promise<boolean>;
|
||||
$getResourceRoot(handle: number): Promise<string>;
|
||||
}
|
||||
|
||||
export interface ExtHostEditorInsetsShape {
|
||||
|
@ -558,7 +559,6 @@ export interface MainThreadWebviewsShape extends IDisposable {
|
|||
$setHtml(handle: WebviewPanelHandle, value: string): void;
|
||||
$setOptions(handle: WebviewPanelHandle, options: modes.IWebviewOptions): void;
|
||||
$postMessage(handle: WebviewPanelHandle, value: any): Promise<boolean>;
|
||||
$getResourceRoot(handle: WebviewPanelHandle): Promise<string>;
|
||||
|
||||
$registerSerializer(viewType: string): void;
|
||||
$unregisterSerializer(viewType: string): void;
|
||||
|
|
|
@ -4,12 +4,13 @@
|
|||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
import * as vscode from 'vscode';
|
||||
import { MainThreadEditorInsetsShape, ExtHostEditorInsetsShape } from './extHost.protocol';
|
||||
import { ExtHostEditors } from 'vs/workbench/api/common/extHostTextEditors';
|
||||
import { DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { ExtHostTextEditor } from 'vs/workbench/api/common/extHostTextEditor';
|
||||
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
import { ExtHostTextEditor } from 'vs/workbench/api/common/extHostTextEditor';
|
||||
import { ExtHostEditors } from 'vs/workbench/api/common/extHostTextEditors';
|
||||
import * as vscode from 'vscode';
|
||||
import { ExtHostEditorInsetsShape, MainThreadEditorInsetsShape } from './extHost.protocol';
|
||||
import { toWebviewResource, WebviewInitData } from 'vs/workbench/api/common/shared/webview';
|
||||
|
||||
export class ExtHostEditorInsets implements ExtHostEditorInsetsShape {
|
||||
|
||||
|
@ -19,7 +20,8 @@ export class ExtHostEditorInsets implements ExtHostEditorInsetsShape {
|
|||
|
||||
constructor(
|
||||
private readonly _proxy: MainThreadEditorInsetsShape,
|
||||
private readonly _editors: ExtHostEditors
|
||||
private readonly _editors: ExtHostEditors,
|
||||
private readonly _initData: WebviewInitData
|
||||
) {
|
||||
|
||||
// dispose editor inset whenever the hosting editor goes away
|
||||
|
@ -61,8 +63,12 @@ export class ExtHostEditorInsets implements ExtHostEditorInsetsShape {
|
|||
private _html: string = '';
|
||||
private _options: vscode.WebviewOptions;
|
||||
|
||||
get resourceRoot(): Promise<string> {
|
||||
return that._proxy.$getResourceRoot(handle);
|
||||
toWebviewResource(resource: vscode.Uri): vscode.Uri {
|
||||
return toWebviewResource(that._initData, resource);
|
||||
}
|
||||
|
||||
get cspRule(): string {
|
||||
return that._initData.webviewCspRule;
|
||||
}
|
||||
|
||||
set options(value: vscode.WebviewOptions) {
|
||||
|
|
|
@ -12,6 +12,7 @@ import { ExtHostWebviewsShape, IMainContext, MainContext, MainThreadWebviewsShap
|
|||
import { Disposable } from './extHostTypes';
|
||||
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
import * as modes from 'vs/editor/common/modes';
|
||||
import { WebviewInitData, toWebviewResource } from 'vs/workbench/api/common/shared/webview';
|
||||
|
||||
type IconPath = URI | { light: URI, dark: URI };
|
||||
|
||||
|
@ -28,7 +29,8 @@ export class ExtHostWebview implements vscode.Webview {
|
|||
constructor(
|
||||
handle: WebviewPanelHandle,
|
||||
proxy: MainThreadWebviewsShape,
|
||||
options: vscode.WebviewOptions
|
||||
options: vscode.WebviewOptions,
|
||||
private readonly initData: WebviewInitData
|
||||
) {
|
||||
this._handle = handle;
|
||||
this._proxy = proxy;
|
||||
|
@ -39,8 +41,12 @@ export class ExtHostWebview implements vscode.Webview {
|
|||
this._onMessageEmitter.dispose();
|
||||
}
|
||||
|
||||
public get resourceRoot(): Promise<string> {
|
||||
return this._proxy.$getResourceRoot(this._handle);
|
||||
public toWebviewResource(resource: vscode.Uri): vscode.Uri {
|
||||
return toWebviewResource(this.initData, resource);
|
||||
}
|
||||
|
||||
public get cspRule(): string {
|
||||
return this.initData.webviewCspRule;
|
||||
}
|
||||
|
||||
public get html(): string {
|
||||
|
@ -243,7 +249,8 @@ export class ExtHostWebviews implements ExtHostWebviewsShape {
|
|||
private readonly _serializers = new Map<string, vscode.WebviewPanelSerializer>();
|
||||
|
||||
constructor(
|
||||
mainContext: IMainContext
|
||||
mainContext: IMainContext,
|
||||
private readonly initData: WebviewInitData
|
||||
) {
|
||||
this._proxy = mainContext.getProxy(MainContext.MainThreadWebviews);
|
||||
}
|
||||
|
@ -264,7 +271,7 @@ export class ExtHostWebviews implements ExtHostWebviewsShape {
|
|||
const handle = ExtHostWebviews.newHandle();
|
||||
this._proxy.$createWebviewPanel(handle, viewType, title, webviewShowOptions, convertWebviewOptions(options), extension.identifier, extension.extensionLocation);
|
||||
|
||||
const webview = new ExtHostWebview(handle, this._proxy, options);
|
||||
const webview = new ExtHostWebview(handle, this._proxy, options, this.initData);
|
||||
const panel = new ExtHostWebviewPanel(handle, this._proxy, viewType, title, viewColumn, options, webview);
|
||||
this._webviewPanels.set(handle, panel);
|
||||
return panel;
|
||||
|
@ -337,7 +344,7 @@ export class ExtHostWebviews implements ExtHostWebviewsShape {
|
|||
return Promise.reject(new Error(`No serializer found for '${viewType}'`));
|
||||
}
|
||||
|
||||
const webview = new ExtHostWebview(webviewHandle, this._proxy, options);
|
||||
const webview = new ExtHostWebview(webviewHandle, this._proxy, options, this.initData);
|
||||
const revivedPanel = new ExtHostWebviewPanel(webviewHandle, this._proxy, viewType, title, typeof position === 'number' && position >= 0 ? typeConverters.ViewColumn.to(position) : undefined, options, webview);
|
||||
this._webviewPanels.set(webviewHandle, revivedPanel);
|
||||
return Promise.resolve(serializer.deserializeWebviewPanel(revivedPanel, state));
|
||||
|
|
24
src/vs/workbench/api/common/shared/webview.ts
Normal file
24
src/vs/workbench/api/common/shared/webview.ts
Normal file
|
@ -0,0 +1,24 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
export interface WebviewInitData {
|
||||
readonly webviewResourceRoot: string;
|
||||
readonly webviewCspRule: string;
|
||||
}
|
||||
|
||||
export function toWebviewResource(
|
||||
initData: WebviewInitData,
|
||||
resource: vscode.Uri
|
||||
): vscode.Uri {
|
||||
const rootUri = URI.parse(initData.webviewResourceRoot);
|
||||
return rootUri.with({
|
||||
path: rootUri.path + resource.path,
|
||||
query: resource.query,
|
||||
fragment: resource.fragment,
|
||||
});
|
||||
}
|
|
@ -98,7 +98,7 @@ export function createApiFactory(
|
|||
// Addressable instances
|
||||
rpcProtocol.set(ExtHostContext.ExtHostLogService, extHostLogService);
|
||||
const extHostDecorations = rpcProtocol.set(ExtHostContext.ExtHostDecorations, new ExtHostDecorations(rpcProtocol));
|
||||
const extHostWebviews = rpcProtocol.set(ExtHostContext.ExtHostWebviews, new ExtHostWebviews(rpcProtocol));
|
||||
const extHostWebviews = rpcProtocol.set(ExtHostContext.ExtHostWebviews, new ExtHostWebviews(rpcProtocol, initData.environment));
|
||||
const extHostUrls = rpcProtocol.set(ExtHostContext.ExtHostUrls, new ExtHostUrls(rpcProtocol));
|
||||
const extHostDocumentsAndEditors = rpcProtocol.set(ExtHostContext.ExtHostDocumentsAndEditors, new ExtHostDocumentsAndEditors(rpcProtocol));
|
||||
const extHostDocuments = rpcProtocol.set(ExtHostContext.ExtHostDocuments, new ExtHostDocuments(rpcProtocol, extHostDocumentsAndEditors));
|
||||
|
@ -109,7 +109,7 @@ export function createApiFactory(
|
|||
const extHostTreeViews = rpcProtocol.set(ExtHostContext.ExtHostTreeViews, new ExtHostTreeViews(rpcProtocol.getProxy(MainContext.MainThreadTreeViews), extHostCommands, extHostLogService));
|
||||
rpcProtocol.set(ExtHostContext.ExtHostWorkspace, extHostWorkspace);
|
||||
rpcProtocol.set(ExtHostContext.ExtHostConfiguration, extHostConfiguration);
|
||||
const extHostEditorInsets = rpcProtocol.set(ExtHostContext.ExtHostEditorInsets, new ExtHostEditorInsets(rpcProtocol.getProxy(MainContext.MainThreadEditorInsets), extHostEditors));
|
||||
const extHostEditorInsets = rpcProtocol.set(ExtHostContext.ExtHostEditorInsets, new ExtHostEditorInsets(rpcProtocol.getProxy(MainContext.MainThreadEditorInsets), extHostEditors, initData.environment));
|
||||
const extHostDiagnostics = rpcProtocol.set(ExtHostContext.ExtHostDiagnostics, new ExtHostDiagnostics(rpcProtocol));
|
||||
const extHostLanguageFeatures = rpcProtocol.set(ExtHostContext.ExtHostLanguageFeatures, new ExtHostLanguageFeatures(rpcProtocol, uriTransformer, extHostDocuments, extHostCommands, extHostDiagnostics, extHostLogService));
|
||||
const extHostFileSystem = rpcProtocol.set(ExtHostContext.ExtHostFileSystem, new ExtHostFileSystem(rpcProtocol, extHostLanguageFeatures));
|
||||
|
|
|
@ -4,8 +4,8 @@
|
|||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IWebviewService, Webview, WebviewContentOptions, WebviewOptions } from 'vs/workbench/contrib/webview/common/webview';
|
||||
import { WebviewElement } from 'vs/workbench/contrib/webview/electron-browser/webviewElement';
|
||||
import { IWebviewService, WebviewOptions, WebviewContentOptions, Webview } from 'vs/workbench/contrib/webview/common/webview';
|
||||
|
||||
export class WebviewService implements IWebviewService {
|
||||
_serviceBrand: any;
|
||||
|
|
|
@ -149,4 +149,8 @@ export class BrowserWorkbenchEnvironmentService implements IEnvironmentService {
|
|||
get webviewResourceRoot(): string {
|
||||
return this.webviewEndpoint ? this.webviewEndpoint + '/vscode-resource' : 'vscode-resource:';
|
||||
}
|
||||
|
||||
get webviewCspRule(): string {
|
||||
return this.webviewEndpoint ? this.webviewEndpoint : 'vscode-resource:';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -190,6 +190,8 @@ export class RemoteExtensionHostClient extends Disposable implements IExtensionH
|
|||
extensionTestsLocationURI: this._environmentService.extensionTestsLocationURI,
|
||||
globalStorageHome: remoteExtensionHostData.globalStorageHome,
|
||||
userHome: remoteExtensionHostData.userHome,
|
||||
webviewResourceRoot: this._environmentService.webviewResourceRoot,
|
||||
webviewCspRule: this._environmentService.webviewCspRule,
|
||||
},
|
||||
workspace: this._contextService.getWorkbenchState() === WorkbenchState.EMPTY ? null : {
|
||||
configuration: workspace.configuration,
|
||||
|
|
|
@ -400,6 +400,8 @@ 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,
|
||||
webviewCspRule: this._environmentService.webviewCspRule,
|
||||
},
|
||||
workspace: this._contextService.getWorkbenchState() === WorkbenchState.EMPTY ? undefined : {
|
||||
configuration: withNullAsUndefined(workspace.configuration),
|
||||
|
|
|
@ -17,7 +17,7 @@ suite('ExtHostWebview', function () {
|
|||
const viewType = 'view.type';
|
||||
|
||||
const shape = createNoopMainThreadWebviews();
|
||||
const extHostWebviews = new ExtHostWebviews(SingleProxyRPCProtocol(shape));
|
||||
const extHostWebviews = new ExtHostWebviews(SingleProxyRPCProtocol(shape), { webviewCspRule: '', webviewResourceRoot: '' });
|
||||
|
||||
let lastInvokedDeserializer: vscode.WebviewPanelSerializer | undefined = undefined;
|
||||
|
||||
|
|
Loading…
Reference in a new issue