Get expected service worker version from renderer instead of main.js

Due to caching, it is possible that a webview ends up running an older service worker than the current renderer version. This can be triggered if a user has webview open, updates vs code (to a version that makes service worker related changes), and then has the webview restored. Depending on what was changed in the service worker, this could potentially break the webview

Previously we tried to avoid this by checking a version number stored in `main.js`. However I don't think this is reliable as `main.js` can also be cached.

The fix here to check the sw version against an expected version from the renderer. In code, these two should always be kept in sync
This commit is contained in:
Matt Bierner 2021-05-28 01:47:25 -07:00
parent 3bfa3455a6
commit 676340ffad
No known key found for this signature in database
GPG key ID: 099C331567E11888
4 changed files with 17 additions and 7 deletions

View file

@ -83,6 +83,8 @@ namespace WebviewState {
export abstract class BaseWebview<T extends HTMLElement> extends Disposable {
protected readonly _expectedServiceWorkerVersion = 2; // Keep this in sync with the version in service-worker.js
private _element: T | undefined;
protected get element(): T | undefined { return this._element; }

View file

@ -25,6 +25,7 @@ const isSafari = navigator.vendor && navigator.vendor.indexOf('Apple') > -1 &&
const searchParams = new URL(location.toString()).searchParams;
const ID = searchParams.get('id');
const expectedWorkerVersion = parseInt(searchParams.get('swVersion'));
/**
* Use polling to track focus of main webview and iframes within the webview
@ -210,16 +211,16 @@ const workerReady = new Promise(async (resolve, reject) => {
return reject(new Error('Service Workers are not enabled in browser. Webviews will not work.'));
}
const expectedWorkerVersion = 2;
const swPath = `service-worker.js${self.location.search}`;
navigator.serviceWorker.register(`service-worker.js${self.location.search}`).then(
navigator.serviceWorker.register(swPath).then(
async registration => {
await navigator.serviceWorker.ready;
/**
* @param {MessageEvent} event
*/
const versionHandler = (event) => {
const versionHandler = async (event) => {
if (event.data.channel !== 'version') {
return;
}
@ -228,10 +229,16 @@ const workerReady = new Promise(async (resolve, reject) => {
if (event.data.version === expectedWorkerVersion) {
return resolve();
} else {
// If we have the wrong version, try once to unregister and re-register
return registration.update()
console.log(`Found unexpected service worker version. Found: ${event.data.version}. Expected: ${expectedWorkerVersion}`);
console.log(`Attempting to reload service worker`);
// If we have the wrong version, try once (and only once) to unregister and re-register
// Note that `.update` doesn't seem to work desktop electron at the moment so we use
// `unregister` and `register` here.
return registration.unregister()
.then(() => navigator.serviceWorker.register(swPath))
.then(() => navigator.serviceWorker.ready)
.finally(resolve);
.finally(() => { resolve(); });
}
};
navigator.serviceWorker.addEventListener('message', versionHandler);

View file

@ -100,6 +100,7 @@ export class IFrameWebview extends BaseWebview<HTMLIFrameElement> implements Web
protected initElement(extension: WebviewExtensionDescription | undefined, options: WebviewOptions, extraParams?: { [key: string]: string }) {
const params: { [key: string]: string } = {
id: this.id,
swVersion: String(this._expectedServiceWorkerVersion),
extensionId: extension?.id.value ?? '', // The extensionId and purpose in the URL are used for filtering in js-debug:
...extraParams,
'vscode-resource-base-authority': this.webviewRootResourceAuthority,

View file

@ -159,7 +159,7 @@ export class ElectronWebviewBasedWebview extends BaseWebview<WebviewTag> impleme
// and not the `vscode-file` URI because preload scripts are loaded
// via node.js from the main side and only allow `file:` protocol
this.element!.preload = FileAccess.asFileUri('./pre/electron-index.js', require).toString(true);
this.element!.src = `${Schemas.vscodeWebview}://${this.id}/electron-browser-index.html?platform=electron&id=${this.id}&vscode-resource-base-authority=${encodeURIComponent(this.webviewRootResourceAuthority)}`;
this.element!.src = `${Schemas.vscodeWebview}://${this.id}/electron-browser-index.html?platform=electron&id=${this.id}&vscode-resource-base-authority=${encodeURIComponent(this.webviewRootResourceAuthority)}&swVersion=${this._expectedServiceWorkerVersion}`;
}
protected createElement(options: WebviewOptions) {