提交 1ac7165c 编写于 作者: M Matt Bierner

Handle multiple clients in the webview service worker

上级 6cfe053d
......@@ -123,8 +123,6 @@
navigator.serviceWorker.register('service-worker.js');
navigator.serviceWorker.ready.then(registration => {
registration.active.postMessage('ping');
host.onMessage('loaded-resource', event => {
registration.active.postMessage({ channel: 'loaded-resource', data: event.data.args });
});
......@@ -232,6 +230,8 @@
};
document.addEventListener('DOMContentLoaded', () => {
const ID = document.location.search.match(/\bid=([\w-]+)/)[1];
if (!document.body) {
return;
}
......@@ -363,7 +363,7 @@
// seeing the service worker applying properly.
// Fake load an empty on the correct origin and then write real html
// into it to get around this.
newFrame.src = '/fake.html';
newFrame.src = `/fake.html?id=${ID}`;
}
newFrame.style.cssText = 'display: block; margin: 0; overflow: hidden; position: absolute; width: 100%; height: 100%; visibility: hidden';
document.body.appendChild(newFrame);
......
......@@ -2,15 +2,72 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
// Listen for messages from clients.
const resolvedPaths = new Map();
/**
* Root path for resources
*/
const resourceRoot = '/vscode-resource';
/**
* @typedef {{resolve: () => void, promise: Promise<Response> }} ResourcePathEntry
*/
/**
* Map of requested paths to responses.
*/
const resourceRequestManager = new class ResourceRequestManager {
constructor() {
/** @type {Map<string, ResourcePathEntry>} */
this.map = new Map();
}
/**
* @param {string} webviewId
* @param {string} path
* @return {ResourcePathEntry | undefined}
*/
get(webviewId, path) {
return this.map.get(this._key(webviewId, path));
}
/**
* @param {string} webviewId
* @param {string} path
* @return {boolean}
*/
has(webviewId, path) {
return this.map.has(this._key(webviewId, path));
}
/**
* @param {string} webviewId
* @param {string} path
* @param {ResourcePathEntry} entry
*/
set(webviewId, path, entry) {
this.map.set(this._key(webviewId, path), entry);
}
/**
* @param {string} webviewId
* @param {string} path
* @return {string}
*/
_key(webviewId, path) {
return `${webviewId}@@@${path}`;
}
}();
const notFoundResponse = new Response('Not Found', {
status: 404,
});
self.addEventListener('message', (event) => {
switch (event.data.channel) {
case 'loaded-resource':
{
const webviewId = getWebviewIdForClient(event.source);
const data = event.data.data;
const target = resolvedPaths.get(data.path);
const target = resourceRequestManager.get(webviewId, data.path);
if (!target) {
console.log('Loaded unknown resource', data.path);
return;
......@@ -20,19 +77,16 @@ self.addEventListener('message', (event) => {
target.resolve(new Response(data.data, {
status: 200,
headers: { 'Content-Type': data.mime },
}).clone());
}));
} else {
target.resolve(new Response('Not Found', {
status: 404,
}).clone());
target.resolve(notFoundResponse.clone());
}
return;
}
return;
}
});
var clients;
const resourceRoot = '/vscode-resource';
console.log('Unknown message');
});
self.addEventListener('fetch', (event) => {
const requestUrl = new URL(event.request.url);
......@@ -42,36 +96,44 @@ self.addEventListener('fetch', (event) => {
}
event.respondWith((async () => {
const client = await self.clients.get(event.clientId);
if (!client) {
console.log('Could not find inner client for request');
return notFoundResponse.clone();
}
const webviewId = getWebviewIdForClient(client);
const resourcePath = requestUrl.pathname.replace(resourceRoot, '');
const existing = resolvedPaths.get(resourcePath);
const existing = resourceRequestManager.get(webviewId, resourcePath);
if (existing) {
return existing.promise.then(r => r.clone());
}
const allClients = await clients.matchAll({
includeUncontrolled: true
});
const allClients = await self.clients.matchAll({ includeUncontrolled: true });
if (resourceRequestManager.has(webviewId, resourcePath)) {
// Someone else added it in the meantime
return resourceRequestManager.get(resourceRequestManager).promise.then(r => r.clone());
}
// Find parent iframe
for (const client of allClients) {
const clientUrl = new URL(client.url);
if (clientUrl.pathname === '/') {
if (clientUrl.pathname === '/' && clientUrl.search.match(new RegExp('\\bid=' + webviewId))) {
client.postMessage({
channel: 'load-resource',
path: resourcePath
});
if (resolvedPaths.has(resourcePath)) {
// Someone else added it in the mean time
return resolvedPaths.get(resolvedPaths).promise;
}
let resolve;
const promise = new Promise(r => resolve = r);
resolvedPaths.set(resourcePath, { resolve, promise });
resourceRequestManager.set(webviewId, resourcePath, { resolve, promise });
return promise.then(r => r.clone());
}
}
console.log('Could not find parent client for request');
return notFoundResponse.clone();
})());
});
......@@ -82,3 +144,9 @@ self.addEventListener('install', (event) => {
self.addEventListener('activate', (event) => {
event.waitUntil(self.clients.claim()); // Become available to all pages
});
function getWebviewIdForClient(client) {
const requesterClientUrl = new URL(client.url);
return requesterClientUrl.search.match(/\bid=([a-z0-9-]+)/i)[1];
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册