From ad0f846b846ca1bf9fcd7ca66a247d3912e33dec Mon Sep 17 00:00:00 2001 From: ExE Boss <3889017+ExE-Boss@users.noreply.github.com> Date: Wed, 1 Apr 2020 18:30:00 +0200 Subject: [PATCH 1/5] =?UTF-8?q?Migrate=C2=A0`CSSStyleDeclaration`=20to?= =?UTF-8?q?=C2=A0WebIDL2JS?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Timothy Gu --- .eslintignore | 4 + .gitignore | 5 + .npmignore | 2 + .travis.yml | 1 - README.md | 45 ++++- index.js | 34 ++++ jest.config.js | 2 + ...aration.js => CSSStyleDeclaration-impl.js} | 155 +++++++++--------- lib/CSSStyleDeclaration.test.js | 14 +- lib/allExtraProperties.js | 1 - lib/allWebkitProperties.js | 2 +- lib/parsers.js | 63 ++++--- lib/parsers.test.js | 4 +- lib/properties/borderSpacing.js | 2 +- package-lock.json | 80 ++++++++- package.json | 16 +- scripts/convert-idl.js | 83 ++++++++++ scripts/download_latest_properties.js | 4 +- scripts/generate_implemented_properties.js | 12 +- scripts/generate_properties.js | 17 +- src/CSSStyleDeclaration.webidl | 18 ++ webidl2js-wrapper.js | 2 + 22 files changed, 426 insertions(+), 140 deletions(-) create mode 100644 index.js rename lib/{CSSStyleDeclaration.js => CSSStyleDeclaration-impl.js} (67%) create mode 100644 scripts/convert-idl.js create mode 100644 src/CSSStyleDeclaration.webidl create mode 100644 webidl2js-wrapper.js diff --git a/.eslintignore b/.eslintignore index 9f55fbbb..de78dd4f 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,4 +1,8 @@ node_modules +lib/CSSStyleDeclaration.js +lib/Function.js +lib/VoidFunction.js lib/implementedProperties.js lib/properties.js +lib/utils.js jest.config.js diff --git a/.gitignore b/.gitignore index 558dbbb4..4cd4b401 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,10 @@ node_modules npm-debug.log +lib/CSSStyleDeclaration.js +lib/Function.js +lib/VoidFunction.js lib/implementedProperties.js lib/properties.js +lib/utils.js coverage +src/CSSStyleDeclaration-properties.webidl diff --git a/.npmignore b/.npmignore index 4c9a487d..d9a5f721 100644 --- a/.npmignore +++ b/.npmignore @@ -1,3 +1,5 @@ /* !lib/ +lib/Function.js +lib/VoidFunction.js !LICENSE diff --git a/.travis.yml b/.travis.yml index 3e4789bd..f605a283 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,7 +9,6 @@ install: - npm install - npm install -g codecov node_js: - - "8" - "10" - "12" diff --git a/README.md b/README.md index e0ce185d..aec91bd9 100644 --- a/README.md +++ b/README.md @@ -4,12 +4,47 @@ A Node JS implementation of the CSS Object Model [CSSStyleDeclaration interface] [![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) ---- +## Background -#### Background +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. -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. - -It was originally created by Chad Walker, it is now maintained by the jsdom community. +It was originally created by Chad Walker, it is now maintained by Jon Sakas and other open source contributors. Bug reports and pull requests are welcome. + +## APIs + +This package exposes two flavors of the `CSSStyleDeclaration` interface depending on the imported module. + +### `cssstyle` module + +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. + +Here is a crude example of using the `onChangeCallback` to implement the `style` property of `HTMLElement`: +```js +const CSSStyleDeclaration = require('cssstyle'); + +class HTMLElement extends Element { + constructor() { + this._style = new CSSStyleDeclaration(newCSSText => { + this.setAttributeNS(null, "style", newCSSText); + }); + } + + get style() { + return this._style; + } + + set style(text) { + this._style.cssText = text; + } +} +``` + +### `cssstyle/webidl2js-wrapper` module + +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. + +#### `privateData` + +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. diff --git a/index.js b/index.js new file mode 100644 index 00000000..aa2fd632 --- /dev/null +++ b/index.js @@ -0,0 +1,34 @@ +'use strict'; +const webidlWrapper = require('./webidl2js-wrapper.js'); + +const sharedGlobalObject = {}; +webidlWrapper.install(sharedGlobalObject, ['Window']); + +const origCSSStyleDeclaration = sharedGlobalObject.CSSStyleDeclaration; + +/** + * @constructor + * @param {((cssText: string) => void) | null} [onChangeCallback] + * The callback that is invoked whenever a property changes. + */ +function CSSStyleDeclaration(onChangeCallback = null) { + if (new.target === undefined) { + throw new TypeError("Class constructor CSSStyleDeclaration cannot be invoked without 'new'"); + } + + if (onChangeCallback !== null && typeof onChangeCallback !== 'function') { + throw new TypeError('Failed to construct CSSStyleDeclaration: parameter 1 is not a function'); + } + + return webidlWrapper.create(sharedGlobalObject, undefined, { onChangeCallback }); +} + +sharedGlobalObject.CSSStyleDeclaration = CSSStyleDeclaration; +Object.defineProperty(CSSStyleDeclaration, 'prototype', { + value: origCSSStyleDeclaration.prototype, + writable: false, +}); +CSSStyleDeclaration.prototype.constructor = CSSStyleDeclaration; +Object.setPrototypeOf(CSSStyleDeclaration, Object.getPrototypeOf(origCSSStyleDeclaration)); + +module.exports = CSSStyleDeclaration; diff --git a/jest.config.js b/jest.config.js index 565eada6..fa357eef 100644 --- a/jest.config.js +++ b/jest.config.js @@ -3,8 +3,10 @@ module.exports = { "collectCoverage": true, "collectCoverageFrom": [ "lib/**/*.js", + "!lib/CSSStyleDeclaration.js", "!lib/implementedProperties.js", "!lib/properties.js", + "!lib/utils.js", ], "coverageDirectory": "coverage", }; diff --git a/lib/CSSStyleDeclaration.js b/lib/CSSStyleDeclaration-impl.js similarity index 67% rename from lib/CSSStyleDeclaration.js rename to lib/CSSStyleDeclaration-impl.js index bded9a44..9ce8567a 100644 --- a/lib/CSSStyleDeclaration.js +++ b/lib/CSSStyleDeclaration-impl.js @@ -7,25 +7,28 @@ var CSSOM = require('cssom'); var allProperties = require('./allProperties'); var allExtraProperties = require('./allExtraProperties'); var implementedProperties = require('./implementedProperties'); -var { dashedToCamelCase } = require('./parsers'); +var { cssPropertyToIDLAttribute } = require('./parsers'); var getBasicPropertyDescriptor = require('./utils/getBasicPropertyDescriptor'); +const idlUtils = require('./utils.js'); -/** - * @constructor - * @see http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSStyleDeclaration - */ -var CSSStyleDeclaration = function CSSStyleDeclaration(onChangeCallback) { - this._values = {}; - this._importants = {}; - this._length = 0; - this._onChange = - onChangeCallback || - function() { - return; - }; -}; -CSSStyleDeclaration.prototype = { - constructor: CSSStyleDeclaration, +class CSSStyleDeclarationImpl { + /** + * @constructor + * @see http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSStyleDeclaration + * + * @param {object} globalObject + * @param {*[]} args + * @param {object} privateData + * @param {((cssText: string) => void) | null} [privateData.onChangeCallback] + */ + constructor(globalObject, args, { onChangeCallback }) { + this._globalObject = globalObject; + this._values = Object.create(null); + this._importants = Object.create(null); + this._length = 0; + this._onChange = onChangeCallback || (() => {}); + this.parentRule = null; + } /** * @@ -34,25 +37,19 @@ CSSStyleDeclaration.prototype = { * @return {string} the value of the property if it has been explicitly set for this declaration block. * Returns the empty string if the property has not been set. */ - getPropertyValue: function(name) { - if (!this._values.hasOwnProperty(name)) { - return ''; - } - return this._values[name].toString(); - }, + getPropertyValue(name) { + return this._values[name] || ''; + } /** * * @param {string} name * @param {string} value - * @param {string} [priority=null] "important" or null + * @param {string} [priority=""] "important" or "" * @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 === '') { + setProperty(name, value, priority = '') { + if (value === '') { this.removeProperty(name); return; } @@ -68,8 +65,16 @@ CSSStyleDeclaration.prototype = { this[lowercaseName] = value; this._importants[lowercaseName] = priority; - }, - _setProperty: function(name, value, priority) { + } + + /** + * @param {string} name + * @param {string | null} value + * @param {string} [priority=""] + */ + _setProperty(name, value, priority = '') { + // FIXME: A good chunk of the implemented properties call this method + // with `value = undefined`, expecting it to do nothing: if (value === undefined) { return; } @@ -92,7 +97,7 @@ CSSStyleDeclaration.prototype = { this._values[name] = value; this._importants[name] = priority; this._onChange(this.cssText); - }, + } /** * @@ -101,8 +106,8 @@ CSSStyleDeclaration.prototype = { * @return {string} the value of the property if it has been explicitly set for this declaration block. * Returns the empty string if the property has not been set or the property name does not correspond to a known CSS property. */ - removeProperty: function(name) { - if (!this._values.hasOwnProperty(name)) { + removeProperty(name) { + if (!idlUtils.hasOwn(this._values, name)) { return ''; } @@ -123,49 +128,36 @@ CSSStyleDeclaration.prototype = { this._onChange(this.cssText); return prevValue; - }, + } /** * * @param {String} name */ - getPropertyPriority: function(name) { + getPropertyPriority(name) { return this._importants[name] || ''; - }, - - getPropertyCSSValue: function() { - //FIXME - return; - }, - - /** - * element.style.overflow = "auto" - * element.style.getPropertyShorthand("overflow-x") - * -> "overflow" - */ - getPropertyShorthand: function() { - //FIXME - return; - }, - - isPropertyImplicit: function() { - //FIXME - return; - }, + } /** * http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSStyleDeclaration-item */ - item: function(index) { - index = parseInt(index, 10); + item(index) { if (index < 0 || index >= this._length) { return ''; } return this[index]; - }, -}; + } + + [idlUtils.supportsPropertyIndex](index) { + return index >= 0 && index < this._length; + } -Object.defineProperties(CSSStyleDeclaration.prototype, { + [idlUtils.supportedPropertyIndices]() { + return Array.prototype.keys.call(this); + } +} + +Object.defineProperties(CSSStyleDeclarationImpl.prototype, { cssText: { get: function() { var properties = []; @@ -178,9 +170,9 @@ Object.defineProperties(CSSStyleDeclaration.prototype, { value = this.getPropertyValue(name); priority = this.getPropertyPriority(name); if (priority !== '') { - priority = ' !' + priority; + priority = ` !${priority}`; } - properties.push([name, ': ', value, priority, ';'].join('')); + properties.push(`${name}: ${value}${priority};`); } return properties.join(' '); }, @@ -211,13 +203,6 @@ Object.defineProperties(CSSStyleDeclaration.prototype, { enumerable: true, configurable: true, }, - parentRule: { - get: function() { - return null; - }, - enumerable: true, - configurable: true, - }, length: { get: function() { return this._length; @@ -239,22 +224,38 @@ Object.defineProperties(CSSStyleDeclaration.prototype, { }, }); -require('./properties')(CSSStyleDeclaration.prototype); +require('./properties')(CSSStyleDeclarationImpl.prototype); +// TODO: Consider using `[Reflect]` for basic properties allProperties.forEach(function(property) { if (!implementedProperties.has(property)) { var declaration = getBasicPropertyDescriptor(property); - Object.defineProperty(CSSStyleDeclaration.prototype, property, declaration); - Object.defineProperty(CSSStyleDeclaration.prototype, dashedToCamelCase(property), declaration); + Object.defineProperty(CSSStyleDeclarationImpl.prototype, property, declaration); + Object.defineProperty( + CSSStyleDeclarationImpl.prototype, + cssPropertyToIDLAttribute(property), + declaration + ); } }); allExtraProperties.forEach(function(property) { if (!implementedProperties.has(property)) { var declaration = getBasicPropertyDescriptor(property); - Object.defineProperty(CSSStyleDeclaration.prototype, property, declaration); - Object.defineProperty(CSSStyleDeclaration.prototype, dashedToCamelCase(property), declaration); + Object.defineProperty(CSSStyleDeclarationImpl.prototype, property, declaration); + Object.defineProperty( + CSSStyleDeclarationImpl.prototype, + cssPropertyToIDLAttribute(property), + declaration + ); + if (property.startsWith('-webkit-')) { + Object.defineProperty( + CSSStyleDeclarationImpl.prototype, + cssPropertyToIDLAttribute(property, /* lowercaseFirst = */ true), + declaration + ); + } } }); -exports.CSSStyleDeclaration = CSSStyleDeclaration; +exports.implementation = CSSStyleDeclarationImpl; diff --git a/lib/CSSStyleDeclaration.test.js b/lib/CSSStyleDeclaration.test.js index 9fa8ed3b..6b6a07ac 100644 --- a/lib/CSSStyleDeclaration.test.js +++ b/lib/CSSStyleDeclaration.test.js @@ -1,20 +1,20 @@ 'use strict'; -var { CSSStyleDeclaration } = require('./CSSStyleDeclaration'); +var CSSStyleDeclaration = require('../index.js'); var allProperties = require('./allProperties'); var allExtraProperties = require('./allExtraProperties'); var implementedProperties = require('./implementedProperties'); -var parsers = require('./parsers'); +var { cssPropertyToIDLAttribute } = require('./parsers'); var dashedProperties = [...allProperties, ...allExtraProperties]; -var allowedProperties = dashedProperties.map(parsers.dashedToCamelCase); -implementedProperties = Array.from(implementedProperties).map(parsers.dashedToCamelCase); +var allowedProperties = ['cssFloat', ...dashedProperties.map(p => cssPropertyToIDLAttribute(p))]; +implementedProperties = Array.from(implementedProperties, p => cssPropertyToIDLAttribute(p)); var invalidProperties = implementedProperties.filter(prop => !allowedProperties.includes(prop)); describe('CSSStyleDeclaration', () => { test('has only valid properties implemented', () => { - expect(invalidProperties.length).toEqual(0); + expect(invalidProperties).toEqual([]); }); test('has all properties', () => { @@ -41,9 +41,6 @@ describe('CSSStyleDeclaration', () => { expect(typeof style.setProperty).toEqual('function'); expect(typeof style.getPropertyPriority).toEqual('function'); expect(typeof style.removeProperty).toEqual('function'); - - // TODO - deprecated according to MDN and not implemented at all, can we remove? - expect(typeof style.getPropertyCSSValue).toEqual('function'); }); test('has special properties', () => { @@ -52,7 +49,6 @@ describe('CSSStyleDeclaration', () => { expect(style.__lookupGetter__('cssText')).toBeTruthy(); expect(style.__lookupSetter__('cssText')).toBeTruthy(); expect(style.__lookupGetter__('length')).toBeTruthy(); - expect(style.__lookupSetter__('length')).toBeTruthy(); expect(style.__lookupGetter__('parentRule')).toBeTruthy(); }); diff --git a/lib/allExtraProperties.js b/lib/allExtraProperties.js index 44b9c296..1a4b8e0b 100644 --- a/lib/allExtraProperties.js +++ b/lib/allExtraProperties.js @@ -16,7 +16,6 @@ module.exports = new Set( 'color-interpolation', 'color-profile', 'color-rendering', - 'css-float', 'enable-background', 'fill', 'fill-opacity', diff --git a/lib/allWebkitProperties.js b/lib/allWebkitProperties.js index d6e71df6..57b2799b 100644 --- a/lib/allWebkitProperties.js +++ b/lib/allWebkitProperties.js @@ -191,4 +191,4 @@ module.exports = [ 'wrap-through', 'writing-mode', 'zoom', -].map(prop => 'webkit-' + prop); +].map(prop => '-webkit-' + prop); diff --git a/lib/parsers.js b/lib/parsers.js index 8ecdf5e3..544996f6 100644 --- a/lib/parsers.js +++ b/lib/parsers.js @@ -447,22 +447,35 @@ exports.parseKeyword = function parseKeyword(val, valid_keywords) { return undefined; }; -// utility to translate from border-width to borderWidth -var dashedToCamelCase = function(dashed) { - var i; - var camel = ''; - var nextCap = false; - for (i = 0; i < dashed.length; i++) { - if (dashed[i] !== '-') { - camel += nextCap ? dashed[i].toUpperCase() : dashed[i]; - nextCap = false; +/** + * utility to translate from border-width to borderWidth + * + * @param {string} property + * @param {boolean} [lowercaseFirst] + * @see https://drafts.csswg.org/cssom/#css-property-to-idl-attribute + */ +function cssPropertyToIDLAttribute(property, lowercaseFirst = false) { + let output = ''; + let uppercaseNext = false; + + if (lowercaseFirst) { + property = property.substring(1); + } + + for (const c of property) { + if (c === '-') { + uppercaseNext = true; + } else if (uppercaseNext) { + uppercaseNext = false; + output += c.toUpperCase(); } else { - nextCap = true; + output += c; } } - return camel; -}; -exports.dashedToCamelCase = dashedToCamelCase; + + return output; +} +exports.cssPropertyToIDLAttribute = cssPropertyToIDLAttribute; var is_space = /\s/; var opening_deliminators = ['"', "'", '(']; @@ -566,7 +579,7 @@ exports.shorthandSetter = function(property, shorthand_for) { Object.keys(obj).forEach(function(subprop) { // in case subprop is an implicit property, this will clear // *its* subpropertiesX - var camel = dashedToCamelCase(subprop); + var camel = cssPropertyToIDLAttribute(subprop); this[camel] = obj[subprop]; // in case it gets translated into something else (0 -> 0px) obj[subprop] = this[camel]; @@ -708,15 +721,15 @@ exports.subImplicitSetter = function(prefix, part, isValid, parser) { }; }; -var camel_to_dashed = /[A-Z]/g; -var first_segment = /^\([^-]\)-/; -var vendor_prefixes = ['o', 'moz', 'ms', 'webkit']; -exports.camelToDashed = function(camel_case) { - var match; - var dashed = camel_case.replace(camel_to_dashed, '-$&').toLowerCase(); - match = dashed.match(first_segment); - if (match && vendor_prefixes.indexOf(match[1]) !== -1) { - dashed = '-' + dashed; - } - return dashed; +const camel_to_dashed = /[A-Z]/g; + +/** + * @param {string} attribute + * @param {boolean} [dashPrefix] + * @see https://drafts.csswg.org/cssom/#idl-attribute-to-css-property + */ +exports.idlAttributeToCSSProperty = (attribute, dashPrefix = false) => { + let output = dashPrefix ? '-' : ''; + output += attribute.replace(camel_to_dashed, '-$&').toLowerCase(); + return output; }; diff --git a/lib/parsers.test.js b/lib/parsers.test.js index 926f7e74..792b4440 100644 --- a/lib/parsers.test.js +++ b/lib/parsers.test.js @@ -116,7 +116,7 @@ describe('parseAngle', () => { describe('parseKeyword', () => { it.todo('test'); }); -describe('dashedToCamelCase', () => { +describe('cssPropertyToIDLAttribute', () => { it.todo('test'); }); describe('shorthandParser', () => { @@ -134,6 +134,6 @@ describe('implicitSetter', () => { describe('subImplicitSetter', () => { it.todo('test'); }); -describe('camelToDashed', () => { +describe('idlAttributeToCSSProperty', () => { it.todo('test'); }); diff --git a/lib/properties/borderSpacing.js b/lib/properties/borderSpacing.js index ff1ce882..d603e21b 100644 --- a/lib/properties/borderSpacing.js +++ b/lib/properties/borderSpacing.js @@ -10,7 +10,7 @@ var parse = function parse(v) { if (v === '' || v === null) { return undefined; } - if (v === 0) { + if (v === '0' || v === 0) { return '0px'; } if (v.toLowerCase() === 'inherit') { diff --git a/package-lock.json b/package-lock.json index 1e7f6ffb..a78d88eb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1387,9 +1387,9 @@ "dev": true }, "cssom": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.6.tgz", - "integrity": "sha512-DtUeseGk9/GBW0hl0vVPpU22iHL6YB5BUX7ml1hB+GMpo0NX5G4voX3kdWiMSEguFtcW3Vh3djqNF4aIe6ne0A==" + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz", + "integrity": "sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw==" }, "cssstyle": { "version": "1.2.2", @@ -1398,6 +1398,14 @@ "dev": true, "requires": { "cssom": "0.3.x" + }, + "dependencies": { + "cssom": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", + "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", + "dev": true + } } }, "dashdash": { @@ -1429,6 +1437,14 @@ "lodash.sortby": "^4.7.0", "tr46": "^1.0.1", "webidl-conversions": "^4.0.2" + }, + "dependencies": { + "webidl-conversions": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", + "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", + "dev": true + } } } } @@ -1553,6 +1569,14 @@ "dev": true, "requires": { "webidl-conversions": "^4.0.2" + }, + "dependencies": { + "webidl-conversions": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", + "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", + "dev": true + } } }, "ecc-jsbn": { @@ -4674,6 +4698,18 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.4.tgz", "integrity": "sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg==", "dev": true + }, + "cssom": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", + "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", + "dev": true + }, + "webidl-conversions": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", + "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", + "dev": true } } }, @@ -6760,11 +6796,35 @@ } }, "webidl-conversions": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", - "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz", + "integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==" + }, + "webidl2": { + "version": "23.12.1", + "resolved": "https://registry.npmjs.org/webidl2/-/webidl2-23.12.1.tgz", + "integrity": "sha512-CbBwugDZl4vO2hH6oY5ZxCZM+6OB3+k8tXzkKTk1y1w4eLExHKMXS/mtIMFBj2vA7JanD8toNcDCp4EH7J3gLg==", "dev": true }, + "webidl2js": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/webidl2js/-/webidl2js-16.2.0.tgz", + "integrity": "sha512-+ujeVqKYxe1efW1xmhS0rM4O1rqtoXOqBM3J96v/8SqID65Chwc9QotVaqCJtaIYqGa0B7lBn1kqUIXk52mgPw==", + "dev": true, + "requires": { + "prettier": "^2.0.4", + "webidl-conversions": "^6.1.0", + "webidl2": "^23.12.1" + }, + "dependencies": { + "prettier": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.0.5.tgz", + "integrity": "sha512-7PtVymN48hGcO4fGjybyBSIWDsLU4H4XlvOHfq91pz9kkGlonzwTfYkaIEwiRg/dAJF9YlbsduBAgtYLi+8cFg==", + "dev": true + } + } + }, "whatwg-encoding": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", @@ -6789,6 +6849,14 @@ "lodash.sortby": "^4.7.0", "tr46": "^1.0.1", "webidl-conversions": "^4.0.2" + }, + "dependencies": { + "webidl-conversions": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", + "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", + "dev": true + } } }, "which": { diff --git a/package.json b/package.json index 7ded3070..4d278677 100644 --- a/package.json +++ b/package.json @@ -33,11 +33,14 @@ "lib": "./lib" }, "files": [ + "index.js", + "webidl2js-wrapper.js", "lib/" ], - "main": "./lib/CSSStyleDeclaration.js", + "main": "./index.js", "dependencies": { - "cssom": "~0.3.6" + "cssom": "^0.4.4", + "webidl-conversions": "^6.1.0" }, "devDependencies": { "babel-generator": "~6.26.1", @@ -51,13 +54,16 @@ "npm-run-all": "^4.1.5", "prettier": "~1.18.0", "request": "^2.88.0", - "resolve": "~1.11.1" + "resolve": "~1.11.1", + "webidl2js": "^16.2.0" }, "scripts": { + "prepare": "npm run generate", "download": "node ./scripts/download_latest_properties.js && eslint lib/allProperties.js --fix", - "generate": "run-p generate:*", + "generate": "run-s generate:*", "generate:implemented_properties": "node ./scripts/generate_implemented_properties.js", "generate:properties": "node ./scripts/generate_properties.js", + "generate:webidl2js": "node ./scripts/convert-idl.js", "lint": "npm run generate && eslint . --max-warnings 0", "lint:fix": "eslint . --fix --max-warnings 0", "prepublishOnly": "npm run lint && npm run test", @@ -67,6 +73,6 @@ }, "license": "MIT", "engines": { - "node": ">=8" + "node": ">=10.4" } } diff --git a/scripts/convert-idl.js b/scripts/convert-idl.js new file mode 100644 index 00000000..4eaf08e6 --- /dev/null +++ b/scripts/convert-idl.js @@ -0,0 +1,83 @@ +'use strict'; + +const fs = require('fs'); +const path = require('path'); +const Transformer = require('webidl2js'); + +const allProperties = require('../lib/allProperties.js'); +const allExtraProperties = require('../lib/allExtraProperties.js'); +const { cssPropertyToIDLAttribute } = require('../lib/parsers.js'); + +const srcDir = path.resolve(__dirname, '../src'); +const implDir = path.resolve(__dirname, '../lib'); +const outputDir = implDir; + +const propertyNames = [ + ...allProperties, + ...Array.from(allExtraProperties).filter(prop => { + return !allProperties.has(prop); + }), +].sort(); + +// TODO: This should be natively supported by WebIDL2JS's Transformer +// https://github.com/jsdom/webidl2js/issues/188 +const genIDL = fs.createWriteStream( + path.resolve(__dirname, '../src/CSSStyleDeclaration-properties.webidl'), + { + encoding: 'utf-8', + } +); + +{ + genIDL.write(`\ +// autogenerated by scripts/convert-idl.js. do not edit! ${new Date().toISOString()} + +partial interface CSSStyleDeclaration { + // https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-camel_cased_attribute +`); + + for (const property of propertyNames) { + const camelCasedAttribute = cssPropertyToIDLAttribute(property); + genIDL.write(`\ + [CEReactions] attribute [LegacyNullToEmptyString] CSSOMString _${camelCasedAttribute}; +`); + } + + genIDL.write(` + // https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-webkit_cased_attribute +`); + + for (const property of propertyNames) { + if (!property.startsWith('-webkit-')) continue; + const webkitCasedAttribute = cssPropertyToIDLAttribute(property, /* lowercaseFirst = */ true); + genIDL.write(`\ + [CEReactions] attribute [LegacyNullToEmptyString] CSSOMString _${webkitCasedAttribute}; +`); + } + + genIDL.write(` + // https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-dashed_attribute +`); + + for (const property of propertyNames) { + if (!property.includes('-')) continue; + genIDL.write(`\ + [CEReactions] attribute [LegacyNullToEmptyString] CSSOMString ${property}; +`); + } + + genIDL.end('};\n'); +} + +const transformer = new Transformer({ + implSuffix: '-impl', + // TODO: Add support for `[CEReactions]` +}); + +transformer.addSource(srcDir, implDir); +new Promise(resolve => genIDL.on('finish', resolve)) + .then(() => transformer.generate(outputDir)) + .catch(err => { + console.error(err.stack); + process.exit(1); + }); diff --git a/scripts/download_latest_properties.js b/scripts/download_latest_properties.js index 15f85123..177651d0 100644 --- a/scripts/download_latest_properties.js +++ b/scripts/download_latest_properties.js @@ -23,7 +23,7 @@ var path = require('path'); var request = require('request'); -const { camelToDashed } = require('../lib/parsers'); +const { idlAttributeToCSSProperty } = require('../lib/parsers'); var url = 'https://www.w3.org/Style/CSS/all-properties.en.json'; @@ -69,7 +69,7 @@ request(url, function(error, response, body) { out_file.write('/*\n *\n * https://www.w3.org/Style/CSS/all-properties.en.html\n */\n\n'); out_file.write( 'module.exports = new Set(' + - JSON.stringify(CSSpropertyNames.map(camelToDashed), null, 2) + + JSON.stringify(CSSpropertyNames.map(p => idlAttributeToCSSProperty(p)), null, 2) + ');\n' ); diff --git a/scripts/generate_implemented_properties.js b/scripts/generate_implemented_properties.js index caa88f12..30e255d2 100644 --- a/scripts/generate_implemented_properties.js +++ b/scripts/generate_implemented_properties.js @@ -4,12 +4,18 @@ const fs = require('fs'); const path = require('path'); const t = require('babel-types'); const generate = require('babel-generator').default; -const camelToDashed = require('../lib/parsers').camelToDashed; +const { idlAttributeToCSSProperty } = require('../lib/parsers'); +const webkitPropertyName = /^webkit[A-Z]/; const dashedProperties = fs .readdirSync(path.resolve(__dirname, '../lib/properties')) - .filter(propertyFile => propertyFile.substr(-3) === '.js') - .map(propertyFile => camelToDashed(propertyFile.replace('.js', ''))); + .filter(propertyFile => path.extname(propertyFile) === '.js') + .map(propertyFile => { + return idlAttributeToCSSProperty( + path.basename(propertyFile, '.js'), + /* dashPrefix = */ webkitPropertyName.test(propertyFile) + ); + }); const out_file = fs.createWriteStream(path.resolve(__dirname, '../lib/implementedProperties.js'), { encoding: 'utf-8', diff --git a/scripts/generate_properties.js b/scripts/generate_properties.js index 33a42728..917db424 100644 --- a/scripts/generate_properties.js +++ b/scripts/generate_properties.js @@ -8,7 +8,7 @@ var generate = require('babel-generator').default; var traverse = require('babel-traverse').default; var resolve = require('resolve'); -var camelToDashed = require('../lib/parsers').camelToDashed; +var { idlAttributeToCSSProperty, cssPropertyToIDLAttribute } = require('../lib/parsers'); var basename = path.basename; var dirname = path.dirname; @@ -56,12 +56,17 @@ function isRequire(node, filename) { } } +const webkitPropertyName = /^webkit[A-Z]/; + // step 1: parse all files and figure out their dependencies var parsedFilesByPath = {}; property_files.map(function(property) { var filename = path.resolve(__dirname, '../lib/properties/' + property); var src = fs.readFileSync(filename, 'utf8'); property = basename(property, '.js'); + if (webkitPropertyName.test(property)) { + property = property[0].toUpperCase() + property.substring(1); + } var ast = babylon.parse(src); var dependencies = []; traverse(ast, { @@ -254,13 +259,21 @@ parsedFiles.forEach(function(file) { }); var propertyDefinitions = []; parsedFiles.forEach(function(file) { - var dashed = camelToDashed(file.property); + var dashed = idlAttributeToCSSProperty(file.property); propertyDefinitions.push( t.objectProperty( t.identifier(file.property), t.identifier(file.property + '_export_definition') ) ); + if (dashed.startsWith('-webkit-')) { + propertyDefinitions.push( + t.objectProperty( + t.identifier(cssPropertyToIDLAttribute(dashed, /* lowercaseFirst = */ true)), + t.identifier(file.property + '_export_definition') + ) + ); + } if (file.property !== dashed) { propertyDefinitions.push( t.objectProperty(t.stringLiteral(dashed), t.identifier(file.property + '_export_definition')) diff --git a/src/CSSStyleDeclaration.webidl b/src/CSSStyleDeclaration.webidl new file mode 100644 index 00000000..6c3d3357 --- /dev/null +++ b/src/CSSStyleDeclaration.webidl @@ -0,0 +1,18 @@ +// https://drafts.csswg.org/cssom/#cssomstring +typedef DOMString CSSOMString; + +// https://drafts.csswg.org/cssom/#cssstyledeclaration +[Exposed=Window] +interface CSSStyleDeclaration { + [CEReactions] attribute CSSOMString cssText; + readonly attribute unsigned long length; + getter CSSOMString item(unsigned long index); + CSSOMString getPropertyValue(CSSOMString property); + CSSOMString getPropertyPriority(CSSOMString property); + [CEReactions] void setProperty(CSSOMString property, [LegacyNullToEmptyString] CSSOMString value, optional [LegacyNullToEmptyString] CSSOMString priority = ""); + [CEReactions] CSSOMString removeProperty(CSSOMString property); + readonly attribute CSSRule? parentRule; + [CEReactions] attribute [LegacyNullToEmptyString] CSSOMString cssFloat; +}; + +// Additional partial interfaces are generated by `scripts/convert-idl.js`. diff --git a/webidl2js-wrapper.js b/webidl2js-wrapper.js new file mode 100644 index 00000000..0c6f5215 --- /dev/null +++ b/webidl2js-wrapper.js @@ -0,0 +1,2 @@ +'use strict'; +module.exports = require('./lib/CSSStyleDeclaration.js'); From c6b72e05454df35ddf1c2aa608e4329f00e4e95b Mon Sep 17 00:00:00 2001 From: ExE Boss <3889017+ExE-Boss@users.noreply.github.com> Date: Sat, 27 Mar 2021 09:20:00 +0100 Subject: [PATCH 2/5] =?UTF-8?q?Use=C2=A0`[ReflectStyle]`=20for=C2=A0basic?= =?UTF-8?q?=C2=A0properties?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/CSSStyleDeclaration-impl.js | 213 ++++++++++-------------- lib/utils/getBasicPropertyDescriptor.js | 14 -- scripts/convert-idl.js | 30 +++- src/CSSStyleDeclaration.webidl | 2 +- 4 files changed, 112 insertions(+), 147 deletions(-) delete mode 100644 lib/utils/getBasicPropertyDescriptor.js diff --git a/lib/CSSStyleDeclaration-impl.js b/lib/CSSStyleDeclaration-impl.js index 9ce8567a..a2e92600 100644 --- a/lib/CSSStyleDeclaration-impl.js +++ b/lib/CSSStyleDeclaration-impl.js @@ -3,18 +3,16 @@ * https://github.com/NV/CSSOM ********************************************************************/ 'use strict'; -var CSSOM = require('cssom'); -var allProperties = require('./allProperties'); -var allExtraProperties = require('./allExtraProperties'); -var implementedProperties = require('./implementedProperties'); -var { cssPropertyToIDLAttribute } = require('./parsers'); -var getBasicPropertyDescriptor = require('./utils/getBasicPropertyDescriptor'); +const CSSOM = require('cssom'); +const allProperties = require('./allProperties'); +const allExtraProperties = require('./allExtraProperties'); +const implementedProperties = require('./implementedProperties'); const idlUtils = require('./utils.js'); class CSSStyleDeclarationImpl { /** * @constructor - * @see http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSStyleDeclaration + * @see https://drafts.csswg.org/cssom/#cssstyledeclaration * * @param {object} globalObject * @param {*[]} args @@ -25,15 +23,67 @@ class CSSStyleDeclarationImpl { this._globalObject = globalObject; this._values = Object.create(null); this._importants = Object.create(null); - this._length = 0; + this._list = []; this._onChange = onChangeCallback || (() => {}); this.parentRule = null; } + /** + * @see https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-csstext + */ + get cssText() { + const { _list } = this; + const properties = []; + for (let i = 0; i < _list.length; i++) { + const name = _list[i]; + const value = this.getPropertyValue(name); + let priority = this.getPropertyPriority(name); + if (priority !== '') { + priority = ` !${priority}`; + } + properties.push(`${name}: ${value}${priority};`); + } + return properties.join(' '); + } + + set cssText(value) { + this._values = Object.create(null); + this._importants = Object.create(null); + this._list = []; + let dummyRule; + try { + dummyRule = CSSOM.parse('#bogus{' + value + '}').cssRules[0].style; + } catch (err) { + // malformed css, just return + return; + } + const rule_length = dummyRule.length; + for (let i = 0; i < rule_length; ++i) { + const name = dummyRule[i]; + this.setProperty( + dummyRule[i], + dummyRule.getPropertyValue(name), + dummyRule.getPropertyPriority(name) + ); + } + this._onChange(this.cssText); + } + + /** + * @see https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-length + */ + get length() { + return this._list.length; + } + + set length(value) { + this._list.length = value; + } + /** * * @param {string} name - * @see http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSStyleDeclaration-getPropertyValue + * @see https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-getpropertyvalue * @return {string} the value of the property if it has been explicitly set for this declaration block. * Returns the empty string if the property has not been set. */ @@ -46,24 +96,29 @@ class CSSStyleDeclarationImpl { * @param {string} name * @param {string} value * @param {string} [priority=""] "important" or "" - * @see http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSStyleDeclaration-setProperty + * @see https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-setproperty */ setProperty(name, value, priority = '') { if (value === '') { this.removeProperty(name); return; } - var isCustomProperty = name.indexOf('--') === 0; - if (isCustomProperty) { + + if (name.startsWith('--')) { this._setProperty(name, value, priority); return; } - var lowercaseName = name.toLowerCase(); + + const lowercaseName = name.toLowerCase(); if (!allProperties.has(lowercaseName) && !allExtraProperties.has(lowercaseName)) { return; } - this[lowercaseName] = value; + if (implementedProperties.has(lowercaseName)) { + this[lowercaseName] = value; + } else { + this._setProperty(lowercaseName, value, priority); + } this._importants[lowercaseName] = priority; } @@ -84,15 +139,12 @@ class CSSStyleDeclarationImpl { } if (this._values[name]) { // Property already exist. Overwrite it. - var index = Array.prototype.indexOf.call(this, name); - if (index < 0) { - this[this._length] = name; - this._length++; + if (!this._list.includes(name)) { + this._list.push(name); } } else { // New property. - this[this._length] = name; - this._length++; + this._list.push(name); } this._values[name] = value; this._importants[name] = priority; @@ -102,7 +154,7 @@ class CSSStyleDeclarationImpl { /** * * @param {string} name - * @see http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSStyleDeclaration-removeProperty + * @see https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-removeproperty * @return {string} the value of the property if it has been explicitly set for this declaration block. * Returns the empty string if the property has not been set or the property name does not correspond to a known CSS property. */ @@ -111,20 +163,20 @@ class CSSStyleDeclarationImpl { return ''; } - var prevValue = this._values[name]; + const prevValue = this._values[name]; delete this._values[name]; delete this._importants[name]; - var index = Array.prototype.indexOf.call(this, name); + const index = this._list.indexOf(name); if (index < 0) { return prevValue; } // That's what WebKit and Opera do - Array.prototype.splice.call(this, index, 1); + this._list.splice(index, 1); // That's what Firefox does - //this[index] = "" + //this._list[index] = '' this._onChange(this.cssText); return prevValue; @@ -133,129 +185,32 @@ class CSSStyleDeclarationImpl { /** * * @param {String} name + * @see https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-getpropertypriority */ getPropertyPriority(name) { return this._importants[name] || ''; } /** - * http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSStyleDeclaration-item + * @see https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-item */ item(index) { - if (index < 0 || index >= this._length) { + const { _list } = this; + if (index < 0 || index >= _list.length) { return ''; } - return this[index]; + return _list[index]; } [idlUtils.supportsPropertyIndex](index) { - return index >= 0 && index < this._length; + return index >= 0 && index < this._list.length; } [idlUtils.supportedPropertyIndices]() { - return Array.prototype.keys.call(this); + return this._list.keys(); } } -Object.defineProperties(CSSStyleDeclarationImpl.prototype, { - cssText: { - get: function() { - var properties = []; - var i; - var name; - var value; - var priority; - for (i = 0; i < this._length; i++) { - name = this[i]; - value = this.getPropertyValue(name); - priority = this.getPropertyPriority(name); - if (priority !== '') { - priority = ` !${priority}`; - } - properties.push(`${name}: ${value}${priority};`); - } - return properties.join(' '); - }, - set: function(value) { - var i; - this._values = {}; - Array.prototype.splice.call(this, 0, this._length); - this._importants = {}; - var dummyRule; - try { - dummyRule = CSSOM.parse('#bogus{' + value + '}').cssRules[0].style; - } catch (err) { - // malformed css, just return - return; - } - var rule_length = dummyRule.length; - var name; - for (i = 0; i < rule_length; ++i) { - name = dummyRule[i]; - this.setProperty( - dummyRule[i], - dummyRule.getPropertyValue(name), - dummyRule.getPropertyPriority(name) - ); - } - this._onChange(this.cssText); - }, - enumerable: true, - configurable: true, - }, - length: { - get: function() { - return this._length; - }, - /** - * This deletes indices if the new length is less then the current - * length. If the new length is more, it does nothing, the new indices - * will be undefined until set. - **/ - set: function(value) { - var i; - for (i = value; i < this._length; i++) { - delete this[i]; - } - this._length = value; - }, - enumerable: true, - configurable: true, - }, -}); - require('./properties')(CSSStyleDeclarationImpl.prototype); -// TODO: Consider using `[Reflect]` for basic properties -allProperties.forEach(function(property) { - if (!implementedProperties.has(property)) { - var declaration = getBasicPropertyDescriptor(property); - Object.defineProperty(CSSStyleDeclarationImpl.prototype, property, declaration); - Object.defineProperty( - CSSStyleDeclarationImpl.prototype, - cssPropertyToIDLAttribute(property), - declaration - ); - } -}); - -allExtraProperties.forEach(function(property) { - if (!implementedProperties.has(property)) { - var declaration = getBasicPropertyDescriptor(property); - Object.defineProperty(CSSStyleDeclarationImpl.prototype, property, declaration); - Object.defineProperty( - CSSStyleDeclarationImpl.prototype, - cssPropertyToIDLAttribute(property), - declaration - ); - if (property.startsWith('-webkit-')) { - Object.defineProperty( - CSSStyleDeclarationImpl.prototype, - cssPropertyToIDLAttribute(property, /* lowercaseFirst = */ true), - declaration - ); - } - } -}); - exports.implementation = CSSStyleDeclarationImpl; diff --git a/lib/utils/getBasicPropertyDescriptor.js b/lib/utils/getBasicPropertyDescriptor.js deleted file mode 100644 index ded2cc45..00000000 --- a/lib/utils/getBasicPropertyDescriptor.js +++ /dev/null @@ -1,14 +0,0 @@ -'use strict'; - -module.exports = function getBasicPropertyDescriptor(name) { - return { - set: function(v) { - this._setProperty(name, v); - }, - get: function() { - return this.getPropertyValue(name); - }, - enumerable: true, - configurable: true, - }; -}; diff --git a/scripts/convert-idl.js b/scripts/convert-idl.js index 4eaf08e6..f719cb35 100644 --- a/scripts/convert-idl.js +++ b/scripts/convert-idl.js @@ -6,6 +6,7 @@ const Transformer = require('webidl2js'); const allProperties = require('../lib/allProperties.js'); const allExtraProperties = require('../lib/allExtraProperties.js'); +const implementedProperties = require('../lib/implementedProperties.js'); const { cssPropertyToIDLAttribute } = require('../lib/parsers.js'); const srcDir = path.resolve(__dirname, '../src'); @@ -38,8 +39,12 @@ partial interface CSSStyleDeclaration { for (const property of propertyNames) { const camelCasedAttribute = cssPropertyToIDLAttribute(property); + let extAttrs = 'CEReactions'; + if (!implementedProperties.has(property)) { + extAttrs += `,ReflectStyle="${property}"`; + } genIDL.write(`\ - [CEReactions] attribute [LegacyNullToEmptyString] CSSOMString _${camelCasedAttribute}; + [${extAttrs}] attribute [LegacyNullToEmptyString] CSSOMString _${camelCasedAttribute}; `); } @@ -50,8 +55,12 @@ partial interface CSSStyleDeclaration { for (const property of propertyNames) { if (!property.startsWith('-webkit-')) continue; const webkitCasedAttribute = cssPropertyToIDLAttribute(property, /* lowercaseFirst = */ true); + let extAttrs = 'CEReactions'; + if (!implementedProperties.has(property)) { + extAttrs += `,ReflectStyle="${property}"`; + } genIDL.write(`\ - [CEReactions] attribute [LegacyNullToEmptyString] CSSOMString _${webkitCasedAttribute}; + [${extAttrs}] attribute [LegacyNullToEmptyString] CSSOMString _${webkitCasedAttribute}; `); } @@ -61,8 +70,12 @@ partial interface CSSStyleDeclaration { for (const property of propertyNames) { if (!property.includes('-')) continue; + let extAttrs = 'CEReactions'; + if (!implementedProperties.has(property)) { + extAttrs += `,ReflectStyle="${property}"`; + } genIDL.write(`\ - [CEReactions] attribute [LegacyNullToEmptyString] CSSOMString ${property}; + [${extAttrs}] attribute [LegacyNullToEmptyString] CSSOMString ${property}; `); } @@ -72,6 +85,17 @@ partial interface CSSStyleDeclaration { const transformer = new Transformer({ implSuffix: '-impl', // TODO: Add support for `[CEReactions]` + processReflect(idl, implName) { + const reflectStyle = idl.extAttrs.find(extAttr => extAttr.name === 'ReflectStyle'); + if (!reflectStyle || !reflectStyle.rhs || reflectStyle.rhs.type !== 'string') { + throw new Error(`Internal error: Invalid [ReflectStyle] for attribute ${idl.name}`); + } + + return { + get: `return ${implName}.getPropertyValue(${reflectStyle.rhs.value});`, + set: `${implName}._setProperty(${reflectStyle.rhs.value}, V);`, + }; + }, }); transformer.addSource(srcDir, implDir); diff --git a/src/CSSStyleDeclaration.webidl b/src/CSSStyleDeclaration.webidl index 6c3d3357..1e6447ee 100644 --- a/src/CSSStyleDeclaration.webidl +++ b/src/CSSStyleDeclaration.webidl @@ -12,7 +12,7 @@ interface CSSStyleDeclaration { [CEReactions] void setProperty(CSSOMString property, [LegacyNullToEmptyString] CSSOMString value, optional [LegacyNullToEmptyString] CSSOMString priority = ""); [CEReactions] CSSOMString removeProperty(CSSOMString property); readonly attribute CSSRule? parentRule; - [CEReactions] attribute [LegacyNullToEmptyString] CSSOMString cssFloat; + [CEReactions, ReflectStyle="float"] attribute [LegacyNullToEmptyString] CSSOMString cssFloat; }; // Additional partial interfaces are generated by `scripts/convert-idl.js`. From badf2c9789728a81617beda88e91e8ceb2535f89 Mon Sep 17 00:00:00 2001 From: ExE Boss Date: Tue, 23 Jul 2024 15:30:00 +0200 Subject: [PATCH 3/5] fixup! Merge branch 'main' into feat/migrate-to-webidl --- lib/CSSStyleDeclaration-impl.js | 40 +++++++++++++-------------- lib/CSSStyleDeclaration.test.js | 4 +-- lib/parsers.js | 2 +- scripts/convert-idl.js | 8 +++--- scripts/download_latest_properties.js | 6 +++- 5 files changed, 32 insertions(+), 28 deletions(-) diff --git a/lib/CSSStyleDeclaration-impl.js b/lib/CSSStyleDeclaration-impl.js index 530a19fc..428e29ef 100644 --- a/lib/CSSStyleDeclaration-impl.js +++ b/lib/CSSStyleDeclaration-impl.js @@ -3,12 +3,11 @@ * https://github.com/NV/CSSOM ********************************************************************/ 'use strict'; -var CSSOM = require('rrweb-cssom'); -var allProperties = require('./allProperties'); -var allExtraProperties = require('./allExtraProperties'); -var implementedProperties = require('./implementedProperties'); -var { dashedToCamelCase } = require('./parsers'); -var getBasicPropertyDescriptor = require('./utils/getBasicPropertyDescriptor'); +const CSSOM = require('rrweb-cssom'); +const allProperties = require('./allProperties'); +const allExtraProperties = require('./allExtraProperties'); +const implementedProperties = require('./implementedProperties'); +const idlUtils = require('./utils.js'); class CSSStyleDeclarationImpl { /** @@ -70,13 +69,15 @@ class CSSStyleDeclarationImpl { ); } this._setInProgress = false; - this._onChange?.(this.cssText); + if (this._onChange) { + this._onChange(this.cssText); + } } /** * @see https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-length */ - get length () { + get length() { return this._list.length; } @@ -85,7 +86,7 @@ class CSSStyleDeclarationImpl { * length. If the new length is more, it does nothing, the new indices * will be undefined until set. **/ - set length (value) { + set length(value) { this._list.length = value; } @@ -96,11 +97,8 @@ class CSSStyleDeclarationImpl { * @return {string} the value of the property if it has been explicitly set for this declaration block. * Returns the empty string if the property has not been set. */ - getPropertyValue (name) { - if (!this._values.hasOwnProperty(name)) { - return ''; - } - return this._values[name].toString(); + getPropertyValue(name) { + return this._values[name] || ''; } /** @@ -110,7 +108,7 @@ class CSSStyleDeclarationImpl { * @param {string} [priority=""] "important" or "" * @see https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-setproperty */ - setProperty (name, value, priority = '') { + setProperty(name, value, priority = '') { if (value === '') { this.removeProperty(name); return; @@ -139,7 +137,7 @@ class CSSStyleDeclarationImpl { * @param {string | null} value * @param {string} [priority=""] */ - _setProperty (name, value, priority = '') { + _setProperty(name, value, priority = '') { // FIXME: A good chunk of the implemented properties call this method // with `value = undefined`, expecting it to do nothing: if (value === undefined) { @@ -178,7 +176,7 @@ class CSSStyleDeclarationImpl { * @return {string} the value of the property if it has been explicitly set for this declaration block. * Returns the empty string if the property has not been set or the property name does not correspond to a known CSS property. */ - removeProperty (name) { + removeProperty(name) { if (!idlUtils.hasOwn(this._values, name)) { return ''; } @@ -198,7 +196,9 @@ class CSSStyleDeclarationImpl { // That's what Firefox does //this._list[index] = '' - this._onChange?.(this.cssText); + if (this._onChange) { + this._onChange(this.cssText); + } return prevValue; } @@ -207,14 +207,14 @@ class CSSStyleDeclarationImpl { * @param {String} name * @see https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-getpropertypriority */ - getPropertyPriority (name) { + getPropertyPriority(name) { return this._importants[name] || ''; } /** * @see https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-item */ - item (index) { + item(index) { const { _list } = this; if (index < 0 || index >= _list.length) { return ''; diff --git a/lib/CSSStyleDeclaration.test.js b/lib/CSSStyleDeclaration.test.js index 2bb30289..e2641090 100644 --- a/lib/CSSStyleDeclaration.test.js +++ b/lib/CSSStyleDeclaration.test.js @@ -8,8 +8,8 @@ var implementedProperties = require('./implementedProperties'); var { cssPropertyToIDLAttribute } = require('./parsers'); var dashedProperties = [...allProperties, ...allExtraProperties]; -var allowedProperties = ['cssFloat', ...dashedProperties.map(p => cssPropertyToIDLAttribute(p))]; -implementedProperties = Array.from(implementedProperties, p => cssPropertyToIDLAttribute(p)); +var allowedProperties = ['cssFloat', ...dashedProperties.map((p) => cssPropertyToIDLAttribute(p))]; +implementedProperties = Array.from(implementedProperties, (p) => cssPropertyToIDLAttribute(p)); var invalidProperties = implementedProperties.filter((prop) => !allowedProperties.includes(prop)); describe('CSSStyleDeclaration', () => { diff --git a/lib/parsers.js b/lib/parsers.js index 4c184c63..00700251 100644 --- a/lib/parsers.js +++ b/lib/parsers.js @@ -455,7 +455,7 @@ exports.parseKeyword = function parseKeyword(val, valid_keywords) { * @param {boolean} [lowercaseFirst] * @see https://drafts.csswg.org/cssom/#css-property-to-idl-attribute */ -function cssPropertyToIDLAttribute (property, lowercaseFirst = false) { +function cssPropertyToIDLAttribute(property, lowercaseFirst = false) { let output = ''; let uppercaseNext = false; diff --git a/scripts/convert-idl.js b/scripts/convert-idl.js index f719cb35..068cb713 100644 --- a/scripts/convert-idl.js +++ b/scripts/convert-idl.js @@ -15,7 +15,7 @@ const outputDir = implDir; const propertyNames = [ ...allProperties, - ...Array.from(allExtraProperties).filter(prop => { + ...Array.from(allExtraProperties).filter((prop) => { return !allProperties.has(prop); }), ].sort(); @@ -86,7 +86,7 @@ const transformer = new Transformer({ implSuffix: '-impl', // TODO: Add support for `[CEReactions]` processReflect(idl, implName) { - const reflectStyle = idl.extAttrs.find(extAttr => extAttr.name === 'ReflectStyle'); + const reflectStyle = idl.extAttrs.find((extAttr) => extAttr.name === 'ReflectStyle'); if (!reflectStyle || !reflectStyle.rhs || reflectStyle.rhs.type !== 'string') { throw new Error(`Internal error: Invalid [ReflectStyle] for attribute ${idl.name}`); } @@ -99,9 +99,9 @@ const transformer = new Transformer({ }); transformer.addSource(srcDir, implDir); -new Promise(resolve => genIDL.on('finish', resolve)) +new Promise((resolve) => genIDL.on('finish', resolve)) .then(() => transformer.generate(outputDir)) - .catch(err => { + .catch((err) => { console.error(err.stack); process.exit(1); }); diff --git a/scripts/download_latest_properties.js b/scripts/download_latest_properties.js index 7f3331c7..c33f594b 100644 --- a/scripts/download_latest_properties.js +++ b/scripts/download_latest_properties.js @@ -71,7 +71,11 @@ async function main() { out_file.write('/*\n *\n * https://www.w3.org/Style/CSS/all-properties.en.html\n */\n\n'); out_file.write( 'module.exports = new Set(' + - JSON.stringify(CSSpropertyNames.map((p) => idlAttributeToCSSProperty(p)), null, 2) + + JSON.stringify( + CSSpropertyNames.map((p) => idlAttributeToCSSProperty(p)), + null, + 2 + ) + ');\n' ); From fafcf746e0f6ae9a3a444662a659db919b68b94f Mon Sep 17 00:00:00 2001 From: ExE Boss Date: Tue, 23 Jul 2024 16:10:00 +0200 Subject: [PATCH 4/5] =?UTF-8?q?Update=C2=A0WebIDL2JS?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- index.js | 2 +- jest.config.js | 2 ++ package-lock.json | 61 +++++++++++++++++++++++++---------------------- package.json | 4 ++-- 4 files changed, 37 insertions(+), 32 deletions(-) diff --git a/index.js b/index.js index aa2fd632..9727f3df 100644 --- a/index.js +++ b/index.js @@ -1,7 +1,7 @@ 'use strict'; const webidlWrapper = require('./webidl2js-wrapper.js'); -const sharedGlobalObject = {}; +const sharedGlobalObject = { Object, String, Number, Array, TypeError }; webidlWrapper.install(sharedGlobalObject, ['Window']); const origCSSStyleDeclaration = sharedGlobalObject.CSSStyleDeclaration; diff --git a/jest.config.js b/jest.config.js index fa357eef..e9ee0caf 100644 --- a/jest.config.js +++ b/jest.config.js @@ -4,6 +4,8 @@ module.exports = { "collectCoverageFrom": [ "lib/**/*.js", "!lib/CSSStyleDeclaration.js", + "!lib/Function.js", + "!lib/VoidFunction.js", "!lib/implementedProperties.js", "!lib/properties.js", "!lib/utils.js", diff --git a/package-lock.json b/package-lock.json index ef150ae9..fc3ed174 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "license": "MIT", "dependencies": { "rrweb-cssom": "^0.6.0", - "webidl-conversions": "^6.1.0" + "webidl-conversions": "^7.0.0" }, "devDependencies": { "babel-generator": "^6.26.1", @@ -24,7 +24,7 @@ "npm-run-all": "^4.1.5", "prettier": "^3.1.1", "resolve": "^1.22.1", - "webidl2js": "^16.2.0" + "webidl2js": "^18.0.0" }, "engines": { "node": ">=18" @@ -7581,34 +7581,37 @@ } }, "node_modules/webidl-conversions": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz", - "integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", "license": "BSD-2-Clause", "engines": { - "node": ">=10.4" + "node": ">=12" } }, "node_modules/webidl2": { - "version": "23.13.1", - "resolved": "https://registry.npmjs.org/webidl2/-/webidl2-23.13.1.tgz", - "integrity": "sha512-u5njf3ZyqPr/4K8D6Cxm3B6MwSgwAdtrzxqHklwKaUdP1aQTmtKN5b65k4wlweJ/pRM6fpKUKYz8RlCJ9tka+w==", + "version": "24.4.1", + "resolved": "https://registry.npmjs.org/webidl2/-/webidl2-24.4.1.tgz", + "integrity": "sha512-cPToqvZlxTAlaMucZyU28XtFLJz3XPdTdIWK/r3IaP1jfkjqne3OTniJS8DZqfzee1aBUQn80d6s2vYert50kg==", "dev": true, - "license": "W3C" + "license": "W3C", + "engines": { + "node": ">= 14" + } }, "node_modules/webidl2js": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/webidl2js/-/webidl2js-16.2.0.tgz", - "integrity": "sha512-+ujeVqKYxe1efW1xmhS0rM4O1rqtoXOqBM3J96v/8SqID65Chwc9QotVaqCJtaIYqGa0B7lBn1kqUIXk52mgPw==", + "version": "18.0.0", + "resolved": "https://registry.npmjs.org/webidl2js/-/webidl2js-18.0.0.tgz", + "integrity": "sha512-67EXwjCU0E7QaYAGEw5VldR/QiHBLA9EQxUB1Q7woM8+J3cz/jSw/xi+Q6O/v2QUhyZ5oiqaP9az4OXJB0bUZQ==", "dev": true, "license": "MIT", "dependencies": { - "prettier": "^2.0.4", - "webidl-conversions": "^6.1.0", - "webidl2": "^23.12.1" + "prettier": "^2.8.8", + "webidl-conversions": "^7.0.0", + "webidl2": "^24.4.1" }, "engines": { - "node": ">=10" + "node": ">=18" } }, "node_modules/webidl2js/node_modules/prettier": { @@ -13408,25 +13411,25 @@ } }, "webidl-conversions": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz", - "integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==" + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==" }, "webidl2": { - "version": "23.13.1", - "resolved": "https://registry.npmjs.org/webidl2/-/webidl2-23.13.1.tgz", - "integrity": "sha512-u5njf3ZyqPr/4K8D6Cxm3B6MwSgwAdtrzxqHklwKaUdP1aQTmtKN5b65k4wlweJ/pRM6fpKUKYz8RlCJ9tka+w==", + "version": "24.4.1", + "resolved": "https://registry.npmjs.org/webidl2/-/webidl2-24.4.1.tgz", + "integrity": "sha512-cPToqvZlxTAlaMucZyU28XtFLJz3XPdTdIWK/r3IaP1jfkjqne3OTniJS8DZqfzee1aBUQn80d6s2vYert50kg==", "dev": true }, "webidl2js": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/webidl2js/-/webidl2js-16.2.0.tgz", - "integrity": "sha512-+ujeVqKYxe1efW1xmhS0rM4O1rqtoXOqBM3J96v/8SqID65Chwc9QotVaqCJtaIYqGa0B7lBn1kqUIXk52mgPw==", + "version": "18.0.0", + "resolved": "https://registry.npmjs.org/webidl2js/-/webidl2js-18.0.0.tgz", + "integrity": "sha512-67EXwjCU0E7QaYAGEw5VldR/QiHBLA9EQxUB1Q7woM8+J3cz/jSw/xi+Q6O/v2QUhyZ5oiqaP9az4OXJB0bUZQ==", "dev": true, "requires": { - "prettier": "^2.0.4", - "webidl-conversions": "^6.1.0", - "webidl2": "^23.12.1" + "prettier": "^2.8.8", + "webidl-conversions": "^7.0.0", + "webidl2": "^24.4.1" }, "dependencies": { "prettier": { diff --git a/package.json b/package.json index 111db097..c312ec5f 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,7 @@ "main": "./index.js", "dependencies": { "rrweb-cssom": "^0.6.0", - "webidl-conversions": "^6.1.0" + "webidl-conversions": "^7.0.0" }, "devDependencies": { "babel-generator": "^6.26.1", @@ -54,7 +54,7 @@ "npm-run-all": "^4.1.5", "prettier": "^3.1.1", "resolve": "^1.22.1", - "webidl2js": "^16.2.0" + "webidl2js": "^18.0.0" }, "scripts": { "prepare": "npm run generate", From a8c1763ddab15cf1944e6b4c3b0837ecd8c215d8 Mon Sep 17 00:00:00 2001 From: ExE Boss Date: Tue, 23 Jul 2024 16:15:00 +0200 Subject: [PATCH 5/5] =?UTF-8?q?Fix=C2=A0`.npmignore`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .npmignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.npmignore b/.npmignore index d9a5f721..bad2b5e1 100644 --- a/.npmignore +++ b/.npmignore @@ -1,4 +1,6 @@ /* +!index.js +!webidl2js-wrapper.js !lib/ lib/Function.js lib/VoidFunction.js