Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
160 changes: 0 additions & 160 deletions src/vs/base/browser/dom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -922,105 +922,6 @@ export function getActiveWindow(): CodeWindow {
return (document.defaultView?.window ?? mainWindow) as CodeWindow;
}

const globalStylesheets = new Map<HTMLStyleElement /* main stylesheet */, Set<HTMLStyleElement /* aux window clones that track the main stylesheet */>>();

export function isGlobalStylesheet(node: Node): boolean {
return globalStylesheets.has(node as HTMLStyleElement);
}

/**
* A version of createStyleSheet which has a unified API to initialize/set the style content.
*/
export function createStyleSheet2(): WrappedStyleElement {
return new WrappedStyleElement();
}

class WrappedStyleElement {
private _currentCssStyle = '';
private _styleSheet: HTMLStyleElement | undefined = undefined;

public setStyle(cssStyle: string): void {
if (cssStyle === this._currentCssStyle) {
return;
}
this._currentCssStyle = cssStyle;

if (!this._styleSheet) {
this._styleSheet = createStyleSheet(mainWindow.document.head, (s) => s.innerText = cssStyle);
} else {
this._styleSheet.innerText = cssStyle;
}
}

public dispose(): void {
if (this._styleSheet) {
this._styleSheet.remove();
this._styleSheet = undefined;
}
}
}

export function createStyleSheet(container: HTMLElement = mainWindow.document.head, beforeAppend?: (style: HTMLStyleElement) => void, disposableStore?: DisposableStore): HTMLStyleElement {
const style = document.createElement('style');
style.type = 'text/css';
style.media = 'screen';
beforeAppend?.(style);
container.appendChild(style);

if (disposableStore) {
disposableStore.add(toDisposable(() => style.remove()));
}

// With <head> as container, the stylesheet becomes global and is tracked
// to support auxiliary windows to clone the stylesheet.
if (container === mainWindow.document.head) {
const globalStylesheetClones = new Set<HTMLStyleElement>();
globalStylesheets.set(style, globalStylesheetClones);

for (const { window: targetWindow, disposables } of getWindows()) {
if (targetWindow === mainWindow) {
continue; // main window is already tracked
}

const cloneDisposable = disposables.add(cloneGlobalStyleSheet(style, globalStylesheetClones, targetWindow));
disposableStore?.add(cloneDisposable);
}
}

return style;
}

export function cloneGlobalStylesheets(targetWindow: Window): IDisposable {
const disposables = new DisposableStore();

for (const [globalStylesheet, clonedGlobalStylesheets] of globalStylesheets) {
disposables.add(cloneGlobalStyleSheet(globalStylesheet, clonedGlobalStylesheets, targetWindow));
}

return disposables;
}

function cloneGlobalStyleSheet(globalStylesheet: HTMLStyleElement, globalStylesheetClones: Set<HTMLStyleElement>, targetWindow: Window): IDisposable {
const disposables = new DisposableStore();

const clone = globalStylesheet.cloneNode(true) as HTMLStyleElement;
targetWindow.document.head.appendChild(clone);
disposables.add(toDisposable(() => clone.remove()));

for (const rule of getDynamicStyleSheetRules(globalStylesheet)) {
clone.sheet?.insertRule(rule.cssText, clone.sheet?.cssRules.length);
}

disposables.add(sharedMutationObserver.observe(globalStylesheet, disposables, { childList: true })(() => {
clone.textContent = globalStylesheet.textContent;
}));

globalStylesheetClones.add(clone);
disposables.add(toDisposable(() => globalStylesheetClones.delete(clone)));

return disposables;
}

interface IMutationObserver {
users: number;
readonly observer: MutationObserver;
Expand Down Expand Up @@ -1088,67 +989,6 @@ function createHeadElement(tagName: string, container: HTMLElement = mainWindow.
return element;
}

let _sharedStyleSheet: HTMLStyleElement | null = null;
function getSharedStyleSheet(): HTMLStyleElement {
if (!_sharedStyleSheet) {
_sharedStyleSheet = createStyleSheet();
}
return _sharedStyleSheet;
}

function getDynamicStyleSheetRules(style: HTMLStyleElement) {
if (style?.sheet?.rules) {
// Chrome, IE
return style.sheet.rules;
}
if (style?.sheet?.cssRules) {
// FF
return style.sheet.cssRules;
}
return [];
}

export function createCSSRule(selector: string, cssText: string, style = getSharedStyleSheet()): void {
if (!style || !cssText) {
return;
}

style.sheet?.insertRule(`${selector} {${cssText}}`, 0);

// Apply rule also to all cloned global stylesheets
for (const clonedGlobalStylesheet of globalStylesheets.get(style) ?? []) {
createCSSRule(selector, cssText, clonedGlobalStylesheet);
}
}

export function removeCSSRulesContainingSelector(ruleName: string, style = getSharedStyleSheet()): void {
if (!style) {
return;
}

const rules = getDynamicStyleSheetRules(style);
const toDelete: number[] = [];
for (let i = 0; i < rules.length; i++) {
const rule = rules[i];
if (isCSSStyleRule(rule) && rule.selectorText.indexOf(ruleName) !== -1) {
toDelete.push(i);
}
}

for (let i = toDelete.length - 1; i >= 0; i--) {
style.sheet?.deleteRule(toDelete[i]);
}

// Remove rules also from all cloned global stylesheets
for (const clonedGlobalStylesheet of globalStylesheets.get(style) ?? []) {
removeCSSRulesContainingSelector(ruleName, clonedGlobalStylesheet);
}
}

function isCSSStyleRule(rule: CSSRule): rule is CSSStyleRule {
return typeof (rule as CSSStyleRule).selectorText === 'string';
}

export function isHTMLElement(e: unknown): e is HTMLElement {
// eslint-disable-next-line no-restricted-syntax
return e instanceof HTMLElement || e instanceof getWindow(e as Node).HTMLElement;
Expand Down
2 changes: 1 addition & 1 deletion src/vs/base/browser/domObservable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { createStyleSheet2 } from './dom.js';
import { createStyleSheet2 } from './domStylesheets.js';
import { DisposableStore, IDisposable } from '../common/lifecycle.js';
import { autorun, IObservable } from '../common/observable.js';

Expand Down
168 changes: 168 additions & 0 deletions src/vs/base/browser/domStylesheets.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { DisposableStore, toDisposable, IDisposable } from '../common/lifecycle.js';
import { getWindows, sharedMutationObserver } from './dom.js';
import { mainWindow } from './window.js';

const globalStylesheets = new Map<HTMLStyleElement /* main stylesheet */, Set<HTMLStyleElement /* aux window clones that track the main stylesheet */>>();

export function isGlobalStylesheet(node: Node): boolean {
return globalStylesheets.has(node as HTMLStyleElement);
}

/**
* A version of createStyleSheet which has a unified API to initialize/set the style content.
*/
export function createStyleSheet2(): WrappedStyleElement {
return new WrappedStyleElement();
}

class WrappedStyleElement {
private _currentCssStyle = '';
private _styleSheet: HTMLStyleElement | undefined = undefined;

public setStyle(cssStyle: string): void {
if (cssStyle === this._currentCssStyle) {
return;
}
this._currentCssStyle = cssStyle;

if (!this._styleSheet) {
this._styleSheet = createStyleSheet(mainWindow.document.head, (s) => s.innerText = cssStyle);
} else {
this._styleSheet.innerText = cssStyle;
}
}

public dispose(): void {
if (this._styleSheet) {
this._styleSheet.remove();
this._styleSheet = undefined;
}
}
}

export function createStyleSheet(container: HTMLElement = mainWindow.document.head, beforeAppend?: (style: HTMLStyleElement) => void, disposableStore?: DisposableStore): HTMLStyleElement {
const style = document.createElement('style');
style.type = 'text/css';
style.media = 'screen';
beforeAppend?.(style);
container.appendChild(style);

if (disposableStore) {
disposableStore.add(toDisposable(() => style.remove()));
}

// With <head> as container, the stylesheet becomes global and is tracked
// to support auxiliary windows to clone the stylesheet.
if (container === mainWindow.document.head) {
const globalStylesheetClones = new Set<HTMLStyleElement>();
globalStylesheets.set(style, globalStylesheetClones);

for (const { window: targetWindow, disposables } of getWindows()) {
if (targetWindow === mainWindow) {
continue; // main window is already tracked
}

const cloneDisposable = disposables.add(cloneGlobalStyleSheet(style, globalStylesheetClones, targetWindow));
disposableStore?.add(cloneDisposable);
}
}

return style;
}

export function cloneGlobalStylesheets(targetWindow: Window): IDisposable {
const disposables = new DisposableStore();

for (const [globalStylesheet, clonedGlobalStylesheets] of globalStylesheets) {
disposables.add(cloneGlobalStyleSheet(globalStylesheet, clonedGlobalStylesheets, targetWindow));
}

return disposables;
}

function cloneGlobalStyleSheet(globalStylesheet: HTMLStyleElement, globalStylesheetClones: Set<HTMLStyleElement>, targetWindow: Window): IDisposable {
const disposables = new DisposableStore();

const clone = globalStylesheet.cloneNode(true) as HTMLStyleElement;
targetWindow.document.head.appendChild(clone);
disposables.add(toDisposable(() => clone.remove()));

for (const rule of getDynamicStyleSheetRules(globalStylesheet)) {
clone.sheet?.insertRule(rule.cssText, clone.sheet?.cssRules.length);
}

disposables.add(sharedMutationObserver.observe(globalStylesheet, disposables, { childList: true })(() => {
clone.textContent = globalStylesheet.textContent;
}));

globalStylesheetClones.add(clone);
disposables.add(toDisposable(() => globalStylesheetClones.delete(clone)));

return disposables;
}

let _sharedStyleSheet: HTMLStyleElement | null = null;
function getSharedStyleSheet(): HTMLStyleElement {
if (!_sharedStyleSheet) {
_sharedStyleSheet = createStyleSheet();
}
return _sharedStyleSheet;
}

function getDynamicStyleSheetRules(style: HTMLStyleElement) {
if (style?.sheet?.rules) {
// Chrome, IE
return style.sheet.rules;
}
if (style?.sheet?.cssRules) {
// FF
return style.sheet.cssRules;
}
return [];
}

export function createCSSRule(selector: string, cssText: string, style = getSharedStyleSheet()): void {
if (!style || !cssText) {
return;
}

style.sheet?.insertRule(`${selector} {${cssText}}`, 0);

// Apply rule also to all cloned global stylesheets
for (const clonedGlobalStylesheet of globalStylesheets.get(style) ?? []) {
createCSSRule(selector, cssText, clonedGlobalStylesheet);
}
}

export function removeCSSRulesContainingSelector(ruleName: string, style = getSharedStyleSheet()): void {
if (!style) {
return;
}

const rules = getDynamicStyleSheetRules(style);
const toDelete: number[] = [];
for (let i = 0; i < rules.length; i++) {
const rule = rules[i];
if (isCSSStyleRule(rule) && rule.selectorText.indexOf(ruleName) !== -1) {
toDelete.push(i);
}
}

for (let i = toDelete.length - 1; i >= 0; i--) {
style.sheet?.deleteRule(toDelete[i]);
}

// Remove rules also from all cloned global stylesheets
for (const clonedGlobalStylesheet of globalStylesheets.get(style) ?? []) {
removeCSSRulesContainingSelector(ruleName, clonedGlobalStylesheet);
}
}

function isCSSStyleRule(rule: CSSRule): rule is CSSStyleRule {
return typeof (rule as CSSStyleRule).selectorText === 'string';
}
3 changes: 2 additions & 1 deletion src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
*--------------------------------------------------------------------------------------------*/

import * as dom from '../../dom.js';
import * as domStylesheetsJs from '../../domStylesheets.js';
import { IMouseEvent } from '../../mouseEvent.js';
import { DomScrollableElement } from '../scrollbar/scrollableElement.js';
import { commonPrefixLength } from '../../../common/arrays.js';
Expand Down Expand Up @@ -83,7 +84,7 @@ export class BreadcrumbsWidget {
this._disposables.add(dom.addStandardDisposableListener(this._domNode, 'click', e => this._onClick(e)));
container.appendChild(this._scrollable.getDomNode());

const styleElement = dom.createStyleSheet(this._domNode);
const styleElement = domStylesheetsJs.createStyleSheet(this._domNode);
this._style(styleElement, styles);

const focusTracker = dom.trackFocus(this._domNode);
Expand Down
3 changes: 2 additions & 1 deletion src/vs/base/browser/ui/list/listWidget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
*--------------------------------------------------------------------------------------------*/

import { IDragAndDropData } from '../../dnd.js';
import { createStyleSheet, Dimension, EventHelper, getActiveElement, getWindow, isActiveElement, isEditableElement, isHTMLElement, isMouseEvent } from '../../dom.js';
import { Dimension, EventHelper, getActiveElement, getWindow, isActiveElement, isEditableElement, isHTMLElement, isMouseEvent } from '../../dom.js';
import { createStyleSheet } from '../../domStylesheets.js';
import { asCssValueWithDefault } from '../../cssValue.js';
import { DomEmitter } from '../../event.js';
import { IKeyboardEvent, StandardKeyboardEvent } from '../../keyboardEvent.js';
Expand Down
3 changes: 2 additions & 1 deletion src/vs/base/browser/ui/menu/menu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@

import { isFirefox } from '../../browser.js';
import { EventType as TouchEventType, Gesture } from '../../touch.js';
import { $, addDisposableListener, append, clearNode, createStyleSheet, Dimension, EventHelper, EventLike, EventType, getActiveElement, getWindow, IDomNodePagePosition, isAncestor, isInShadowDOM } from '../../dom.js';
import { $, addDisposableListener, append, clearNode, Dimension, EventHelper, EventLike, EventType, getActiveElement, getWindow, IDomNodePagePosition, isAncestor, isInShadowDOM } from '../../dom.js';
import { createStyleSheet } from '../../domStylesheets.js';
import { StandardKeyboardEvent } from '../../keyboardEvent.js';
import { StandardMouseEvent } from '../../mouseEvent.js';
import { ActionBar, ActionsOrientation, IActionViewItemProvider } from '../actionbar/actionbar.js';
Expand Down
3 changes: 2 additions & 1 deletion src/vs/base/browser/ui/sash/sash.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { $, append, createStyleSheet, EventHelper, EventLike, getWindow, isHTMLElement } from '../../dom.js';
import { $, append, EventHelper, EventLike, getWindow, isHTMLElement } from '../../dom.js';
import { createStyleSheet } from '../../domStylesheets.js';
import { DomEmitter } from '../../event.js';
import { EventType, Gesture } from '../../touch.js';
import { Delayer } from '../../../common/async.js';
Expand Down
Loading