/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";
requestLongerTimeout(2);

/**
 * Test MEMBER_OF relation caching on HTML radio buttons
 */
addAccessibleTask(
  `
  <input type="radio" id="r1">I have no name<br>
  <input type="radio" id="r2">I also have no name<br>
  <input type="radio" id="r3" name="n">I have a name<br>
  <input type="radio" id="r4" name="a">I have a different name<br>
  <fieldset role="radiogroup">
    <input type="radio" id="r5" name="n">I have an already used name
     and am in a different part of the tree
    <input type="radio" id="r6" name="r">I have a different name but am
     in the same group
  </fieldset>`,
  async function (browser, accDoc) {
    const r1 = findAccessibleChildByID(accDoc, "r1");
    const r2 = findAccessibleChildByID(accDoc, "r2");
    const r3 = findAccessibleChildByID(accDoc, "r3");
    const r4 = findAccessibleChildByID(accDoc, "r4");
    const r5 = findAccessibleChildByID(accDoc, "r5");
    const r6 = findAccessibleChildByID(accDoc, "r6");

    await testCachedRelation(r1, RELATION_MEMBER_OF, null);
    await testCachedRelation(r2, RELATION_MEMBER_OF, null);
    await testCachedRelation(r3, RELATION_MEMBER_OF, [r3, r5]);
    await testCachedRelation(r4, RELATION_MEMBER_OF, r4);
    await testCachedRelation(r5, RELATION_MEMBER_OF, [r3, r5]);
    await testCachedRelation(r6, RELATION_MEMBER_OF, r6);

    await invokeContentTask(browser, [], () => {
      content.document.getElementById("r5").name = "a";
    });

    await testCachedRelation(r3, RELATION_MEMBER_OF, r3);
    await testCachedRelation(r4, RELATION_MEMBER_OF, [r5, r4]);
    await testCachedRelation(r5, RELATION_MEMBER_OF, [r5, r4]);
  },
  { chrome: true, iframe: true, remoteIframe: true }
);

/*
 * Test MEMBER_OF relation caching on aria radio buttons
 */
addAccessibleTask(
  `
  <div role="radio" id="r1">I have no radio group</div><br>
  <fieldset role="radiogroup" id="fs">
    <div role="radio" id="r2">hello</div><br>
    <div role="radio" id="r3">world</div><br>
  </fieldset>`,
  async function (browser, accDoc) {
    const r1 = findAccessibleChildByID(accDoc, "r1");
    const r2 = findAccessibleChildByID(accDoc, "r2");
    let r3 = findAccessibleChildByID(accDoc, "r3");

    await testCachedRelation(r1, RELATION_MEMBER_OF, null);
    await testCachedRelation(r2, RELATION_MEMBER_OF, [r2, r3]);
    await testCachedRelation(r3, RELATION_MEMBER_OF, [r2, r3]);
    const r = waitForEvent(EVENT_INNER_REORDER, "fs");
    await invokeContentTask(browser, [], () => {
      let innerRadio = content.document.getElementById("r3");
      content.document.body.appendChild(innerRadio);
    });
    await r;

    r3 = findAccessibleChildByID(accDoc, "r3");
    await testCachedRelation(r1, RELATION_MEMBER_OF, null);
    await testCachedRelation(r2, RELATION_MEMBER_OF, r2);
    await testCachedRelation(r3, RELATION_MEMBER_OF, null);
  },
  {
    chrome: true,
    iframe: true,
    remoteIframe: true,
  }
);

/*
 * Test mutation of LABEL relations via accessible shutdown.
 */
addAccessibleTask(
  `
  <div id="d"></div>
  <label id="l">
    <select id="s">
  `,
  async function (browser, accDoc) {
    const label = findAccessibleChildByID(accDoc, "l");
    const select = findAccessibleChildByID(accDoc, "s");
    const div = findAccessibleChildByID(accDoc, "d");

    await testCachedRelation(label, RELATION_LABEL_FOR, select);
    await testCachedRelation(select, RELATION_LABELLED_BY, label);
    await testCachedRelation(div, RELATION_LABELLED_BY, null);

    const r = waitForEvent(EVENT_REORDER, "l");
    await invokeContentTask(browser, [], () => {
      content.document.getElementById("s").remove();
    });
    await r;
    await invokeContentTask(browser, [], () => {
      const l = content.document.getElementById("l");
      l.htmlFor = "d";
    });
    await testCachedRelation(label, RELATION_LABEL_FOR, div);
    await testCachedRelation(div, RELATION_LABELLED_BY, label);
  },
  {
    chrome: false,
    iframe: true,
    remoteIframe: true,
    topLevel: true,
  }
);

/*
 * Test mutation of LABEL relations via DOM ID reuse.
 */
addAccessibleTask(
  `
  <div id="label">before</div><input id="input" aria-labelledby="label">
  `,
  async function (browser, accDoc) {
    let label = findAccessibleChildByID(accDoc, "label");
    const input = findAccessibleChildByID(accDoc, "input");

    await testCachedRelation(label, RELATION_LABEL_FOR, input);
    await testCachedRelation(input, RELATION_LABELLED_BY, label);

    const r = waitForEvent(EVENT_REORDER, accDoc);
    await invokeContentTask(browser, [], () => {
      content.document.getElementById("label").remove();
      let l = content.document.createElement("div");
      l.id = "label";
      l.textContent = "after";
      content.document.body.insertBefore(
        l,
        content.document.getElementById("input")
      );
    });
    await r;
    label = findAccessibleChildByID(accDoc, "label");
    await testCachedRelation(label, RELATION_LABEL_FOR, input);
    await testCachedRelation(input, RELATION_LABELLED_BY, label);
  },
  {
    chrome: true,
    iframe: true,
    remoteIframe: true,
  }
);

/*
 * Test LINKS_TO relation caching an anchor with multiple hashes
 */
addAccessibleTask(
  `
  <a id="link" href="#foo#bar">Origin</a><br>
  <a id="anchor" name="foo#bar">Destination`,
  async function (browser, accDoc) {
    const link = findAccessibleChildByID(accDoc, "link");
    const anchor = findAccessibleChildByID(accDoc, "anchor");

    await testCachedRelation(link, RELATION_LINKS_TO, anchor);
  },
  {
    chrome: true,
    // IA2 doesn't have a LINKS_TO relation and Windows non-cached
    // RemoteAccessible uses IA2, so we can't run these tests in this case.
    topLevel: true,
    iframe: true,
    remoteIframe: true,
  }
);

/*
 * Test mutation of LABEL relations via accessible shutdown.
 */
addAccessibleTask(
  `
  <div id="d"></div>
  <label id="l">
    <select id="s">
  `,
  async function (browser, accDoc) {
    const label = findAccessibleChildByID(accDoc, "l");
    const select = findAccessibleChildByID(accDoc, "s");
    const div = findAccessibleChildByID(accDoc, "d");

    await testCachedRelation(label, RELATION_LABEL_FOR, select);
    await testCachedRelation(select, RELATION_LABELLED_BY, label);
    await testCachedRelation(div, RELATION_LABELLED_BY, null);
    await untilCacheOk(() => {
      try {
        // We should get an acc ID back from this, but we don't have a way of
        // verifying its correctness -- it should be the ID of the select.
        return label.cache.getStringProperty("for");
      } catch (e) {
        ok(false, "Exception thrown while trying to read from the cache");
        return false;
      }
    }, "Label for relation exists");

    const r = waitForEvent(EVENT_REORDER, "l");
    await invokeContentTask(browser, [], () => {
      content.document.getElementById("s").remove();
    });
    await r;
    await untilCacheOk(() => {
      try {
        label.cache.getStringProperty("for");
      } catch (e) {
        // This property should no longer exist in the cache, so we should
        // get an exception if we try to fetch it.
        return true;
      }
      return false;
    }, "Label for relation exists");

    await invokeContentTask(browser, [], () => {
      const l = content.document.getElementById("l");
      l.htmlFor = "d";
    });
    await testCachedRelation(label, RELATION_LABEL_FOR, div);
    await testCachedRelation(div, RELATION_LABELLED_BY, label);
  },
  {
    /**
     * This functionality is broken in our LocalAcccessible implementation,
     * so we avoid running this test in chrome or when the cache is off.
     */
    chrome: false,
    iframe: true,
    remoteIframe: true,
    topLevel: true,
  }
);

/**
 * Test label relations on HTML figure/figcaption.
 */
addAccessibleTask(
  `
<figure id="figure1">
  before
  <figcaption id="caption1">caption1</figcaption>
  after
</figure>
<figure id="figure2" aria-labelledby="label">
  <figcaption id="caption2">caption2</figure>
</figure>
<div id="label">label</div>
  `,
  async function (browser, docAcc) {
    const figure1 = findAccessibleChildByID(docAcc, "figure1");
    let caption1 = findAccessibleChildByID(docAcc, "caption1");
    await testCachedRelation(figure1, RELATION_LABELLED_BY, caption1);
    await testCachedRelation(caption1, RELATION_LABEL_FOR, figure1);

    info("Hiding caption1");
    let mutated = waitForEvent(EVENT_HIDE, caption1);
    await invokeContentTask(browser, [], () => {
      content.document.getElementById("caption1").hidden = true;
    });
    await mutated;
    await testCachedRelation(figure1, RELATION_LABELLED_BY, null);

    info("Showing caption1");
    mutated = waitForEvent(EVENT_SHOW, "caption1");
    await invokeContentTask(browser, [], () => {
      content.document.getElementById("caption1").hidden = false;
    });
    caption1 = (await mutated).accessible;
    await testCachedRelation(figure1, RELATION_LABELLED_BY, caption1);
    await testCachedRelation(caption1, RELATION_LABEL_FOR, figure1);

    const figure2 = findAccessibleChildByID(docAcc, "figure2");
    const caption2 = findAccessibleChildByID(docAcc, "caption2");
    const label = findAccessibleChildByID(docAcc, "label");
    await testCachedRelation(figure2, RELATION_LABELLED_BY, [label, caption2]);
    await testCachedRelation(caption2, RELATION_LABEL_FOR, figure2);
    await testCachedRelation(label, RELATION_LABEL_FOR, figure2);
  },
  { chrome: true, topLevel: true }
);