replace mutation observer with dom#asDomUri, #75061

This commit is contained in:
Johannes Rieken 2019-07-04 10:19:40 +02:00
parent 7c88e07cc6
commit 7980cd2697
13 changed files with 50 additions and 107 deletions

View file

@ -14,6 +14,8 @@ import { Emitter, Event } from 'vs/base/common/event';
import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import * as platform from 'vs/base/common/platform';
import { coalesce } from 'vs/base/common/arrays';
import { URI } from 'vs/base/common/uri';
import { Schemas } from 'vs/base/common/network';
export function clearNode(node: HTMLElement): void {
while (node.firstChild) {
@ -1181,3 +1183,23 @@ export function animate(fn: () => void): IDisposable {
let stepDisposable = scheduleAtNextAnimationFrame(step);
return toDisposable(() => stepDisposable.dispose());
}
const _location = URI.parse(window.location.href);
export function asDomUri(uri: URI): URI {
if (!uri) {
return uri;
}
if (!platform.isWeb) {
//todo@joh remove this once we have sw in electron going
return uri;
}
if (Schemas.vscodeRemote === uri.scheme) {
// rewrite vscode-remote-uris to uris of the window location
// so that they can be intercepted by the service worker
return _location.with({ path: '/vscode-resources/fetch', query: uri.toString() });
}
return uri;
}

View file

@ -75,12 +75,15 @@ export function renderMarkdown(markdown: IMarkdownString, options: RenderOptions
return encodeURIComponent(JSON.stringify(data));
};
const _href = function (href: string): string {
const _href = function (href: string, isDomUri: boolean): string {
const data = markdown.uris && markdown.uris[href];
if (!data) {
return href;
}
let uri = URI.revive(data);
if (isDomUri) {
uri = DOM.asDomUri(uri);
}
if (uri.query) {
uri = uri.with({ query: _uriMassage(uri.query) });
}
@ -97,7 +100,7 @@ export function renderMarkdown(markdown: IMarkdownString, options: RenderOptions
const renderer = new marked.Renderer();
renderer.image = (href: string, title: string, text: string) => {
href = _href(href);
href = _href(href, true);
let dimensions: string[] = [];
if (href) {
const splitted = href.split('|').map(s => s.trim());
@ -138,7 +141,7 @@ export function renderMarkdown(markdown: IMarkdownString, options: RenderOptions
if (href === text) { // raw link case
text = removeMarkdownEscapes(text);
}
href = _href(href);
href = _href(href, false);
title = removeMarkdownEscapes(title);
href = removeMarkdownEscapes(href);
if (

View file

@ -399,7 +399,7 @@ class DecorationCSSRules {
if (typeof opts !== 'undefined') {
this.collectBorderSettingsCSSText(opts, cssTextArr);
if (typeof opts.contentIconPath !== 'undefined') {
cssTextArr.push(strings.format(_CSS_MAP.contentIconPath, URI.revive(opts.contentIconPath).toString(true).replace(/'/g, '%27')));
cssTextArr.push(strings.format(_CSS_MAP.contentIconPath, dom.asDomUri(URI.revive(opts.contentIconPath)).toString(true).replace(/'/g, '%27')));
}
if (typeof opts.contentText === 'string') {
const truncated = opts.contentText.match(/^.*$/m)![0]; // only take first line
@ -426,7 +426,7 @@ class DecorationCSSRules {
const cssTextArr: string[] = [];
if (typeof opts.gutterIconPath !== 'undefined') {
cssTextArr.push(strings.format(_CSS_MAP.gutterIconPath, URI.revive(opts.gutterIconPath).toString(true).replace(/'/g, '%27')));
cssTextArr.push(strings.format(_CSS_MAP.gutterIconPath, dom.asDomUri(URI.revive(opts.gutterIconPath)).toString(true).replace(/'/g, '%27')));
if (typeof opts.gutterIconSize !== 'undefined') {
cssTextArr.push(strings.format(_CSS_MAP.gutterIconSize, opts.gutterIconSize));
}

View file

@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { addClasses, createCSSRule, removeClasses } from 'vs/base/browser/dom';
import { addClasses, createCSSRule, removeClasses, asDomUri } from 'vs/base/browser/dom';
import { domEvent } from 'vs/base/browser/event';
import { ActionViewItem, Separator } from 'vs/base/browser/ui/actionbar/actionbar';
import { IAction } from 'vs/base/common/actions';
@ -244,8 +244,8 @@ export class MenuEntryActionViewItem extends ActionViewItem {
iconClass = MenuEntryActionViewItem.ICON_PATH_TO_CSS_RULES.get(iconPathMapKey)!;
} else {
iconClass = ids.nextId();
createCSSRule(`.icon.${iconClass}`, `background-image: url("${(item.iconLocation.light || item.iconLocation.dark).toString()}")`);
createCSSRule(`.vs-dark .icon.${iconClass}, .hc-black .icon.${iconClass}`, `background-image: url("${item.iconLocation.dark.toString()}")`);
createCSSRule(`.icon.${iconClass}`, `background-image: url("${asDomUri(item.iconLocation.light || item.iconLocation.dark).toString()}")`);
createCSSRule(`.vs-dark .icon.${iconClass}, .hc-black .icon.${iconClass}`, `background-image: url("${asDomUri(item.iconLocation.dark).toString()}")`);
MenuEntryActionViewItem.ICON_PATH_TO_CSS_RULES.set(iconPathMapKey, iconClass);
}

View file

@ -36,7 +36,7 @@ import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions';
import { SyncActionDescriptor } from 'vs/platform/actions/common/actions';
import { createCSSRule } from 'vs/base/browser/dom';
import { createCSSRule, asDomUri } from 'vs/base/browser/dom';
export interface IUserFriendlyViewsContainerDescriptor {
id: string;
@ -327,7 +327,7 @@ class ViewsExtensionHandler implements IWorkbenchContribution {
// Generate CSS to show the icon in the activity bar
const iconClass = `.monaco-workbench .activitybar .monaco-action-bar .action-label.${cssClass}`;
createCSSRule(iconClass, `-webkit-mask: url('${icon}') no-repeat 50% 50%; -webkit-mask-size: 24px;`);
createCSSRule(iconClass, `-webkit-mask: url('${asDomUri(icon)}') no-repeat 50% 50%; -webkit-mask-size: 24px;`);
}
return viewContainer;
@ -456,4 +456,4 @@ class ViewsExtensionHandler implements IWorkbenchContribution {
}
const workbenchRegistry = Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench);
workbenchRegistry.registerWorkbenchContribution(ViewsExtensionHandler, LifecyclePhase.Starting);
workbenchRegistry.registerWorkbenchContribution(ViewsExtensionHandler, LifecyclePhase.Starting);

View file

@ -173,7 +173,7 @@ export class PlaceHolderViewletActivityAction extends ViewletActivityAction {
super({ id, name: id, cssClass: `extensionViewlet-placeholder-${id.replace(/\./g, '-')}` }, viewletService, layoutService, telemetryService);
const iconClass = `.monaco-workbench .activitybar .monaco-action-bar .action-label.${this.class}`; // Generate Placeholder CSS to show the icon in the activity bar
DOM.createCSSRule(iconClass, `-webkit-mask: url('${iconUrl || ''}') no-repeat 50% 50%; -webkit-mask-size: 24px;`);
DOM.createCSSRule(iconClass, `-webkit-mask: url('${DOM.asDomUri(iconUrl) || ''}') no-repeat 50% 50%; -webkit-mask-size: 24px;`);
}
setActivity(activity: IActivity): void {

View file

@ -22,8 +22,8 @@ export function getIconClass(iconPath: { dark: URI; light?: URI; } | undefined):
iconClass = iconPathToClass[key];
} else {
iconClass = iconClassGenerator.nextId();
dom.createCSSRule(`.${iconClass}`, `background-image: url("${(iconPath.light || iconPath.dark).toString()}")`);
dom.createCSSRule(`.vs-dark .${iconClass}, .hc-black .${iconClass}`, `background-image: url("${iconPath.dark.toString()}")`);
dom.createCSSRule(`.${iconClass}`, `background-image: url("${dom.asDomUri(iconPath.light || iconPath.dark).toString()}")`);
dom.createCSSRule(`.vs-dark .${iconClass}, .hc-black .${iconClass}`, `background-image: url("${dom.asDomUri(iconPath.dark).toString()}")`);
iconPathToClass[key] = iconClass;
}

View file

@ -674,7 +674,7 @@ class TreeRenderer implements IRenderer {
templateData.resourceLabel.setResource({ name: label, description }, { title, hideIcon: true, extraClasses: ['custom-view-tree-node-item-resourceLabel'], matches });
}
templateData.icon.style.backgroundImage = iconUrl ? `url('${iconUrl.toString(true)}')` : '';
templateData.icon.style.backgroundImage = iconUrl ? `url('${DOM.asDomUri(iconUrl).toString(true)}')` : '';
DOM.toggleClass(templateData.icon, 'custom-view-tree-node-item-icon', !!iconUrl);
templateData.actionBar.context = (<TreeViewItemHandleArg>{ $treeViewId: this.treeViewId, $treeItemHandle: node.handle });
templateData.actionBar.push(this.menus.getResourceActions(node), { icon: true, label: false });

View file

@ -10,88 +10,6 @@ import { Registry } from 'vs/platform/registry/common/platform';
import { IWorkbenchContributionsRegistry, Extensions } from 'vs/workbench/common/contributions';
import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
// todo@joh explore alternative, explicit approach
class ResourcesMutationObserver {
private readonly _urlCache = new Map<string, string>();
private readonly _observer: MutationObserver;
private readonly _regexp = /url\(('|")?(vscode-remote:\/\/(.*?))\1\)/ig;
constructor() {
this._observer = new MutationObserver(r => this._handleMutation(r));
this._observer.observe(document, {
subtree: true,
childList: true,
attributes: true,
attributeFilter: ['style']
});
this.scan();
}
scan(): void {
document.querySelectorAll('style').forEach(value => this._handleStyleNode(value));
// todo@joh more!
}
dispose(): void {
this._observer.disconnect();
this._urlCache.forEach(value => URL.revokeObjectURL(value));
}
private _handleMutation(records: MutationRecord[]): void {
for (const record of records) {
if (record.target.nodeName === 'STYLE') {
// style-element directly modified
this._handleStyleNode(record.target);
} else if (record.target.nodeName === 'HEAD' && record.type === 'childList') {
// style-element added to head
record.addedNodes.forEach(node => {
if (node.nodeName === 'STYLE') {
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);
});
}
}
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> {
return textContent.replace(this._regexp, function (_m, quote = '', url) {
return `url(${quote}${location.href}vscode-resources/fetch?${encodeURIComponent(url)}${quote})`;
});
}
}
class ResourceServiceWorker {
private readonly _disposables = new DisposableStore();
@ -114,7 +32,6 @@ class ResourceServiceWorker {
return navigator.serviceWorker.ready;
}).then(() => {
// console.log('ready');
this._disposables.add(new ResourcesMutationObserver());
}).catch(err => {
console.error(err);
});

View file

@ -39,10 +39,10 @@ export class WebviewEditorInput extends EditorInput {
this._icons.forEach((value, key) => {
const webviewSelector = `.show-file-icons .webview-${key}-name-file-icon::before`;
if (URI.isUri(value)) {
cssRules.push(`${webviewSelector} { content: ""; background-image: url(${value.toString()}); }`);
cssRules.push(`${webviewSelector} { content: ""; background-image: url(${dom.asDomUri(value).toString()}); }`);
} else {
cssRules.push(`.vs ${webviewSelector} { content: ""; background-image: url(${value.light.toString()}); }`);
cssRules.push(`.vs-dark ${webviewSelector} { content: ""; background-image: url(${value.dark.toString()}); }`);
cssRules.push(`.vs ${webviewSelector} { content: ""; background-image: url(${dom.asDomUri(value.light).toString()}); }`);
cssRules.push(`.vs-dark ${webviewSelector} { content: ""; background-image: url(${dom.asDomUri(value.dark).toString()}); }`);
}
});
this._styleElement.innerHTML = cssRules.join('\n');

View file

@ -11,6 +11,7 @@ import * as Json from 'vs/base/common/json';
import { ExtensionData, IThemeExtensionPoint, IFileIconTheme } from 'vs/workbench/services/themes/common/workbenchThemeService';
import { IFileService } from 'vs/platform/files/common/files';
import { getParseErrorMessage } from 'vs/base/common/jsonErrorMessages';
import { asDomUri } from 'vs/base/browser/dom';
export class FileIconThemeData implements IFileIconTheme {
id: string;
@ -331,7 +332,7 @@ function _processIconThemeDocument(id: string, iconThemeDocumentLocation: URI, i
let fonts = iconThemeDocument.fonts;
if (Array.isArray(fonts)) {
fonts.forEach(font => {
let src = font.src.map(l => `url('${resolvePath(l.path)}') format('${l.format}')`).join(', ');
let src = font.src.map(l => `url('${asDomUri(resolvePath(l.path))}') format('${l.format}')`).join(', ');
cssRules.push(`@font-face { src: ${src}; font-family: '${font.id}'; font-weight: ${font.weight}; font-style: ${font.style}; }`);
});
cssRules.push(`.show-file-icons .file-icon::before, .show-file-icons .folder-icon::before, .show-file-icons .rootfolder-icon::before { font-family: '${fonts[0].id}'; font-size: ${fonts[0].size || '150%'}}`);
@ -342,7 +343,7 @@ function _processIconThemeDocument(id: string, iconThemeDocumentLocation: URI, i
let definition = iconThemeDocument.iconDefinitions[defId];
if (definition) {
if (definition.iconPath) {
cssRules.push(`${selectors.join(', ')} { content: ' '; background-image: url("${resolvePath(definition.iconPath)}"); }`);
cssRules.push(`${selectors.join(', ')} { content: ' '; background-image: url("${asDomUri(resolvePath(definition.iconPath))}"); }`);
}
if (definition.fontCharacter || definition.fontColor) {
let body = '';

View file

@ -11,7 +11,7 @@ import { ExtensionsRegistry, ExtensionMessageCollector } from 'vs/workbench/serv
import { ExtensionData, IThemeExtensionPoint } from 'vs/workbench/services/themes/common/workbenchThemeService';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { Event, Emitter } from 'vs/base/common/event';
import { FileIconThemeData } from 'vs/workbench/services/themes/common/fileIconThemeData';
import { FileIconThemeData } from 'vs/workbench/services/themes/browser/fileIconThemeData';
import { URI } from 'vs/base/common/uri';
import { Disposable } from 'vs/base/common/lifecycle';

View file

@ -19,8 +19,8 @@ import { Event, Emitter } from 'vs/base/common/event';
import { registerFileIconThemeSchemas } from 'vs/workbench/services/themes/common/fileIconThemeSchema';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { ColorThemeStore } from 'vs/workbench/services/themes/common/colorThemeStore';
import { FileIconThemeStore } from 'vs/workbench/services/themes/common/fileIconThemeStore';
import { FileIconThemeData } from 'vs/workbench/services/themes/common/fileIconThemeData';
import { FileIconThemeStore } from 'vs/workbench/services/themes/browser/fileIconThemeStore';
import { FileIconThemeData } from 'vs/workbench/services/themes/browser/fileIconThemeData';
import { removeClasses, addClasses } from 'vs/base/browser/dom';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { IFileService, FileChangeType } from 'vs/platform/files/common/files';
@ -696,4 +696,4 @@ const tokenColorCustomizationConfiguration: IConfigurationNode = {
};
configurationRegistry.registerConfiguration(tokenColorCustomizationConfiguration);
registerSingleton(IWorkbenchThemeService, WorkbenchThemeService);
registerSingleton(IWorkbenchThemeService, WorkbenchThemeService);