Skip to content

Commit

Permalink
handle style-attribute modifications, cache requests in addition to r…
Browse files Browse the repository at this point in the history
…esults, #75061
  • Loading branch information
jrieken committed Jun 20, 2019
1 parent e0cb499 commit 6c5bb44
Showing 1 changed file with 57 additions and 41 deletions.
98 changes: 57 additions & 41 deletions src/vs/workbench/browser/web.resources.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,25 @@ import { getMediaMime } from 'vs/base/common/mime';
export class WebResources {

private readonly _regexp = /url\(('|")?(vscode-remote:\/\/.*?)\1\)/g;
private readonly _cache = new Map<string, string>();
private readonly _urlCache = new Map<string, string>();
private readonly _requestCache = new Map<string, Promise<any>>();
private readonly _observer: MutationObserver;

constructor(@IFileService private readonly _fileService: IFileService) {
this._observer = new MutationObserver(r => this._handleMutation(r));

// todo@joh add observer to more than head-element
// todo@joh explore alternative approach
this._observer.observe(document.head, { subtree: true, childList: true });
this._observer = new MutationObserver(r => this._handleMutation(r));
this._observer.observe(document, {
subtree: true,
childList: true,
attributes: true,
attributeFilter: ['style']
});
}

dispose(): void {
this._observer.disconnect();
this._cache.forEach(value => URL.revokeObjectURL(value));
this._urlCache.forEach(value => URL.revokeObjectURL(value));
}

private _handleMutation(records: MutationRecord[]): void {
Expand All @@ -39,62 +44,73 @@ export class WebResources {
this._handleStyleNode(node);
}
});
} else if (record.type === 'attributes') {
// style-attribute
this._handleAttrMutation(record.target);
}
}
}

private _handleStyleNode(target: Node): void {
if (target.textContent && target.textContent.indexOf('vscode-remote://') >= 0) {
const content = target.textContent;
this._rewriteUrls(content).then(value => {
if (content === target.textContent) {
target.textContent = value;
}
}).catch(e => {
console.error(e);
});
}
}

if (!target.textContent) {
return;
private _handleAttrMutation(target: Node): void {
const styleValue = (<HTMLElement>target).getAttribute('style');
if (styleValue && styleValue.indexOf('vscode-remote://') >= 0) {
this._rewriteUrls(styleValue).then(value => {
if (value !== styleValue) {
(<HTMLElement>target).setAttribute('style', value);
}
}).catch(e => {
console.error(e);
});
}
}

private async _rewriteUrls(textContent: string): Promise<string> {

const positions: number[] = [];
const promises: Promise<any>[] = [];

let match: RegExpMatchArray | null = null;
while (match = this._regexp.exec(target.textContent)) {
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._cache.has(remoteUrl)) {
promises.push(Promise.resolve());

} else {
const uri = URI.parse(remoteUrl, true);
promises.push(this._fileService.readFile(uri).then(file => {
this._cache.set(remoteUrl, URL.createObjectURL(new Blob(
[file.value.buffer], { type: getMediaMime(uri.path) }
)));
}));
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);
}
}

if (promises.length === 0) {
return;
let content = textContent;
await Promise.all(promises);
for (let i = positions.length - 1; i >= 0; i -= 2) {
const start = positions[i - 1];
const len = positions[i];
const url = this._urlCache.get(content.substr(start, len));
content = content.substring(0, start) + url + content.substring(start + len);
}

let content = target.textContent;

Promise.all(promises).then(() => {

if (target.textContent !== content) {
return;
}

for (let i = positions.length - 1; i >= 0; i -= 2) {
const start = positions[i - 1];
const len = positions[i];
const url = this._cache.get(content.substr(start, len));
content = content.substring(0, start) + url + content.substring(start + len);
}

target.textContent = content;

}).catch(e => {
console.error(e);
});
return content;
}
}

0 comments on commit 6c5bb44

Please sign in to comment.