Source code
Revision control
Copy as Markdown
Other Tools
/* Any copyright is dedicated to the Public Domain.
/**
* reflect.js is a collection of methods to test HTML attribute reflection.
* Each of attribute is reflected differently, depending on various parameters,
* see:
*
* Do not forget to add these line at the beginning of each new reflect* method:
* ok(attr in element, attr + " should be an IDL attribute of this element");
* is(typeof element[attr], <type>, attr + " IDL attribute should be a <type>");
*/
/**
* Checks that a given attribute is correctly reflected as a string.
*
* @param aParameters Object object containing the parameters, which are:
* - element Element node to test
* - attribute String name of the attribute
* OR
* attribute Object object containing two attributes, 'content' and 'idl'
* - otherValues Array [optional] other values to test in addition of the default ones
* - extendedAttributes Object object which can have 'TreatNullAs': "EmptyString"
*/
function reflectString(aParameters) {
var element = aParameters.element;
var contentAttr =
typeof aParameters.attribute === "string"
? aParameters.attribute
: aParameters.attribute.content;
var idlAttr =
typeof aParameters.attribute === "string"
? aParameters.attribute
: aParameters.attribute.idl;
var otherValues =
aParameters.otherValues !== undefined ? aParameters.otherValues : [];
var treatNullAs = aParameters.extendedAttributes
? aParameters.extendedAttributes.TreatNullAs
: null;
ok(
idlAttr in element,
idlAttr + " should be an IDL attribute of this element"
);
is(
typeof element[idlAttr],
"string",
"'" + idlAttr + "' IDL attribute should be a string"
);
// Tests when the attribute isn't set.
is(
element.getAttribute(contentAttr),
null,
"When not set, the content attribute should be null."
);
is(
element[idlAttr],
"",
"When not set, the IDL attribute should return the empty string"
);
/**
* TODO: as long as null stringification doesn't follow the WebIDL
* specifications, don't add it to the loop below and keep it here.
*/
element.setAttribute(contentAttr, null);
is(
element.getAttribute(contentAttr),
"null",
"null should have been stringified to 'null' for '" + contentAttr + "'"
);
is(
element[idlAttr],
"null",
"null should have been stringified to 'null' for '" + idlAttr + "'"
);
element.removeAttribute(contentAttr);
element[idlAttr] = null;
if (treatNullAs == "EmptyString") {
is(
element.getAttribute(contentAttr),
"",
"null should have been stringified to '' for '" + contentAttr + "'"
);
is(
element[idlAttr],
"",
"null should have been stringified to '' for '" + idlAttr + "'"
);
} else {
is(
element.getAttribute(contentAttr),
"null",
"null should have been stringified to 'null' for '" + contentAttr + "'"
);
is(
element[idlAttr],
"null",
"null should have been stringified to 'null' for '" + contentAttr + "'"
);
}
element.removeAttribute(contentAttr);
// Tests various strings.
var stringsToTest = [
// [ test value, expected result ]
["", ""],
["null", "null"],
["undefined", "undefined"],
["foo", "foo"],
[contentAttr, contentAttr],
[idlAttr, idlAttr],
// TODO: uncomment this when null stringification will follow the specs.
// [ null, "null" ],
[undefined, "undefined"],
[true, "true"],
[false, "false"],
[42, "42"],
// ES5, verse 8.12.8.
[
{
toString() {
return "foo";
},
},
"foo",
],
[
{
valueOf() {
return "foo";
},
},
"[object Object]",
],
[
{
valueOf() {
return "quux";
},
toString: undefined,
},
"quux",
],
[
{
valueOf() {
return "foo";
},
toString() {
return "bar";
},
},
"bar",
],
];
otherValues.forEach(function (v) {
stringsToTest.push([v, v]);
});
stringsToTest.forEach(function ([v, r]) {
element.setAttribute(contentAttr, v);
is(
element[idlAttr],
r,
"IDL attribute '" +
idlAttr +
"' should return the value it has been set to."
);
is(
element.getAttribute(contentAttr),
r,
"Content attribute '" +
contentAttr +
"'should return the value it has been set to."
);
element.removeAttribute(contentAttr);
element[idlAttr] = v;
is(
element[idlAttr],
r,
"IDL attribute '" +
idlAttr +
"' should return the value it has been set to."
);
is(
element.getAttribute(contentAttr),
r,
"Content attribute '" +
contentAttr +
"' should return the value it has been set to."
);
element.removeAttribute(contentAttr);
});
// Tests after removeAttribute() is called. Should be equivalent with not set.
is(
element.getAttribute(contentAttr),
null,
"When not set, the content attribute should be null."
);
is(
element[idlAttr],
"",
"When not set, the IDL attribute should return the empty string"
);
}
/**
* Checks that a given attribute name for a given element is correctly reflected
* as an unsigned int.
*
* @param aParameters Object object containing the parameters, which are:
* - element Element node to test on
* - attribute String name of the attribute
* - nonZero Boolean whether the attribute should be non-null
* - defaultValue Integer [optional] default value, if different from the default one
*/
function reflectUnsignedInt(aParameters) {
var element = aParameters.element;
var attr = aParameters.attribute;
var nonZero = aParameters.nonZero;
var defaultValue = aParameters.defaultValue;
var fallback = aParameters.fallback;
if (defaultValue === undefined) {
if (nonZero) {
defaultValue = 1;
} else {
defaultValue = 0;
}
}
if (fallback === undefined) {
fallback = false;
}
ok(attr in element, attr + " should be an IDL attribute of this element");
is(
typeof element[attr],
"number",
attr + " IDL attribute should be a number"
);
// Check default value.
is(element[attr], defaultValue, "default value should be " + defaultValue);
ok(!element.hasAttribute(attr), attr + " shouldn't be present");
var values = [1, 3, 42, 2147483647];
for (var value of values) {
element[attr] = value;
is(element[attr], value, "." + attr + " should be equals " + value);
is(
element.getAttribute(attr),
String(value),
"@" + attr + " should be equals " + value
);
element.setAttribute(attr, value);
is(element[attr], value, "." + attr + " should be equals " + value);
is(
element.getAttribute(attr),
String(value),
"@" + attr + " should be equals " + value
);
}
// -3000000000 is equivalent to 1294967296 when using the IDL attribute.
element[attr] = -3000000000;
is(element[attr], 1294967296, "." + attr + " should be equals to 1294967296");
is(
element.getAttribute(attr),
"1294967296",
"@" + attr + " should be equals to 1294967296"
);
// When setting the content attribute, it's a string so it will be invalid.
element.setAttribute(attr, -3000000000);
is(
element.getAttribute(attr),
"-3000000000",
"@" + attr + " should be equals to " + -3000000000
);
is(
element[attr],
defaultValue,
"." + attr + " should be equals to " + defaultValue
);
// When interpreted as unsigned 32-bit integers, all of these fall between
// 2^31 and 2^32 - 1, so per spec they return the default value.
var nonValidValues = [-2147483648, -1, 3147483647];
for (var value of nonValidValues) {
element[attr] = value;
is(
element.getAttribute(attr),
String(defaultValue),
"@" + attr + " should be equals to " + defaultValue
);
is(
element[attr],
defaultValue,
"." + attr + " should be equals to " + defaultValue
);
}
for (var values of nonValidValues) {
element.setAttribute(attr, values[0]);
is(
element.getAttribute(attr),
String(values[0]),
"@" + attr + " should be equals to " + values[0]
);
is(
element[attr],
defaultValue,
"." + attr + " should be equals to " + defaultValue
);
}
// Setting to 0 should throw an error if nonZero is true.
var caught = false;
try {
element[attr] = 0;
} catch (e) {
caught = true;
is(e.name, "IndexSizeError", "exception should be IndexSizeError");
is(
e.code,
DOMException.INDEX_SIZE_ERR,
"exception code should be INDEX_SIZE_ERR"
);
}
if (nonZero && !fallback) {
ok(caught, "an exception should have been caught");
} else {
ok(!caught, "no exception should have been caught");
}
// If 0 is set in @attr, it will be ignored when calling .attr.
element.setAttribute(attr, "0");
is(element.getAttribute(attr), "0", "@" + attr + " should be equals to 0");
if (nonZero) {
is(
element[attr],
defaultValue,
"." + attr + " should be equals to " + defaultValue
);
} else {
is(element[attr], 0, "." + attr + " should be equals to 0");
}
}
/**
* Checks that a given attribute is correctly reflected as limited to known
* values enumerated attribute.
*
* @param aParameters Object object containing the parameters, which are:
* - element Element node to test on
* - attribute String name of the attribute
* OR
* attribute Object object containing two attributes, 'content' and 'idl'
* - validValues Array valid values we support
* - invalidValues Array invalid values
* - defaultValue String [optional] default value when no valid value is set
* OR
* defaultValue Object [optional] object containing two attributes, 'invalid' and 'missing'
* - unsupportedValues Array [optional] valid values we do not support
* - nullable boolean [optional] whether the attribute is nullable
*/
function reflectLimitedEnumerated(aParameters) {
var element = aParameters.element;
var contentAttr =
typeof aParameters.attribute === "string"
? aParameters.attribute
: aParameters.attribute.content;
var idlAttr =
typeof aParameters.attribute === "string"
? aParameters.attribute
: aParameters.attribute.idl;
var validValues = aParameters.validValues;
var invalidValues = aParameters.invalidValues;
var defaultValueInvalid =
aParameters.defaultValue === undefined
? ""
: typeof aParameters.defaultValue === "string"
? aParameters.defaultValue
: aParameters.defaultValue.invalid;
var defaultValueMissing =
aParameters.defaultValue === undefined
? ""
: typeof aParameters.defaultValue === "string"
? aParameters.defaultValue
: aParameters.defaultValue.missing;
var unsupportedValues =
aParameters.unsupportedValues !== undefined
? aParameters.unsupportedValues
: [];
var nullable = aParameters.nullable;
ok(
idlAttr in element,
idlAttr + " should be an IDL attribute of this element"
);
if (nullable) {
// The missing value default is null, which is typeof == "object"
is(
typeof element[idlAttr],
"object",
"'" +
idlAttr +
"' IDL attribute should be null, which has typeof == object"
);
is(
element[idlAttr],
null,
"'" + idlAttr + "' IDL attribute should be null"
);
} else {
is(
typeof element[idlAttr],
"string",
"'" + idlAttr + "' IDL attribute should be a string"
);
}
if (nullable) {
element.setAttribute(contentAttr, "something");
// Now it will be a string
is(
typeof element[idlAttr],
"string",
"'" + idlAttr + "' IDL attribute should be a string"
);
}
// Explicitly check the default value.
element.removeAttribute(contentAttr);
is(
element[idlAttr],
defaultValueMissing,
"When no attribute is set, the value should be the default value."
);
// Check valid values.
validValues.forEach(function (v) {
element.setAttribute(contentAttr, v);
is(
element[idlAttr],
v,
"'" + v + "' should be accepted as a valid value for " + idlAttr
);
is(
element.getAttribute(contentAttr),
v,
"Content attribute should return the value it has been set to."
);
element.removeAttribute(contentAttr);
element.setAttribute(contentAttr, v.toUpperCase());
is(
element[idlAttr],
v,
"Enumerated attributes should be case-insensitive."
);
is(
element.getAttribute(contentAttr),
v.toUpperCase(),
"Content attribute should not be lower-cased."
);
element.removeAttribute(contentAttr);
element[idlAttr] = v;
is(
element[idlAttr],
v,
"'" + v + "' should be accepted as a valid value for " + idlAttr
);
is(
element.getAttribute(contentAttr),
v,
"Content attribute should return the value it has been set to."
);
element.removeAttribute(contentAttr);
element[idlAttr] = v.toUpperCase();
is(
element[idlAttr],
v,
"Enumerated attributes should be case-insensitive."
);
is(
element.getAttribute(contentAttr),
v.toUpperCase(),
"Content attribute should not be lower-cased."
);
element.removeAttribute(contentAttr);
});
// Check invalid values.
invalidValues.forEach(function (v) {
element.setAttribute(contentAttr, v);
is(
element[idlAttr],
defaultValueInvalid,
"When the content attribute is set to an invalid value, the default value should be returned."
);
is(
element.getAttribute(contentAttr),
v,
"Content attribute should not have been changed."
);
element.removeAttribute(contentAttr);
element[idlAttr] = v;
is(
element[idlAttr],
defaultValueInvalid,
"When the value is set to an invalid value, the default value should be returned."
);
is(
element.getAttribute(contentAttr),
v,
"Content attribute should not have been changed."
);
element.removeAttribute(contentAttr);
});
// Check valid values we currently do not support.
// Basically, it's like the checks for the valid values but with some todo's.
unsupportedValues.forEach(function (v) {
element.setAttribute(contentAttr, v);
todo_is(
element[idlAttr],
v,
"'" + v + "' should be accepted as a valid value for " + idlAttr
);
is(
element.getAttribute(contentAttr),
v,
"Content attribute should return the value it has been set to."
);
element.removeAttribute(contentAttr);
element.setAttribute(contentAttr, v.toUpperCase());
todo_is(
element[idlAttr],
v,
"Enumerated attributes should be case-insensitive."
);
is(
element.getAttribute(contentAttr),
v.toUpperCase(),
"Content attribute should not be lower-cased."
);
element.removeAttribute(contentAttr);
element[idlAttr] = v;
todo_is(
element[idlAttr],
v,
"'" + v + "' should be accepted as a valid value for " + idlAttr
);
is(
element.getAttribute(contentAttr),
v,
"Content attribute should return the value it has been set to."
);
element.removeAttribute(contentAttr);
element[idlAttr] = v.toUpperCase();
todo_is(
element[idlAttr],
v,
"Enumerated attributes should be case-insensitive."
);
is(
element.getAttribute(contentAttr),
v.toUpperCase(),
"Content attribute should not be lower-cased."
);
element.removeAttribute(contentAttr);
});
if (nullable) {
is(
defaultValueMissing,
null,
"Missing default value should be null for nullable attributes"
);
ok(validValues.length, "We better have at least one valid value");
element.setAttribute(contentAttr, validValues[0]);
ok(
element.hasAttribute(contentAttr),
"Should have content attribute: we just set it"
);
element[idlAttr] = null;
ok(
!element.hasAttribute(contentAttr),
"Should have removed content attribute"
);
}
}
/**
* Checks that a given attribute is correctly reflected as a boolean.
*
* @param aParameters Object object containing the parameters, which are:
* - element Element node to test on
* - attribute String name of the attribute
* OR
* attribute Object object containing two attributes, 'content' and 'idl'
*/
function reflectBoolean(aParameters) {
var element = aParameters.element;
var contentAttr =
typeof aParameters.attribute === "string"
? aParameters.attribute
: aParameters.attribute.content;
var idlAttr =
typeof aParameters.attribute === "string"
? aParameters.attribute
: aParameters.attribute.idl;
ok(
idlAttr in element,
idlAttr + " should be an IDL attribute of this element"
);
is(
typeof element[idlAttr],
"boolean",
idlAttr + " IDL attribute should be a boolean"
);
// Tests when the attribute isn't set.
is(
element.getAttribute(contentAttr),
null,
"When not set, the content attribute should be null."
);
is(
element[idlAttr],
false,
"When not set, the IDL attribute should return false"
);
/**
* Test various values.
* Each value to test is actually an object containing a 'value' property
* containing the value to actually test, a 'stringified' property containing
* the stringified value and a 'result' property containing the expected
* result when the value is set to the IDL attribute.
*/
var valuesToTest = [
{ value: true, stringified: "true", result: true },
{ value: false, stringified: "false", result: false },
{ value: "true", stringified: "true", result: true },
{ value: "false", stringified: "false", result: true },
{ value: "foo", stringified: "foo", result: true },
{ value: idlAttr, stringified: idlAttr, result: true },
{ value: contentAttr, stringified: contentAttr, result: true },
{ value: "null", stringified: "null", result: true },
{ value: "undefined", stringified: "undefined", result: true },
{ value: "", stringified: "", result: false },
{ value: undefined, stringified: "undefined", result: false },
{ value: null, stringified: "null", result: false },
{ value: +0, stringified: "0", result: false },
{ value: -0, stringified: "0", result: false },
{ value: NaN, stringified: "NaN", result: false },
{ value: 42, stringified: "42", result: true },
{ value: Infinity, stringified: "Infinity", result: true },
{ value: -Infinity, stringified: "-Infinity", result: true },
// ES5, verse 9.2.
{
value: {
toString() {
return "foo";
},
},
stringified: "foo",
result: true,
},
{
value: {
valueOf() {
return "foo";
},
},
stringified: "[object Object]",
result: true,
},
{
value: {
valueOf() {
return "quux";
},
toString: undefined,
},
stringified: "quux",
result: true,
},
{
value: {
valueOf() {
return "foo";
},
toString() {
return "bar";
},
},
stringified: "bar",
result: true,
},
{
value: {
valueOf() {
return false;
},
},
stringified: "[object Object]",
result: true,
},
{
value: { foo: false, bar: false },
stringified: "[object Object]",
result: true,
},
{ value: {}, stringified: "[object Object]", result: true },
];
valuesToTest.forEach(function (v) {
element.setAttribute(contentAttr, v.value);
is(
element[idlAttr],
true,
"IDL attribute should return always return 'true' if the content attribute has been set"
);
is(
element.getAttribute(contentAttr),
v.stringified,
"Content attribute should return the stringified value it has been set to."
);
element.removeAttribute(contentAttr);
element[idlAttr] = v.value;
is(element[idlAttr], v.result, "IDL attribute should return " + v.result);
is(
element.getAttribute(contentAttr),
v.result ? "" : null,
v.result
? "Content attribute should return the empty string."
: "Content attribute should return null."
);
is(
element.hasAttribute(contentAttr),
v.result,
v.result
? contentAttr + " should not be present"
: contentAttr + " should be present"
);
element.removeAttribute(contentAttr);
});
// Tests after removeAttribute() is called. Should be equivalent with not set.
is(
element.getAttribute(contentAttr),
null,
"When not set, the content attribute should be null."
);
is(
element[contentAttr],
false,
"When not set, the IDL attribute should return false"
);
}
/**
* Checks that a given attribute name for a given element is correctly reflected
* as an signed integer.
*
* @param aParameters Object object containing the parameters, which are:
* - element Element node to test on
* - attribute String name of the attribute
* - nonNegative Boolean true if the attribute is limited to 'non-negative numbers', false otherwise
* - defaultValue Integer [optional] default value, if one exists
*/
function reflectInt(aParameters) {
// Expected value returned by .getAttribute() when |value| has been previously passed to .setAttribute().
function expectedGetAttributeResult(value) {
return String(value);
}
function stringToInteger(value, nonNegative, defaultValue) {
// Parse: Ignore leading whitespace, find [+/-][numbers]
var result = /^[ \t\n\f\r]*([\+\-]?[0-9]+)/.exec(value);
if (result) {
var resultInt = parseInt(result[1], 10);
if (
(nonNegative ? 0 : -0x80000000) <= resultInt &&
resultInt <= 0x7fffffff
) {
// If the value is within allowed value range for signed/unsigned
// integer, return it -- but add 0 to it to convert a possible -0 into
// +0, the only zero present in the signed integer range.
return resultInt + 0;
}
}
return defaultValue;
}
// Expected value returned by .getAttribute(attr) or .attr if |value| has been set via the IDL attribute.
function expectedIdlAttributeResult(value) {
// This returns the result of calling the ES ToInt32 algorithm on value.
return value << 0;
}
var element = aParameters.element;
var attr = aParameters.attribute;
var nonNegative = aParameters.nonNegative;
var defaultValue =
aParameters.defaultValue !== undefined
? aParameters.defaultValue
: nonNegative
? -1
: 0;
ok(attr in element, attr + " should be an IDL attribute of this element");
is(
typeof element[attr],
"number",
attr + " IDL attribute should be a number"
);
// Check default value.
is(element[attr], defaultValue, "default value should be " + defaultValue);
ok(!element.hasAttribute(attr), attr + " shouldn't be present");
/**
* Test various values.
* value: The test value that will be set using both setAttribute(value) and
* element[attr] = value
*/
var valuesToTest = [
// Test numeric inputs up to max signed integer
0,
1,
55555,
2147483647,
+42,
// Test string inputs up to max signed integer
"0",
"1",
"777777",
"2147483647",
"+42",
// Test negative numeric inputs up to min signed integer
-0,
-1,
-3333,
-2147483648,
// Test negative string inputs up to min signed integer
"-0",
"-1",
"-222",
"-2147483647",
"-2147483648",
// Test numeric inputs that are outside legal 32 bit signed values
-2147483649,
-3000000000,
-4294967296,
2147483649,
4000000000,
-4294967297,
// Test string inputs with extra padding
" 1111111",
" 23456 ",
// Test non-numeric string inputs
"",
" ",
"+",
"-",
"foo",
"+foo",
"-foo",
"+ foo",
"- foo",
"+-2",
"-+2",
"++2",
"--2",
"hello1234",
"1234hello",
"444 world 555",
"why 567 what",
"-3 nots",
"2e5",
"300e2",
"42+-$",
"+42foo",
"-514not",
"\vblah",
"0x10FFFF",
"-0xABCDEF",
// Test decimal numbers
1.2345,
42.0,
3456789.1,
-2.3456,
-6789.12345,
-2147483649.1234,
// Test decimal strings
"1.2345",
"42.0",
"3456789.1",
"-2.3456",
"-6789.12345",
"-2147483649.1234",
// Test special values
undefined,
null,
NaN,
Infinity,
-Infinity,
];
valuesToTest.forEach(function (v) {
var intValue = stringToInteger(v, nonNegative, defaultValue);
element.setAttribute(attr, v);
is(
element.getAttribute(attr),
expectedGetAttributeResult(v),
element.localName +
".setAttribute(" +
attr +
", " +
v +
"), " +
element.localName +
".getAttribute(" +
attr +
") "
);
is(
element[attr],
intValue,
element.localName +
".setAttribute(" +
attr +
", " +
v +
"), " +
element.localName +
"[" +
attr +
"] "
);
element.removeAttribute(attr);
if (nonNegative && expectedIdlAttributeResult(v) < 0) {
try {
element[attr] = v;
ok(
false,
element.localName +
"[" +
attr +
"] = " +
v +
" should throw IndexSizeError"
);
} catch (e) {
is(
e.name,
"IndexSizeError",
element.localName +
"[" +
attr +
"] = " +
v +
" should throw IndexSizeError"
);
is(
e.code,
DOMException.INDEX_SIZE_ERR,
element.localName +
"[" +
attr +
"] = " +
v +
" should throw INDEX_SIZE_ERR"
);
}
} else {
element[attr] = v;
is(
element[attr],
expectedIdlAttributeResult(v),
element.localName +
"[" +
attr +
"] = " +
v +
", " +
element.localName +
"[" +
attr +
"] "
);
is(
element.getAttribute(attr),
String(expectedIdlAttributeResult(v)),
element.localName +
"[" +
attr +
"] = " +
v +
", " +
element.localName +
".getAttribute(" +
attr +
") "
);
}
element.removeAttribute(attr);
});
// Tests after removeAttribute() is called. Should be equivalent with not set.
is(
element.getAttribute(attr),
null,
"When not set, the content attribute should be null."
);
is(
element[attr],
defaultValue,
"When not set, the IDL attribute should return default value."
);
}
/**
* Checks that a given attribute is correctly reflected as a url.
*
* @param aParameters Object object containing the parameters, which are:
* - element Element node to test
* - attribute String name of the attribute
* OR
* attribute Object object containing two attributes, 'content' and 'idl'
*/
function reflectURL(aParameters) {
var element = aParameters.element;
var contentAttr =
typeof aParameters.attribute === "string"
? aParameters.attribute
: aParameters.attribute.content;
var idlAttr =
typeof aParameters.attribute === "string"
? aParameters.attribute
: aParameters.attribute.idl;
element[idlAttr] = "";
is(
element[idlAttr],
document.URL,
"Empty string should resolve to document URL"
);
}