Skip to content
Closed
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
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