Skip to content

Commit 66283a6

Browse files
ExE-BossTimothyGu
andcommitted
Migrate CSSStyleDeclaration to WebIDL2JS
Co-authored-by: Timothy Gu <[email protected]>
1 parent b527ed7 commit 66283a6

22 files changed

+431
-140
lines changed

.eslintignore

+4
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11
node_modules
2+
lib/CSSStyleDeclaration.js
3+
lib/Function.js
4+
lib/VoidFunction.js
25
lib/implementedProperties.js
36
lib/properties.js
7+
lib/utils.js
48
jest.config.js

.gitignore

+5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
node_modules
22
npm-debug.log
3+
lib/CSSStyleDeclaration.js
4+
lib/Function.js
5+
lib/VoidFunction.js
36
lib/implementedProperties.js
47
lib/properties.js
8+
lib/utils.js
59
coverage
10+
src/CSSStyleDeclaration-properties.webidl

.npmignore

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
/*
22
!lib/
3+
lib/Function.js
4+
lib/VoidFunction.js
35
!LICENSE

.travis.yml

-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ install:
99
- npm install
1010
- npm install -g codecov
1111
node_js:
12-
- "8"
1312
- "10"
1413
- "12"
1514

README.md

+40-5
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,47 @@ A Node JS implementation of the CSS Object Model [CSSStyleDeclaration interface]
44

55
[![NpmVersion](https://img.shields.io/npm/v/cssstyle.svg)](https://www.npmjs.com/package/cssstyle) [![Build Status](https://travis-ci.org/jsdom/cssstyle.svg?branch=master)](https://travis-ci.org/jsdom/cssstyle) [![codecov](https://codecov.io/gh/jsdom/cssstyle/branch/master/graph/badge.svg)](https://codecov.io/gh/jsdom/cssstyle)
66

7-
---
7+
## Background
88

9-
#### Background
9+
This package is an extension of the CSSStyleDeclaration class in Nikita Vasilyev's [CSSOM](https://github.com/NV/CSSOM) with added support for CSS 2 & 3 properties. The primary use case is for testing browser code in a Node environment.
1010

11-
This package is an extension of the CSSStyleDeclaration class in Nikita Vasilyev's [CSSOM](https://github.com/NV/CSSOM) with added support for CSS 2 & 3 properties. The primary use case is for testing browser code in a Node environment.
12-
13-
It was originally created by Chad Walker, it is now maintained by the jsdom community.
11+
It was originally created by Chad Walker, it is now maintained by Jon Sakas and other open source contributors.
1412

1513
Bug reports and pull requests are welcome.
14+
15+
## APIs
16+
17+
This package exposes two flavors of the `CSSStyleDeclaration` interface depending on the imported module.
18+
19+
### `cssstyle` module
20+
21+
This module default-exports the `CSSStyleDeclaration` interface constructor, with the change that it can be constructed with an optional `onChangeCallback` parameter. Whenever any CSS property is modified through an instance of this class, the callback (if provided) will be called with a string that represents all CSS properties of this element, serialized. This allows the embedding environment to properly reflect the style changes to an element's `style` attribute.
22+
23+
Here is a crude example of using the `onChangeCallback` to implement the `style` property of `HTMLElement`:
24+
```js
25+
const CSSStyleDeclaration = require('cssstyle');
26+
27+
class HTMLElement extends Element {
28+
constructor() {
29+
this._style = new CSSStyleDeclaration(newCSSText => {
30+
this.setAttributeNS(null, "style", newCSSText);
31+
});
32+
}
33+
34+
get style() {
35+
return this._style;
36+
}
37+
38+
set style(text) {
39+
this._style.cssText = text;
40+
}
41+
}
42+
```
43+
44+
### `cssstyle/webidl2js-wrapper` module
45+
46+
This module exports the `CSSStyleDeclaration` [interface wrapper API](https://github.com/jsdom/webidl2js#for-interfaces) generated by [webidl2js](https://github.com/jsdom/webidl2js). Unlike the default export, `CSSStyleDeclaration` constructors installed by the webidl2js wrapper do _not_ support construction, just like how they actually are in browsers. Creating new `CSSStyleDeclaration` objects can be done with the [`create`](https://github.com/jsdom/webidl2js#createglobalobject-constructorargs-privatedata) method of the wrapper.
47+
48+
#### `privateData`
49+
50+
The `privateData` parameter of `create` and `createImpl` provides a way to specify the `onChangeCallback` that is a constructor parameter in the default export. Only the `onChangeCallback` property is supported on `privateData` currently, with the same semantics as the constructor parameter documented above.

index.js

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
'use strict';
2+
const webidlWrapper = require('./webidl2js-wrapper.js');
3+
4+
const sharedGlobalObject = {};
5+
webidlWrapper.install(sharedGlobalObject, ['Window']);
6+
7+
const origCSSStyleDeclaration = sharedGlobalObject.CSSStyleDeclaration;
8+
9+
/**
10+
* @constructor
11+
* @param {((cssText: string) => void) | null} [onChangeCallback]
12+
* The callback that is invoked whenever a property changes.
13+
*/
14+
function CSSStyleDeclaration(onChangeCallback = null) {
15+
if (new.target === undefined) {
16+
throw new TypeError("Class constructor CSSStyleDeclaration cannot be invoked without 'new'");
17+
}
18+
19+
if (onChangeCallback !== null && typeof onChangeCallback !== 'function') {
20+
throw new TypeError('Failed to construct CSSStyleDeclaration: parameter 1 is not a function');
21+
}
22+
23+
return webidlWrapper.create(sharedGlobalObject, undefined, { onChangeCallback });
24+
}
25+
26+
sharedGlobalObject.CSSStyleDeclaration = CSSStyleDeclaration;
27+
Object.defineProperty(CSSStyleDeclaration, 'prototype', {
28+
value: origCSSStyleDeclaration.prototype,
29+
writable: false,
30+
});
31+
CSSStyleDeclaration.prototype.constructor = CSSStyleDeclaration;
32+
Object.setPrototypeOf(CSSStyleDeclaration, Object.getPrototypeOf(origCSSStyleDeclaration));
33+
34+
module.exports = CSSStyleDeclaration;

jest.config.js

+2
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@ module.exports = {
33
"collectCoverage": true,
44
"collectCoverageFrom": [
55
"lib/**/*.js",
6+
"!lib/CSSStyleDeclaration.js",
67
"!lib/implementedProperties.js",
78
"!lib/properties.js",
9+
"!lib/utils.js",
810
],
911
"coverageDirectory": "coverage",
1012
};

lib/CSSStyleDeclaration.js renamed to lib/CSSStyleDeclaration-impl.js

+78-77
Original file line numberDiff line numberDiff line change
@@ -7,25 +7,28 @@ var CSSOM = require('cssom');
77
var allProperties = require('./allProperties');
88
var allExtraProperties = require('./allExtraProperties');
99
var implementedProperties = require('./implementedProperties');
10-
var { dashedToCamelCase } = require('./parsers');
10+
var { cssPropertyToIDLAttribute } = require('./parsers');
1111
var getBasicPropertyDescriptor = require('./utils/getBasicPropertyDescriptor');
12+
const idlUtils = require('./utils.js');
1213

13-
/**
14-
* @constructor
15-
* @see http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSStyleDeclaration
16-
*/
17-
var CSSStyleDeclaration = function CSSStyleDeclaration(onChangeCallback) {
18-
this._values = {};
19-
this._importants = {};
20-
this._length = 0;
21-
this._onChange =
22-
onChangeCallback ||
23-
function() {
24-
return;
25-
};
26-
};
27-
CSSStyleDeclaration.prototype = {
28-
constructor: CSSStyleDeclaration,
14+
class CSSStyleDeclarationImpl {
15+
/**
16+
* @constructor
17+
* @see http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSStyleDeclaration
18+
*
19+
* @param {object} globalObject
20+
* @param {*[]} args
21+
* @param {object} privateData
22+
* @param {((cssText: string) => void) | null} [privateData.onChangeCallback]
23+
*/
24+
constructor(globalObject, args, { onChangeCallback }) {
25+
this._globalObject = globalObject;
26+
this._values = Object.create(null);
27+
this._importants = Object.create(null);
28+
this._length = 0;
29+
this._onChange = onChangeCallback || (() => {});
30+
this.parentRule = null;
31+
}
2932

3033
/**
3134
*
@@ -34,25 +37,19 @@ CSSStyleDeclaration.prototype = {
3437
* @return {string} the value of the property if it has been explicitly set for this declaration block.
3538
* Returns the empty string if the property has not been set.
3639
*/
37-
getPropertyValue: function(name) {
38-
if (!this._values.hasOwnProperty(name)) {
39-
return '';
40-
}
41-
return this._values[name].toString();
42-
},
40+
getPropertyValue(name) {
41+
return this._values[name] || '';
42+
}
4343

4444
/**
4545
*
4646
* @param {string} name
4747
* @param {string} value
48-
* @param {string} [priority=null] "important" or null
48+
* @param {string} [priority=""] "important" or ""
4949
* @see http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSStyleDeclaration-setProperty
5050
*/
51-
setProperty: function(name, value, priority) {
52-
if (value === undefined) {
53-
return;
54-
}
55-
if (value === null || value === '') {
51+
setProperty(name, value, priority = '') {
52+
if (value === '') {
5653
this.removeProperty(name);
5754
return;
5855
}
@@ -68,8 +65,16 @@ CSSStyleDeclaration.prototype = {
6865

6966
this[lowercaseName] = value;
7067
this._importants[lowercaseName] = priority;
71-
},
72-
_setProperty: function(name, value, priority) {
68+
}
69+
70+
/**
71+
* @param {string} name
72+
* @param {string | null} value
73+
* @param {string} [priority=""]
74+
*/
75+
_setProperty(name, value, priority = '') {
76+
// FIXME: A good chunk of the implemented properties call this method
77+
// with `value = undefined`, expecting it to do nothing:
7378
if (value === undefined) {
7479
return;
7580
}
@@ -92,7 +97,7 @@ CSSStyleDeclaration.prototype = {
9297
this._values[name] = value;
9398
this._importants[name] = priority;
9499
this._onChange(this.cssText);
95-
},
100+
}
96101

97102
/**
98103
*
@@ -101,8 +106,8 @@ CSSStyleDeclaration.prototype = {
101106
* @return {string} the value of the property if it has been explicitly set for this declaration block.
102107
* Returns the empty string if the property has not been set or the property name does not correspond to a known CSS property.
103108
*/
104-
removeProperty: function(name) {
105-
if (!this._values.hasOwnProperty(name)) {
109+
removeProperty(name) {
110+
if (!idlUtils.hasOwn(this._values, name)) {
106111
return '';
107112
}
108113

@@ -123,49 +128,36 @@ CSSStyleDeclaration.prototype = {
123128

124129
this._onChange(this.cssText);
125130
return prevValue;
126-
},
131+
}
127132

128133
/**
129134
*
130135
* @param {String} name
131136
*/
132-
getPropertyPriority: function(name) {
137+
getPropertyPriority(name) {
133138
return this._importants[name] || '';
134-
},
135-
136-
getPropertyCSSValue: function() {
137-
//FIXME
138-
return;
139-
},
140-
141-
/**
142-
* element.style.overflow = "auto"
143-
* element.style.getPropertyShorthand("overflow-x")
144-
* -> "overflow"
145-
*/
146-
getPropertyShorthand: function() {
147-
//FIXME
148-
return;
149-
},
150-
151-
isPropertyImplicit: function() {
152-
//FIXME
153-
return;
154-
},
139+
}
155140

156141
/**
157142
* http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSStyleDeclaration-item
158143
*/
159-
item: function(index) {
160-
index = parseInt(index, 10);
144+
item(index) {
161145
if (index < 0 || index >= this._length) {
162146
return '';
163147
}
164148
return this[index];
165-
},
166-
};
149+
}
150+
151+
[idlUtils.supportsPropertyIndex](index) {
152+
return index >= 0 && index < this._length;
153+
}
167154

168-
Object.defineProperties(CSSStyleDeclaration.prototype, {
155+
[idlUtils.supportedPropertyIndices]() {
156+
return Array.prototype.keys.call(this);
157+
}
158+
}
159+
160+
Object.defineProperties(CSSStyleDeclarationImpl.prototype, {
169161
cssText: {
170162
get: function() {
171163
var properties = [];
@@ -178,9 +170,9 @@ Object.defineProperties(CSSStyleDeclaration.prototype, {
178170
value = this.getPropertyValue(name);
179171
priority = this.getPropertyPriority(name);
180172
if (priority !== '') {
181-
priority = ' !' + priority;
173+
priority = ` !${priority}`;
182174
}
183-
properties.push([name, ': ', value, priority, ';'].join(''));
175+
properties.push(`${name}: ${value}${priority};`);
184176
}
185177
return properties.join(' ');
186178
},
@@ -211,13 +203,6 @@ Object.defineProperties(CSSStyleDeclaration.prototype, {
211203
enumerable: true,
212204
configurable: true,
213205
},
214-
parentRule: {
215-
get: function() {
216-
return null;
217-
},
218-
enumerable: true,
219-
configurable: true,
220-
},
221206
length: {
222207
get: function() {
223208
return this._length;
@@ -239,22 +224,38 @@ Object.defineProperties(CSSStyleDeclaration.prototype, {
239224
},
240225
});
241226

242-
require('./properties')(CSSStyleDeclaration.prototype);
227+
require('./properties')(CSSStyleDeclarationImpl.prototype);
243228

229+
// TODO: Consider using `[Reflect]` for basic properties
244230
allProperties.forEach(function(property) {
245231
if (!implementedProperties.has(property)) {
246232
var declaration = getBasicPropertyDescriptor(property);
247-
Object.defineProperty(CSSStyleDeclaration.prototype, property, declaration);
248-
Object.defineProperty(CSSStyleDeclaration.prototype, dashedToCamelCase(property), declaration);
233+
Object.defineProperty(CSSStyleDeclarationImpl.prototype, property, declaration);
234+
Object.defineProperty(
235+
CSSStyleDeclarationImpl.prototype,
236+
cssPropertyToIDLAttribute(property),
237+
declaration
238+
);
249239
}
250240
});
251241

252242
allExtraProperties.forEach(function(property) {
253243
if (!implementedProperties.has(property)) {
254244
var declaration = getBasicPropertyDescriptor(property);
255-
Object.defineProperty(CSSStyleDeclaration.prototype, property, declaration);
256-
Object.defineProperty(CSSStyleDeclaration.prototype, dashedToCamelCase(property), declaration);
245+
Object.defineProperty(CSSStyleDeclarationImpl.prototype, property, declaration);
246+
Object.defineProperty(
247+
CSSStyleDeclarationImpl.prototype,
248+
cssPropertyToIDLAttribute(property),
249+
declaration
250+
);
251+
if (property.startsWith('-webkit-')) {
252+
Object.defineProperty(
253+
CSSStyleDeclarationImpl.prototype,
254+
cssPropertyToIDLAttribute(property, /* lowercaseFirst = */ true),
255+
declaration
256+
);
257+
}
257258
}
258259
});
259260

260-
exports.CSSStyleDeclaration = CSSStyleDeclaration;
261+
exports.implementation = CSSStyleDeclarationImpl;

0 commit comments

Comments
 (0)