mirror of
https://github.com/Microsoft/vscode
synced 2024-08-24 11:36:01 +00:00
Merge pull request #76465 from microsoft/joh/sw
Fetch vscode-remote resources with web worker
This commit is contained in:
commit
46b449b5e0
|
@ -29,7 +29,6 @@ import { URI } from 'vs/base/common/uri';
|
||||||
import { IWorkspaceInitializationPayload } from 'vs/platform/workspaces/common/workspaces';
|
import { IWorkspaceInitializationPayload } from 'vs/platform/workspaces/common/workspaces';
|
||||||
import { WorkspaceService } from 'vs/workbench/services/configuration/browser/configurationService';
|
import { WorkspaceService } from 'vs/workbench/services/configuration/browser/configurationService';
|
||||||
import { ConfigurationCache } from 'vs/workbench/services/configuration/browser/configurationCache';
|
import { ConfigurationCache } from 'vs/workbench/services/configuration/browser/configurationCache';
|
||||||
import { WebResources } from 'vs/workbench/browser/web.resources';
|
|
||||||
import { ISignService } from 'vs/platform/sign/common/sign';
|
import { ISignService } from 'vs/platform/sign/common/sign';
|
||||||
import { SignService } from 'vs/platform/sign/browser/signService';
|
import { SignService } from 'vs/platform/sign/browser/signService';
|
||||||
import { hash } from 'vs/base/common/hash';
|
import { hash } from 'vs/base/common/hash';
|
||||||
|
@ -68,9 +67,6 @@ class CodeRendererMain extends Disposable {
|
||||||
// Layout
|
// Layout
|
||||||
this._register(addDisposableListener(window, EventType.RESIZE, () => this.workbench.layout()));
|
this._register(addDisposableListener(window, EventType.RESIZE, () => this.workbench.layout()));
|
||||||
|
|
||||||
// Resource Loading
|
|
||||||
this._register(new WebResources(<IFileService>services.serviceCollection.get(IFileService)));
|
|
||||||
|
|
||||||
// Workbench Lifecycle
|
// Workbench Lifecycle
|
||||||
this._register(this.workbench.onShutdown(() => this.dispose()));
|
this._register(this.workbench.onShutdown(() => this.dispose()));
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,74 @@
|
||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
import { URI } from 'vs/base/common/uri';
|
||||||
|
import { generateUuid } from 'vs/base/common/uuid';
|
||||||
|
import { getMediaMime } from 'vs/base/common/mime';
|
||||||
|
|
||||||
|
const cacheName = 'vscode-resources';
|
||||||
|
|
||||||
|
declare const clients: { get(s: string): Promise<any> };
|
||||||
|
|
||||||
|
|
||||||
|
const _pending = new Map<string, Function>();
|
||||||
|
|
||||||
|
export function handleMessageEvent(event: MessageEvent): void {
|
||||||
|
const fn = _pending.get(event.data.token);
|
||||||
|
if (fn) {
|
||||||
|
fn(event.data.data);
|
||||||
|
_pending.delete(event.data.token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function handleFetchEvent(event: any): Promise<Response | undefined> {
|
||||||
|
|
||||||
|
const url = URI.parse(event.request.url);
|
||||||
|
|
||||||
|
if (url.path !== '/vscode-resources/fetch') {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!event.clientId) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const cachedValue = await caches.open(cacheName).then(cache => cache.match(event.request));
|
||||||
|
if (cachedValue) {
|
||||||
|
return cachedValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// console.log('fetch', url.query);
|
||||||
|
try {
|
||||||
|
const token = generateUuid();
|
||||||
|
return new Promise<Response>(async resolve => {
|
||||||
|
|
||||||
|
const handle = setTimeout(() => {
|
||||||
|
resolve(new Response(undefined, { status: 500, statusText: 'timeout' }));
|
||||||
|
_pending.delete(token);
|
||||||
|
}, 5000);
|
||||||
|
|
||||||
|
_pending.set(token, (data: ArrayBuffer) => {
|
||||||
|
clearTimeout(handle);
|
||||||
|
const res = new Response(data, {
|
||||||
|
status: 200,
|
||||||
|
headers: { 'Content-Type': getMediaMime(URI.parse(url.query).path) || 'text/plain' }
|
||||||
|
});
|
||||||
|
caches.open(cacheName).then(cache => {
|
||||||
|
cache.put(event.request, res.clone());
|
||||||
|
resolve(res);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const client = await clients.get(event.clientId);
|
||||||
|
client.postMessage({ uri: url.query, token });
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
return new Response(err, { status: 500 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -4,19 +4,21 @@
|
||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
import { IFileService } from 'vs/platform/files/common/files';
|
import { IFileService } from 'vs/platform/files/common/files';
|
||||||
|
import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle';
|
||||||
import { URI } from 'vs/base/common/uri';
|
import { URI } from 'vs/base/common/uri';
|
||||||
import { getMediaMime } from 'vs/base/common/mime';
|
import { Registry } from 'vs/platform/registry/common/platform';
|
||||||
|
import { IWorkbenchContributionsRegistry, Extensions } from 'vs/workbench/common/contributions';
|
||||||
|
import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
|
||||||
|
|
||||||
export class WebResources {
|
// todo@joh explore alternative, explicit approach
|
||||||
|
class ResourcesMutationObserver {
|
||||||
|
|
||||||
private readonly _regexp = /url\(('|")?(vscode-remote:\/\/.*?)\1\)/g;
|
|
||||||
private readonly _urlCache = new Map<string, string>();
|
private readonly _urlCache = new Map<string, string>();
|
||||||
private readonly _requestCache = new Map<string, Promise<any>>();
|
|
||||||
private readonly _observer: MutationObserver;
|
private readonly _observer: MutationObserver;
|
||||||
|
|
||||||
constructor(@IFileService private readonly _fileService: IFileService) {
|
private readonly _regexp = /url\(('|")?(vscode-remote:\/\/(.*?))\1\)/ig;
|
||||||
// todo@joh add observer to more than head-element
|
|
||||||
// todo@joh explore alternative approach
|
constructor() {
|
||||||
this._observer = new MutationObserver(r => this._handleMutation(r));
|
this._observer = new MutationObserver(r => this._handleMutation(r));
|
||||||
this._observer.observe(document, {
|
this._observer.observe(document, {
|
||||||
subtree: true,
|
subtree: true,
|
||||||
|
@ -24,6 +26,12 @@ export class WebResources {
|
||||||
attributes: true,
|
attributes: true,
|
||||||
attributeFilter: ['style']
|
attributeFilter: ['style']
|
||||||
});
|
});
|
||||||
|
this.scan();
|
||||||
|
}
|
||||||
|
|
||||||
|
scan(): void {
|
||||||
|
document.querySelectorAll('style').forEach(value => this._handleStyleNode(value));
|
||||||
|
// todo@joh more!
|
||||||
}
|
}
|
||||||
|
|
||||||
dispose(): void {
|
dispose(): void {
|
||||||
|
@ -78,39 +86,60 @@ export class WebResources {
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _rewriteUrls(textContent: string): Promise<string> {
|
private async _rewriteUrls(textContent: string): Promise<string> {
|
||||||
|
return textContent.replace(this._regexp, function (_m, quote, url) {
|
||||||
const positions: number[] = [];
|
return `url(${quote}${location.href}vscode-resources/fetch?${encodeURIComponent(url)}${quote})`;
|
||||||
const promises: Promise<any>[] = [];
|
|
||||||
|
|
||||||
let match: RegExpMatchArray | null = null;
|
|
||||||
while (match = this._regexp.exec(textContent)) {
|
|
||||||
|
|
||||||
const remoteUrl = match[2];
|
|
||||||
positions.push(match.index! + 'url('.length + (typeof match[1] === 'string' ? match[1].length : 0));
|
|
||||||
positions.push(remoteUrl.length);
|
|
||||||
|
|
||||||
if (!this._urlCache.has(remoteUrl)) {
|
|
||||||
let request = this._requestCache.get(remoteUrl);
|
|
||||||
if (!request) {
|
|
||||||
const uri = URI.parse(remoteUrl, true);
|
|
||||||
request = this._fileService.readFile(uri).then(file => {
|
|
||||||
const blobUrl = URL.createObjectURL(new Blob([file.value.buffer], { type: getMediaMime(uri.path) }));
|
|
||||||
this._urlCache.set(remoteUrl, blobUrl);
|
|
||||||
});
|
});
|
||||||
this._requestCache.set(remoteUrl, request);
|
|
||||||
}
|
|
||||||
promises.push(request);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let content = textContent;
|
class ResourceServiceWorker {
|
||||||
await Promise.all(promises);
|
|
||||||
for (let i = positions.length - 1; i >= 0; i -= 2) {
|
private readonly _disposables = new DisposableStore();
|
||||||
const start = positions[i - 1];
|
|
||||||
const len = positions[i];
|
constructor(
|
||||||
const url = this._urlCache.get(content.substr(start, len));
|
@IFileService private readonly _fileService: IFileService,
|
||||||
content = content.substring(0, start) + url + content.substring(start + len);
|
) {
|
||||||
|
this._initServiceWorker();
|
||||||
|
this._initFetchHandler();
|
||||||
}
|
}
|
||||||
return content;
|
|
||||||
|
dispose(): void {
|
||||||
|
this._disposables.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
private _initServiceWorker(): void {
|
||||||
|
const url = './resourceServiceWorkerMain.js';
|
||||||
|
navigator.serviceWorker.register(url).then(() => {
|
||||||
|
// console.log('registered');
|
||||||
|
return navigator.serviceWorker.ready;
|
||||||
|
}).then(() => {
|
||||||
|
// console.log('ready');
|
||||||
|
this._disposables.add(new ResourcesMutationObserver());
|
||||||
|
}).catch(err => {
|
||||||
|
console.error(err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _initFetchHandler(): void {
|
||||||
|
|
||||||
|
const fetchListener: (this: ServiceWorkerContainer, ev: MessageEvent) => void = event => {
|
||||||
|
const uri = URI.parse(event.data.uri);
|
||||||
|
this._fileService.readFile(uri).then(file => {
|
||||||
|
// todo@joh typings
|
||||||
|
(<any>event.source).postMessage({
|
||||||
|
token: event.data.token,
|
||||||
|
data: file.value.buffer.buffer
|
||||||
|
}, [file.value.buffer.buffer]);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
navigator.serviceWorker.addEventListener('message', fetchListener);
|
||||||
|
this._disposables.add(toDisposable(() => navigator.serviceWorker.removeEventListener('message', fetchListener)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Registry.as<IWorkbenchContributionsRegistry>(Extensions.Workbench).registerWorkbenchContribution(
|
||||||
|
ResourceServiceWorker,
|
||||||
|
LifecyclePhase.Starting
|
||||||
|
);
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,49 @@
|
||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
(function () {
|
||||||
|
type Handler = {
|
||||||
|
handleFetchEvent(event: Event): Promise<Response | undefined>;
|
||||||
|
handleMessageEvent(event: MessageEvent): void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlerPromise = new Promise<Handler>((resolve, reject) => {
|
||||||
|
// load loader
|
||||||
|
const baseUrl = './out/';
|
||||||
|
importScripts(baseUrl + 'vs/loader.js');
|
||||||
|
require.config({
|
||||||
|
baseUrl,
|
||||||
|
catchError: true
|
||||||
|
});
|
||||||
|
require(['vs/workbench/contrib/resources/browser/resourceServiceWorker'], resolve, reject);
|
||||||
|
});
|
||||||
|
|
||||||
|
self.addEventListener('message', event => {
|
||||||
|
handlerPromise.then(handler => {
|
||||||
|
handler.handleMessageEvent(event);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
self.addEventListener('fetch', (event: any) => {
|
||||||
|
event.respondWith(handlerPromise.then(handler => {
|
||||||
|
return handler.handleFetchEvent(event).then(value => {
|
||||||
|
if (value instanceof Response) {
|
||||||
|
return value;
|
||||||
|
} else {
|
||||||
|
return fetch(event.request);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
self.addEventListener('install', event => {
|
||||||
|
//@ts-ignore
|
||||||
|
event.waitUntil(self.skipWaiting());
|
||||||
|
});
|
||||||
|
|
||||||
|
self.addEventListener('activate', event => {
|
||||||
|
//@ts-ignore
|
||||||
|
event.waitUntil(self.clients.claim()); // Become available to all pages
|
||||||
|
});
|
||||||
|
})();
|
|
@ -28,6 +28,11 @@ import 'vs/workbench/browser/parts/quickinput/quickInputActions';
|
||||||
|
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
|
//#region --- Remote Resource loading
|
||||||
|
|
||||||
|
import 'vs/workbench/contrib/resources/browser/resourceServiceWorkerClient';
|
||||||
|
|
||||||
|
//#endregion
|
||||||
|
|
||||||
//#region --- API Extension Points
|
//#region --- API Extension Points
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue