jsdom / cssstyle

A Node.js implementation of the CSS Object Model CSSStyleDeclaration interface
MIT License
107 stars 70 forks source link

Values are not properly converted to DOMString #129

Open cdoublev opened 3 years ago

cdoublev commented 3 years ago

I'm not sure that the following behavior tested in Chrome and Firefox is described in any specification:

// Case 1: toString() from prototype
class Prop {
  toString() {
    return 1 // Number
  }
}
style.setProperty('opacity', new Prop) // Ok, new value: 1

// Case 2: toString() from instance
const Prop = {
  toString() {
    return 1
  }
}
style.setProperty('opacity', Prop) // Ok, new value: 1

// Case 3: recursive toString()
class RecursiveProp {
  toString() {
    return new Prop()
  }
}
style.setProperty('opacity', new RecursiveProp) // Error

Ie. the value should be converted to String if it has a toString() method. cssstyle currently returns '' for all cases, ie. it results to an invalid value. Chrome and Firefox throw an error for case 3.

cdoublev commented 3 years ago

From what I understand, a value is converted to a DOMString when passing from JavaScript to a DOM element style interface.

Interface CSSStyleDeclaration

void setProperty(in DOMString propertyName, in DOMString value, in DOMString priority) raises(DOMException);

https://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSStyleDeclaration

ECMAScript values are converted to an IDL value when passed to a platform object expecting that type

https://heycam.github.io/webidl/#es-type-mapping

An ECMAScript value V is converted to an IDL DOMString value by running the following algorithm:

  1. If V is null and the conversion is to an IDL type associated with the LegacyNullToEmptyString extended attribute, then return the DOMString value that represents the empty string.
  2. Let x be ToString(V)
  3. Return the IDL DOMString value [...]

https://heycam.github.io/webidl/#es-DOMString

I renamed the title of this issue to handle the following cases that were also not handled correctly:

target.style.opacity = Symbol('1')                             // Error
target.style.opacity = { toString: () => [0] }                 // Error
target.style.opacity = BigInt(1)                               // '1'
target.style.setProperty('--custom', undefined)                // 'undefined'
target.style.setProperty('--custom', true)                     // 'true'
target.style.setProperty('--custom', { toString: () => null }) // 'null'