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