mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 18:46:40 -05:00
Merge from vscode 011858832762aaff245b2336fb1c38166e7a10fb (#4663)
This commit is contained in:
@@ -245,6 +245,7 @@ export class WebviewEditor extends BaseEditor {
|
||||
webview.update(input.html, {
|
||||
allowScripts: input.options.enableScripts,
|
||||
localResourceRoots: input.options.localResourceRoots || this.getDefaultLocalResourceRoots(),
|
||||
portMappings: input.options.portMapping,
|
||||
}, !!input.options.retainContextWhenHidden);
|
||||
|
||||
if (this._webviewContent) {
|
||||
|
||||
@@ -9,9 +9,8 @@ import { URI } from 'vs/base/common/uri';
|
||||
import { IEditorModel } from 'vs/platform/editor/common/editor';
|
||||
import { EditorInput, EditorModel, GroupIdentifier, IEditorInput } from 'vs/workbench/common/editor';
|
||||
import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService';
|
||||
import * as vscode from 'vscode';
|
||||
import { WebviewEvents, WebviewInputOptions } from './webviewEditorService';
|
||||
import { WebviewElement } from './webviewElement';
|
||||
import { WebviewElement, WebviewOptions } from './webviewElement';
|
||||
|
||||
export class WebviewEditorInput extends EditorInput {
|
||||
private static handlePool = 0;
|
||||
@@ -195,7 +194,7 @@ export class WebviewEditorInput extends EditorInput {
|
||||
return this._options;
|
||||
}
|
||||
|
||||
public setOptions(value: vscode.WebviewOptions) {
|
||||
public setOptions(value: WebviewOptions) {
|
||||
this._options = {
|
||||
...this._options,
|
||||
...value
|
||||
@@ -204,7 +203,8 @@ export class WebviewEditorInput extends EditorInput {
|
||||
if (this._webview) {
|
||||
this._webview.options = {
|
||||
allowScripts: this._options.enableScripts,
|
||||
localResourceRoots: this._options.localResourceRoots
|
||||
localResourceRoots: this._options.localResourceRoots,
|
||||
portMappings: this._options.portMapping,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,9 +35,9 @@ export class WebviewEditorInputFactory implements IEditorInputFactory {
|
||||
|
||||
public serialize(
|
||||
input: WebviewEditorInput
|
||||
): string | null {
|
||||
): string | undefined {
|
||||
if (!this._webviewService.shouldPersist(input)) {
|
||||
return null;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const data: SerializedWebview = {
|
||||
@@ -54,7 +54,7 @@ export class WebviewEditorInputFactory implements IEditorInputFactory {
|
||||
try {
|
||||
return JSON.stringify(data);
|
||||
} catch {
|
||||
return null;
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,8 +11,8 @@ import { createDecorator, IInstantiationService } from 'vs/platform/instantiatio
|
||||
import { GroupIdentifier } from 'vs/workbench/common/editor';
|
||||
import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
|
||||
import { ACTIVE_GROUP_TYPE, IEditorService, SIDE_GROUP_TYPE } from 'vs/workbench/services/editor/common/editorService';
|
||||
import * as vscode from 'vscode';
|
||||
import { RevivedWebviewEditorInput, WebviewEditorInput } from './webviewEditorInput';
|
||||
import { IWebviewOptions, IWebviewPanelOptions } from 'vs/editor/common/modes';
|
||||
|
||||
export const IWebviewEditorService = createDecorator<IWebviewEditorService>('webviewEditorService');
|
||||
|
||||
@@ -72,10 +72,10 @@ export interface WebviewReviver {
|
||||
export interface WebviewEvents {
|
||||
onMessage?(message: any): void;
|
||||
onDispose?(): void;
|
||||
onDidClickLink?(link: URI, options: vscode.WebviewOptions): void;
|
||||
onDidClickLink?(link: URI, options: IWebviewOptions): void;
|
||||
}
|
||||
|
||||
export interface WebviewInputOptions extends vscode.WebviewOptions, vscode.WebviewPanelOptions {
|
||||
export interface WebviewInputOptions extends IWebviewOptions, IWebviewPanelOptions {
|
||||
tryRestoreScrollPosition?: boolean;
|
||||
}
|
||||
|
||||
@@ -85,7 +85,8 @@ export function areWebviewInputOptionsEqual(a: WebviewInputOptions, b: WebviewIn
|
||||
&& a.enableScripts === b.enableScripts
|
||||
&& a.retainContextWhenHidden === b.retainContextWhenHidden
|
||||
&& a.tryRestoreScrollPosition === b.tryRestoreScrollPosition
|
||||
&& (a.localResourceRoots === b.localResourceRoots || (Array.isArray(a.localResourceRoots) && Array.isArray(b.localResourceRoots) && equals(a.localResourceRoots, b.localResourceRoots, (a, b) => a.toString() === b.toString())));
|
||||
&& (a.localResourceRoots === b.localResourceRoots || (Array.isArray(a.localResourceRoots) && Array.isArray(b.localResourceRoots) && equals(a.localResourceRoots, b.localResourceRoots, (a, b) => a.toString() === b.toString())))
|
||||
&& (a.portMapping === b.portMapping || (Array.isArray(a.portMapping) && Array.isArray(b.portMapping) && equals(a.portMapping, b.portMapping, (a, b) => a.from === b.from && a.to === b.to)));
|
||||
}
|
||||
|
||||
function canRevive(reviver: WebviewReviver, webview: WebviewEditorInput): boolean {
|
||||
@@ -128,11 +129,11 @@ export class WebviewEditorService implements IWebviewEditorService {
|
||||
viewType: string,
|
||||
title: string,
|
||||
showOptions: ICreateWebViewShowOptions,
|
||||
options: vscode.WebviewOptions,
|
||||
options: IWebviewOptions,
|
||||
extensionLocation: URI | undefined,
|
||||
events: WebviewEvents
|
||||
): WebviewEditorInput {
|
||||
const webviewInput = this._instantiationService.createInstance(WebviewEditorInput, viewType, undefined, title, options, {}, events, extensionLocation, undefined);
|
||||
const webviewInput = this._instantiationService.createInstance(WebviewEditorInput, viewType, undefined, title, options, {}, events, extensionLocation);
|
||||
this._editorService.openEditor(webviewInput, { pinned: true, preserveFocus: showOptions.preserveFocus }, showOptions.group);
|
||||
return webviewInput;
|
||||
}
|
||||
|
||||
@@ -3,9 +3,13 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { OnBeforeRequestDetails, OnHeadersReceivedDetails, Response } from 'electron';
|
||||
import { addClass, addDisposableListener } from 'vs/base/browser/dom';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { once } from 'vs/base/common/functional';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { isMacintosh } from 'vs/base/common/platform';
|
||||
import { endsWith } from 'vs/base/common/strings';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
@@ -15,8 +19,16 @@ import { DARK, ITheme, IThemeService, LIGHT } from 'vs/platform/theme/common/the
|
||||
import { registerFileProtocol, WebviewProtocol } from 'vs/workbench/contrib/webview/electron-browser/webviewProtocols';
|
||||
import { areWebviewInputOptionsEqual } from './webviewEditorService';
|
||||
import { WebviewFindWidget } from './webviewFindWidget';
|
||||
import { endsWith } from 'vs/base/common/strings';
|
||||
import { isMacintosh } from 'vs/base/common/platform';
|
||||
|
||||
export interface WebviewPortMapping {
|
||||
readonly port: number;
|
||||
readonly resolvedPort: number;
|
||||
}
|
||||
|
||||
export interface WebviewPortMapping {
|
||||
readonly port: number;
|
||||
readonly resolvedPort: number;
|
||||
}
|
||||
|
||||
export interface WebviewOptions {
|
||||
readonly allowSvgs?: boolean;
|
||||
@@ -28,6 +40,7 @@ export interface WebviewContentOptions {
|
||||
readonly allowScripts?: boolean;
|
||||
readonly svgWhiteList?: string[];
|
||||
readonly localResourceRoots?: ReadonlyArray<URI>;
|
||||
readonly portMappings?: ReadonlyArray<WebviewPortMapping>;
|
||||
}
|
||||
|
||||
interface IKeydownEvent {
|
||||
@@ -41,6 +54,58 @@ interface IKeydownEvent {
|
||||
repeat: boolean;
|
||||
}
|
||||
|
||||
type OnBeforeRequestDelegate = (details: OnBeforeRequestDetails) => Promise<Response | undefined>;
|
||||
type OnHeadersReceivedDelegate = (details: OnHeadersReceivedDetails) => { cancel: boolean } | undefined;
|
||||
|
||||
class WebviewSession extends Disposable {
|
||||
|
||||
private readonly _onBeforeRequestDelegates: Array<OnBeforeRequestDelegate> = [];
|
||||
private readonly _onHeadersReceivedDelegates: Array<OnHeadersReceivedDelegate> = [];
|
||||
|
||||
public constructor(
|
||||
webview: Electron.WebviewTag,
|
||||
) {
|
||||
super();
|
||||
|
||||
this._register(addDisposableListener(webview, 'did-start-loading', once(() => {
|
||||
const contents = webview.getWebContents();
|
||||
if (!contents) {
|
||||
return;
|
||||
}
|
||||
|
||||
contents.session.webRequest.onBeforeRequest(async (details, callback) => {
|
||||
for (const delegate of this._onBeforeRequestDelegates) {
|
||||
const result = await delegate(details);
|
||||
if (typeof result !== 'undefined') {
|
||||
callback(result);
|
||||
return;
|
||||
}
|
||||
}
|
||||
callback({});
|
||||
});
|
||||
|
||||
contents.session.webRequest.onHeadersReceived((details, callback) => {
|
||||
for (const delegate of this._onHeadersReceivedDelegates) {
|
||||
const result = delegate(details);
|
||||
if (typeof result !== 'undefined') {
|
||||
callback(result);
|
||||
return;
|
||||
}
|
||||
}
|
||||
callback({ cancel: false, responseHeaders: details.responseHeaders });
|
||||
});
|
||||
})));
|
||||
}
|
||||
|
||||
public onBeforeRequest(delegate: OnBeforeRequestDelegate) {
|
||||
this._onBeforeRequestDelegates.push(delegate);
|
||||
}
|
||||
|
||||
public onHeadersReceived(delegate: OnHeadersReceivedDelegate) {
|
||||
this._onHeadersReceivedDelegates.push(delegate);
|
||||
}
|
||||
}
|
||||
|
||||
class WebviewProtocolProvider extends Disposable {
|
||||
constructor(
|
||||
webview: Electron.WebviewTag,
|
||||
@@ -51,21 +116,15 @@ class WebviewProtocolProvider extends Disposable {
|
||||
) {
|
||||
super();
|
||||
|
||||
let loaded = false;
|
||||
this._register(addDisposableListener(webview, 'did-start-loading', () => {
|
||||
if (loaded) {
|
||||
return;
|
||||
}
|
||||
loaded = true;
|
||||
|
||||
this._register(addDisposableListener(webview, 'did-start-loading', once(() => {
|
||||
const contents = webview.getWebContents();
|
||||
if (contents) {
|
||||
this.registerFileProtocols(contents);
|
||||
this.registerProtocols(contents);
|
||||
}
|
||||
}));
|
||||
})));
|
||||
}
|
||||
|
||||
private registerFileProtocols(contents: Electron.WebContents) {
|
||||
private registerProtocols(contents: Electron.WebContents) {
|
||||
if (contents.isDestroyed()) {
|
||||
return;
|
||||
}
|
||||
@@ -82,52 +141,73 @@ class WebviewProtocolProvider extends Disposable {
|
||||
}
|
||||
}
|
||||
|
||||
class WebviewPortMappingProvider extends Disposable {
|
||||
|
||||
constructor(
|
||||
session: WebviewSession,
|
||||
mappings: () => ReadonlyArray<WebviewPortMapping>
|
||||
) {
|
||||
super();
|
||||
|
||||
session.onBeforeRequest(async (details) => {
|
||||
const uri = URI.parse(details.url);
|
||||
if (uri.scheme !== 'http' && uri.scheme !== 'https') {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const localhostMatch = /^localhost:(\d+)$/.exec(uri.authority);
|
||||
if (localhostMatch) {
|
||||
const port = +localhostMatch[1];
|
||||
for (const mapping of mappings()) {
|
||||
if (mapping.port === port && mapping.port !== mapping.resolvedPort) {
|
||||
return {
|
||||
redirectURL: details.url.replace(
|
||||
new RegExp(`^${uri.scheme}://localhost:${mapping.port}/`),
|
||||
`${uri.scheme}://localhost:${mapping.resolvedPort}/`)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class SvgBlocker extends Disposable {
|
||||
|
||||
private readonly _onDidBlockSvg = this._register(new Emitter<void>());
|
||||
public readonly onDidBlockSvg = this._onDidBlockSvg.event;
|
||||
|
||||
constructor(
|
||||
webview: Electron.WebviewTag,
|
||||
session: WebviewSession,
|
||||
private readonly _options: WebviewContentOptions,
|
||||
) {
|
||||
super();
|
||||
|
||||
let loaded = false;
|
||||
this._register(addDisposableListener(webview, 'did-start-loading', () => {
|
||||
if (loaded) {
|
||||
return;
|
||||
}
|
||||
loaded = true;
|
||||
|
||||
const contents = webview.getWebContents();
|
||||
if (!contents) {
|
||||
return;
|
||||
session.onBeforeRequest(async (details) => {
|
||||
if (details.url.indexOf('.svg') > 0) {
|
||||
const uri = URI.parse(details.url);
|
||||
if (uri && !uri.scheme.match(/file/i) && endsWith(uri.path, '.svg') && !this.isAllowedSvg(uri)) {
|
||||
this._onDidBlockSvg.fire();
|
||||
return { cancel: true };
|
||||
}
|
||||
}
|
||||
|
||||
contents.session.webRequest.onBeforeRequest((details, callback) => {
|
||||
if (details.url.indexOf('.svg') > 0) {
|
||||
const uri = URI.parse(details.url);
|
||||
if (uri && !uri.scheme.match(/file/i) && endsWith(uri.path, '.svg') && !this.isAllowedSvg(uri)) {
|
||||
this._onDidBlockSvg.fire();
|
||||
return callback({ cancel: true });
|
||||
}
|
||||
}
|
||||
return callback({});
|
||||
});
|
||||
return undefined;
|
||||
});
|
||||
|
||||
contents.session.webRequest.onHeadersReceived((details, callback) => {
|
||||
const contentType: string[] = details.responseHeaders['content-type'] || details.responseHeaders['Content-Type'];
|
||||
if (contentType && Array.isArray(contentType) && contentType.some(x => x.toLowerCase().indexOf('image/svg') >= 0)) {
|
||||
const uri = URI.parse(details.url);
|
||||
if (uri && !this.isAllowedSvg(uri)) {
|
||||
this._onDidBlockSvg.fire();
|
||||
return callback({ cancel: true });
|
||||
}
|
||||
session.onHeadersReceived((details) => {
|
||||
const contentType: string[] = details.responseHeaders['content-type'] || details.responseHeaders['Content-Type'];
|
||||
if (contentType && Array.isArray(contentType) && contentType.some(x => x.toLowerCase().indexOf('image/svg') >= 0)) {
|
||||
const uri = URI.parse(details.url);
|
||||
if (uri && !this.isAllowedSvg(uri)) {
|
||||
this._onDidBlockSvg.fire();
|
||||
return { cancel: true };
|
||||
}
|
||||
return callback({ cancel: false, responseHeaders: details.responseHeaders });
|
||||
});
|
||||
}));
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
}
|
||||
|
||||
private isAllowedSvg(uri: URI): boolean {
|
||||
@@ -236,7 +316,7 @@ export class WebviewElement extends Disposable {
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IEnvironmentService environmentService: IEnvironmentService,
|
||||
@IFileService fileService: IFileService
|
||||
@IFileService fileService: IFileService,
|
||||
) {
|
||||
super();
|
||||
this._webview = document.createElement('webview');
|
||||
@@ -264,16 +344,23 @@ export class WebviewElement extends Disposable {
|
||||
}));
|
||||
});
|
||||
|
||||
this._register(
|
||||
new WebviewProtocolProvider(
|
||||
this._webview,
|
||||
this._options.extensionLocation,
|
||||
() => (this._contentOptions.localResourceRoots || []),
|
||||
environmentService,
|
||||
fileService));
|
||||
const session = this._register(new WebviewSession(this._webview));
|
||||
|
||||
this._register(new WebviewProtocolProvider(
|
||||
this._webview,
|
||||
this._options.extensionLocation,
|
||||
() => (this._contentOptions.localResourceRoots || []),
|
||||
environmentService,
|
||||
fileService));
|
||||
|
||||
this._register(new WebviewPortMappingProvider(
|
||||
session,
|
||||
() => (this._contentOptions.portMappings || [])
|
||||
));
|
||||
|
||||
|
||||
if (!this._options.allowSvgs) {
|
||||
const svgBlocker = this._register(new SvgBlocker(this._webview, this._contentOptions));
|
||||
const svgBlocker = this._register(new SvgBlocker(session, this._contentOptions));
|
||||
svgBlocker.onDidBlockSvg(() => this.onDidBlockSvg());
|
||||
}
|
||||
|
||||
@@ -496,7 +583,7 @@ export class WebviewElement extends Disposable {
|
||||
if (!window || !window.webContents || window.webContents.isDestroyed()) {
|
||||
return;
|
||||
}
|
||||
window.webContents.getZoomFactor(factor => {
|
||||
window.webContents.getZoomFactor((factor: number) => {
|
||||
if (contents.isDestroyed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import { extname, sep } from 'vs/base/common/path';
|
||||
import { getMediaMime, MIME_UNKNOWN } from 'vs/base/common/mime';
|
||||
import { extname, sep } from 'vs/base/common/path';
|
||||
import { startsWith } from 'vs/base/common/strings';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
@@ -11,7 +11,7 @@ import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts';
|
||||
|
||||
export const enum WebviewProtocol {
|
||||
CoreResource = 'vscode-core-resource',
|
||||
VsCodeResource = 'vscode-resource'
|
||||
VsCodeResource = 'vscode-resource',
|
||||
}
|
||||
|
||||
function resolveContent(fileService: IFileService, resource: URI, mime: string, callback: any): void {
|
||||
@@ -60,7 +60,7 @@ export function registerFileProtocol(
|
||||
callback({ error: -10 /* ACCESS_DENIED: https://cs.chromium.org/chromium/src/net/base/net_error_list.h */ });
|
||||
}, (error) => {
|
||||
if (error) {
|
||||
console.error('Failed to register protocol ' + protocol);
|
||||
console.error(`Failed to register '${protocol}' protocol`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user