tor-browser

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

browser_text_input.js (19376B)


      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 /* import-globals-from ../../mochitest/states.js */
      9 loadScripts(
     10  { name: "role.js", dir: MOCHITESTS_DIR },
     11  { name: "states.js", dir: MOCHITESTS_DIR }
     12 );
     13 
     14 function testValueChangedEventData(
     15  macIface,
     16  data,
     17  expectedId,
     18  expectedChangeValue,
     19  expectedEditType,
     20  expectedWordAtLeft
     21 ) {
     22  is(
     23    data.AXTextChangeElement.getAttributeValue("AXDOMIdentifier"),
     24    expectedId,
     25    "Correct AXTextChangeElement"
     26  );
     27  is(
     28    data.AXTextStateChangeType,
     29    AXTextStateChangeTypeEdit,
     30    "Correct AXTextStateChangeType"
     31  );
     32 
     33  let changeValues = data.AXTextChangeValues;
     34  is(changeValues.length, 1, "One element in AXTextChangeValues");
     35  is(
     36    changeValues[0].AXTextChangeValue,
     37    expectedChangeValue,
     38    "Correct AXTextChangeValue"
     39  );
     40  is(
     41    changeValues[0].AXTextEditType,
     42    expectedEditType,
     43    "Correct AXTextEditType"
     44  );
     45 
     46  let textMarker = changeValues[0].AXTextChangeValueStartMarker;
     47  ok(textMarker, "There is a AXTextChangeValueStartMarker");
     48  let range = macIface.getParameterizedAttributeValue(
     49    "AXLeftWordTextMarkerRangeForTextMarker",
     50    textMarker
     51  );
     52  let str = macIface.getParameterizedAttributeValue(
     53    "AXStringForTextMarkerRange",
     54    range,
     55    "correct word before caret"
     56  );
     57  is(str, expectedWordAtLeft);
     58 }
     59 
     60 // Return true if the first given object a subset of the second
     61 function isSubset(subset, superset) {
     62  if (typeof subset != "object" || typeof superset != "object") {
     63    return superset == subset;
     64  }
     65 
     66  for (let [prop, val] of Object.entries(subset)) {
     67    if (!isSubset(val, superset[prop])) {
     68      return false;
     69    }
     70  }
     71 
     72  return true;
     73 }
     74 
     75 function matchWebArea(expectedId, expectedInfo) {
     76  return (iface, data) => {
     77    if (!data) {
     78      return false;
     79    }
     80 
     81    let textChangeElemID =
     82      data.AXTextChangeElement.getAttributeValue("AXDOMIdentifier");
     83 
     84    return (
     85      iface.getAttributeValue("AXRole") == "AXWebArea" &&
     86      textChangeElemID == expectedId &&
     87      isSubset(expectedInfo, data)
     88    );
     89  };
     90 }
     91 
     92 function matchInput(expectedId, expectedInfo) {
     93  return (iface, data) => {
     94    if (!data) {
     95      return false;
     96    }
     97 
     98    return (
     99      iface.getAttributeValue("AXDOMIdentifier") == expectedId &&
    100      isSubset(expectedInfo, data)
    101    );
    102  };
    103 }
    104 
    105 async function synthKeyAndTestSelectionChanged(
    106  synthKey,
    107  synthEvent,
    108  expectedId,
    109  expectedSelectionString,
    110  expectedSelectionInfo
    111 ) {
    112  let selectionChangedEvents = Promise.all([
    113    waitForMacEventWithInfo(
    114      "AXSelectedTextChanged",
    115      matchWebArea(expectedId, expectedSelectionInfo)
    116    ),
    117    waitForMacEventWithInfo(
    118      "AXSelectedTextChanged",
    119      matchInput(expectedId, expectedSelectionInfo)
    120    ),
    121  ]);
    122 
    123  EventUtils.synthesizeKey(synthKey, synthEvent);
    124  let [webareaEvent, inputEvent] = await selectionChangedEvents;
    125  is(
    126    inputEvent.data.AXTextChangeElement.getAttributeValue("AXDOMIdentifier"),
    127    expectedId,
    128    "Correct AXTextChangeElement"
    129  );
    130 
    131  let rangeString = inputEvent.macIface.getParameterizedAttributeValue(
    132    "AXStringForTextMarkerRange",
    133    inputEvent.data.AXSelectedTextMarkerRange
    134  );
    135  is(
    136    rangeString,
    137    expectedSelectionString,
    138    `selection has correct value (${expectedSelectionString})`
    139  );
    140 
    141  let rangeBounds = inputEvent.macIface.getParameterizedAttributeValue(
    142    "AXBoundsForTextMarkerRange",
    143    inputEvent.data.AXSelectedTextMarkerRange
    144  );
    145 
    146  ok(
    147    rangeBounds.origin && rangeBounds.size && rangeBounds.size[0],
    148    "Selection range has bounds"
    149  );
    150 
    151  is(
    152    webareaEvent.macIface.getAttributeValue("AXDOMIdentifier"),
    153    "body",
    154    "Input event target is top-level WebArea"
    155  );
    156  rangeString = webareaEvent.macIface.getParameterizedAttributeValue(
    157    "AXStringForTextMarkerRange",
    158    inputEvent.data.AXSelectedTextMarkerRange
    159  );
    160  is(
    161    rangeString,
    162    expectedSelectionString,
    163    `selection has correct value (${expectedSelectionString}) via top document`
    164  );
    165 
    166  return inputEvent;
    167 }
    168 
    169 function testSelectionEventLeftChar(event, expectedChar) {
    170  const selStart = event.macIface.getParameterizedAttributeValue(
    171    "AXStartTextMarkerForTextMarkerRange",
    172    event.data.AXSelectedTextMarkerRange
    173  );
    174  const selLeft = event.macIface.getParameterizedAttributeValue(
    175    "AXPreviousTextMarkerForTextMarker",
    176    selStart
    177  );
    178  const leftCharRange = event.macIface.getParameterizedAttributeValue(
    179    "AXTextMarkerRangeForUnorderedTextMarkers",
    180    [selLeft, selStart]
    181  );
    182  const leftCharString = event.macIface.getParameterizedAttributeValue(
    183    "AXStringForTextMarkerRange",
    184    leftCharRange
    185  );
    186  is(leftCharString, expectedChar, "Left character is correct");
    187 }
    188 
    189 function testSelectionEventLine(event, expectedLine) {
    190  const selStart = event.macIface.getParameterizedAttributeValue(
    191    "AXStartTextMarkerForTextMarkerRange",
    192    event.data.AXSelectedTextMarkerRange
    193  );
    194  const lineRange = event.macIface.getParameterizedAttributeValue(
    195    "AXLineTextMarkerRangeForTextMarker",
    196    selStart
    197  );
    198  const lineString = event.macIface.getParameterizedAttributeValue(
    199    "AXStringForTextMarkerRange",
    200    lineRange
    201  );
    202  is(lineString, expectedLine, "Line is correct");
    203 }
    204 
    205 async function synthKeyAndTestValueChanged(
    206  synthKey,
    207  synthEvent,
    208  expectedId,
    209  expectedTextSelectionId,
    210  expectedChangeValue,
    211  expectedEditType,
    212  expectedWordAtLeft
    213 ) {
    214  let valueChangedEvents = Promise.all([
    215    waitForMacEvent(
    216      "AXSelectedTextChanged",
    217      matchWebArea(expectedTextSelectionId, {
    218        AXTextStateChangeType: AXTextStateChangeTypeSelectionMove,
    219      })
    220    ),
    221    waitForMacEvent(
    222      "AXSelectedTextChanged",
    223      matchInput(expectedTextSelectionId, {
    224        AXTextStateChangeType: AXTextStateChangeTypeSelectionMove,
    225      })
    226    ),
    227    waitForMacEventWithInfo(
    228      "AXValueChanged",
    229      matchWebArea(expectedId, {
    230        AXTextStateChangeType: AXTextStateChangeTypeEdit,
    231        AXTextChangeValues: [
    232          {
    233            AXTextChangeValue: expectedChangeValue,
    234            AXTextEditType: expectedEditType,
    235          },
    236        ],
    237      })
    238    ),
    239    waitForMacEventWithInfo(
    240      "AXValueChanged",
    241      matchInput(expectedId, {
    242        AXTextStateChangeType: AXTextStateChangeTypeEdit,
    243        AXTextChangeValues: [
    244          {
    245            AXTextChangeValue: expectedChangeValue,
    246            AXTextEditType: expectedEditType,
    247          },
    248        ],
    249      })
    250    ),
    251  ]);
    252 
    253  EventUtils.synthesizeKey(synthKey, synthEvent);
    254  let [, , webareaEvent, inputEvent] = await valueChangedEvents;
    255 
    256  testValueChangedEventData(
    257    webareaEvent.macIface,
    258    webareaEvent.data,
    259    expectedId,
    260    expectedChangeValue,
    261    expectedEditType,
    262    expectedWordAtLeft
    263  );
    264  testValueChangedEventData(
    265    inputEvent.macIface,
    266    inputEvent.data,
    267    expectedId,
    268    expectedChangeValue,
    269    expectedEditType,
    270    expectedWordAtLeft
    271  );
    272 }
    273 
    274 async function focusIntoInput(accDoc, inputId, innerContainerId) {
    275  let selectionId = innerContainerId ? innerContainerId : inputId;
    276  let input = getNativeInterface(accDoc, inputId);
    277  ok(!input.getAttributeValue("AXFocused"), "input is not focused");
    278  ok(input.isAttributeSettable("AXFocused"), "input is focusable");
    279  let events = Promise.all([
    280    waitForMacEvent(
    281      "AXFocusedUIElementChanged",
    282      iface => iface.getAttributeValue("AXDOMIdentifier") == inputId
    283    ),
    284    waitForMacEventWithInfo(
    285      "AXSelectedTextChanged",
    286      matchWebArea(selectionId, {
    287        AXTextStateChangeType: AXTextStateChangeTypeSelectionMove,
    288      })
    289    ),
    290    waitForMacEventWithInfo(
    291      "AXSelectedTextChanged",
    292      matchInput(selectionId, {
    293        AXTextStateChangeType: AXTextStateChangeTypeSelectionMove,
    294      })
    295    ),
    296  ]);
    297  input.setAttributeValue("AXFocused", true);
    298  await events;
    299 }
    300 
    301 async function focusIntoInputAndType(accDoc, inputId, innerContainerId) {
    302  let selectionId = innerContainerId ? innerContainerId : inputId;
    303  await focusIntoInput(accDoc, inputId, innerContainerId);
    304 
    305  async function testTextInput(
    306    synthKey,
    307    expectedChangeValue,
    308    expectedWordAtLeft
    309  ) {
    310    await synthKeyAndTestValueChanged(
    311      synthKey,
    312      null,
    313      inputId,
    314      selectionId,
    315      expectedChangeValue,
    316      AXTextEditTypeTyping,
    317      expectedWordAtLeft
    318    );
    319  }
    320 
    321  await testTextInput("h", "h", "h");
    322  await testTextInput("e", "e", "he");
    323  await testTextInput("l", "l", "hel");
    324  await testTextInput("l", "l", "hell");
    325  await testTextInput("o", "o", "hello");
    326  await testTextInput(" ", " ", "hello");
    327  // You would expect this to be useless but this is what VO
    328  // consumes. I guess it concats the inserted text data to the
    329  // word to the left of the marker.
    330  await testTextInput("w", "w", " ");
    331  await testTextInput("o", "o", "wo");
    332  await testTextInput("r", "r", "wor");
    333  await testTextInput("l", "l", "worl");
    334  await testTextInput("d", "d", "world");
    335 
    336  async function testTextDelete(expectedChangeValue, expectedWordAtLeft) {
    337    await synthKeyAndTestValueChanged(
    338      "KEY_Backspace",
    339      null,
    340      inputId,
    341      selectionId,
    342      expectedChangeValue,
    343      AXTextEditTypeDelete,
    344      expectedWordAtLeft
    345    );
    346  }
    347 
    348  await testTextDelete("d", "worl");
    349  await testTextDelete("l", "wor");
    350 
    351  await synthKeyAndTestSelectionChanged(
    352    "KEY_ArrowLeft",
    353    null,
    354    selectionId,
    355    "",
    356    {
    357      AXTextStateChangeType: AXTextStateChangeTypeSelectionMove,
    358      AXTextSelectionDirection: AXTextSelectionDirectionPrevious,
    359      AXTextSelectionGranularity: AXTextSelectionGranularityCharacter,
    360    }
    361  );
    362  await synthKeyAndTestSelectionChanged(
    363    "KEY_ArrowLeft",
    364    { shiftKey: true },
    365    selectionId,
    366    "o",
    367    {
    368      AXTextStateChangeType: AXTextStateChangeTypeSelectionExtend,
    369      AXTextSelectionDirection: AXTextSelectionDirectionPrevious,
    370      AXTextSelectionGranularity: AXTextSelectionGranularityCharacter,
    371    }
    372  );
    373  await synthKeyAndTestSelectionChanged(
    374    "KEY_ArrowLeft",
    375    { shiftKey: true },
    376    selectionId,
    377    "wo",
    378    {
    379      AXTextStateChangeType: AXTextStateChangeTypeSelectionExtend,
    380      AXTextSelectionDirection: AXTextSelectionDirectionPrevious,
    381      AXTextSelectionGranularity: AXTextSelectionGranularityCharacter,
    382    }
    383  );
    384  await synthKeyAndTestSelectionChanged(
    385    "KEY_ArrowLeft",
    386    null,
    387    selectionId,
    388    "",
    389    { AXTextStateChangeType: AXTextStateChangeTypeSelectionMove }
    390  );
    391  await synthKeyAndTestSelectionChanged(
    392    "KEY_ArrowLeft",
    393    { shiftKey: true, metaKey: true },
    394    selectionId,
    395    "hello ",
    396    {
    397      AXTextStateChangeType: AXTextStateChangeTypeSelectionExtend,
    398      AXTextSelectionDirection: AXTextSelectionDirectionBeginning,
    399      AXTextSelectionGranularity: AXTextSelectionGranularityLine,
    400    }
    401  );
    402  await synthKeyAndTestSelectionChanged(
    403    "KEY_ArrowLeft",
    404    null,
    405    selectionId,
    406    "",
    407    { AXTextStateChangeType: AXTextStateChangeTypeSelectionMove }
    408  );
    409  await synthKeyAndTestSelectionChanged(
    410    "KEY_ArrowRight",
    411    { shiftKey: true, altKey: true },
    412    selectionId,
    413    "hello",
    414    {
    415      AXTextStateChangeType: AXTextStateChangeTypeSelectionExtend,
    416      AXTextSelectionDirection: AXTextSelectionDirectionNext,
    417      AXTextSelectionGranularity: AXTextSelectionGranularityWord,
    418    }
    419  );
    420 }
    421 
    422 // Test text input
    423 addAccessibleTask(
    424  `<a href="#">link</a> <input id="input">`,
    425  async (browser, accDoc) => {
    426    await focusIntoInputAndType(accDoc, "input");
    427  },
    428  { topLevel: true, iframe: true, remoteIframe: true }
    429 );
    430 
    431 // Test content editable
    432 addAccessibleTask(
    433  `<div id="input" contentEditable="true" tabindex="0" role="textbox" aria-multiline="true"><div id="inner"><br /></div></div>`,
    434  async (browser, accDoc) => {
    435    const inner = getNativeInterface(accDoc, "inner");
    436    const editableAncestor = inner.getAttributeValue("AXEditableAncestor");
    437    is(
    438      editableAncestor.getAttributeValue("AXDOMIdentifier"),
    439      "input",
    440      "Editable ancestor is input"
    441    );
    442    await focusIntoInputAndType(accDoc, "input");
    443  }
    444 );
    445 
    446 // Test input that gets role::EDITCOMBOBOX
    447 addAccessibleTask(`<input type="text" id="box">`, async (browser, accDoc) => {
    448  const box = getNativeInterface(accDoc, "box");
    449  const editableAncestor = box.getAttributeValue("AXEditableAncestor");
    450  is(
    451    editableAncestor.getAttributeValue("AXDOMIdentifier"),
    452    "box",
    453    "Editable ancestor is box itself"
    454  );
    455  await focusIntoInputAndType(accDoc, "box");
    456 });
    457 
    458 // Test multiline caret control in a text area
    459 addAccessibleTask(
    460  `<textarea id="input" cols="15">one two three four five six seven eight</textarea>`,
    461  async (browser, accDoc) => {
    462    await focusIntoInput(accDoc, "input");
    463 
    464    await synthKeyAndTestSelectionChanged("KEY_ArrowRight", null, "input", "", {
    465      AXTextStateChangeType: AXTextStateChangeTypeSelectionMove,
    466      AXTextSelectionDirection: AXTextSelectionDirectionNext,
    467      AXTextSelectionGranularity: AXTextSelectionGranularityCharacter,
    468    });
    469 
    470    await synthKeyAndTestSelectionChanged("KEY_ArrowDown", null, "input", "", {
    471      AXTextStateChangeType: AXTextStateChangeTypeSelectionMove,
    472      AXTextSelectionDirection: AXTextSelectionDirectionNext,
    473      AXTextSelectionGranularity: AXTextSelectionGranularityLine,
    474    });
    475 
    476    await synthKeyAndTestSelectionChanged(
    477      "KEY_ArrowLeft",
    478      { metaKey: true },
    479      "input",
    480      "",
    481      {
    482        AXTextStateChangeType: AXTextStateChangeTypeSelectionMove,
    483        AXTextSelectionDirection: AXTextSelectionDirectionBeginning,
    484        AXTextSelectionGranularity: AXTextSelectionGranularityLine,
    485      }
    486    );
    487 
    488    await synthKeyAndTestSelectionChanged(
    489      "KEY_ArrowRight",
    490      { metaKey: true },
    491      "input",
    492      "",
    493      {
    494        AXTextStateChangeType: AXTextStateChangeTypeSelectionMove,
    495        AXTextSelectionDirection: AXTextSelectionDirectionEnd,
    496        AXTextSelectionGranularity: AXTextSelectionGranularityLine,
    497      }
    498    );
    499  },
    500  { topLevel: true, iframe: true, remoteIframe: true }
    501 );
    502 
    503 /**
    504 * Test that the caret returns the correct marker when it is positioned after
    505 * the last character (to facilitate appending text).
    506 */
    507 addAccessibleTask(
    508  `<input id="input" value="abc">`,
    509  async function (browser, docAcc) {
    510    await focusIntoInput(docAcc, "input");
    511 
    512    let event = await synthKeyAndTestSelectionChanged(
    513      "KEY_ArrowRight",
    514      null,
    515      "input",
    516      "",
    517      {
    518        AXTextStateChangeType: AXTextStateChangeTypeSelectionMove,
    519        AXTextSelectionDirection: AXTextSelectionDirectionNext,
    520        AXTextSelectionGranularity: AXTextSelectionGranularityCharacter,
    521      }
    522    );
    523    testSelectionEventLeftChar(event, "a");
    524    event = await synthKeyAndTestSelectionChanged(
    525      "KEY_ArrowRight",
    526      null,
    527      "input",
    528      "",
    529      {
    530        AXTextStateChangeType: AXTextStateChangeTypeSelectionMove,
    531        AXTextSelectionDirection: AXTextSelectionDirectionNext,
    532        AXTextSelectionGranularity: AXTextSelectionGranularityCharacter,
    533      }
    534    );
    535    testSelectionEventLeftChar(event, "b");
    536    event = await synthKeyAndTestSelectionChanged(
    537      "KEY_ArrowRight",
    538      null,
    539      "input",
    540      "",
    541      {
    542        AXTextStateChangeType: AXTextStateChangeTypeSelectionMove,
    543        AXTextSelectionDirection: AXTextSelectionDirectionNext,
    544        AXTextSelectionGranularity: AXTextSelectionGranularityCharacter,
    545      }
    546    );
    547    testSelectionEventLeftChar(event, "c");
    548  },
    549  { chrome: true, topLevel: true }
    550 );
    551 
    552 /**
    553 * Test that the caret returns the correct line when the caret is at the start
    554 * of the line.
    555 */
    556 addAccessibleTask(
    557  `
    558 <textarea id="hard">ab
    559 cd
    560 ef
    561 
    562 gh
    563 </textarea>
    564 <div role="textbox" id="wrapped" contenteditable style="width: 1ch;">a b c</div>
    565  `,
    566  async function (browser, docAcc) {
    567    let hard = getNativeInterface(docAcc, "hard");
    568    await focusIntoInput(docAcc, "hard");
    569    is(hard.getAttributeValue("AXInsertionPointLineNumber"), 0);
    570    let event = await synthKeyAndTestSelectionChanged(
    571      "KEY_ArrowDown",
    572      null,
    573      "hard",
    574      "",
    575      {
    576        AXTextStateChangeType: AXTextStateChangeTypeSelectionMove,
    577        AXTextSelectionDirection: AXTextSelectionDirectionNext,
    578        AXTextSelectionGranularity: AXTextSelectionGranularityLine,
    579      }
    580    );
    581    testSelectionEventLine(event, "cd");
    582    is(hard.getAttributeValue("AXInsertionPointLineNumber"), 1);
    583    event = await synthKeyAndTestSelectionChanged(
    584      "KEY_ArrowDown",
    585      null,
    586      "hard",
    587      "",
    588      {
    589        AXTextStateChangeType: AXTextStateChangeTypeSelectionMove,
    590        AXTextSelectionDirection: AXTextSelectionDirectionNext,
    591        AXTextSelectionGranularity: AXTextSelectionGranularityLine,
    592      }
    593    );
    594    testSelectionEventLine(event, "ef");
    595    is(hard.getAttributeValue("AXInsertionPointLineNumber"), 2);
    596    event = await synthKeyAndTestSelectionChanged(
    597      "KEY_ArrowDown",
    598      null,
    599      "hard",
    600      "",
    601      {
    602        AXTextStateChangeType: AXTextStateChangeTypeSelectionMove,
    603        AXTextSelectionDirection: AXTextSelectionDirectionNext,
    604        AXTextSelectionGranularity: AXTextSelectionGranularityLine,
    605      }
    606    );
    607    testSelectionEventLine(event, "");
    608    is(hard.getAttributeValue("AXInsertionPointLineNumber"), 3);
    609    event = await synthKeyAndTestSelectionChanged(
    610      "KEY_ArrowDown",
    611      null,
    612      "hard",
    613      "",
    614      {
    615        AXTextStateChangeType: AXTextStateChangeTypeSelectionMove,
    616        AXTextSelectionDirection: AXTextSelectionDirectionNext,
    617        AXTextSelectionGranularity: AXTextSelectionGranularityLine,
    618      }
    619    );
    620    testSelectionEventLine(event, "gh");
    621    is(hard.getAttributeValue("AXInsertionPointLineNumber"), 4);
    622    event = await synthKeyAndTestSelectionChanged(
    623      "KEY_ArrowDown",
    624      null,
    625      "hard",
    626      "",
    627      {
    628        AXTextStateChangeType: AXTextStateChangeTypeSelectionMove,
    629        AXTextSelectionDirection: AXTextSelectionDirectionNext,
    630        AXTextSelectionGranularity: AXTextSelectionGranularityLine,
    631      }
    632    );
    633    testSelectionEventLine(event, "");
    634    is(hard.getAttributeValue("AXInsertionPointLineNumber"), 5);
    635 
    636    let wrapped = getNativeInterface(docAcc, "wrapped");
    637    await focusIntoInput(docAcc, "wrapped");
    638    is(wrapped.getAttributeValue("AXInsertionPointLineNumber"), 0);
    639    event = await synthKeyAndTestSelectionChanged(
    640      "KEY_ArrowDown",
    641      null,
    642      "wrapped",
    643      "",
    644      {
    645        AXTextStateChangeType: AXTextStateChangeTypeSelectionMove,
    646        AXTextSelectionDirection: AXTextSelectionDirectionNext,
    647        AXTextSelectionGranularity: AXTextSelectionGranularityLine,
    648      }
    649    );
    650    testSelectionEventLine(event, "b ");
    651    is(wrapped.getAttributeValue("AXInsertionPointLineNumber"), 1);
    652    event = await synthKeyAndTestSelectionChanged(
    653      "KEY_ArrowDown",
    654      null,
    655      "wrapped",
    656      "",
    657      {
    658        AXTextStateChangeType: AXTextStateChangeTypeSelectionMove,
    659        AXTextSelectionDirection: AXTextSelectionDirectionNext,
    660        AXTextSelectionGranularity: AXTextSelectionGranularityLine,
    661      }
    662    );
    663    testSelectionEventLine(event, "c");
    664    is(wrapped.getAttributeValue("AXInsertionPointLineNumber"), 2);
    665  },
    666  { chrome: true, topLevel: true }
    667 );