/* Any copyright is dedicated to the Public Domain.
   http://creativecommons.org/publicdomain/zero/1.0/ */

/**
 * reflect.js is a collection of methods to test HTML attribute reflection.
 * Each of attribute is reflected differently, depending on various parameters,
 * see:
 * http://www.whatwg.org/html/#reflecting-content-attributes-in-idl-attributes
 *
 * 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 > 0, "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"
  );
}
