Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/base/browser/domStylesheets.ts
3292 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { DisposableStore, toDisposable, IDisposable } from '../common/lifecycle.js';
7
import { autorun, IObservable } from '../common/observable.js';
8
import { getWindows, sharedMutationObserver } from './dom.js';
9
import { mainWindow } from './window.js';
10
11
const globalStylesheets = new Map<HTMLStyleElement /* main stylesheet */, Set<HTMLStyleElement /* aux window clones that track the main stylesheet */>>();
12
13
export function isGlobalStylesheet(node: Node): boolean {
14
return globalStylesheets.has(node as HTMLStyleElement);
15
}
16
17
/**
18
* A version of createStyleSheet which has a unified API to initialize/set the style content.
19
*/
20
export function createStyleSheet2(): WrappedStyleElement {
21
return new WrappedStyleElement();
22
}
23
24
class WrappedStyleElement {
25
private _currentCssStyle = '';
26
private _styleSheet: HTMLStyleElement | undefined = undefined;
27
28
public setStyle(cssStyle: string): void {
29
if (cssStyle === this._currentCssStyle) {
30
return;
31
}
32
this._currentCssStyle = cssStyle;
33
34
if (!this._styleSheet) {
35
this._styleSheet = createStyleSheet(mainWindow.document.head, (s) => s.textContent = cssStyle);
36
} else {
37
this._styleSheet.textContent = cssStyle;
38
}
39
}
40
41
public dispose(): void {
42
if (this._styleSheet) {
43
this._styleSheet.remove();
44
this._styleSheet = undefined;
45
}
46
}
47
}
48
49
export function createStyleSheet(container: HTMLElement = mainWindow.document.head, beforeAppend?: (style: HTMLStyleElement) => void, disposableStore?: DisposableStore): HTMLStyleElement {
50
const style = document.createElement('style');
51
style.type = 'text/css';
52
style.media = 'screen';
53
beforeAppend?.(style);
54
container.appendChild(style);
55
56
if (disposableStore) {
57
disposableStore.add(toDisposable(() => style.remove()));
58
}
59
60
// With <head> as container, the stylesheet becomes global and is tracked
61
// to support auxiliary windows to clone the stylesheet.
62
if (container === mainWindow.document.head) {
63
const globalStylesheetClones = new Set<HTMLStyleElement>();
64
globalStylesheets.set(style, globalStylesheetClones);
65
if (disposableStore) {
66
disposableStore.add(toDisposable(() => globalStylesheets.delete(style)));
67
}
68
69
for (const { window: targetWindow, disposables } of getWindows()) {
70
if (targetWindow === mainWindow) {
71
continue; // main window is already tracked
72
}
73
74
const cloneDisposable = disposables.add(cloneGlobalStyleSheet(style, globalStylesheetClones, targetWindow));
75
disposableStore?.add(cloneDisposable);
76
}
77
}
78
79
return style;
80
}
81
82
export function cloneGlobalStylesheets(targetWindow: Window): IDisposable {
83
const disposables = new DisposableStore();
84
85
for (const [globalStylesheet, clonedGlobalStylesheets] of globalStylesheets) {
86
disposables.add(cloneGlobalStyleSheet(globalStylesheet, clonedGlobalStylesheets, targetWindow));
87
}
88
89
return disposables;
90
}
91
92
function cloneGlobalStyleSheet(globalStylesheet: HTMLStyleElement, globalStylesheetClones: Set<HTMLStyleElement>, targetWindow: Window): IDisposable {
93
const disposables = new DisposableStore();
94
95
const clone = globalStylesheet.cloneNode(true) as HTMLStyleElement;
96
targetWindow.document.head.appendChild(clone);
97
disposables.add(toDisposable(() => clone.remove()));
98
99
for (const rule of getDynamicStyleSheetRules(globalStylesheet)) {
100
clone.sheet?.insertRule(rule.cssText, clone.sheet?.cssRules.length);
101
}
102
103
disposables.add(sharedMutationObserver.observe(globalStylesheet, disposables, { childList: true })(() => {
104
clone.textContent = globalStylesheet.textContent;
105
}));
106
107
globalStylesheetClones.add(clone);
108
disposables.add(toDisposable(() => globalStylesheetClones.delete(clone)));
109
110
return disposables;
111
}
112
113
let _sharedStyleSheet: HTMLStyleElement | null = null;
114
function getSharedStyleSheet(): HTMLStyleElement {
115
if (!_sharedStyleSheet) {
116
_sharedStyleSheet = createStyleSheet();
117
}
118
return _sharedStyleSheet;
119
}
120
121
function getDynamicStyleSheetRules(style: HTMLStyleElement) {
122
if (style?.sheet?.rules) {
123
// Chrome, IE
124
return style.sheet.rules;
125
}
126
if (style?.sheet?.cssRules) {
127
// FF
128
return style.sheet.cssRules;
129
}
130
return [];
131
}
132
133
export function createCSSRule(selector: string, cssText: string, style = getSharedStyleSheet()): void {
134
if (!style || !cssText) {
135
return;
136
}
137
138
style.sheet?.insertRule(`${selector} {${cssText}}`, 0);
139
140
// Apply rule also to all cloned global stylesheets
141
for (const clonedGlobalStylesheet of globalStylesheets.get(style) ?? []) {
142
createCSSRule(selector, cssText, clonedGlobalStylesheet);
143
}
144
}
145
146
export function removeCSSRulesContainingSelector(ruleName: string, style = getSharedStyleSheet()): void {
147
if (!style) {
148
return;
149
}
150
151
const rules = getDynamicStyleSheetRules(style);
152
const toDelete: number[] = [];
153
for (let i = 0; i < rules.length; i++) {
154
const rule = rules[i];
155
if (isCSSStyleRule(rule) && rule.selectorText.indexOf(ruleName) !== -1) {
156
toDelete.push(i);
157
}
158
}
159
160
for (let i = toDelete.length - 1; i >= 0; i--) {
161
style.sheet?.deleteRule(toDelete[i]);
162
}
163
164
// Remove rules also from all cloned global stylesheets
165
for (const clonedGlobalStylesheet of globalStylesheets.get(style) ?? []) {
166
removeCSSRulesContainingSelector(ruleName, clonedGlobalStylesheet);
167
}
168
}
169
170
function isCSSStyleRule(rule: CSSRule): rule is CSSStyleRule {
171
return typeof (rule as CSSStyleRule).selectorText === 'string';
172
}
173
174
export function createStyleSheetFromObservable(css: IObservable<string>): IDisposable {
175
const store = new DisposableStore();
176
const w = store.add(createStyleSheet2());
177
store.add(autorun(reader => {
178
w.setStyle(css.read(reader));
179
}));
180
return store;
181
}
182
183