Skip to content

Commit 500e680

Browse files
authored
Aux window: editor global stylesheet does not work (fix microsoft#195969) (microsoft#196357)
* Aux window: editor global stylesheet does not work (fix microsoft#195969) * improve
1 parent 9d2ff86 commit 500e680

File tree

4 files changed

+94
-20
lines changed

4 files changed

+94
-20
lines changed

src/vs/base/browser/dom.ts

Lines changed: 75 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -775,16 +775,69 @@ export function focusWindow(element: Node): void {
775775
}
776776
}
777777

778-
export function createStyleSheet(container: HTMLElement = document.getElementsByTagName('head')[0], beforeAppend?: (style: HTMLStyleElement) => void): HTMLStyleElement {
778+
const globalStylesheets = new Map<HTMLStyleElement /* main stylesheet */, Set<HTMLStyleElement /* aux window clones that track the main stylesheet */>>();
779+
780+
export function createStyleSheet(container: HTMLElement = document.head, beforeAppend?: (style: HTMLStyleElement) => void): HTMLStyleElement {
779781
const style = document.createElement('style');
780782
style.type = 'text/css';
781783
style.media = 'screen';
782784
beforeAppend?.(style);
783785
container.appendChild(style);
786+
787+
// With <head> as container, the stylesheet becomes global and is tracked
788+
// to support auxiliary windows to clone the stylesheet.
789+
if (container === document.head) {
790+
const clonedGlobalStylesheets = new Set<HTMLStyleElement>();
791+
globalStylesheets.set(style, clonedGlobalStylesheets);
792+
793+
for (const targetWindow of getWindows()) {
794+
if (targetWindow === window) {
795+
continue; // main window is already tracked
796+
}
797+
798+
const clone = cloneGlobalStyleSheet(style, targetWindow);
799+
clonedGlobalStylesheets.add(clone);
800+
801+
event.Event.once(onDidUnregisterWindow)(unregisteredWindow => {
802+
if (unregisteredWindow === targetWindow) {
803+
clonedGlobalStylesheets.delete(clone);
804+
}
805+
});
806+
}
807+
}
808+
784809
return style;
785810
}
786811

787-
export function createMetaElement(container: HTMLElement = document.getElementsByTagName('head')[0]): HTMLMetaElement {
812+
export function isGlobalStylesheet(node: Node): boolean {
813+
return globalStylesheets.has(node as HTMLStyleElement);
814+
}
815+
816+
export function cloneGlobalStylesheets(targetWindow: Window & typeof globalThis): IDisposable {
817+
const disposables = new DisposableStore();
818+
819+
for (const [globalStylesheet, clonedGlobalStylesheets] of globalStylesheets) {
820+
const clone = cloneGlobalStyleSheet(globalStylesheet, targetWindow);
821+
822+
clonedGlobalStylesheets.add(clone);
823+
disposables.add(toDisposable(() => clonedGlobalStylesheets.delete(clone)));
824+
}
825+
826+
return disposables;
827+
}
828+
829+
function cloneGlobalStyleSheet(globalStylesheet: HTMLStyleElement, targetWindow: Window & typeof globalThis): HTMLStyleElement {
830+
const clone = globalStylesheet.cloneNode(true) as HTMLStyleElement;
831+
targetWindow.document.head.appendChild(clone);
832+
833+
for (const rule of getDynamicStyleSheetRules(globalStylesheet)) {
834+
clone.sheet?.insertRule(rule.cssText, clone.sheet?.cssRules.length);
835+
}
836+
837+
return clone;
838+
}
839+
840+
export function createMetaElement(container: HTMLElement = document.head): HTMLMetaElement {
788841
const meta = document.createElement('meta');
789842
container.appendChild(meta);
790843
return meta;
@@ -798,7 +851,7 @@ function getSharedStyleSheet(): HTMLStyleElement {
798851
return _sharedStyleSheet;
799852
}
800853

801-
function getDynamicStyleSheetRules(style: any) {
854+
function getDynamicStyleSheetRules(style: HTMLStyleElement) {
802855
if (style?.sheet?.rules) {
803856
// Chrome, IE
804857
return style.sheet.rules;
@@ -810,15 +863,20 @@ function getDynamicStyleSheetRules(style: any) {
810863
return [];
811864
}
812865

813-
export function createCSSRule(selector: string, cssText: string, style: HTMLStyleElement = getSharedStyleSheet()): void {
866+
export function createCSSRule(selector: string, cssText: string, style = getSharedStyleSheet()): void {
814867
if (!style || !cssText) {
815868
return;
816869
}
817870

818-
(<CSSStyleSheet>style.sheet).insertRule(selector + '{' + cssText + '}', 0);
871+
style.sheet?.insertRule(`${selector} {${cssText}}`, 0);
872+
873+
// Apply rule also to all cloned global stylesheets
874+
for (const clonedGlobalStylesheet of globalStylesheets.get(style) ?? []) {
875+
createCSSRule(selector, cssText, clonedGlobalStylesheet);
876+
}
819877
}
820878

821-
export function removeCSSRulesContainingSelector(ruleName: string, style: HTMLStyleElement = getSharedStyleSheet()): void {
879+
export function removeCSSRulesContainingSelector(ruleName: string, style = getSharedStyleSheet()): void {
822880
if (!style) {
823881
return;
824882
}
@@ -827,14 +885,23 @@ export function removeCSSRulesContainingSelector(ruleName: string, style: HTMLSt
827885
const toDelete: number[] = [];
828886
for (let i = 0; i < rules.length; i++) {
829887
const rule = rules[i];
830-
if (rule.selectorText.indexOf(ruleName) !== -1) {
888+
if (isCSSStyleRule(rule) && rule.selectorText.indexOf(ruleName) !== -1) {
831889
toDelete.push(i);
832890
}
833891
}
834892

835893
for (let i = toDelete.length - 1; i >= 0; i--) {
836-
(<any>style.sheet).deleteRule(toDelete[i]);
894+
style.sheet?.deleteRule(toDelete[i]);
837895
}
896+
897+
// Remove rules also from all cloned global stylesheets
898+
for (const clonedGlobalStylesheet of globalStylesheets.get(style) ?? []) {
899+
removeCSSRulesContainingSelector(ruleName, clonedGlobalStylesheet);
900+
}
901+
}
902+
903+
function isCSSStyleRule(rule: CSSRule): rule is CSSStyleRule {
904+
return typeof (rule as CSSStyleRule).selectorText === 'string';
838905
}
839906

840907
export function isMouseEvent(e: unknown): e is MouseEvent {

src/vs/editor/browser/services/abstractCodeEditorService.ts

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -346,9 +346,8 @@ class RefCountedStyleSheet {
346346
}
347347
}
348348

349-
public insertRule(rule: string, index?: number): void {
350-
const sheet = <CSSStyleSheet>this._styleSheet.sheet;
351-
sheet.insertRule(rule, index);
349+
public insertRule(selector: string, rule: string): void {
350+
dom.createCSSRule(selector, rule, this._styleSheet);
352351
}
353352

354353
public removeRulesContainingSelector(ruleName: string): void {
@@ -373,9 +372,8 @@ export class GlobalStyleSheet {
373372
public unref(): void {
374373
}
375374

376-
public insertRule(rule: string, index?: number): void {
377-
const sheet = <CSSStyleSheet>this._styleSheet.sheet;
378-
sheet.insertRule(rule, index);
375+
public insertRule(selector: string, rule: string): void {
376+
dom.createCSSRule(selector, rule, this._styleSheet);
379377
}
380378

381379
public removeRulesContainingSelector(ruleName: string): void {
@@ -714,15 +712,15 @@ class DecorationCSSRules {
714712

715713
let hasContent = false;
716714
if (unthemedCSS.length > 0) {
717-
sheet.insertRule(`${this._unThemedSelector} {${unthemedCSS}}`, 0);
715+
sheet.insertRule(this._unThemedSelector, unthemedCSS);
718716
hasContent = true;
719717
}
720718
if (lightCSS.length > 0) {
721-
sheet.insertRule(`.vs${this._unThemedSelector}, .hc-light${this._unThemedSelector} {${lightCSS}}`, 0);
719+
sheet.insertRule(`.vs${this._unThemedSelector}, .hc-light${this._unThemedSelector}`, lightCSS);
722720
hasContent = true;
723721
}
724722
if (darkCSS.length > 0) {
725-
sheet.insertRule(`.vs-dark${this._unThemedSelector}, .hc-black${this._unThemedSelector} {${darkCSS}}`, 0);
723+
sheet.insertRule(`.vs-dark${this._unThemedSelector}, .hc-black${this._unThemedSelector}`, darkCSS);
726724
hasContent = true;
727725
}
728726
this._hasContent = hasContent;

src/vs/editor/test/browser/editorTestServices.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,8 @@ export class TestGlobalStyleSheet extends GlobalStyleSheet {
3636
super(null!);
3737
}
3838

39-
public override insertRule(rule: string, index?: number): void {
40-
this.rules.unshift(rule);
39+
public override insertRule(selector: string, rule: string): void {
40+
this.rules.unshift(`${selector} {${rule}}`);
4141
}
4242

4343
public override removeRulesContainingSelector(ruleName: string): void {

src/vs/workbench/services/auxiliaryWindow/browser/auxiliaryWindowService.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
import { localize } from 'vs/nls';
77
import { Emitter, Event } from 'vs/base/common/event';
8-
import { Dimension, EventHelper, EventType, addDisposableListener, copyAttributes, getActiveWindow, getClientArea, position, registerWindow, size, trackAttributes } from 'vs/base/browser/dom';
8+
import { Dimension, EventHelper, EventType, addDisposableListener, cloneGlobalStylesheets, copyAttributes, getActiveWindow, getClientArea, isGlobalStylesheet, position, registerWindow, size, trackAttributes } from 'vs/base/browser/dom';
99
import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
1010
import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions';
1111
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
@@ -154,6 +154,10 @@ export class BrowserAuxiliaryWindowService extends Disposable implements IAuxili
154154
const mapOriginalToClone = new Map<Node /* original */, Node /* clone */>();
155155

156156
function cloneNode(originalNode: Node): void {
157+
if (isGlobalStylesheet(originalNode)) {
158+
return; // global stylesheets are handled by `cloneGlobalStylesheets` below
159+
}
160+
157161
const clonedNode = auxiliaryWindow.document.head.appendChild(originalNode.cloneNode(true));
158162
mapOriginalToClone.set(originalNode, clonedNode);
159163
}
@@ -163,6 +167,11 @@ export class BrowserAuxiliaryWindowService extends Disposable implements IAuxili
163167
cloneNode(originalNode);
164168
}
165169

170+
// Global stylesheets in <head> are cloned in a special way because the mutation
171+
// observer is not firing for changes done via `style.sheet` API. Only text changes
172+
// can be observed.
173+
disposables.add(cloneGlobalStylesheets(auxiliaryWindow));
174+
166175
// Listen to new stylesheets as they are being added or removed in the main window
167176
// and apply to child window (including changes to existing stylesheets elements)
168177
const observer = new MutationObserver(mutations => {

0 commit comments

Comments
 (0)