diff --git a/build/buildfile.js b/build/buildfile.js index 9430fb3d7be..614d5f9cdad 100644 --- a/build/buildfile.js +++ b/build/buildfile.js @@ -45,7 +45,8 @@ exports.code = [ createModuleDescription('vs/code/electron-utility/sharedProcess/sharedProcessMain'), createModuleDescription('vs/code/electron-sandbox/processExplorer/processExplorerMain'), createModuleDescription('vs/code/electron-sandbox/workbench/workbench'), - createModuleDescription('vs/code/electron-sandbox/processExplorer/processExplorer') + createModuleDescription('vs/code/electron-sandbox/processExplorer/processExplorer'), + createModuleDescription('vs/workbench/contrib/webview/browser/pre/service-worker') ]; exports.codeWeb = createModuleDescription('vs/code/browser/workbench/workbench'); diff --git a/src/vs/workbench/contrib/webview/browser/pre/service-worker.js b/src/vs/workbench/contrib/webview/browser/pre/service-worker.ts similarity index 76% rename from src/vs/workbench/contrib/webview/browser/pre/service-worker.js rename to src/vs/workbench/contrib/webview/browser/pre/service-worker.ts index 3d74103ec24..ab6f674be34 100644 --- a/src/vs/workbench/contrib/webview/browser/pre/service-worker.js +++ b/src/vs/workbench/contrib/webview/browser/pre/service-worker.ts @@ -2,11 +2,9 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -// @ts-check - /// -const sw = /** @type {ServiceWorkerGlobalScope} */ (/** @type {any} */ (self)); +const sw: ServiceWorkerGlobalScope = self as any as ServiceWorkerGlobalScope; const VERSION = 4; @@ -27,46 +25,29 @@ const resourceBaseAuthority = searchParams.get('vscode-resource-base-authority') const resolveTimeout = 30_000; -/** - * @template T - * @typedef {{ status: 'ok'; value: T } | { status: 'timeout' }} RequestStoreResult - */ +type RequestStoreResult = { + status: 'ok'; + value: T; +} | { + status: 'timeout'; +}; -/** - * @template T - * @typedef {{ - * resolve: (x: RequestStoreResult) => void, - * promise: Promise> - * }} RequestStoreEntry - */ +interface RequestStoreEntry { + resolve: (x: RequestStoreResult) => void; + promise: Promise>; +} -/** - * Caches - * @template T - */ -class RequestStore { - constructor() { - /** @type {Map>} */ - this.map = new Map(); +class RequestStore { + private map: Map> = new Map(); + private requestPool: number = 0; - this.requestPool = 0; - } - - /** - * @returns {{ requestId: number, promise: Promise> }} - */ - create() { + create(): { requestId: number; promise: Promise> } { const requestId = ++this.requestPool; - /** @type {undefined | ((x: RequestStoreResult) => void)} */ - let resolve; - - /** @type {Promise>} */ - const promise = new Promise(r => resolve = r); - - /** @type {RequestStoreEntry} */ - const entry = { resolve: /** @type {(x: RequestStoreResult) => void} */ (resolve), promise }; + let resolve: (x: RequestStoreResult) => void; + const promise = new Promise>(r => resolve = r); + const entry: RequestStoreEntry = { resolve: resolve!, promise }; this.map.set(requestId, entry); const dispose = () => { @@ -75,19 +56,13 @@ class RequestStore { if (existingEntry === entry) { existingEntry.resolve({ status: 'timeout' }); this.map.delete(requestId); - return; } }; const timeout = setTimeout(dispose, resolveTimeout); return { requestId, promise }; } - /** - * @param {number} requestId - * @param {T} result - * @return {boolean} - */ - resolve(requestId, result) { + resolve(requestId: number, result: T): boolean { const entry = this.map.get(requestId); if (!entry) { return false; @@ -98,26 +73,15 @@ class RequestStore { } } -/** - * @typedef {{ readonly status: 200; id: number; path: string; mime: string; data: Uint8Array; etag: string | undefined; mtime: number | undefined; } - * | { readonly status: 304; id: number; path: string; mime: string; mtime: number | undefined } - * | { readonly status: 401; id: number; path: string } - * | { readonly status: 404; id: number; path: string }} ResourceResponse - */ - /** * Map of requested paths to responses. - * - * @type {RequestStore} */ -const resourceRequestStore = new RequestStore(); +const resourceRequestStore = new RequestStore(); /** * Map of requested localhost origins to optional redirects. - * - * @type {RequestStore} */ -const localhostRequestStore = new RequestStore(); +const localhostRequestStore = new RequestStore(); const unauthorized = () => new Response('Unauthorized', { status: 401, }); @@ -131,10 +95,14 @@ const methodNotAllowed = () => const requestTimeout = () => new Response('Request Timeout', { status: 408, }); -sw.addEventListener('message', async (event) => { +sw.addEventListener('message', async (event: ExtendableMessageEvent) => { + if (!event.source) { + return; + } + + const source = event.source as Client; switch (event.data.channel) { case 'version': { - const source = /** @type {Client} */ (event.source); sw.clients.get(source.id).then(client => { if (client) { client.postMessage({ @@ -146,8 +114,7 @@ sw.addEventListener('message', async (event) => { return; } case 'did-load-resource': { - /** @type {ResourceResponse} */ - const response = event.data.data; + const response = event.data.data as ResourceResponse; if (!resourceRequestStore.resolve(response.id, response)) { console.log('Could not resolve unknown resource', response.path); } @@ -167,7 +134,7 @@ sw.addEventListener('message', async (event) => { } }); -sw.addEventListener('fetch', (event) => { +sw.addEventListener('fetch', (event: FetchEvent) => { const requestUrl = new URL(event.request.url); if (typeof resourceBaseAuthority === 'string' && requestUrl.protocol === 'https:' && requestUrl.hostname.endsWith('.' + resourceBaseAuthority)) { switch (event.request.method) { @@ -216,26 +183,27 @@ sw.addEventListener('fetch', (event) => { } }); -sw.addEventListener('install', (event) => { +sw.addEventListener('install', (event: ExtendableEvent) => { event.waitUntil(sw.skipWaiting()); // Activate worker immediately }); -sw.addEventListener('activate', (event) => { +sw.addEventListener('activate', (event: ExtendableEvent) => { event.waitUntil(sw.clients.claim()); // Become available to all pages }); -/** - * @param {FetchEvent} event - * @param {{ - * scheme: string; - * authority: string; - * path: string; - * query: string; - * }} requestUrlComponents - */ -async function processResourceRequest(event, requestUrlComponents) { +interface ResourceRequestUrlComponents { + scheme: string; + authority: string; + path: string; + query: string; +} + +async function processResourceRequest( + event: FetchEvent, + requestUrlComponents: ResourceRequestUrlComponents +): Promise { const client = await sw.clients.get(event.clientId); - let webviewId; + let webviewId: string | null | undefined; if (!client) { const workerClient = await getWorkerClientForId(event.clientId); if (!workerClient) { @@ -255,11 +223,10 @@ async function processResourceRequest(event, requestUrlComponents) { const shouldTryCaching = (event.request.method === 'GET'); - /** - * @param {RequestStoreResult} result - * @param {Response | undefined} cachedResponse - */ - const resolveResourceEntry = (result, cachedResponse) => { + const resolveResourceEntry = ( + result: RequestStoreResult, + cachedResponse: Response | undefined + ): Response => { if (result.status === 'timeout') { return requestTimeout(); } @@ -281,8 +248,7 @@ async function processResourceRequest(event, requestUrlComponents) { return notFound(); } - /** @type {Record} */ - const commonHeaders = { + const commonHeaders: Record = { 'Access-Control-Allow-Origin': '*', }; @@ -317,8 +283,7 @@ async function processResourceRequest(event, requestUrlComponents) { } } - /** @type {Record} */ - const headers = { + const headers: Record = { ...commonHeaders, 'Content-Type': entry.mime, 'Content-Length': byteLength.toString(), @@ -362,8 +327,7 @@ async function processResourceRequest(event, requestUrlComponents) { return notFound(); } - /** @type {Response | undefined} */ - let cached; + let cached: Response | undefined; if (shouldTryCaching) { const cache = await caches.open(resourceCacheName); cached = await cache.match(event.request); @@ -386,12 +350,10 @@ async function processResourceRequest(event, requestUrlComponents) { return promise.then(entry => resolveResourceEntry(entry, cached)); } -/** - * @param {FetchEvent} event - * @param {URL} requestUrl - * @return {Promise} - */ -async function processLocalhostRequest(event, requestUrl) { +async function processLocalhostRequest( + event: FetchEvent, + requestUrl: URL +): Promise { const client = await sw.clients.get(event.clientId); if (!client) { // This is expected when requesting resources on other localhost ports @@ -406,11 +368,9 @@ async function processLocalhostRequest(event, requestUrl) { const origin = requestUrl.origin; - /** - * @param {RequestStoreResult} result - * @return {Promise} - */ - const resolveRedirect = async (result) => { + const resolveRedirect = async ( + result: RequestStoreResult + ): Promise => { if (result.status !== 'ok' || !result.value) { return fetch(event.request); } @@ -443,11 +403,7 @@ async function processLocalhostRequest(event, requestUrl) { return promise.then(resolveRedirect); } -/** - * @param {Client} client - * @returns {string | null} - */ -function getWebviewIdForClient(client) { +function getWebviewIdForClient(client: Client): string | null { // Refs https://github.com/microsoft/vscode/issues/244143 // With PlzDedicatedWorker, worker subresources and blob wokers // will use clients different from the window client. @@ -461,11 +417,7 @@ function getWebviewIdForClient(client) { return requesterClientUrl.searchParams.get('id'); } -/** - * @param {string} webviewId - * @returns {Promise} - */ -async function getOuterIframeClient(webviewId) { +async function getOuterIframeClient(webviewId: string): Promise { const allClients = await sw.clients.matchAll({ includeUncontrolled: true }); return allClients.filter(client => { const clientUrl = new URL(client.url); @@ -474,11 +426,7 @@ async function getOuterIframeClient(webviewId) { }); } -/** - * @param {string} clientId - * @returns {Promise} - */ -async function getWorkerClientForId(clientId) { +async function getWorkerClientForId(clientId: string): Promise { const allDedicatedWorkerClients = await sw.clients.matchAll({ type: 'worker' }); const allSharedWorkerClients = await sw.clients.matchAll({ type: 'sharedworker' }); const allWorkerClients = [...allDedicatedWorkerClients, ...allSharedWorkerClients]; @@ -486,3 +434,10 @@ async function getWorkerClientForId(clientId) { return client.id === clientId; }); } + +type ResourceResponse = + | { readonly status: 200; id: number; path: string; mime: string; data: Uint8Array; etag: string | undefined; mtime: number | undefined } + | { readonly status: 304; id: number; path: string; mime: string; mtime: number | undefined } + | { readonly status: 401; id: number; path: string } + | { readonly status: 404; id: number; path: string } + ;