From 7980cd2697482e14a1f2ea9a8a36c064aebc180b Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Thu, 4 Jul 2019 10:19:40 +0200 Subject: [PATCH] replace mutation observer with dom#asDomUri, #75061 --- src/vs/base/browser/dom.ts | 22 +++++ src/vs/base/browser/htmlContentRenderer.ts | 9 +- .../browser/services/codeEditorServiceImpl.ts | 4 +- .../browser/menuEntryActionViewItem.ts | 6 +- .../api/browser/viewsExtensionPoint.ts | 6 +- .../parts/activitybar/activitybarActions.ts | 2 +- .../parts/quickinput/quickInputUtils.ts | 4 +- .../browser/parts/views/customView.ts | 2 +- .../browser/resourceServiceWorkerClient.ts | 83 ------------------- .../webview/browser/webviewEditorInput.ts | 6 +- .../{common => browser}/fileIconThemeData.ts | 5 +- .../{common => browser}/fileIconThemeStore.ts | 2 +- .../themes/browser/workbenchThemeService.ts | 6 +- 13 files changed, 50 insertions(+), 107 deletions(-) rename src/vs/workbench/services/themes/{common => browser}/fileIconThemeData.ts (98%) rename src/vs/workbench/services/themes/{common => browser}/fileIconThemeStore.ts (99%) diff --git a/src/vs/base/browser/dom.ts b/src/vs/base/browser/dom.ts index e201902ae24..2ea764a81e1 100644 --- a/src/vs/base/browser/dom.ts +++ b/src/vs/base/browser/dom.ts @@ -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; +} diff --git a/src/vs/base/browser/htmlContentRenderer.ts b/src/vs/base/browser/htmlContentRenderer.ts index a5c2821936d..68fe5d89e22 100644 --- a/src/vs/base/browser/htmlContentRenderer.ts +++ b/src/vs/base/browser/htmlContentRenderer.ts @@ -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 ( diff --git a/src/vs/editor/browser/services/codeEditorServiceImpl.ts b/src/vs/editor/browser/services/codeEditorServiceImpl.ts index 0779fe452a4..f439007c476 100644 --- a/src/vs/editor/browser/services/codeEditorServiceImpl.ts +++ b/src/vs/editor/browser/services/codeEditorServiceImpl.ts @@ -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)); } diff --git a/src/vs/platform/actions/browser/menuEntryActionViewItem.ts b/src/vs/platform/actions/browser/menuEntryActionViewItem.ts index 2ec3d2e5d78..76729934062 100644 --- a/src/vs/platform/actions/browser/menuEntryActionViewItem.ts +++ b/src/vs/platform/actions/browser/menuEntryActionViewItem.ts @@ -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); } diff --git a/src/vs/workbench/api/browser/viewsExtensionPoint.ts b/src/vs/workbench/api/browser/viewsExtensionPoint.ts index 7a2b3b679e5..687ce57cd78 100644 --- a/src/vs/workbench/api/browser/viewsExtensionPoint.ts +++ b/src/vs/workbench/api/browser/viewsExtensionPoint.ts @@ -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(WorkbenchExtensions.Workbench); -workbenchRegistry.registerWorkbenchContribution(ViewsExtensionHandler, LifecyclePhase.Starting); \ No newline at end of file +workbenchRegistry.registerWorkbenchContribution(ViewsExtensionHandler, LifecyclePhase.Starting); diff --git a/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts b/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts index 1138e9db748..873dc45aadf 100644 --- a/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts +++ b/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts @@ -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 { diff --git a/src/vs/workbench/browser/parts/quickinput/quickInputUtils.ts b/src/vs/workbench/browser/parts/quickinput/quickInputUtils.ts index 3ea185f4750..209fbe1cff4 100644 --- a/src/vs/workbench/browser/parts/quickinput/quickInputUtils.ts +++ b/src/vs/workbench/browser/parts/quickinput/quickInputUtils.ts @@ -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; } diff --git a/src/vs/workbench/browser/parts/views/customView.ts b/src/vs/workbench/browser/parts/views/customView.ts index 5a758eb786f..11812612e5d 100644 --- a/src/vs/workbench/browser/parts/views/customView.ts +++ b/src/vs/workbench/browser/parts/views/customView.ts @@ -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 = ({ $treeViewId: this.treeViewId, $treeItemHandle: node.handle }); templateData.actionBar.push(this.menus.getResourceActions(node), { icon: true, label: false }); diff --git a/src/vs/workbench/contrib/resources/browser/resourceServiceWorkerClient.ts b/src/vs/workbench/contrib/resources/browser/resourceServiceWorkerClient.ts index bed5775bf1e..a8d6c9ee756 100644 --- a/src/vs/workbench/contrib/resources/browser/resourceServiceWorkerClient.ts +++ b/src/vs/workbench/contrib/resources/browser/resourceServiceWorkerClient.ts @@ -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(); - 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 = (target).getAttribute('style'); - if (styleValue && styleValue.indexOf('vscode-remote://') >= 0) { - this._rewriteUrls(styleValue).then(value => { - if (value !== styleValue) { - (target).setAttribute('style', value); - } - }).catch(e => { - console.error(e); - }); - } - } - - private async _rewriteUrls(textContent: string): Promise { - 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); }); diff --git a/src/vs/workbench/contrib/webview/browser/webviewEditorInput.ts b/src/vs/workbench/contrib/webview/browser/webviewEditorInput.ts index 6d4d096a9c7..d0619038471 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewEditorInput.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewEditorInput.ts @@ -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'); diff --git a/src/vs/workbench/services/themes/common/fileIconThemeData.ts b/src/vs/workbench/services/themes/browser/fileIconThemeData.ts similarity index 98% rename from src/vs/workbench/services/themes/common/fileIconThemeData.ts rename to src/vs/workbench/services/themes/browser/fileIconThemeData.ts index efdaaaef68c..3059579a649 100644 --- a/src/vs/workbench/services/themes/common/fileIconThemeData.ts +++ b/src/vs/workbench/services/themes/browser/fileIconThemeData.ts @@ -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 = ''; diff --git a/src/vs/workbench/services/themes/common/fileIconThemeStore.ts b/src/vs/workbench/services/themes/browser/fileIconThemeStore.ts similarity index 99% rename from src/vs/workbench/services/themes/common/fileIconThemeStore.ts rename to src/vs/workbench/services/themes/browser/fileIconThemeStore.ts index dfc2b180f4e..e4eb557f54c 100644 --- a/src/vs/workbench/services/themes/common/fileIconThemeStore.ts +++ b/src/vs/workbench/services/themes/browser/fileIconThemeStore.ts @@ -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'; diff --git a/src/vs/workbench/services/themes/browser/workbenchThemeService.ts b/src/vs/workbench/services/themes/browser/workbenchThemeService.ts index d17412a1930..84cf2bb355b 100644 --- a/src/vs/workbench/services/themes/browser/workbenchThemeService.ts +++ b/src/vs/workbench/services/themes/browser/workbenchThemeService.ts @@ -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); \ No newline at end of file +registerSingleton(IWorkbenchThemeService, WorkbenchThemeService);