diff --git a/src/vs/base/browser/dom.ts b/src/vs/base/browser/dom.ts index b751eb7c578..7a8d0efd1e3 100644 --- a/src/vs/base/browser/dom.ts +++ b/src/vs/base/browser/dom.ts @@ -820,6 +820,23 @@ export function hasParentWithClass(node: HTMLElement, clazz: string, stopAtClazz return !!findParentWithClass(node, clazz, stopAtClazzOrNode); } +export function isShadowRoot(node: Node): node is ShadowRoot { + return ( + node && !!(node).host && !!(node).mode + ); +} + +export function isInShadowDOM(domNode: Node): boolean { + while (domNode.parentNode) { + if (domNode === document.body) { + // reached the body + return false; + } + domNode = domNode.parentNode; + } + return isShadowRoot(domNode); +} + export function createStyleSheet(container: HTMLElement | null = null): HTMLStyleElement { if (!container) { if ((window as any).monacoShadowRoot) { diff --git a/src/vs/editor/browser/editorBrowser.ts b/src/vs/editor/browser/editorBrowser.ts index 5764ecd2ae6..6356facd14b 100644 --- a/src/vs/editor/browser/editorBrowser.ts +++ b/src/vs/editor/browser/editorBrowser.ts @@ -723,6 +723,11 @@ export interface ICodeEditor extends editorCommon.IEditor { */ getTelemetryData(): { [key: string]: any } | undefined; + /** + * Returns the editor's container dom node + */ + getContainerDomNode(): HTMLElement; + /** * Returns the editor's dom node */ diff --git a/src/vs/editor/browser/services/abstractCodeEditorService.ts b/src/vs/editor/browser/services/abstractCodeEditorService.ts index a7e8785c3a8..bb96485db3e 100644 --- a/src/vs/editor/browser/services/abstractCodeEditorService.ts +++ b/src/vs/editor/browser/services/abstractCodeEditorService.ts @@ -89,7 +89,7 @@ export abstract class AbstractCodeEditorService extends Disposable implements IC return editorWithWidgetFocus; } - abstract registerDecorationType(key: string, options: IDecorationRenderOptions, parentTypeKey?: string): void; + abstract registerDecorationType(key: string, options: IDecorationRenderOptions, parentTypeKey?: string, editor?: ICodeEditor): void; abstract removeDecorationType(key: string): void; abstract resolveDecorationOptions(decorationTypeKey: string | undefined, writable: boolean): IModelDecorationOptions; diff --git a/src/vs/editor/browser/services/codeEditorService.ts b/src/vs/editor/browser/services/codeEditorService.ts index 22f0e9e4844..145cb9d2641 100644 --- a/src/vs/editor/browser/services/codeEditorService.ts +++ b/src/vs/editor/browser/services/codeEditorService.ts @@ -37,7 +37,7 @@ export interface ICodeEditorService { */ getFocusedCodeEditor(): ICodeEditor | null; - registerDecorationType(key: string, options: IDecorationRenderOptions, parentTypeKey?: string): void; + registerDecorationType(key: string, options: IDecorationRenderOptions, parentTypeKey?: string, editor?: ICodeEditor): void; removeDecorationType(key: string): void; resolveDecorationOptions(typeKey: string, writable: boolean): IModelDecorationOptions; diff --git a/src/vs/editor/browser/services/codeEditorServiceImpl.ts b/src/vs/editor/browser/services/codeEditorServiceImpl.ts index c7cf1da3f45..944a6d0f20f 100644 --- a/src/vs/editor/browser/services/codeEditorServiceImpl.ts +++ b/src/vs/editor/browser/services/codeEditorServiceImpl.ts @@ -14,31 +14,101 @@ import { IModelDecorationOptions, IModelDecorationOverviewRulerOptions, Overview import { IResourceInput } from 'vs/platform/editor/common/editor'; import { ITheme, IThemeService, ThemeColor } from 'vs/platform/theme/common/themeService'; +class RefCountedStyleSheet { + + private readonly _parent: CodeEditorServiceImpl; + private readonly _editorId: string; + public readonly styleSheet: HTMLStyleElement; + private _refCount: number; + + constructor(parent: CodeEditorServiceImpl, editorId: string, styleSheet: HTMLStyleElement) { + this._parent = parent; + this._editorId = editorId; + this.styleSheet = styleSheet; + this._refCount = 0; + } + + public ref(): void { + this._refCount++; + } + + public unref(): void { + this._refCount--; + if (this._refCount === 0) { + this.styleSheet.parentNode?.removeChild(this.styleSheet); + this._parent._removeEditorStyleSheets(this._editorId); + } + } +} + +class GlobalStyleSheet { + public readonly styleSheet: HTMLStyleElement; + + constructor(styleSheet: HTMLStyleElement) { + this.styleSheet = styleSheet; + } + + public ref(): void { + } + + public unref(): void { + } +} + export abstract class CodeEditorServiceImpl extends AbstractCodeEditorService { - private readonly _styleSheet: HTMLStyleElement; + private _globalStyleSheet: GlobalStyleSheet | null; private readonly _decorationOptionProviders = new Map(); + private readonly _editorStyleSheets = new Map(); private readonly _themeService: IThemeService; - constructor(@IThemeService themeService: IThemeService, styleSheet = dom.createStyleSheet()) { + constructor(@IThemeService themeService: IThemeService, styleSheet: HTMLStyleElement | null = null) { super(); - this._styleSheet = styleSheet; + this._globalStyleSheet = styleSheet ? new GlobalStyleSheet(styleSheet) : null; this._themeService = themeService; } - public registerDecorationType(key: string, options: IDecorationRenderOptions, parentTypeKey?: string): void { + private _getOrCreateGlobalStyleSheet(): GlobalStyleSheet { + if (!this._globalStyleSheet) { + this._globalStyleSheet = new GlobalStyleSheet(dom.createStyleSheet()); + } + return this._globalStyleSheet; + } + + private _getOrCreateStyleSheet(editor: ICodeEditor | undefined): GlobalStyleSheet | RefCountedStyleSheet { + if (!editor) { + return this._getOrCreateGlobalStyleSheet(); + } + const domNode = editor.getContainerDomNode(); + if (!dom.isInShadowDOM(domNode)) { + return this._getOrCreateGlobalStyleSheet(); + } + const editorId = editor.getId(); + if (!this._editorStyleSheets.has(editorId)) { + const refCountedStyleSheet = new RefCountedStyleSheet(this, editorId, dom.createStyleSheet(domNode)); + this._editorStyleSheets.set(editorId, refCountedStyleSheet); + } + return this._editorStyleSheets.get(editorId)!; + } + + _removeEditorStyleSheets(editorId: string): void { + this._editorStyleSheets.delete(editorId); + } + + public registerDecorationType(key: string, options: IDecorationRenderOptions, parentTypeKey?: string, editor?: ICodeEditor): void { let provider = this._decorationOptionProviders.get(key); if (!provider) { + const styleSheet = this._getOrCreateStyleSheet(editor); const providerArgs: ProviderArguments = { - styleSheet: this._styleSheet, + styleSheet: styleSheet.styleSheet, key: key, parentTypeKey: parentTypeKey, options: options || Object.create(null) }; if (!parentTypeKey) { - provider = new DecorationTypeOptionsProvider(this._themeService, providerArgs); + provider = new DecorationTypeOptionsProvider(this._themeService, styleSheet, providerArgs); } else { - provider = new DecorationSubTypeOptionsProvider(this._themeService, providerArgs); + provider = new DecorationSubTypeOptionsProvider(this._themeService, styleSheet, providerArgs); } this._decorationOptionProviders.set(key, provider); } @@ -76,13 +146,16 @@ interface IModelDecorationOptionsProvider extends IDisposable { class DecorationSubTypeOptionsProvider implements IModelDecorationOptionsProvider { + private readonly _styleSheet: GlobalStyleSheet | RefCountedStyleSheet; public refCount: number; private readonly _parentTypeKey: string | undefined; private _beforeContentRules: DecorationCSSRules | null; private _afterContentRules: DecorationCSSRules | null; - constructor(themeService: IThemeService, providerArgs: ProviderArguments) { + constructor(themeService: IThemeService, styleSheet: GlobalStyleSheet | RefCountedStyleSheet, providerArgs: ProviderArguments) { + this._styleSheet = styleSheet; + this._styleSheet.ref(); this._parentTypeKey = providerArgs.parentTypeKey; this.refCount = 0; @@ -110,6 +183,7 @@ class DecorationSubTypeOptionsProvider implements IModelDecorationOptionsProvide this._afterContentRules.dispose(); this._afterContentRules = null; } + this._styleSheet.unref(); } } @@ -124,6 +198,7 @@ interface ProviderArguments { class DecorationTypeOptionsProvider implements IModelDecorationOptionsProvider { private readonly _disposables = new DisposableStore(); + private readonly _styleSheet: GlobalStyleSheet | RefCountedStyleSheet; public refCount: number; public className: string | undefined; @@ -136,7 +211,9 @@ class DecorationTypeOptionsProvider implements IModelDecorationOptionsProvider { public overviewRuler: IModelDecorationOverviewRulerOptions | undefined; public stickiness: TrackedRangeStickiness | undefined; - constructor(themeService: IThemeService, providerArgs: ProviderArguments) { + constructor(themeService: IThemeService, styleSheet: GlobalStyleSheet | RefCountedStyleSheet, providerArgs: ProviderArguments) { + this._styleSheet = styleSheet; + this._styleSheet.ref(); this.refCount = 0; const createCSSRules = (type: ModelDecorationCSSRuleType) => { @@ -202,6 +279,7 @@ class DecorationTypeOptionsProvider implements IModelDecorationOptionsProvider { public dispose(): void { this._disposables.dispose(); + this._styleSheet.unref(); } } diff --git a/src/vs/editor/browser/widget/codeEditorWidget.ts b/src/vs/editor/browser/widget/codeEditorWidget.ts index f81d9490fea..c23eba3621a 100644 --- a/src/vs/editor/browser/widget/codeEditorWidget.ts +++ b/src/vs/editor/browser/widget/codeEditorWidget.ts @@ -1165,6 +1165,10 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE return this._modelData.view.createOverviewRuler(cssClassName); } + public getContainerDomNode(): HTMLElement { + return this._domElement; + } + public getDomNode(): HTMLElement | null { if (!this._modelData || !this._modelData.hasRealView) { return null; @@ -1548,7 +1552,7 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE } private _registerDecorationType(key: string, options: editorCommon.IDecorationRenderOptions, parentTypeKey?: string): void { - this._codeEditorService.registerDecorationType(key, options, parentTypeKey); + this._codeEditorService.registerDecorationType(key, options, parentTypeKey, this); } private _removeDecorationType(key: string): void { diff --git a/src/vs/editor/contrib/codelens/codelensController.ts b/src/vs/editor/contrib/codelens/codelensController.ts index f905d33c94e..3de2e6e2849 100644 --- a/src/vs/editor/contrib/codelens/codelensController.ts +++ b/src/vs/editor/contrib/codelens/codelensController.ts @@ -18,7 +18,7 @@ import { ICommandService } from 'vs/platform/commands/common/commands'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { ICodeLensCache } from 'vs/editor/contrib/codelens/codeLensCache'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; -import { createStyleSheet } from 'vs/base/browser/dom'; +import * as dom from 'vs/base/browser/dom'; import { hash } from 'vs/base/common/hash'; export class CodeLensContribution implements IEditorContribution { @@ -65,7 +65,11 @@ export class CodeLensContribution implements IEditorContribution { this._onModelChange(); this._styleClassName = hash(this._editor.getId()).toString(16); - this._styleElement = createStyleSheet(); + this._styleElement = dom.createStyleSheet( + dom.isInShadowDOM(this._editor.getContainerDomNode()) + ? this._editor.getContainerDomNode() + : null + ); this._updateLensStyle(); } diff --git a/src/vs/editor/contrib/colorPicker/colorDetector.ts b/src/vs/editor/contrib/colorPicker/colorDetector.ts index 9e2113c38d9..90f0db8ac67 100644 --- a/src/vs/editor/contrib/colorPicker/colorDetector.ts +++ b/src/vs/editor/contrib/colorPicker/colorDetector.ts @@ -192,7 +192,7 @@ export class ColorDetector extends Disposable implements IEditorContribution { border: 'solid 0.1em #eee' } } - }); + }, undefined, this._editor); } newDecorationsTypes[key] = true; diff --git a/src/vs/editor/standalone/browser/standaloneCodeEditor.ts b/src/vs/editor/standalone/browser/standaloneCodeEditor.ts index 4c6212d9a19..891fe556b0e 100644 --- a/src/vs/editor/standalone/browser/standaloneCodeEditor.ts +++ b/src/vs/editor/standalone/browser/standaloneCodeEditor.ts @@ -351,6 +351,7 @@ export class StandaloneEditor extends StandaloneCodeEditor implements IStandalon @IAccessibilityService accessibilityService: IAccessibilityService ) { applyConfigurationValues(configurationService, options, false); + const themeDomRegistration = themeService.registerEditorContainer(domElement); options = options || {}; if (typeof options.theme === 'string') { themeService.setTheme(options.theme); @@ -362,6 +363,7 @@ export class StandaloneEditor extends StandaloneCodeEditor implements IStandalon this._contextViewService = contextViewService; this._configurationService = configurationService; this._register(toDispose); + this._register(themeDomRegistration); let model: ITextModel | null; if (typeof _model === 'undefined') { @@ -430,6 +432,7 @@ export class StandaloneDiffEditor extends DiffEditorWidget implements IStandalon @optional(IClipboardService) clipboardService: IClipboardService | null, ) { applyConfigurationValues(configurationService, options, true); + const themeDomRegistration = themeService.registerEditorContainer(domElement); options = options || {}; if (typeof options.theme === 'string') { options.theme = themeService.setTheme(options.theme); @@ -441,6 +444,7 @@ export class StandaloneDiffEditor extends DiffEditorWidget implements IStandalon this._configurationService = configurationService; this._register(toDispose); + this._register(themeDomRegistration); this._contextViewService.setContainer(this._containerDomElement); } diff --git a/src/vs/editor/standalone/browser/standaloneThemeServiceImpl.ts b/src/vs/editor/standalone/browser/standaloneThemeServiceImpl.ts index 445bf8a2528..62add292d62 100644 --- a/src/vs/editor/standalone/browser/standaloneThemeServiceImpl.ts +++ b/src/vs/editor/standalone/browser/standaloneThemeServiceImpl.ts @@ -5,7 +5,7 @@ import * as dom from 'vs/base/browser/dom'; import { Color } from 'vs/base/common/color'; -import { Emitter, Event } from 'vs/base/common/event'; +import { Emitter } from 'vs/base/common/event'; import { TokenizationRegistry } from 'vs/editor/common/modes'; import { ITokenThemeRule, TokenTheme, generateTokensCSSForColorMap } from 'vs/editor/common/modes/supports/tokenization'; import { BuiltinTheme, IStandaloneTheme, IStandaloneThemeData, IStandaloneThemeService } from 'vs/editor/standalone/common/standaloneThemeService'; @@ -14,6 +14,7 @@ import { IEnvironmentService } from 'vs/platform/environment/common/environment' import { Registry } from 'vs/platform/registry/common/platform'; import { ColorIdentifier, Extensions, IColorRegistry } from 'vs/platform/theme/common/colorRegistry'; import { Extensions as ThemingExtensions, ICssStyleCollector, IIconTheme, IThemingRegistry } from 'vs/platform/theme/common/themeService'; +import { IDisposable, Disposable } from 'vs/base/common/lifecycle'; const VS_THEME_NAME = 'vs'; const VS_DARK_THEME_NAME = 'vs-dark'; @@ -163,32 +164,68 @@ function newBuiltInTheme(builtinTheme: BuiltinTheme): StandaloneTheme { return new StandaloneTheme(builtinTheme, themeData); } -export class StandaloneThemeServiceImpl implements IStandaloneThemeService { +export class StandaloneThemeServiceImpl extends Disposable implements IStandaloneThemeService { _serviceBrand: undefined; + private readonly _onThemeChange = this._register(new Emitter()); + public readonly onThemeChange = this._onThemeChange.event; + + private readonly _onIconThemeChange = this._register(new Emitter()); + public readonly onIconThemeChange = this._onIconThemeChange.event; + + private readonly _environment: IEnvironmentService = Object.create(null); private readonly _knownThemes: Map; - private readonly _styleElement: HTMLStyleElement; + private _css: string; + private _globalStyleElement: HTMLStyleElement | null; + private _styleElements: HTMLStyleElement[]; private _theme!: IStandaloneTheme; - private readonly _onThemeChange: Emitter; - private readonly _onIconThemeChange: Emitter; - private readonly environment: IEnvironmentService = Object.create(null); constructor() { - this._onThemeChange = new Emitter(); - this._onIconThemeChange = new Emitter(); + super(); this._knownThemes = new Map(); this._knownThemes.set(VS_THEME_NAME, newBuiltInTheme(VS_THEME_NAME)); this._knownThemes.set(VS_DARK_THEME_NAME, newBuiltInTheme(VS_DARK_THEME_NAME)); this._knownThemes.set(HC_BLACK_THEME_NAME, newBuiltInTheme(HC_BLACK_THEME_NAME)); - this._styleElement = dom.createStyleSheet(); - this._styleElement.className = 'monaco-colors'; + this._css = ''; + this._globalStyleElement = null; + this._styleElements = []; this.setTheme(VS_THEME_NAME); } - public get onThemeChange(): Event { - return this._onThemeChange.event; + public registerEditorContainer(domNode: HTMLElement): IDisposable { + if (dom.isInShadowDOM(domNode)) { + return this._registerShadowDomContainer(domNode); + } + return this._registerRegularEditorContainer(); + } + + private _registerRegularEditorContainer(): IDisposable { + if (!this._globalStyleElement) { + this._globalStyleElement = dom.createStyleSheet(); + this._globalStyleElement.className = 'monaco-colors'; + this._globalStyleElement.innerHTML = this._css; + this._styleElements.push(this._globalStyleElement); + } + return Disposable.None; + } + + private _registerShadowDomContainer(domNode: HTMLElement): IDisposable { + const styleElement = dom.createStyleSheet(domNode); + styleElement.className = 'monaco-colors'; + styleElement.innerHTML = this._css; + this._styleElements.push(styleElement); + return { + dispose: () => { + for (let i = 0; i < this._styleElements.length; i++) { + if (this._styleElements[i] === styleElement) { + this._styleElements.splice(i, 1); + return; + } + } + } + }; } public defineTheme(themeName: string, themeData: IStandaloneThemeData): void { @@ -240,13 +277,14 @@ export class StandaloneThemeServiceImpl implements IStandaloneThemeService { } } }; - themingRegistry.getThemingParticipants().forEach(p => p(theme, ruleCollector, this.environment)); + themingRegistry.getThemingParticipants().forEach(p => p(theme, ruleCollector, this._environment)); let tokenTheme = theme.tokenTheme; let colorMap = tokenTheme.getColorMap(); ruleCollector.addRule(generateTokensCSSForColorMap(colorMap)); - this._styleElement.innerHTML = cssRules.join('\n'); + this._css = cssRules.join('\n'); + this._styleElements.forEach(styleElement => styleElement.innerHTML = this._css); TokenizationRegistry.setColorMap(colorMap); this._onThemeChange.fire(theme); @@ -261,8 +299,4 @@ export class StandaloneThemeServiceImpl implements IStandaloneThemeService { hidesExplorerArrows: false }; } - - public get onIconThemeChange(): Event { - return this._onIconThemeChange.event; - } } diff --git a/src/vs/editor/standalone/common/standaloneThemeService.ts b/src/vs/editor/standalone/common/standaloneThemeService.ts index 0fca97422c1..8be747d72cb 100644 --- a/src/vs/editor/standalone/common/standaloneThemeService.ts +++ b/src/vs/editor/standalone/common/standaloneThemeService.ts @@ -6,6 +6,7 @@ import { ITokenThemeRule, TokenTheme } from 'vs/editor/common/modes/supports/tokenization'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { ITheme, IThemeService } from 'vs/platform/theme/common/themeService'; +import { IDisposable } from 'vs/base/common/lifecycle'; export const IStandaloneThemeService = createDecorator('themeService'); @@ -28,6 +29,8 @@ export interface IStandaloneTheme extends ITheme { export interface IStandaloneThemeService extends IThemeService { _serviceBrand: undefined; + registerEditorContainer(domNode: HTMLElement): IDisposable; + setTheme(themeName: string): string; defineTheme(themeName: string, themeData: IStandaloneThemeData): void; diff --git a/src/vs/editor/standalone/test/browser/standaloneLanguages.test.ts b/src/vs/editor/standalone/test/browser/standaloneLanguages.test.ts index 12bfb828dff..b6c7718aefd 100644 --- a/src/vs/editor/standalone/test/browser/standaloneLanguages.test.ts +++ b/src/vs/editor/standalone/test/browser/standaloneLanguages.test.ts @@ -13,6 +13,7 @@ import { ILineTokens, IToken, TokenizationSupport2Adapter, TokensProvider } from import { IStandaloneTheme, IStandaloneThemeData, IStandaloneThemeService } from 'vs/editor/standalone/common/standaloneThemeService'; import { ColorIdentifier } from 'vs/platform/theme/common/colorRegistry'; import { IIconTheme, ITheme, LIGHT } from 'vs/platform/theme/common/themeService'; +import { IDisposable } from 'vs/base/common/lifecycle'; suite('TokenizationSupport2Adapter', () => { @@ -34,6 +35,9 @@ suite('TokenizationSupport2Adapter', () => { class MockThemeService implements IStandaloneThemeService { _serviceBrand: undefined; + public registerEditorContainer(domNode: HTMLElement): IDisposable { + throw new Error('Not implemented'); + } public setTheme(themeName: string): string { throw new Error('Not implemented'); } diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 3b19ebaaec8..2f129a25bb6 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -4487,6 +4487,10 @@ declare namespace monaco.editor { * Get the vertical position (top offset) for the position w.r.t. to the first line. */ getTopForPosition(lineNumber: number, column: number): number; + /** + * Returns the editor's container dom node + */ + getContainerDomNode(): HTMLElement; /** * Returns the editor's dom node */