diff --git a/src/vs/workbench/contrib/webview/browser/baseWebviewElement.ts b/src/vs/workbench/contrib/webview/browser/baseWebviewElement.ts index 05c9310208f..7f97d5592bd 100644 --- a/src/vs/workbench/contrib/webview/browser/baseWebviewElement.ts +++ b/src/vs/workbench/contrib/webview/browser/baseWebviewElement.ts @@ -543,6 +543,7 @@ export abstract class BaseWebview extends Disposable { mime: result.mimeType, data: buffer, etag: result.etag, + mtime: result.mtime }); } case WebviewResourceResponse.Type.NotModified: @@ -552,6 +553,7 @@ export abstract class BaseWebview extends Disposable { status: 304, // not modified path: uri.path, mime: result.mimeType, + mtime: result.mtime }); } case WebviewResourceResponse.Type.AccessDenied: diff --git a/src/vs/workbench/contrib/webview/browser/pre/service-worker.js b/src/vs/workbench/contrib/webview/browser/pre/service-worker.js index 9463b4a7764..dfa0a5e08f7 100644 --- a/src/vs/workbench/contrib/webview/browser/pre/service-worker.js +++ b/src/vs/workbench/contrib/webview/browser/pre/service-worker.js @@ -100,7 +100,9 @@ class RequestStore { /** * Map of requested paths to responses. - * @typedef {{ type: 'response', body: any, mime: string, etag: string | undefined, } | { type: 'not-modified', mime: string } | undefined} ResourceResponse + * @typedef {{ type: 'response', body: Uint8Array, mime: string, etag: string | undefined, mtime: number | undefined } | + * { type: 'not-modified', mime: string, mtime: number | undefined } | + * undefined} ResourceResponse * @type {RequestStore} */ const resourceRequestStore = new RequestStore(); @@ -142,12 +144,12 @@ sw.addEventListener('message', async (event) => { switch (data.status) { case 200: { - response = { type: 'response', body: data.data, mime: data.mime, etag: data.etag }; + response = { type: 'response', body: data.data, mime: data.mime, etag: data.etag, mtime: data.mtime }; break; } case 304: { - response = { type: 'not-modified', mime: data.mime }; + response = { type: 'not-modified', mime: data.mime, mtime: data.mtime }; break; } } @@ -233,15 +235,19 @@ async function processResourceRequest(event, requestUrl) { } } - /** @type {Record} */ + /** @type {Record} */ const headers = { 'Content-Type': entry.mime, + 'Content-Length': entry.body.byteLength.toString(), 'Access-Control-Allow-Origin': '*', }; if (entry.etag) { headers['ETag'] = entry.etag; headers['Cache-Control'] = 'no-cache'; } + if (entry.mtime) { + headers['Last-Modified'] = new Date(entry.mtime).toUTCString(); + } const response = new Response(entry.body, { status: 200, headers diff --git a/src/vs/workbench/contrib/webview/browser/resourceLoading.ts b/src/vs/workbench/contrib/webview/browser/resourceLoading.ts index 5b6ebc58ac2..bba1ae1d6b6 100644 --- a/src/vs/workbench/contrib/webview/browser/resourceLoading.ts +++ b/src/vs/workbench/contrib/webview/browser/resourceLoading.ts @@ -22,6 +22,7 @@ export namespace WebviewResourceResponse { constructor( public readonly stream: VSBufferReadableStream, public readonly etag: string | undefined, + public readonly mtime: number | undefined, public readonly mimeType: string, ) { } } @@ -34,6 +35,7 @@ export namespace WebviewResourceResponse { constructor( public readonly mimeType: string, + public readonly mtime: number | undefined, ) { } } @@ -50,7 +52,7 @@ export async function loadLocalResource( logService: ILogService, token: CancellationToken, ): Promise { - logService.debug(`loadLocalResource - being. requestUri=${requestUri}`); + logService.debug(`loadLocalResource - begin. requestUri=${requestUri}`); const resourceToLoad = getResourceToLoad(requestUri, options.roots); @@ -64,14 +66,14 @@ export async function loadLocalResource( try { const result = await fileService.readFileStream(resourceToLoad, { etag: options.ifNoneMatch }); - return new WebviewResourceResponse.StreamSuccess(result.value, result.etag, mime); + return new WebviewResourceResponse.StreamSuccess(result.value, result.etag, result.mtime, mime); } catch (err) { if (err instanceof FileOperationError) { const result = err.fileOperationResult; // NotModified status is expected and can be handled gracefully if (result === FileOperationResult.FILE_NOT_MODIFIED_SINCE) { - return new WebviewResourceResponse.NotModified(mime); + return new WebviewResourceResponse.NotModified(mime, err.options?.mtime); } }