<!doctype html>
<meta charset=utf-8>
<!-- Creating iframes is slow in browsers -->
<meta name=timeout content=long>
<title>Test handling of attributes that map to pixel length properties</title>
<link rel="help"
      href="https://html.spec.whatwg.org/multipage/rendering.html#maps-to-the-pixel-length-property">
<script src=/resources/testharness.js></script>
<script src=/resources/testharnessreport.js></script>
<body>
  <div id="container" style="display: none">
    <img id="defaultImg">
    <object id="defaultObject"></object>
    <input type="image" id="defaultInput"></input>
  </div>
<script>
 /*
  * This test tests
  * https://html.spec.whatwg.org/multipage/rendering.html#maps-to-the-pixel-length-property
  * for various elements and various values.
  */

 /*
  * Array of input/output pairs.  The input is the string to use as the
  * attribute value.  The output is the string expected as the computed style
  * for the relevant CSS property.
  */
const valid_values = [
  [ "200", "200px" ],
  [ "1007", "1007px" ],
  [ "   00523   ", "523px" ],
  [ "200.", "200px" ],
  [ "200.25", "200px" ],
  [ "200.7", "200px" ],
  [ "0", "0px" ],
  [ "-0", "0px" ],
  [ "+0", "0px" ],
  [ "+200", "200px" ],
  [ "200in", "200px" ],
  [ "200.25in", "200px" ],
  [ "   +200in    ", "200px" ],
  [ "200%", "200px" ],
  [ "200.%", "200px" ],
  [ "200.25%", "200px" ],
];

 /*
  * Array of invalid values.  These should lead to the default value of the
  * relevant CSS property.
  */
const invalid_values = [
  "-200",
  "-200px",
  "   -200",
  "+-200",
  "-+200",
];

/*
 * Array of tests.  Each test consists of the following information:
 *
 * 1) A function to call to create the element to test.  This returns a
 *    3-element array: The element to set the attribute on, the element to
 *    compute style on, and a cleanup function to call when done.
 * 2) The name of the attribute to set.
 * 3) The name of the computed style property to get.
 * 4) An element that can be used to determine the fallback style value for
 *    invalid values.
 */
const tests = [
  [ createIframe, "marginwidth", "marginLeft", document.body ],
  [ createIframe, "marginwidth", "marginRight", document.body ],
  [ createIframe, "marginheight", "marginTop", document.body ],
  [ createIframe, "marginheight", "marginBottom", document.body ],
  [ createFrame, "marginwidth", "marginLeft", document.body ],
  [ createFrame, "marginwidth", "marginRight", document.body ],
  [ createFrame, "marginheight", "marginTop", document.body ],
  [ createFrame, "marginheight", "marginBottom", document.body ],
  [ createBody, "marginwidth", "marginLeft", document.body ],
  [ createBody, "marginwidth", "marginRight", document.body ],
  [ createBody, "leftmargin", "marginLeft", document.body ],
  [ createBody, "rightmargin", "marginRight", document.body ],
  [ createBody, "marginheight", "marginTop", document.body ],
  [ createBody, "marginheight", "marginBottom", document.body ],
  [ createBody, "topmargin", "marginTop", document.body ],
  [ createBody, "bottommargin", "marginBottom", document.body ],
  [ newElem("img"), "border", "borderTopWidth", defaultImg ],
  [ newElem("img"), "border", "borderRightWidth", defaultImg ],
  [ newElem("img"), "border", "borderBottomWidth", defaultImg ],
  [ newElem("img"), "border", "borderLeftWidth", defaultImg ],
  [ newElem("object"), "border", "borderTopWidth", defaultObject ],
  [ newElem("object"), "border", "borderRightWidth", defaultObject ],
  [ newElem("object"), "border", "borderBottomWidth", defaultObject ],
  [ newElem("object"), "border", "borderLeftWidth", defaultObject ],
  [ newImageInput, "border", "borderTopWidth", defaultInput ],
  [ newImageInput, "border", "borderRightWidth", defaultInput ],
  [ newImageInput, "border", "borderBottomWidth", defaultInput ],
  [ newImageInput, "border", "borderLeftWidth", defaultInput ],
];

function newElem(name) {
  return () => {
    var elem = document.createElement(name);
    document.getElementById("container").appendChild(elem);
    return [ elem, elem, () => elem.remove() ];
  }
}

function newImageInput() {
  var elem = document.createElement("input");
  elem.type = "image";
  document.getElementById("container").appendChild(elem);
  return [ elem, elem, () => elem.remove() ];
}

function createIframe() {
  let ifr = document.createElement("iframe");
  document.body.appendChild(ifr);
  return [ ifr, ifr.contentDocument.body, () => ifr.remove() ];
 }

function createBody() {
  let ifr = document.createElement("iframe");
  document.body.appendChild(ifr);
  return [ ifr.contentDocument.body, ifr.contentDocument.body, () => ifr.remove() ];
}

function createFrame() {
  // We need to create a separate iframe to put our frameset into.
  let ifr = document.createElement("iframe");
  document.body.appendChild(ifr);
  let doc = ifr.contentDocument;
  let root = doc.documentElement;
  while (root.firstChild) {
    root.firstChild.remove();
  }
  let frameset = doc.createElement("frameset");
  frameset.setAttribute("rows", "*");
  frameset.setAttribute("cols", "*");
  let frame = doc.createElement("frame");
  frameset.appendChild(frame);
  root.appendChild(frameset);
  return [ frame, frame.contentDocument.body, () => ifr.remove() ];
}

function style(element) {
  return element.ownerDocument.defaultView.getComputedStyle(element);
}

for (let [setup, attr, prop, default_elem] of tests) {
  for (let [value, result] of valid_values) {
    let [ attr_elem, prop_elem, cleanup ] = setup();
    test(function() {
      this.add_cleanup(cleanup);
      attr_elem.setAttribute(attr, value);
      assert_equals(attr_elem.getAttribute(attr), value);
      assert_equals(style(prop_elem)[prop], result);
    }, `<${attr_elem.localName} ${attr}="${value}"> mapping to ${prop}`);
  }

  let defaultVal = style(default_elem)[prop];
  for (let value of invalid_values) {
    let [ attr_elem, prop_elem, cleanup ] = setup();
    test(function() {
      this.add_cleanup(cleanup);
      attr_elem.setAttribute(attr, value);
      assert_equals(attr_elem.getAttribute(attr), value);
      assert_equals(style(prop_elem)[prop], defaultVal);
    }, `<${attr_elem.localName} ${attr}="${value}"> mapping to ${prop}`);
  }
}
</script>
</body>
