Skip to content

Commit 3d16e3b

Browse files
committed
fix: convert value to DOMString
1 parent b527ed7 commit 3d16e3b

10 files changed

+8469
-418
lines changed

.eslintrc.js

+1-7
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,12 @@ module.exports = {
88
},
99
env: {
1010
es6: true,
11+
node: true,
1112
},
1213
globals: {
1314
exports: true,
1415
module: true,
1516
require: true,
16-
window: true,
1717
describe: true,
1818
it: true,
1919
test: true,
@@ -45,11 +45,5 @@ module.exports = {
4545
'no-console': 'off',
4646
},
4747
},
48-
{
49-
files: ['scripts/**/*', 'tests/**/*'],
50-
env: {
51-
node: true,
52-
},
53-
},
5448
],
5549
};

lib/CSSStyleDeclaration.js

+25-8
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@
66
var CSSOM = require('cssom');
77
var allProperties = require('./allProperties');
88
var allExtraProperties = require('./allExtraProperties');
9-
var implementedProperties = require('./implementedProperties');
10-
var { dashedToCamelCase } = require('./parsers');
9+
var { camelToDashed, dashedToCamelCase, toDOMString } = require('./parsers');
1110
var getBasicPropertyDescriptor = require('./utils/getBasicPropertyDescriptor');
11+
const fs = require('fs');
12+
const path = require('path');
1213

1314
/**
1415
* @constructor
@@ -49,10 +50,8 @@ CSSStyleDeclaration.prototype = {
4950
* @see http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSStyleDeclaration-setProperty
5051
*/
5152
setProperty: function(name, value, priority) {
52-
if (value === undefined) {
53-
return;
54-
}
55-
if (value === null || value === '') {
53+
value = toDOMString(value);
54+
if (value === '') {
5655
this.removeProperty(name);
5756
return;
5857
}
@@ -73,7 +72,7 @@ CSSStyleDeclaration.prototype = {
7372
if (value === undefined) {
7473
return;
7574
}
76-
if (value === null || value === '') {
75+
if (value === '') {
7776
this.removeProperty(name);
7877
return;
7978
}
@@ -239,7 +238,25 @@ Object.defineProperties(CSSStyleDeclaration.prototype, {
239238
},
240239
});
241240

242-
require('./properties')(CSSStyleDeclaration.prototype);
241+
function createSetter(initialSetter) {
242+
return function set(v) {
243+
return initialSetter.bind(this)(toDOMString(v));
244+
};
245+
}
246+
247+
const implementedProperties = fs
248+
.readdirSync(path.resolve(__dirname, 'properties'))
249+
.reduce((props, filename) => {
250+
const { name: camelCaseName } = path.parse(filename);
251+
const dashedCaseName = camelToDashed(camelCaseName);
252+
const { definition } = require(`./properties/${filename}`);
253+
definition.set = createSetter(definition.set);
254+
Object.defineProperty(CSSStyleDeclaration.prototype, camelCaseName, definition);
255+
Object.defineProperty(CSSStyleDeclaration.prototype, dashedCaseName, definition);
256+
return props.add(dashedCaseName);
257+
}, new Set());
258+
259+
module.exports.implementedProperties = implementedProperties;
243260

244261
allProperties.forEach(function(property) {
245262
if (!implementedProperties.has(property)) {

lib/CSSStyleDeclaration.test.js

+55-2
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,18 @@
11
'use strict';
22

3-
var { CSSStyleDeclaration } = require('./CSSStyleDeclaration');
3+
var { CSSStyleDeclaration, implementedProperties } = require('./CSSStyleDeclaration');
44

55
var allProperties = require('./allProperties');
66
var allExtraProperties = require('./allExtraProperties');
7-
var implementedProperties = require('./implementedProperties');
87
var parsers = require('./parsers');
98

109
var dashedProperties = [...allProperties, ...allExtraProperties];
1110
var allowedProperties = dashedProperties.map(parsers.dashedToCamelCase);
1211
implementedProperties = Array.from(implementedProperties).map(parsers.dashedToCamelCase);
1312
var invalidProperties = implementedProperties.filter(prop => !allowedProperties.includes(prop));
1413

14+
var BigInt = BigInt || Number;
15+
1516
describe('CSSStyleDeclaration', () => {
1617
test('has only valid properties implemented', () => {
1718
expect(invalidProperties.length).toEqual(0);
@@ -352,6 +353,58 @@ describe('CSSStyleDeclaration', () => {
352353
expect(style.fillOpacity).toEqual('0');
353354
});
354355

356+
test('setting a property with a value that can not be converted to string should throw an error', () => {
357+
const style = new CSSStyleDeclaration();
358+
359+
expect(() => (style.opacity = Symbol('0'))).toThrow('Cannot convert symbol to string');
360+
expect(() => (style.opacity = { toString: () => [0] })).toThrow(
361+
'Cannot convert object to primitive value'
362+
);
363+
});
364+
365+
test('setting a property with a value that can be converted to string should work', () => {
366+
const style = new CSSStyleDeclaration();
367+
368+
// Property with custom setter
369+
style.borderStyle = { toString: () => 'solid' };
370+
expect(style.borderStyle).toEqual('solid');
371+
372+
// Property with default setter
373+
style.opacity = 1;
374+
expect(style.opacity).toBe('1');
375+
style.opacity = { toString: () => '0' };
376+
expect(style.opacity).toBe('0');
377+
378+
style.opacity = 1;
379+
expect(style.opacity).toBe('1');
380+
style.opacity = { toString: () => 0 };
381+
expect(style.opacity).toEqual('0');
382+
383+
style.opacity = BigInt(1);
384+
expect(style.opacity).toBe('1');
385+
style.opacity = { toString: () => BigInt(0) };
386+
expect(style.opacity).toEqual('0');
387+
388+
style.setProperty('--custom', [1]);
389+
expect(style.getPropertyValue('--custom')).toEqual('1');
390+
391+
style.setProperty('--custom', null);
392+
expect(style.getPropertyValue('--custom')).toBe('');
393+
style.setProperty('--custom', { toString: () => null });
394+
expect(style.getPropertyValue('--custom')).toBe('null');
395+
396+
style.setProperty('--custom', undefined);
397+
expect(style.getPropertyValue('--custom')).toBe('undefined');
398+
style.setProperty('--custom', null);
399+
style.setProperty('--custom', { toString: () => undefined });
400+
expect(style.getPropertyValue('--custom')).toBe('undefined');
401+
402+
style.setProperty('--custom', false);
403+
expect(style.getPropertyValue('--custom')).toBe('false');
404+
style.setProperty('--custom', { toString: () => true });
405+
expect(style.getPropertyValue('--custom')).toBe('true');
406+
});
407+
355408
test('onchange callback should be called when the csstext changes', () => {
356409
var style = new CSSStyleDeclaration(function(cssText) {
357410
expect(cssText).toEqual('opacity: 0;');

lib/parsers.js

+26-20
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ exports.TYPES = {
1717
STRING: 7,
1818
ANGLE: 8,
1919
KEYWORD: 9,
20-
NULL_OR_EMPTY_STR: 10,
20+
EMPTY: 10,
2121
CALC: 11,
2222
};
2323

@@ -35,19 +35,25 @@ var calcRegEx = /^calc\(([^)]*)\)$/;
3535
var colorRegEx4 = /^hsla?\(\s*(-?\d+|-?\d*.\d+)\s*,\s*(-?\d+|-?\d*.\d+)%\s*,\s*(-?\d+|-?\d*.\d+)%\s*(,\s*(-?\d+|-?\d*.\d+)\s*)?\)/;
3636
var angleRegEx = /^([-+]?[0-9]*\.?[0-9]+)(deg|grad|rad)$/;
3737

38-
// This will return one of the above types based on the passed in string
39-
exports.valueType = function valueType(val) {
40-
if (val === '' || val === null) {
41-
return exports.TYPES.NULL_OR_EMPTY_STR;
38+
// https://heycam.github.io/webidl/#es-DOMString
39+
exports.toDOMString = function toDOMString(val) {
40+
if (val === null) {
41+
return '';
4242
}
43-
if (typeof val === 'number') {
44-
val = val.toString();
43+
if (typeof val === 'string') {
44+
return val;
4545
}
46-
47-
if (typeof val !== 'string') {
48-
return undefined;
46+
if (typeof val === 'symbol') {
47+
throw Error('Cannot convert symbol to string');
4948
}
49+
return String(val);
50+
};
5051

52+
// This will return one of the above types based on the passed in string
53+
exports.valueType = function valueType(val) {
54+
if (val === '') {
55+
return exports.TYPES.EMPTY;
56+
}
5157
if (integerRegEx.test(val)) {
5258
return exports.TYPES.INTEGER;
5359
}
@@ -157,7 +163,7 @@ exports.valueType = function valueType(val) {
157163

158164
exports.parseInteger = function parseInteger(val) {
159165
var type = exports.valueType(val);
160-
if (type === exports.TYPES.NULL_OR_EMPTY_STR) {
166+
if (type === exports.TYPES.EMPTY) {
161167
return val;
162168
}
163169
if (type !== exports.TYPES.INTEGER) {
@@ -168,7 +174,7 @@ exports.parseInteger = function parseInteger(val) {
168174

169175
exports.parseNumber = function parseNumber(val) {
170176
var type = exports.valueType(val);
171-
if (type === exports.TYPES.NULL_OR_EMPTY_STR) {
177+
if (type === exports.TYPES.EMPTY) {
172178
return val;
173179
}
174180
if (type !== exports.TYPES.NUMBER && type !== exports.TYPES.INTEGER) {
@@ -182,7 +188,7 @@ exports.parseLength = function parseLength(val) {
182188
return '0px';
183189
}
184190
var type = exports.valueType(val);
185-
if (type === exports.TYPES.NULL_OR_EMPTY_STR) {
191+
if (type === exports.TYPES.EMPTY) {
186192
return val;
187193
}
188194
if (type !== exports.TYPES.LENGTH) {
@@ -196,7 +202,7 @@ exports.parsePercent = function parsePercent(val) {
196202
return '0%';
197203
}
198204
var type = exports.valueType(val);
199-
if (type === exports.TYPES.NULL_OR_EMPTY_STR) {
205+
if (type === exports.TYPES.EMPTY) {
200206
return val;
201207
}
202208
if (type !== exports.TYPES.PERCENT) {
@@ -221,7 +227,7 @@ exports.parseMeasurement = function parseMeasurement(val) {
221227

222228
exports.parseUrl = function parseUrl(val) {
223229
var type = exports.valueType(val);
224-
if (type === exports.TYPES.NULL_OR_EMPTY_STR) {
230+
if (type === exports.TYPES.EMPTY) {
225231
return val;
226232
}
227233
var res = urlRegEx.exec(val);
@@ -260,7 +266,7 @@ exports.parseUrl = function parseUrl(val) {
260266

261267
exports.parseString = function parseString(val) {
262268
var type = exports.valueType(val);
263-
if (type === exports.TYPES.NULL_OR_EMPTY_STR) {
269+
if (type === exports.TYPES.EMPTY) {
264270
return val;
265271
}
266272
if (type !== exports.TYPES.STRING) {
@@ -287,7 +293,7 @@ exports.parseString = function parseString(val) {
287293

288294
exports.parseColor = function parseColor(val) {
289295
var type = exports.valueType(val);
290-
if (type === exports.TYPES.NULL_OR_EMPTY_STR) {
296+
if (type === exports.TYPES.EMPTY) {
291297
return val;
292298
}
293299
var red,
@@ -406,7 +412,7 @@ exports.parseColor = function parseColor(val) {
406412

407413
exports.parseAngle = function parseAngle(val) {
408414
var type = exports.valueType(val);
409-
if (type === exports.TYPES.NULL_OR_EMPTY_STR) {
415+
if (type === exports.TYPES.EMPTY) {
410416
return val;
411417
}
412418
if (type !== exports.TYPES.ANGLE) {
@@ -431,7 +437,7 @@ exports.parseAngle = function parseAngle(val) {
431437

432438
exports.parseKeyword = function parseKeyword(val, valid_keywords) {
433439
var type = exports.valueType(val);
434-
if (type === exports.TYPES.NULL_OR_EMPTY_STR) {
440+
if (type === exports.TYPES.EMPTY) {
435441
return val;
436442
}
437443
if (type !== exports.TYPES.KEYWORD) {
@@ -520,7 +526,7 @@ var getParts = function(str) {
520526
exports.shorthandParser = function parse(v, shorthand_for) {
521527
var obj = {};
522528
var type = exports.valueType(v);
523-
if (type === exports.TYPES.NULL_OR_EMPTY_STR) {
529+
if (type === exports.TYPES.EMPTY) {
524530
Object.keys(shorthand_for).forEach(function(property) {
525531
obj[property] = '';
526532
});

lib/properties/borderSpacing.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ var parse = function parse(v) {
1010
if (v === '' || v === null) {
1111
return undefined;
1212
}
13-
if (v === 0) {
13+
if (v === '0') {
1414
return '0px';
1515
}
1616
if (v.toLowerCase() === 'inherit') {

lib/utils/getBasicPropertyDescriptor.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
'use strict';
22

3+
const { toDOMString } = require('../parsers');
4+
35
module.exports = function getBasicPropertyDescriptor(name) {
46
return {
57
set: function(v) {
6-
this._setProperty(name, v);
8+
this._setProperty(name, toDOMString(v));
79
},
810
get: function() {
911
return this.getPropertyValue(name);

0 commit comments

Comments
 (0)