<!DOCTYPE HTML>
<html>
<head>
  <meta charset="utf-8">
  <title>Tests for document.getWireframe()</title>
  <script src="/tests/SimpleTest/SimpleTest.js"></script>
  <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
  <script>
    /**
     * This test creates some simple webpages, captures wireframes for them, and
     * then compares them against some expected wireframe structures.
     */

    /**
     * Converts some RGB values into the same uint32_t nsColor representation
     * that getWireframe uses.
     *
     * @param {Number} r
     *   Red color value.
     * @param {Number} g
     *   Green color value.
     * @param {Number} b
     *   Blue color value.
     * @returns {Number}
     *   The red, green and blue values composed in a single uint32_t-compatible
     *   value.
     */
    function nscolor(r, g, b) {
      return (255 << 24 | b << 16 | g << 8 | r) >>> 0;
    }

    const WHITE_NSCOLOR = nscolor(255, 255, 255);

    const RED_RGB = "rgb(255, 0, 0)";
    const RED_NSCOLOR = nscolor(255, 0, 0);
    const GREEN_RGB = "rgb(0, 255, 0)";
    const GREEN_NSCOLOR = nscolor(0, 255, 0);
    const BLUE_RGB = "rgb(0, 0, 255)";
    const BLUE_NSCOLOR = nscolor(0, 0, 255);
    const BLACK_RGB = "rgb(0, 0, 0)";
    const BLACK_NSCOLOR = nscolor(0, 0, 0);
    const BUILDER = "http://mochi.test:8888/document-builder.sjs?html=";
    const TEST_PATH = "http://mochi.test:8888/tests/dom/base/test/";

    /**
     * This array contains the definition of each test. Each test is an object
     * that expects two properties:
     *
     * {String} html
     *   The markup to be loaded in the page iframe that a wireframe will be
     *   generated for.
     * {Object} expectedWireframe
     *   An approximation of the wireframe that should be generated. The
     *   approximation is due to the fact that different platforms and
     *   execution environments might produce slightly different positioning
     *   of wireframe rects. We skip comparing the position of the rects, and
     *   only look at their dimensions (sometimes only one dimension if the
     *   other is potentially more variable - for example with text). Properties
     *   included in this object will do a strict comparison with the generated
     *   wireframe. Properties in the generated wireframe that are not in the
     *   expectedWireframe will be ignored.
     */
    const kTests = [{
      // Base case: a simple solid background with a single character in the
      // foreground.
      html: `
        <html>
        <style>
          body {
            width: 500px;
            height: 500px;
            background-color: ${RED_RGB};
            color: ${BLACK_RGB};
            overflow: hidden;
            font-size: 12px;
          }
        </style>
        <body>x</body>
        </html>
      `,
      expectedWireframe: {
        canvasBackground: RED_NSCOLOR,
        rects: [{
          color: BLACK_NSCOLOR,
          height: 12,
          type: "text",
        }]
      },
    }, {
      // Additional background on top of the main background.
      html: `
        <html>
        <style>
          body {
            background-color: ${RED_RGB};
            color: ${BLACK_RGB};
            overflow: hidden;
            font-size: 12px;
          }
          div {
            position: absolute;
            top: 0;
            left: 0;
            width: 20px;
            height: 20px;
            background-color: ${GREEN_RGB};
          }
        </style>
        <body>
          <div>x</div>
        </body>
        </html>
      `,
      expectedWireframe: {
        canvasBackground: RED_NSCOLOR,
        rects: [{
          color: GREEN_NSCOLOR,
          height: 20,
          width: 20,
          type: "background",
        }, {
          color: BLACK_NSCOLOR,
          height: 12,
          type: "text",
        }]
      },
    }, {
      // Image on top of the main background with another background
      // floating in the top right.
      html: `
        <html>
        <style>
          body {
            background-color: ${RED_RGB};
            color: ${BLACK_RGB};
            overflow: hidden;
            font-size: 12px;
          }
          div {
            position: absolute;
            top: 0;
            right: 0;
            width: 20px;
            height: 20px;
            background-color: ${GREEN_RGB};
          }
          img {
            position: absolute;
            top: 0;
            left: 0;
            height: 50px;
            width: 50px;
          }
        </style>
        <body>
          <img src="${TEST_PATH}/green.png"/>
          <div>x</div>
        </body>
        </html>
      `,
      expectedWireframe: {
        canvasBackground: RED_NSCOLOR,
        rects: [{
          color: 0,
          height: 50,
          width: 50,
          type: "image",
        }, {
          color: GREEN_NSCOLOR,
          height: 20,
          width: 20,
          type: "background",
        }, {
          color: BLACK_NSCOLOR,
          height: 12,
          type: "text",
        }]
      },
    }, {
      // Image on top of the main background with another background
      // floating over the image
      html: `
        <html>
        <style>
          body {
            width: 500px;
            height: 500px;
            background-color: ${RED_RGB};
            color: ${BLACK_RGB};
            overflow: hidden;
            font-size: 12px;
          }
          div {
            position: absolute;
            top: 0;
            left: 0;
            width: 20px;
            height: 20px;
            background-color: ${BLUE_RGB};
          }
          img {
            position: absolute;
            top: 0;
            left: 0;
            height: 50px;
            width: 50px;
          }
        </style>
        <body>
          <img src="${TEST_PATH}/green.png"/>
          <div>x</div>
        </body>
        </html>
      `,
      expectedWireframe: {
        canvasBackground: RED_NSCOLOR,
        rects: [{
          color: 0,
          height: 50,
          width: 50,
          type: "image",
        }, {
          color: BLUE_NSCOLOR,
          height: 20,
          width: 20,
          type: "background",
        }, {
          color: BLACK_NSCOLOR,
          height: 12,
          type: "text",
        }]
      },
    }, {
      // Bug 1759919 - Transformed items incorrectly causing us to not keep hit
      // testing stuff.
      html: `
        <!doctype html>
        <style>:root { background-color: white; } body { margin: 0 }</style>
        <div style="transform: rotate(90deg); width: 20px; height: 20px; overflow: clip;">
          <div style="width: 100%; height: 100%; background-color: blue;"></div>
        </div>
        <div style="transform: rotate(90deg); width: 20px; height: 20px; overflow: clip;">
          <div style="width: 100%; height: 100%; background-color: red;"></div>
        </div>
      `,
      expectedWireframe: {
        canvasBackground: WHITE_NSCOLOR,
        rects: [{
          color: RED_NSCOLOR,
          height: 20,
          width: 20,
          x: 0,
          y: 0,
          type: "background",
        }, {
          color: BLUE_NSCOLOR,
          height: 20,
          width: 20,
          x: 0,
          y: 20,
          type: "background",
        }],
      }
    }];

    /**
     * Returns a Promise once page has been loaded in frame.
     *
     * @param {Element} frame
     *   The iframe to load the page in.
     * @param {String} page
     *   The URL of the page to load in the frame.
     * @returns Promise
     * @resolves undefined
     *   Once the load event has fired for the frame.
     */
    function loadInIframe(frame, page) {
      return new Promise(resolve => {
        frame.addEventListener("load", resolve, { once: true });
        frame.src = page;
      });
    }

    /**
     * Compares a generated wireframe to an Object that contains some or all of
     * the expected structure of the generated wireframe.
     *
     * If the wireframe doesn't contain the expected number of rects, the
     * serialized structure of both the wireframe and approximateWireframe will
     * be dumped to stdout.
     *
     * @param {Wireframe} wireframe
     *   A wireframe generated via document.getWireframe()
     * @param {Object} approximateWireframe
     *   An object that closely resembles a wireframe but the rects in the
     *   rects property do not need to contain all of the properties expected
     *   in a WireframeTaggedRect. Skipped properties won't be checked.
     */
    function assertApproximateWireframe(wireframe, approximateWireframe) {
      is(
        wireframe.canvasBackground,
        approximateWireframe.canvasBackground,
        "Canvas backgrounds match."
      );
      is(
        wireframe.rects.length,
        approximateWireframe.rects.length,
        "Same rect count"
      );
      if (wireframe.rects.length != approximateWireframe.rects.length) {
        dump(
          "Generated wireframe: " + JSON.stringify(wireframe, null, "\t") + "\n"
        );
        dump(
          "Expected approximate wireframe: " +
            JSON.stringify(approximateWireframe, null, "\t") +
            "\n"
        );
      }

      for (let index = 0; index < approximateWireframe.length; ++index) {
        let wireframeRect = wireframe.rects[index];
        let approximationRect = approximateWireframe.rects[index];
        for (let prop of approximationRect) {
          is(
            wireframeRect[prop],
            approximationRect[prop],
            `Property ${prop} should be equal.`
          );
        }
      }
    }

    add_task(async () => {
      const iframe = document.getElementById("iframe");

      for (let testDefinition of kTests) {
        let pageURL = BUILDER + encodeURIComponent(testDefinition.html);
        await loadInIframe(iframe, pageURL);
        let wireframe = SpecialPowers.wrap(
          iframe.contentDocument
        ).getWireframe();
        assertApproximateWireframe(wireframe, testDefinition.expectedWireframe);
      }
    });
  </script>
</head>
<body>
<p id="display"></p>
<div id="content" style="display: none"></div>
<pre id="test"></pre>
<iframe id="iframe"></iframe>
</body>
</html>
