Skip to content

Commit

Permalink
Allow reusing webview origins across reloads (#149950)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
mjbvz authored May 19, 2022
1 parent 08f247b commit 17c75e1
Show file tree
Hide file tree
Showing 10 changed files with 44 additions and 9 deletions.
2 changes: 1 addition & 1 deletion product.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
1 change: 1 addition & 0 deletions src/vs/workbench/api/browser/mainThreadCustomEditors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export interface CustomDocumentBackupData extends IWorkingCopyBackupMeta {

readonly webview: {
readonly id: string;
readonly origin: string | undefined;
readonly options: SerializedWebviewOptions;
readonly state: any;
};
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
12 changes: 11 additions & 1 deletion src/vs/workbench/contrib/webview/browser/overlayWebview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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,
Expand All @@ -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;
Expand Down Expand Up @@ -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;

Expand Down
4 changes: 2 additions & 2 deletions src/vs/workbench/contrib/webview/browser/pre/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'));

Expand Down Expand Up @@ -314,7 +315,6 @@ const hostMessaging = new class HostMessaging {
};

const parentOrigin = searchParams.get('parentOrigin');
const id = searchParams.get('id');

const hostname = location.hostname;

Expand All @@ -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);
Expand Down
8 changes: 8 additions & 0 deletions src/vs/workbench/contrib/webview/browser/webview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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[];
Expand Down
13 changes: 11 additions & 2 deletions src/vs/workbench/contrib/webview/browser/webviewElement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ namespace WebviewState {

export interface WebviewInitInfo {
readonly id: string;
readonly origin?: string;

readonly options: WebviewOptions;
readonly contentOptions: WebviewContentOptions;
Expand All @@ -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;

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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,
Expand All @@ -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 },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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');
}

Expand Down
2 changes: 1 addition & 1 deletion test/integration/browser/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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}`);
Expand Down

1 comment on commit 17c75e1

@meetsussie
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great ideas to be made

Please sign in to comment.