mirror of
https://github.com/microsoft/vscode.git
synced 2025-12-24 12:19:20 +00:00
Allow reusing webview origins across reloads (#149950)
* Allow reusing webview origins across reloads Currently webviews are always loaded into a unique origin. This keeps them isolated but also means that we can't benefit from caching across window reloads This change adds a new `origin` option that you can pass to webviews which controls the origin they use. If this origin is not provided, we use a random one instead We then save off this origin for webview panels and restore it on window reloads. Webviews restore a little faster on window reload * Update webview fallback version
This commit is contained in:
@@ -27,7 +27,7 @@
|
||||
"licenseFileName": "LICENSE.txt",
|
||||
"reportIssueUrl": "https://github.com/microsoft/vscode/issues/new",
|
||||
"urlProtocol": "code-oss",
|
||||
"webviewContentExternalBaseUrlTemplate": "https://{{uuid}}.vscode-cdn.net/insider/181b43c0e2949e36ecb623d8cc6de29d4fa2bae8/out/vs/workbench/contrib/webview/browser/pre/",
|
||||
"webviewContentExternalBaseUrlTemplate": "https://{{uuid}}.vscode-cdn.net/insider/3c8520fab514b9f56070214496b26ff68d1b1cb5/out/vs/workbench/contrib/webview/browser/pre/",
|
||||
"builtInExtensions": [
|
||||
{
|
||||
"name": "ms-vscode.js-debug-companion",
|
||||
|
||||
@@ -662,6 +662,7 @@ class MainThreadCustomEditorModel extends ResourceWorkingCopy implements ICustom
|
||||
} : undefined,
|
||||
webview: {
|
||||
id: primaryEditor.id,
|
||||
origin: primaryEditor.webview.origin,
|
||||
options: primaryEditor.webview.options,
|
||||
state: primaryEditor.webview.state,
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ export interface CustomDocumentBackupData extends IWorkingCopyBackupMeta {
|
||||
|
||||
readonly webview: {
|
||||
readonly id: string;
|
||||
readonly origin: string | undefined;
|
||||
readonly options: SerializedWebviewOptions;
|
||||
readonly state: any;
|
||||
};
|
||||
@@ -107,9 +108,10 @@ export class CustomEditorInputSerializer extends WebviewEditorInputSerializer {
|
||||
}
|
||||
}
|
||||
|
||||
function reviveWebview(webviewService: IWebviewService, data: { id: string; state: any; webviewOptions: WebviewOptions; contentOptions: WebviewContentOptions; extension?: WebviewExtensionDescription }) {
|
||||
function reviveWebview(webviewService: IWebviewService, data: { id: string; origin: string | undefined; state: any; webviewOptions: WebviewOptions; contentOptions: WebviewContentOptions; extension?: WebviewExtensionDescription }) {
|
||||
const webview = webviewService.createWebviewOverlay({
|
||||
id: data.id,
|
||||
origin: data.origin,
|
||||
options: {
|
||||
purpose: WebviewContentPurpose.CustomEditor,
|
||||
enableFindWidget: data.webviewOptions.enableFindWidget,
|
||||
@@ -185,6 +187,7 @@ export class ComplexCustomWorkingCopyEditorHandler extends Disposable implements
|
||||
const extension = reviveWebviewExtensionDescription(backupData.extension?.id, backupData.extension?.location);
|
||||
const webview = reviveWebview(this._webviewService, {
|
||||
id,
|
||||
origin: backupData.webview.origin,
|
||||
webviewOptions: restoreWebviewOptions(backupData.webview.options),
|
||||
contentOptions: restoreWebviewContentOptions(backupData.webview.options),
|
||||
state: backupData.webview.state,
|
||||
|
||||
@@ -8,6 +8,7 @@ import { IMouseWheelEvent } from 'vs/base/browser/mouseEvent';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { Disposable, DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
|
||||
import { ILayoutService } from 'vs/platform/layout/browser/layoutService';
|
||||
@@ -43,6 +44,7 @@ export class OverlayWebview extends Disposable implements IOverlayWebview {
|
||||
private _findWidgetEnabled: IContextKey<boolean> | undefined;
|
||||
|
||||
public readonly id: string;
|
||||
public readonly origin: string;
|
||||
|
||||
public constructor(
|
||||
initInfo: WebviewInitInfo,
|
||||
@@ -53,6 +55,8 @@ export class OverlayWebview extends Disposable implements IOverlayWebview {
|
||||
super();
|
||||
|
||||
this.id = initInfo.id;
|
||||
this.origin = initInfo.origin ?? generateUuid();
|
||||
|
||||
this._extension = initInfo.extension;
|
||||
this._options = initInfo.options;
|
||||
this._contentOptions = initInfo.contentOptions;
|
||||
@@ -186,7 +190,13 @@ export class OverlayWebview extends Disposable implements IOverlayWebview {
|
||||
}
|
||||
|
||||
if (!this._webview.value) {
|
||||
const webview = this._webviewService.createWebviewElement({ id: this.id, options: this._options, contentOptions: this._contentOptions, extension: this.extension });
|
||||
const webview = this._webviewService.createWebviewElement({
|
||||
id: this.id,
|
||||
origin: this.origin,
|
||||
options: this._options,
|
||||
contentOptions: this._contentOptions,
|
||||
extension: this.extension,
|
||||
});
|
||||
this._webview.value = webview;
|
||||
webview.state = this._state;
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ const isFirefox = (
|
||||
|
||||
const searchParams = new URL(location.toString()).searchParams;
|
||||
const ID = searchParams.get('id');
|
||||
const webviewOrigin = searchParams.get('origin');
|
||||
const onElectron = searchParams.get('platform') === 'electron';
|
||||
const expectedWorkerVersion = parseInt(searchParams.get('swVersion'));
|
||||
|
||||
@@ -314,7 +315,6 @@ const hostMessaging = new class HostMessaging {
|
||||
};
|
||||
|
||||
const parentOrigin = searchParams.get('parentOrigin');
|
||||
const id = searchParams.get('id');
|
||||
|
||||
const hostname = location.hostname;
|
||||
|
||||
@@ -327,7 +327,7 @@ const hostMessaging = new class HostMessaging {
|
||||
// compute a sha-256 composed of `parentOrigin` and `salt` converted to base 32
|
||||
let parentOriginHash;
|
||||
try {
|
||||
const strData = JSON.stringify({ parentOrigin, salt: id });
|
||||
const strData = JSON.stringify({ parentOrigin, salt: webviewOrigin });
|
||||
const encoder = new TextEncoder();
|
||||
const arrData = encoder.encode(strData);
|
||||
const hash = await crypto.subtle.digest('sha-256', arrData);
|
||||
|
||||
@@ -151,8 +151,16 @@ export interface WebviewMessageReceivedEvent {
|
||||
|
||||
export interface IWebview extends IDisposable {
|
||||
|
||||
/**
|
||||
* External identifier of this webview.
|
||||
*/
|
||||
readonly id: string;
|
||||
|
||||
/**
|
||||
* The origin this webview itself is loaded from. May not be unique
|
||||
*/
|
||||
readonly origin: string;
|
||||
|
||||
html: string;
|
||||
contentOptions: WebviewContentOptions;
|
||||
localResourcesRoot: readonly URI[];
|
||||
|
||||
@@ -102,6 +102,7 @@ namespace WebviewState {
|
||||
|
||||
export interface WebviewInitInfo {
|
||||
readonly id: string;
|
||||
readonly origin?: string;
|
||||
|
||||
readonly options: WebviewOptions;
|
||||
readonly contentOptions: WebviewContentOptions;
|
||||
@@ -118,7 +119,12 @@ export class WebviewElement extends Disposable implements IWebview, WebviewFindD
|
||||
public readonly id: string;
|
||||
|
||||
/**
|
||||
* Unique identifier of this webview iframe element.
|
||||
* The origin this webview itself is loaded from. May not be unique
|
||||
*/
|
||||
public readonly origin: string;
|
||||
|
||||
/**
|
||||
* Unique internal identifier of this webview's iframe element.
|
||||
*/
|
||||
private readonly iframeId: string;
|
||||
|
||||
@@ -194,7 +200,9 @@ export class WebviewElement extends Disposable implements IWebview, WebviewFindD
|
||||
|
||||
this.id = initInfo.id;
|
||||
this.iframeId = generateUuid();
|
||||
this.encodedWebviewOriginPromise = parentOriginHash(window.origin, this.iframeId).then(id => this.encodedWebviewOrigin = id);
|
||||
this.origin = initInfo.origin ?? this.iframeId;
|
||||
|
||||
this.encodedWebviewOriginPromise = parentOriginHash(window.origin, this.origin).then(id => this.encodedWebviewOrigin = id);
|
||||
|
||||
this.options = initInfo.options;
|
||||
this.extension = initInfo.extension;
|
||||
@@ -484,6 +492,7 @@ export class WebviewElement extends Disposable implements IWebview, WebviewFindD
|
||||
// The extensionId and purpose in the URL are used for filtering in js-debug:
|
||||
const params: { [key: string]: string } = {
|
||||
id: this.iframeId,
|
||||
origin: this.origin,
|
||||
swVersion: String(this._expectedServiceWorkerVersion),
|
||||
extensionId: extension?.id.value ?? '',
|
||||
platform: this.platform,
|
||||
|
||||
@@ -21,6 +21,7 @@ interface SerializedIconPath {
|
||||
|
||||
export interface SerializedWebview {
|
||||
readonly id: string;
|
||||
readonly origin: string | undefined;
|
||||
readonly viewType: string;
|
||||
readonly title: string;
|
||||
readonly options: SerializedWebviewOptions;
|
||||
@@ -33,6 +34,7 @@ export interface SerializedWebview {
|
||||
|
||||
export interface DeserializedWebview {
|
||||
readonly id: string;
|
||||
readonly origin: string | undefined;
|
||||
readonly viewType: string;
|
||||
readonly title: string;
|
||||
readonly webviewOptions: WebviewOptions;
|
||||
@@ -76,6 +78,7 @@ export class WebviewEditorInputSerializer implements IEditorSerializer {
|
||||
return this._webviewWorkbenchService.reviveWebview({
|
||||
webviewInitInfo: {
|
||||
id: data.id,
|
||||
origin: data.origin,
|
||||
options: data.webviewOptions,
|
||||
contentOptions: data.contentOptions,
|
||||
extension: data.extension,
|
||||
@@ -102,6 +105,7 @@ export class WebviewEditorInputSerializer implements IEditorSerializer {
|
||||
protected toJson(input: WebviewInput): SerializedWebview {
|
||||
return {
|
||||
id: input.id,
|
||||
origin: input.webview.origin,
|
||||
viewType: input.viewType,
|
||||
title: input.getName(),
|
||||
options: { ...input.webview.options, ...input.webview.contentOptions },
|
||||
|
||||
@@ -189,7 +189,7 @@ export class BrowserWorkbenchEnvironmentService implements IBrowserWorkbenchEnvi
|
||||
|
||||
const webviewExternalEndpointCommit = this.payload?.get('webviewExternalEndpointCommit');
|
||||
return endpoint
|
||||
.replace('{{commit}}', webviewExternalEndpointCommit ?? this.productService.commit ?? '181b43c0e2949e36ecb623d8cc6de29d4fa2bae8')
|
||||
.replace('{{commit}}', webviewExternalEndpointCommit ?? this.productService.commit ?? '3c8520fab514b9f56070214496b26ff68d1b1cb5')
|
||||
.replace('{{quality}}', (webviewExternalEndpointCommit ? 'insider' : this.productService.quality) ?? 'insider');
|
||||
}
|
||||
|
||||
|
||||
@@ -65,7 +65,7 @@ async function runTestsInBrowser(browserType: BrowserType, endpoint: url.UrlWith
|
||||
const testExtensionUri = url.format({ pathname: URI.file(path.resolve(optimist.argv.extensionDevelopmentPath)).path, protocol, host, slashes: true });
|
||||
const testFilesUri = url.format({ pathname: URI.file(path.resolve(optimist.argv.extensionTestsPath)).path, protocol, host, slashes: true });
|
||||
|
||||
const payloadParam = `[["extensionDevelopmentPath","${testExtensionUri}"],["extensionTestsPath","${testFilesUri}"],["enableProposedApi",""],["webviewExternalEndpointCommit","181b43c0e2949e36ecb623d8cc6de29d4fa2bae8"],["skipWelcome","true"]]`;
|
||||
const payloadParam = `[["extensionDevelopmentPath","${testExtensionUri}"],["extensionTestsPath","${testFilesUri}"],["enableProposedApi",""],["webviewExternalEndpointCommit","3c8520fab514b9f56070214496b26ff68d1b1cb5"],["skipWelcome","true"]]`;
|
||||
|
||||
if (path.extname(testWorkspacePath) === '.code-workspace') {
|
||||
await page.goto(`${endpoint.href}&workspace=${testWorkspacePath}&payload=${payloadParam}`);
|
||||
|
||||
Reference in New Issue
Block a user