Skip to content

Commit

Permalink
fix: convert value to DOMString
Browse files Browse the repository at this point in the history
  • Loading branch information
cdoublev committed May 3, 2021
1 parent b527ed7 commit 090f3e0
Show file tree
Hide file tree
Showing 23 changed files with 8,484 additions and 489 deletions.
8 changes: 1 addition & 7 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ module.exports = {
},
env: {
es6: true,
node: true,
},
globals: {
exports: true,
module: true,
require: true,
window: true,
describe: true,
it: true,
test: true,
Expand Down Expand Up @@ -45,11 +45,5 @@ module.exports = {
'no-console': 'off',
},
},
{
files: ['scripts/**/*', 'tests/**/*'],
env: {
node: true,
},
},
],
};
33 changes: 25 additions & 8 deletions lib/CSSStyleDeclaration.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@
var CSSOM = require('cssom');
var allProperties = require('./allProperties');
var allExtraProperties = require('./allExtraProperties');
var implementedProperties = require('./implementedProperties');
var { dashedToCamelCase } = require('./parsers');
var { camelToDashed, dashedToCamelCase, toDOMString } = require('./parsers');
var getBasicPropertyDescriptor = require('./utils/getBasicPropertyDescriptor');
const fs = require('fs');
const path = require('path');

/**
* @constructor
Expand Down Expand Up @@ -49,10 +50,8 @@ CSSStyleDeclaration.prototype = {
* @see http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSStyleDeclaration-setProperty
*/
setProperty: function(name, value, priority) {
if (value === undefined) {
return;
}
if (value === null || value === '') {
value = toDOMString(value);
if (value === '') {
this.removeProperty(name);
return;
}
Expand All @@ -73,7 +72,7 @@ CSSStyleDeclaration.prototype = {
if (value === undefined) {
return;
}
if (value === null || value === '') {
if (value === '') {
this.removeProperty(name);
return;
}
Expand Down Expand Up @@ -239,7 +238,25 @@ Object.defineProperties(CSSStyleDeclaration.prototype, {
},
});

require('./properties')(CSSStyleDeclaration.prototype);
function createSetter(initialSetter) {
return function set(v) {
return initialSetter.bind(this)(toDOMString(v));
};
}

const implementedProperties = fs
.readdirSync(path.resolve(__dirname, 'properties'))
.reduce((props, filename) => {
const { name: camelCaseName } = path.parse(filename);
const dashedCaseName = camelToDashed(camelCaseName);
const { definition } = require(`./properties/${filename}`);
definition.set = createSetter(definition.set);
Object.defineProperty(CSSStyleDeclaration.prototype, camelCaseName, definition);
Object.defineProperty(CSSStyleDeclaration.prototype, dashedCaseName, definition);
return props.add(dashedCaseName);
}, new Set());

module.exports.implementedProperties = implementedProperties;

allProperties.forEach(function(property) {
if (!implementedProperties.has(property)) {
Expand Down
57 changes: 55 additions & 2 deletions lib/CSSStyleDeclaration.test.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
'use strict';

var { CSSStyleDeclaration } = require('./CSSStyleDeclaration');
var { CSSStyleDeclaration, implementedProperties } = require('./CSSStyleDeclaration');

var allProperties = require('./allProperties');
var allExtraProperties = require('./allExtraProperties');
var implementedProperties = require('./implementedProperties');
var parsers = require('./parsers');

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

var BigInt = BigInt || Number;

describe('CSSStyleDeclaration', () => {
test('has only valid properties implemented', () => {
expect(invalidProperties.length).toEqual(0);
Expand Down Expand Up @@ -352,6 +353,58 @@ describe('CSSStyleDeclaration', () => {
expect(style.fillOpacity).toEqual('0');
});

test('setting a property with a value that can not be converted to string should throw an error', () => {
const style = new CSSStyleDeclaration();

expect(() => (style.opacity = Symbol('0'))).toThrow('Cannot convert symbol to string');
expect(() => (style.opacity = { toString: () => [0] })).toThrow(
'Cannot convert object to primitive value'
);
});

test('setting a property with a value that can be converted to string should work', () => {
const style = new CSSStyleDeclaration();

// Property with custom setter
style.borderStyle = { toString: () => 'solid' };
expect(style.borderStyle).toEqual('solid');

// Property with default setter
style.opacity = 1;
expect(style.opacity).toBe('1');
style.opacity = { toString: () => '0' };
expect(style.opacity).toBe('0');

style.opacity = 1;
expect(style.opacity).toBe('1');
style.opacity = { toString: () => 0 };
expect(style.opacity).toEqual('0');

style.opacity = BigInt(1);
expect(style.opacity).toBe('1');
style.opacity = { toString: () => BigInt(0) };
expect(style.opacity).toEqual('0');

style.setProperty('--custom', [1]);
expect(style.getPropertyValue('--custom')).toEqual('1');

style.setProperty('--custom', null);
expect(style.getPropertyValue('--custom')).toBe('');
style.setProperty('--custom', { toString: () => null });
expect(style.getPropertyValue('--custom')).toBe('null');

style.setProperty('--custom', undefined);
expect(style.getPropertyValue('--custom')).toBe('undefined');
style.setProperty('--custom', null);
style.setProperty('--custom', { toString: () => undefined });
expect(style.getPropertyValue('--custom')).toBe('undefined');

style.setProperty('--custom', false);
expect(style.getPropertyValue('--custom')).toBe('false');
style.setProperty('--custom', { toString: () => true });
expect(style.getPropertyValue('--custom')).toBe('true');
});

test('onchange callback should be called when the csstext changes', () => {
var style = new CSSStyleDeclaration(function(cssText) {
expect(cssText).toEqual('opacity: 0;');
Expand Down
71 changes: 28 additions & 43 deletions lib/parsers.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ exports.TYPES = {
STRING: 7,
ANGLE: 8,
KEYWORD: 9,
NULL_OR_EMPTY_STR: 10,
EMPTY: 10,
CALC: 11,
};

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

// This will return one of the above types based on the passed in string
exports.valueType = function valueType(val) {
if (val === '' || val === null) {
return exports.TYPES.NULL_OR_EMPTY_STR;
// https://heycam.github.io/webidl/#es-DOMString
exports.toDOMString = function toDOMString(val) {
if (val === null) {
return '';
}
if (typeof val === 'number') {
val = val.toString();
if (typeof val === 'string') {
return val;
}

if (typeof val !== 'string') {
return undefined;
if (typeof val === 'symbol') {
throw Error('Cannot convert symbol to string');
}
return String(val);
};

// This will return one of the above types based on the passed in string
exports.valueType = function valueType(val) {
if (val === '') {
return exports.TYPES.EMPTY;
}
if (integerRegEx.test(val)) {
return exports.TYPES.INTEGER;
}
Expand Down Expand Up @@ -157,7 +163,7 @@ exports.valueType = function valueType(val) {

exports.parseInteger = function parseInteger(val) {
var type = exports.valueType(val);
if (type === exports.TYPES.NULL_OR_EMPTY_STR) {
if (type === exports.TYPES.EMPTY) {
return val;
}
if (type !== exports.TYPES.INTEGER) {
Expand All @@ -168,7 +174,7 @@ exports.parseInteger = function parseInteger(val) {

exports.parseNumber = function parseNumber(val) {
var type = exports.valueType(val);
if (type === exports.TYPES.NULL_OR_EMPTY_STR) {
if (type === exports.TYPES.EMPTY) {
return val;
}
if (type !== exports.TYPES.NUMBER && type !== exports.TYPES.INTEGER) {
Expand All @@ -178,11 +184,11 @@ exports.parseNumber = function parseNumber(val) {
};

exports.parseLength = function parseLength(val) {
if (val === 0 || val === '0') {
if (val === '0') {
return '0px';
}
var type = exports.valueType(val);
if (type === exports.TYPES.NULL_OR_EMPTY_STR) {
if (type === exports.TYPES.EMPTY) {
return val;
}
if (type !== exports.TYPES.LENGTH) {
Expand All @@ -192,11 +198,11 @@ exports.parseLength = function parseLength(val) {
};

exports.parsePercent = function parsePercent(val) {
if (val === 0 || val === '0') {
if (val === '0') {
return '0%';
}
var type = exports.valueType(val);
if (type === exports.TYPES.NULL_OR_EMPTY_STR) {
if (type === exports.TYPES.EMPTY) {
return val;
}
if (type !== exports.TYPES.PERCENT) {
Expand All @@ -221,7 +227,7 @@ exports.parseMeasurement = function parseMeasurement(val) {

exports.parseUrl = function parseUrl(val) {
var type = exports.valueType(val);
if (type === exports.TYPES.NULL_OR_EMPTY_STR) {
if (type === exports.TYPES.EMPTY) {
return val;
}
var res = urlRegEx.exec(val);
Expand Down Expand Up @@ -260,7 +266,7 @@ exports.parseUrl = function parseUrl(val) {

exports.parseString = function parseString(val) {
var type = exports.valueType(val);
if (type === exports.TYPES.NULL_OR_EMPTY_STR) {
if (type === exports.TYPES.EMPTY) {
return val;
}
if (type !== exports.TYPES.STRING) {
Expand All @@ -287,7 +293,7 @@ exports.parseString = function parseString(val) {

exports.parseColor = function parseColor(val) {
var type = exports.valueType(val);
if (type === exports.TYPES.NULL_OR_EMPTY_STR) {
if (type === exports.TYPES.EMPTY) {
return val;
}
var red,
Expand Down Expand Up @@ -406,7 +412,7 @@ exports.parseColor = function parseColor(val) {

exports.parseAngle = function parseAngle(val) {
var type = exports.valueType(val);
if (type === exports.TYPES.NULL_OR_EMPTY_STR) {
if (type === exports.TYPES.EMPTY) {
return val;
}
if (type !== exports.TYPES.ANGLE) {
Expand All @@ -431,7 +437,7 @@ exports.parseAngle = function parseAngle(val) {

exports.parseKeyword = function parseKeyword(val, valid_keywords) {
var type = exports.valueType(val);
if (type === exports.TYPES.NULL_OR_EMPTY_STR) {
if (type === exports.TYPES.EMPTY) {
return val;
}
if (type !== exports.TYPES.KEYWORD) {
Expand Down Expand Up @@ -520,21 +526,12 @@ var getParts = function(str) {
exports.shorthandParser = function parse(v, shorthand_for) {
var obj = {};
var type = exports.valueType(v);
if (type === exports.TYPES.NULL_OR_EMPTY_STR) {
if (type === exports.TYPES.EMPTY) {
Object.keys(shorthand_for).forEach(function(property) {
obj[property] = '';
});
return obj;
}

if (typeof v === 'number') {
v = v.toString();
}

if (typeof v !== 'string') {
return undefined;
}

if (v.toLowerCase() === 'inherit') {
return {};
}
Expand Down Expand Up @@ -623,12 +620,6 @@ exports.implicitSetter = function(property_before, property_after, isValid, pars
var part_names = ['top', 'right', 'bottom', 'left'];

return function(v) {
if (typeof v === 'number') {
v = v.toString();
}
if (typeof v !== 'string') {
return undefined;
}
var parts;
if (v.toLowerCase() === 'inherit' || v === '') {
parts = [v];
Expand Down Expand Up @@ -679,12 +670,6 @@ exports.subImplicitSetter = function(prefix, part, isValid, parser) {
var subparts = [prefix + '-top', prefix + '-right', prefix + '-bottom', prefix + '-left'];

return function(v) {
if (typeof v === 'number') {
v = v.toString();
}
if (typeof v !== 'string') {
return undefined;
}
if (!isValid(v)) {
return undefined;
}
Expand Down
2 changes: 1 addition & 1 deletion lib/properties/backgroundPosition.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ var parsers = require('../parsers');
var valid_keywords = ['top', 'center', 'bottom', 'left', 'right'];

var parse = function parse(v) {
if (v === '' || v === null) {
if (v === '') {
return undefined;
}
var parts = v.split(/\s+/);
Expand Down
3 changes: 0 additions & 3 deletions lib/properties/borderColor.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,6 @@ var parsers = require('../parsers');
var implicitSetter = require('../parsers').implicitSetter;

module.exports.isValid = function parse(v) {
if (typeof v !== 'string') {
return false;
}
return (
v === '' || v.toLowerCase() === 'transparent' || parsers.valueType(v) === parsers.TYPES.COLOR
);
Expand Down
4 changes: 2 additions & 2 deletions lib/properties/borderSpacing.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ var parsers = require('../parsers');
// if two, the first applies to the horizontal and the second applies to vertical spacing

var parse = function parse(v) {
if (v === '' || v === null) {
if (v === '') {
return undefined;
}
if (v === 0) {
if (v === '0') {
return '0px';
}
if (v.toLowerCase() === 'inherit') {
Expand Down
2 changes: 1 addition & 1 deletion lib/properties/borderStyle.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ var styles = [
];

module.exports.isValid = function parse(v) {
return typeof v === 'string' && (v === '' || styles.indexOf(v) !== -1);
return v === '' || styles.indexOf(v) !== -1;
};
var isValid = module.exports.isValid;

Expand Down
Loading

0 comments on commit 090f3e0

Please sign in to comment.