Skip to content

Commit

Permalink
replace mutation observer with dom#asDomUri, #75061
Browse files Browse the repository at this point in the history
  • Loading branch information
jrieken committed Jul 4, 2019
1 parent 7c88e07 commit 7980cd2
Show file tree
Hide file tree
Showing 13 changed files with 50 additions and 107 deletions.
22 changes: 22 additions & 0 deletions src/vs/base/browser/dom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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;
}
9 changes: 6 additions & 3 deletions src/vs/base/browser/htmlContentRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) });
}
Expand All @@ -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());
Expand Down Expand Up @@ -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 (
Expand Down
4 changes: 2 additions & 2 deletions src/vs/editor/browser/services/codeEditorServiceImpl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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));
}
Expand Down
6 changes: 3 additions & 3 deletions src/vs/platform/actions/browser/menuEntryActionViewItem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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);
}

Expand Down
6 changes: 3 additions & 3 deletions src/vs/workbench/api/browser/viewsExtensionPoint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
4 changes: 2 additions & 2 deletions src/vs/workbench/browser/parts/quickinput/quickInputUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
2 changes: 1 addition & 1 deletion src/vs/workbench/browser/parts/views/customView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -114,7 +32,6 @@ class ResourceServiceWorker {
return navigator.serviceWorker.ready;
}).then(() => {
// console.log('ready');
this._disposables.add(new ResourcesMutationObserver());
}).catch(err => {
console.error(err);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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%'}}`);
Expand All @@ -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 = '';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -696,4 +696,4 @@ const tokenColorCustomizationConfiguration: IConfigurationNode = {
};
configurationRegistry.registerConfiguration(tokenColorCustomizationConfiguration);

registerSingleton(IWorkbenchThemeService, WorkbenchThemeService);
registerSingleton(IWorkbenchThemeService, WorkbenchThemeService);

0 comments on commit 7980cd2

Please sign in to comment.