tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

browser_table.js (17840B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 "use strict";
      6 
      7 /* import-globals-from ../../mochitest/role.js */
      8 loadScripts({ name: "role.js", dir: MOCHITESTS_DIR });
      9 
     10 /* import-globals-from ../../mochitest/attributes.js */
     11 loadScripts({ name: "attributes.js", dir: MOCHITESTS_DIR });
     12 
     13 /**
     14 * Helper function to test table consistency.
     15 */
     16 function testTableConsistency(table, expectedRowCount, expectedColumnCount) {
     17  is(table.getAttributeValue("AXRole"), "AXTable", "Correct role for table");
     18 
     19  let tableChildren = table.getAttributeValue("AXChildren");
     20  // XXX: Should be expectedRowCount+ExpectedColumnCount+1 children, rows (incl headers) + cols + headers
     21  // if we're trying to match Safari.
     22  is(
     23    tableChildren.length,
     24    expectedRowCount + expectedColumnCount,
     25    "Table has children = rows (4) + cols (3)"
     26  );
     27  for (let i = 0; i < tableChildren.length; i++) {
     28    let currChild = tableChildren[i];
     29    if (i < expectedRowCount) {
     30      is(
     31        currChild.getAttributeValue("AXRole"),
     32        "AXRow",
     33        "Correct role for row"
     34      );
     35    } else {
     36      is(
     37        currChild.getAttributeValue("AXRole"),
     38        "AXColumn",
     39        "Correct role for col"
     40      );
     41      is(
     42        currChild.getAttributeValue("AXRoleDescription"),
     43        "column",
     44        "Correct role desc for col"
     45      );
     46    }
     47  }
     48 
     49  is(
     50    table.getAttributeValue("AXColumnCount"),
     51    expectedColumnCount,
     52    "Table has correct column count."
     53  );
     54  is(
     55    table.getAttributeValue("AXRowCount"),
     56    expectedRowCount,
     57    "Table has correct row count."
     58  );
     59 
     60  let cols = table.getAttributeValue("AXColumns");
     61  is(cols.length, expectedColumnCount, "Table has col list of correct length");
     62  for (let i = 0; i < cols.length; i++) {
     63    let currCol = cols[i];
     64    let currChildren = currCol.getAttributeValue("AXChildren");
     65    is(
     66      currChildren.length,
     67      expectedRowCount,
     68      "Column has correct number of cells"
     69    );
     70    for (let j = 0; j < currChildren.length; j++) {
     71      let currChild = currChildren[j];
     72      is(
     73        currChild.getAttributeValue("AXRole"),
     74        "AXCell",
     75        "Column child is cell"
     76      );
     77    }
     78  }
     79 
     80  let rows = table.getAttributeValue("AXRows");
     81  is(rows.length, expectedRowCount, "Table has row list of correct length");
     82  for (let i = 0; i < rows.length; i++) {
     83    let currRow = rows[i];
     84    let currChildren = currRow.getAttributeValue("AXChildren");
     85    is(
     86      currChildren.length,
     87      expectedColumnCount,
     88      "Row has correct number of cells"
     89    );
     90    for (let j = 0; j < currChildren.length; j++) {
     91      let currChild = currChildren[j];
     92      is(currChild.getAttributeValue("AXRole"), "AXCell", "Row child is cell");
     93    }
     94  }
     95 }
     96 
     97 add_setup(async function () {
     98  await SpecialPowers.pushPrefEnv({
     99    set: [["test.wait300msAfterTabSwitch", true]],
    100  });
    101 });
    102 
    103 /**
    104 * Test table, columns, rows
    105 */
    106 addAccessibleTask(
    107  `<table id="customers">
    108    <tbody>
    109      <tr id="firstrow"><th>Company</th><th>Contact</th><th>Country</th></tr>
    110      <tr><td>Alfreds Futterkiste</td><td>Maria Anders</td><td>Germany</td></tr>
    111      <tr><td>Centro comercial Moctezuma</td><td>Francisco Chang</td><td>Mexico</td></tr>
    112      <tr><td>Ernst Handel</td><td>Roland Mendel</td><td>Austria</td></tr>
    113    </tbody>
    114  </table>`,
    115  async (browser, accDoc) => {
    116    let table = getNativeInterface(accDoc, "customers");
    117    testTableConsistency(table, 4, 3);
    118 
    119    const rowText = [
    120      "Madrigal Electromotive GmbH",
    121      "Lydia Rodarte-Quayle",
    122      "Germany",
    123    ];
    124    let reorder = waitForEvent(EVENT_REORDER, "customers");
    125    await SpecialPowers.spawn(browser, [rowText], _rowText => {
    126      let tr = content.document.createElement("tr");
    127      for (let t of _rowText) {
    128        let td = content.document.createElement("td");
    129        td.textContent = t;
    130        tr.appendChild(td);
    131      }
    132      content.document.getElementById("customers").appendChild(tr);
    133    });
    134    await reorder;
    135 
    136    let cols = table.getAttributeValue("AXColumns");
    137    is(cols.length, 3, "Table has col list of correct length");
    138    for (let i = 0; i < cols.length; i++) {
    139      let currCol = cols[i];
    140      let currChildren = currCol.getAttributeValue("AXChildren");
    141      is(currChildren.length, 5, "Column has correct number of cells");
    142      let lastCell = currChildren[currChildren.length - 1];
    143      let cellChildren = lastCell.getAttributeValue("AXChildren");
    144      is(cellChildren.length, 1, "Cell has a single text child");
    145      is(
    146        cellChildren[0].getAttributeValue("AXRole"),
    147        "AXStaticText",
    148        "Correct role for cell child"
    149      );
    150      is(
    151        cellChildren[0].getAttributeValue("AXValue"),
    152        rowText[i],
    153        "Correct text for cell"
    154      );
    155    }
    156 
    157    reorder = waitForEvent(EVENT_REORDER, "firstrow");
    158    await SpecialPowers.spawn(browser, [], () => {
    159      let td = content.document.createElement("td");
    160      td.textContent = "Ticker";
    161      content.document.getElementById("firstrow").appendChild(td);
    162    });
    163    await reorder;
    164 
    165    cols = table.getAttributeValue("AXColumns");
    166    is(cols.length, 4, "Table has col list of correct length");
    167    is(
    168      cols[cols.length - 1].getAttributeValue("AXChildren").length,
    169      1,
    170      "Last column has single child"
    171    );
    172 
    173    reorder = waitForEvent(
    174      EVENT_REORDER,
    175      e => e.accessible.role == ROLE_DOCUMENT
    176    );
    177    await SpecialPowers.spawn(browser, [], () => {
    178      content.document.getElementById("customers").remove();
    179    });
    180    await reorder;
    181 
    182    try {
    183      cols[0].getAttributeValue("AXChildren");
    184      ok(false, "Getting children from column of expired table should fail");
    185    } catch (e) {
    186      ok(true, "Getting children from column of expired table should fail");
    187    }
    188  }
    189 );
    190 
    191 addAccessibleTask(
    192  `<table id="table">
    193    <tr>
    194      <th colspan="2" id="header1">Header 1</th>
    195      <th id="header2">Header 2</th>
    196    </tr>
    197    <tr>
    198      <td id="cell1">one</td>
    199      <td id="cell2" rowspan="2">two</td>
    200      <td id="cell3">three</td>
    201    </tr>
    202    <tr>
    203    <td id="cell4">four</td>
    204    <td id="cell5">five</td>
    205  </tr>
    206  </table>`,
    207  (browser, accDoc) => {
    208    let table = getNativeInterface(accDoc, "table");
    209 
    210    let getCellAt = (col, row) =>
    211      table.getParameterizedAttributeValue("AXCellForColumnAndRow", [col, row]);
    212 
    213    function testCell(cell, expectedId, expectedColRange, expectedRowRange) {
    214      is(
    215        cell.getAttributeValue("AXDOMIdentifier"),
    216        expectedId,
    217        "Correct DOM Identifier"
    218      );
    219      Assert.deepEqual(
    220        cell.getAttributeValue("AXColumnIndexRange"),
    221        expectedColRange,
    222        "Correct column range"
    223      );
    224      Assert.deepEqual(
    225        cell.getAttributeValue("AXRowIndexRange"),
    226        expectedRowRange,
    227        "Correct row range"
    228      );
    229    }
    230 
    231    testCell(getCellAt(0, 0), "header1", [0, 2], [0, 1]);
    232    testCell(getCellAt(1, 0), "header1", [0, 2], [0, 1]);
    233    testCell(getCellAt(2, 0), "header2", [2, 1], [0, 1]);
    234 
    235    testCell(getCellAt(0, 1), "cell1", [0, 1], [1, 1]);
    236    testCell(getCellAt(1, 1), "cell2", [1, 1], [1, 2]);
    237    testCell(getCellAt(2, 1), "cell3", [2, 1], [1, 1]);
    238 
    239    testCell(getCellAt(0, 2), "cell4", [0, 1], [2, 1]);
    240    testCell(getCellAt(1, 2), "cell2", [1, 1], [1, 2]);
    241    testCell(getCellAt(2, 2), "cell5", [2, 1], [2, 1]);
    242 
    243    let colHeaders = table.getAttributeValue("AXColumnHeaderUIElements");
    244    Assert.deepEqual(
    245      colHeaders.map(c => c.getAttributeValue("AXDOMIdentifier")),
    246      ["header1", "header1", "header2"],
    247      "Correct column headers"
    248    );
    249  }
    250 );
    251 
    252 addAccessibleTask(
    253  `<table id="table">
    254    <tr>
    255      <td>Foo</td>
    256    </tr>
    257  </table>`,
    258  (browser, accDoc) => {
    259    // Make sure we guess this table to be a layout table.
    260    testAttrs(
    261      findAccessibleChildByID(accDoc, "table"),
    262      { "layout-guess": "true" },
    263      true
    264    );
    265 
    266    let table = getNativeInterface(accDoc, "table");
    267    is(
    268      table.getAttributeValue("AXRole"),
    269      "AXGroup",
    270      "Correct role (AXGroup) for layout table"
    271    );
    272 
    273    let children = table.getAttributeValue("AXChildren");
    274    is(
    275      children.length,
    276      1,
    277      "Layout table has single child (no additional columns)"
    278    );
    279  }
    280 );
    281 
    282 addAccessibleTask(
    283  `<div id="table" role="table">
    284    <span style="display: block;">
    285      <div role="row">
    286        <div role="cell">Cell 1</div>
    287        <div role="cell">Cell 2</div>
    288      </div>
    289    </span>
    290    <span style="display: block;">
    291      <div role="row">
    292        <span style="display: block;">
    293          <div role="cell">Cell 3</div>
    294          <div role="cell">Cell 4</div>
    295        </span>
    296      </div>
    297    </span>
    298  </div>`,
    299  async (browser, accDoc) => {
    300    let table = getNativeInterface(accDoc, "table");
    301    testTableConsistency(table, 2, 2);
    302  }
    303 );
    304 
    305 /*
    306 * After executing function 'change' which operates on 'elem', verify the specified
    307 * 'event' (if not null) is fired on elem. After the event, check if the given
    308 * native accessible 'table' is a layout or data table by role using 'isLayout'.
    309 */
    310 async function testIsLayout(table, elem, event, change, isLayout) {
    311  info(
    312    "Changing " +
    313      elem +
    314      ", expecting table change to " +
    315      (isLayout ? "AXGroup" : "AXTable")
    316  );
    317  const toWait = event ? waitForEvent(event, elem) : null;
    318  await change();
    319  if (toWait) {
    320    await toWait;
    321  }
    322  let intendedRole = isLayout ? "AXGroup" : "AXTable";
    323  await untilCacheIs(
    324    () => table.getAttributeValue("AXRole"),
    325    intendedRole,
    326    "Table role correct after change"
    327  );
    328 }
    329 
    330 /*
    331 * The following attributes should fire an attribute changed
    332 * event, which in turn invalidates the layout-table cache
    333 * associated with the given table. After adding and removing
    334 * each attr, verify the table is a data or layout table,
    335 * appropriately. Attrs: summary, abbr, scope, headers
    336 */
    337 addAccessibleTask(
    338  `<table id="table" summary="example summary">
    339    <tr role="presentation">
    340      <td id="cellOne">cell1</td>
    341      <td>cell2</td>
    342    </tr>
    343    <tr>
    344      <td id="cellThree">cell3</td>
    345      <td>cell4</td>
    346    </tr>
    347  </table>`,
    348  async (browser, accDoc) => {
    349    let table = getNativeInterface(accDoc, "table");
    350    // summary attr should take precedence over role="presentation" to make this
    351    // a data table
    352    is(table.getAttributeValue("AXRole"), "AXTable", "Table is data table");
    353 
    354    info("Removing summary attr");
    355    // after summary is removed, we should have a layout table
    356    await testIsLayout(
    357      table,
    358      "table",
    359      EVENT_OBJECT_ATTRIBUTE_CHANGED,
    360      async () => {
    361        await SpecialPowers.spawn(browser, [], () => {
    362          content.document.getElementById("table").removeAttribute("summary");
    363        });
    364      },
    365      true
    366    );
    367 
    368    info("Setting abbr attr");
    369    // after abbr is set we should have a data table again
    370    await testIsLayout(
    371      table,
    372      "cellThree",
    373      EVENT_OBJECT_ATTRIBUTE_CHANGED,
    374      async () => {
    375        await SpecialPowers.spawn(browser, [], () => {
    376          content.document
    377            .getElementById("cellThree")
    378            .setAttribute("abbr", "hello world");
    379        });
    380      },
    381      false
    382    );
    383 
    384    info("Removing abbr attr");
    385    // after abbr is removed we should have a layout table again
    386    await testIsLayout(
    387      table,
    388      "cellThree",
    389      EVENT_OBJECT_ATTRIBUTE_CHANGED,
    390      async () => {
    391        await SpecialPowers.spawn(browser, [], () => {
    392          content.document.getElementById("cellThree").removeAttribute("abbr");
    393        });
    394      },
    395      true
    396    );
    397 
    398    info("Setting scope attr");
    399    // after scope is set we should have a data table again
    400    await testIsLayout(
    401      table,
    402      "cellThree",
    403      EVENT_OBJECT_ATTRIBUTE_CHANGED,
    404      async () => {
    405        await SpecialPowers.spawn(browser, [], () => {
    406          content.document
    407            .getElementById("cellThree")
    408            .setAttribute("scope", "col");
    409        });
    410      },
    411      false
    412    );
    413 
    414    info("Removing scope attr");
    415    // remove scope should give layout
    416    await testIsLayout(
    417      table,
    418      "cellThree",
    419      EVENT_OBJECT_ATTRIBUTE_CHANGED,
    420      async () => {
    421        await SpecialPowers.spawn(browser, [], () => {
    422          content.document.getElementById("cellThree").removeAttribute("scope");
    423        });
    424      },
    425      true
    426    );
    427 
    428    info("Setting headers attr");
    429    // add headers attr should give data
    430    await testIsLayout(
    431      table,
    432      "cellThree",
    433      EVENT_OBJECT_ATTRIBUTE_CHANGED,
    434      async () => {
    435        await SpecialPowers.spawn(browser, [], () => {
    436          content.document
    437            .getElementById("cellThree")
    438            .setAttribute("headers", "cellOne");
    439        });
    440      },
    441      false
    442    );
    443 
    444    info("Removing headers attr");
    445    // remove headers attr should give layout
    446    await testIsLayout(
    447      table,
    448      "cellThree",
    449      EVENT_OBJECT_ATTRIBUTE_CHANGED,
    450      async () => {
    451        await SpecialPowers.spawn(browser, [], () => {
    452          content.document
    453            .getElementById("cellThree")
    454            .removeAttribute("headers");
    455        });
    456      },
    457      true
    458    );
    459  }
    460 );
    461 
    462 /*
    463 * The following style changes should fire a table style changed
    464 * event, which in turn invalidates the layout-table cache
    465 * associated with the given table.
    466 */
    467 addAccessibleTask(
    468  `<table id="table">
    469    <tr id="rowOne">
    470      <td id="cellOne">cell1</td>
    471      <td>cell2</td>
    472    </tr>
    473    <tr>
    474      <td>cell3</td>
    475      <td>cell4</td>
    476    </tr>
    477  </table>`,
    478  async (browser, accDoc) => {
    479    let table = getNativeInterface(accDoc, "table");
    480    // we should start as a layout table
    481    is(table.getAttributeValue("AXRole"), "AXGroup", "Table is layout table");
    482 
    483    info("Adding cell border");
    484    // after cell border added, we should have a data table
    485    await testIsLayout(
    486      table,
    487      "cellOne",
    488      null,
    489      async () => {
    490        await SpecialPowers.spawn(browser, [], () => {
    491          content.document
    492            .getElementById("cellOne")
    493            .style.setProperty("border", "5px solid green");
    494        });
    495      },
    496      false
    497    );
    498 
    499    info("Removing cell border");
    500    // after cell border removed, we should have a layout table
    501    await testIsLayout(
    502      table,
    503      "cellOne",
    504      null,
    505      async () => {
    506        await SpecialPowers.spawn(browser, [], () => {
    507          content.document
    508            .getElementById("cellOne")
    509            .style.removeProperty("border");
    510        });
    511      },
    512      true
    513    );
    514 
    515    info("Adding row background");
    516    // after row background added, we should have a data table
    517    await testIsLayout(
    518      table,
    519      "rowOne",
    520      null,
    521      async () => {
    522        await SpecialPowers.spawn(browser, [], () => {
    523          content.document
    524            .getElementById("rowOne")
    525            .style.setProperty("background-color", "green");
    526        });
    527      },
    528      false
    529    );
    530 
    531    info("Removing row background");
    532    // after row background removed, we should have a layout table
    533    await testIsLayout(
    534      table,
    535      "rowOne",
    536      null,
    537      async () => {
    538        await SpecialPowers.spawn(browser, [], () => {
    539          content.document
    540            .getElementById("rowOne")
    541            .style.removeProperty("background-color");
    542        });
    543      },
    544      true
    545    );
    546  }
    547 );
    548 
    549 /*
    550 * thead/tbody elements with click handlers should:
    551 * (a) render as AXGroup elements
    552 * (b) expose their rows as part of their parent table's AXRows array
    553 */
    554 addAccessibleTask(
    555  `<table id="table">
    556    <thead id="thead">
    557      <tr><td>head row</td></tr>
    558    </thead>
    559    <tbody id="tbody">
    560      <tr><td>body row</td></tr>
    561      <tr><td>another body row</td></tr>
    562    </tbody>
    563  </table>`,
    564  async (browser, accDoc) => {
    565    let table = getNativeInterface(accDoc, "table");
    566 
    567    // No click handlers present on thead/tbody
    568    let tableChildren = table.getAttributeValue("AXChildren");
    569    let tableRows = table.getAttributeValue("AXRows");
    570 
    571    is(tableChildren.length, 4, "Table has four children (3 row + 1 col)");
    572    is(tableRows.length, 3, "Table has three rows");
    573 
    574    for (let i = 0; i < tableChildren.length; i++) {
    575      const child = tableChildren[i];
    576      if (i < 3) {
    577        is(
    578          child.getAttributeValue("AXRole"),
    579          "AXRow",
    580          "Table's first 3 children are rows"
    581        );
    582      } else {
    583        is(
    584          child.getAttributeValue("AXRole"),
    585          "AXColumn",
    586          "Table's last child is a column"
    587        );
    588      }
    589    }
    590    const reorder = waitForEvent(EVENT_REORDER);
    591    await invokeContentTask(browser, [], () => {
    592      const head = content.document.getElementById("thead");
    593      const body = content.document.getElementById("tbody");
    594 
    595      head.addEventListener("click", function () {});
    596      body.addEventListener("click", function () {});
    597    });
    598    await reorder;
    599 
    600    // Click handlers present
    601    tableChildren = table.getAttributeValue("AXChildren");
    602 
    603    is(tableChildren.length, 3, "Table has three children (2 groups + 1 col)");
    604    is(
    605      tableChildren[0].getAttributeValue("AXRole"),
    606      "AXGroup",
    607      "Child one is a group"
    608    );
    609    is(
    610      tableChildren[0].getAttributeValue("AXChildren").length,
    611      1,
    612      "Child one has one child"
    613    );
    614 
    615    is(
    616      tableChildren[1].getAttributeValue("AXRole"),
    617      "AXGroup",
    618      "Child two is a group"
    619    );
    620    is(
    621      tableChildren[1].getAttributeValue("AXChildren").length,
    622      2,
    623      "Child two has two children"
    624    );
    625 
    626    is(
    627      tableChildren[2].getAttributeValue("AXRole"),
    628      "AXColumn",
    629      "Child three is a col"
    630    );
    631 
    632    tableRows = table.getAttributeValue("AXRows");
    633    is(tableRows.length, 3, "Table has three rows");
    634  }
    635 );