Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: [#1620] Adds support for CSS pseudo selector :scope #1771

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
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
4 changes: 4 additions & 0 deletions cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@
"dictionaries": [],
"words": [
"altgraph",
"antiquewhite",
"clonable",
"Contoso",
"darkmagenta",
"ISVG",
"oncompositionend",
"oncompositionstart",
Expand All @@ -17,6 +19,8 @@
"onscrollsnapchanging",
"onsearch",
"unnestable",
"vmax",
"vmin",
"xlink"
],
"ignoreWords": [],
Expand Down
9 changes: 9 additions & 0 deletions packages/happy-dom/src/PropertySymbol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -391,3 +391,12 @@ export const dispatchError = Symbol('dispatchError');
export const supports = Symbol('supports');
export const reason = Symbol('reason');
export const propertyEventListeners = Symbol('propertyEventListeners');
export const cssRules = Symbol('cssRules');
export const parentRule = Symbol('parentRule');
export const parentStyleSheet = Symbol('parentStyleSheet');
export const conditionText = Symbol('conditionText');
export const keyText = Symbol('keyText');
export const media = Symbol('media');
export const styleMap = Symbol('styleMap');
export const selectorText = Symbol('selectorText');
export const cssParser = Symbol('cssParser');
50 changes: 41 additions & 9 deletions packages/happy-dom/src/css/CSSRule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@ import CSSStyleSheet from './CSSStyleSheet.js';
import CSSRuleTypeEnum from './CSSRuleTypeEnum.js';
import * as PropertySymbol from '../PropertySymbol.js';
import BrowserWindow from '../window/BrowserWindow.js';
import CSSParser from './utilities/CSSParser.js';

/**
* CSSRule interface.
*
* @see https://developer.mozilla.org/en-US/docs/Web/API/CSSRule
*/
export default class CSSRule {
export default abstract class CSSRule {
// Static properties
public static CONTAINER_RULE = CSSRuleTypeEnum.containerRule;
public static STYLE_RULE = CSSRuleTypeEnum.styleRule;
Expand All @@ -25,32 +28,61 @@ export default class CSSRule {

// Internal properties
public [PropertySymbol.window]: BrowserWindow;
public [PropertySymbol.cssParser]: CSSParser;

// Public properties
public parentRule: CSSRule = null;
public parentStyleSheet: CSSStyleSheet = null;
public type: CSSRuleTypeEnum | null = null;
public abstract [PropertySymbol.type]: CSSRuleTypeEnum;
public [PropertySymbol.parentRule]: CSSRule = null;
public [PropertySymbol.parentStyleSheet]: CSSStyleSheet = null;
public [PropertySymbol.cssText] = '';

/**
* Constructor.
*
* @param illegalConstructorSymbol Illegal constructor symbol.
* @param window Window.
* @param cssParser CSS parser.
*/
constructor(illegalConstructorSymbol: Symbol, window: BrowserWindow) {
constructor(illegalConstructorSymbol: Symbol, window: BrowserWindow, cssParser: CSSParser) {
if (illegalConstructorSymbol !== PropertySymbol.illegalConstructor) {
throw new TypeError('Illegal constructor');
}

this[PropertySymbol.window] = window;
this[PropertySymbol.cssParser] = cssParser;
}

/**
* Returns selector text.
* Returns parent rule.
*
* @returns Selector text.
* @returns Parent rule.
*/
public get cssText(): string {
return '';
public get parentRule(): CSSRule {
return this[PropertySymbol.parentRule];
}

/**
* Returns parent style sheet.
*
* @returns Parent style sheet.
*/
public get parentStyleSheet(): CSSStyleSheet {
return this[PropertySymbol.parentStyleSheet];
}

/**
* Returns type.
*
* @returns Type.
*/
public get type(): CSSRuleTypeEnum {
return this[PropertySymbol.type];
}

/**
* Returns CSS text.
*
* @returns CSS text.
*/
public abstract get cssText(): string;
}
5 changes: 3 additions & 2 deletions packages/happy-dom/src/css/CSSStyleSheet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,8 @@ export default class CSSStyleSheet {
);
}

const rules = CSSParser.parseFromString(this, rule);
const parser = new CSSParser(this);
const rules = parser.parseFromString(rule);

if (rules.length === 0 || rules.length > 1) {
throw new this[PropertySymbol.window].DOMException(
Expand Down Expand Up @@ -137,7 +138,7 @@ export default class CSSStyleSheet {
}
if (this.#currentText !== text) {
this.#currentText = text;
(<CSSRule[]>this.cssRules) = CSSParser.parseFromString(this, text);
(<CSSRule[]>this.cssRules) = new CSSParser(this).parseFromString(text);
}
}
}
20 changes: 20 additions & 0 deletions packages/happy-dom/src/css/rules/CSSConditionRule.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import * as PropertySymbol from '../../PropertySymbol.js';
import CSSGroupingRule from './CSSGroupingRule.js';

/**
* CSSConditionRule interface.
*
* @see https://developer.mozilla.org/en-US/docs/Web/API/CSSConditionRule
*/
export default abstract class CSSConditionRule extends CSSGroupingRule {
public [PropertySymbol.conditionText] = '';

/**
* Returns condition text.
*
* @returns Condition text.
*/
public get conditionText(): string {
return this[PropertySymbol.conditionText];
}
}
15 changes: 8 additions & 7 deletions packages/happy-dom/src/css/rules/CSSContainerRule.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import CSSRule from '../CSSRule.js';
import CSSRuleTypeEnum from '../CSSRuleTypeEnum.js';
import CSSConditionRule from './CSSConditionRule.js';
import * as PropertySymbol from '../../PropertySymbol.js';

/**
* CSSRule interface.
* CSSContainerRule interface.
*
* @see https://developer.mozilla.org/en-US/docs/Web/API/CSSContainerRule
*/
export default class CSSContainerRule extends CSSRule {
public readonly type = CSSRuleTypeEnum.containerRule;
public readonly cssRules: CSSRule[] = [];
public readonly conditionText = '';
export default class CSSContainerRule extends CSSConditionRule {
public [PropertySymbol.type] = CSSRuleTypeEnum.containerRule;

/**
* Returns css text.
Expand All @@ -16,7 +17,7 @@ export default class CSSContainerRule extends CSSRule {
*/
public get cssText(): string {
let cssText = '';
for (const cssRule of this.cssRules) {
for (const cssRule of this[PropertySymbol.cssRules]) {
cssText += cssRule.cssText;
}
return `@container ${this.conditionText} { ${cssText} }`;
Expand Down
7 changes: 4 additions & 3 deletions packages/happy-dom/src/css/rules/CSSFontFaceRule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@ import CSSStyleDeclaration from '../declaration/CSSStyleDeclaration.js';
import CSSRuleTypeEnum from '../CSSRuleTypeEnum.js';

/**
* CSSRule interface.
* CSSFontFaceRule interface.
*
* @see https://developer.mozilla.org/en-US/docs/Web/API/CSSFontFaceRule
*/
export default class CSSFontFaceRule extends CSSRule {
public readonly type = CSSRuleTypeEnum.fontFaceRule;
public [PropertySymbol.cssText] = '';
public [PropertySymbol.type] = CSSRuleTypeEnum.fontFaceRule;
#style: CSSStyleDeclaration | null = null;

/**
Expand Down
87 changes: 87 additions & 0 deletions packages/happy-dom/src/css/rules/CSSGroupingRule.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import CSSRule from '../CSSRule.js';
import DOMExceptionNameEnum from '../../exception/DOMExceptionNameEnum.js';
import * as PropertySymbol from '../../PropertySymbol.js';

/**
* CSSGroupingRule interface.
*
* @see https://developer.mozilla.org/en-US/docs/Web/API/CSSGroupingRule
*/
export default abstract class CSSGroupingRule extends CSSRule {
public [PropertySymbol.cssRules]: CSSRule[] = [];

/**
* Returns CSS rules.
*
* @returns CSS rules.
*/
public get cssRules(): CSSRule[] {
return this[PropertySymbol.cssRules];
}

/**
* Inserts a new rule.
*
* @param rule Rule.
* @param [index] Index.
* @returns The index of the new rule.
*/
public insertRule(rule: string, index?: number): number {
if (arguments.length === 0) {
throw new this[PropertySymbol.window].TypeError(
`Failed to execute 'insertRule' on '${this.constructor.name}': 1 argument required, but only 0 present.`
);
}

const rules = this[PropertySymbol.cssParser].parseFromString(rule);

if (rules.length === 0 || rules.length > 1) {
throw new this[PropertySymbol.window].DOMException(
`Failed to execute 'insertRule' on '${this.constructor.name}': Failed to parse the rule '${rule}'.`,
DOMExceptionNameEnum.syntaxError
);
}

if (index !== undefined) {
if (index > this.cssRules.length) {
throw new this[PropertySymbol.window].DOMException(
`Failed to execute 'insertRule' on '${
this.constructor.name
}': The index provided (${index}) is larger than the maximum index (${
this.cssRules.length - 1
}).`,
DOMExceptionNameEnum.indexSizeError
);
}
this.cssRules.splice(index, 0, rules[0]);
return index;
}

const newIndex = this.cssRules.length;

this.cssRules.push(rules[0]);

return newIndex;
}

/**
* Removes a rule.
*
* @param index Index.
*/
public deleteRule(index: number): void {
if (arguments.length === 0) {
throw new this[PropertySymbol.window].TypeError(
`Failed to execute 'deleteRule' on '${this.constructor.name}': 1 argument required, but only 0 present.`
);
}
index = Number(index);
if (isNaN(index) || index < 0 || index >= this.cssRules.length) {
throw new this[PropertySymbol.window].DOMException(
`Failed to execute 'deleteRule' on '${this.constructor.name}': the index (${index}) is greater than the length of the rule list.`,
DOMExceptionNameEnum.indexSizeError
);
}
this.cssRules.splice(index, 1);
}
}
18 changes: 13 additions & 5 deletions packages/happy-dom/src/css/rules/CSSKeyframeRule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,11 @@ import CSSStyleDeclaration from '../declaration/CSSStyleDeclaration.js';
import CSSRuleTypeEnum from '../CSSRuleTypeEnum.js';

/**
* CSSRule interface.
* CSSKeyframeRule interface.
*/
export default class CSSKeyframeRule extends CSSRule {
public readonly type = CSSRuleTypeEnum.keyframeRule;
public readonly keyText: string;
public [PropertySymbol.cssText] = '';
public [PropertySymbol.type] = CSSRuleTypeEnum.keyframeRule;
public [PropertySymbol.keyText] = '';
#style: CSSStyleDeclaration | null = null;

/**
Expand All @@ -35,6 +34,15 @@ export default class CSSKeyframeRule extends CSSRule {
* @returns CSS text.
*/
public get cssText(): string {
return `${this.keyText} { ${this.style.cssText} }`;
return `${this[PropertySymbol.keyText]} { ${this.style.cssText} }`;
}

/**
* Returns key text.
*
* @returns Key text.
*/
public get keyText(): string {
return this[PropertySymbol.keyText];
}
}
Loading
Loading