tor-browser

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

head.js (9207B)


      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 /* exported createTextLeafPoint, DIRECTION_NEXT, DIRECTION_PREVIOUS,
      8 BOUNDARY_FLAG_DEFAULT, BOUNDARY_FLAG_INCLUDE_ORIGIN,
      9 BOUNDARY_FLAG_STOP_IN_EDITABLE, BOUNDARY_FLAG_SKIP_LIST_ITEM_MARKER,
     10 readablePoint, testPointEqual, textBoundaryGenerator, testBoundarySequence,
     11 isFinalValueCorrect, isFinalValueCorrect, testInsertText, testDeleteText,
     12 testCopyText, testPasteText, testCutText, testSetTextContents,
     13 textAttrRangesMatch */
     14 
     15 // Load the shared-head file first.
     16 Services.scriptloader.loadSubScript(
     17  "chrome://mochitests/content/browser/accessible/tests/browser/shared-head.js",
     18  this
     19 );
     20 
     21 // Loading and common.js from accessible/tests/mochitest/ for all tests, as
     22 // well as promisified-events.js.
     23 
     24 /* import-globals-from ../../mochitest/role.js */
     25 
     26 loadScripts(
     27  { name: "common.js", dir: MOCHITESTS_DIR },
     28  { name: "text.js", dir: MOCHITESTS_DIR },
     29  { name: "role.js", dir: MOCHITESTS_DIR },
     30  { name: "promisified-events.js", dir: MOCHITESTS_DIR }
     31 );
     32 
     33 const DIRECTION_NEXT = Ci.nsIAccessibleTextLeafPoint.DIRECTION_NEXT;
     34 const DIRECTION_PREVIOUS = Ci.nsIAccessibleTextLeafPoint.DIRECTION_PREVIOUS;
     35 
     36 const BOUNDARY_FLAG_DEFAULT =
     37  Ci.nsIAccessibleTextLeafPoint.BOUNDARY_FLAG_DEFAULT;
     38 const BOUNDARY_FLAG_INCLUDE_ORIGIN =
     39  Ci.nsIAccessibleTextLeafPoint.BOUNDARY_FLAG_INCLUDE_ORIGIN;
     40 const BOUNDARY_FLAG_STOP_IN_EDITABLE =
     41  Ci.nsIAccessibleTextLeafPoint.BOUNDARY_FLAG_STOP_IN_EDITABLE;
     42 const BOUNDARY_FLAG_SKIP_LIST_ITEM_MARKER =
     43  Ci.nsIAccessibleTextLeafPoint.BOUNDARY_FLAG_SKIP_LIST_ITEM_MARKER;
     44 
     45 function createTextLeafPoint(acc, offset) {
     46  let accService = Cc["@mozilla.org/accessibilityService;1"].getService(
     47    nsIAccessibilityService
     48  );
     49 
     50  return accService.createTextLeafPoint(acc, offset);
     51 }
     52 
     53 // Converts an nsIAccessibleTextLeafPoint into a human/machine
     54 // readable tuple with a readable accessible and the offset within it.
     55 // For a point text leaf it would look like this: ["hello", 2],
     56 // For a point in an empty input it would look like this ["input#name", 0]
     57 function readablePoint(point) {
     58  const readableLeaf = acc => {
     59    let tagName = getAccessibleTagName(acc);
     60    if (tagName && !tagName.startsWith("_moz_generated")) {
     61      let domNodeID = getAccessibleDOMNodeID(acc);
     62      if (domNodeID) {
     63        return `${tagName}#${domNodeID}`;
     64      }
     65      return tagName;
     66    }
     67 
     68    return acc.name;
     69  };
     70 
     71  return [readableLeaf(point.accessible), point.offset];
     72 }
     73 
     74 function sequenceEqual(val, expected, msg) {
     75  Assert.deepEqual(val, expected, msg);
     76 }
     77 
     78 // eslint-disable-next-line camelcase
     79 function sequenceEqualTodo(val, expected, msg) {
     80  todo_is(JSON.stringify(val), JSON.stringify(expected), msg);
     81 }
     82 
     83 function pointsEqual(pointA, pointB) {
     84  return (
     85    pointA.offset == pointB.offset && pointA.accessible == pointB.accessible
     86  );
     87 }
     88 
     89 function testPointEqual(pointA, pointB, msg) {
     90  is(pointA.offset, pointB.offset, `Offset mismatch - ${msg}`);
     91  is(pointA.accessible, pointB.accessible, `Accessible mismatch - ${msg}`);
     92 }
     93 
     94 function* textBoundaryGenerator(
     95  firstPoint,
     96  boundaryType,
     97  direction,
     98  flags = BOUNDARY_FLAG_DEFAULT
     99 ) {
    100  // Our start point should be inclusive of the given point.
    101  let nextLeafPoint = firstPoint.findBoundary(
    102    boundaryType,
    103    direction,
    104    flags | BOUNDARY_FLAG_INCLUDE_ORIGIN
    105  );
    106  let textLeafPoint = null;
    107 
    108  do {
    109    textLeafPoint = nextLeafPoint;
    110    yield textLeafPoint;
    111    nextLeafPoint = textLeafPoint.findBoundary(boundaryType, direction, flags);
    112  } while (!pointsEqual(textLeafPoint, nextLeafPoint));
    113 }
    114 
    115 // This function takes FindBoundary arguments and an expected sequence
    116 // of boundary points formatted with readablePoint.
    117 // For example, word starts would look like this:
    118 // [["one two", 0], ["one two", 4], ["one two", 7]]
    119 function testBoundarySequence(
    120  startPoint,
    121  boundaryType,
    122  direction,
    123  expectedSequence,
    124  msg,
    125  options = {}
    126 ) {
    127  let sequence = [
    128    ...textBoundaryGenerator(
    129      startPoint,
    130      boundaryType,
    131      direction,
    132      options.flags ? options.flags : BOUNDARY_FLAG_DEFAULT
    133    ),
    134  ];
    135  (options.todo ? sequenceEqualTodo : sequenceEqual)(
    136    sequence.map(readablePoint),
    137    expectedSequence,
    138    msg
    139  );
    140 }
    141 
    142 ///////////////////////////////////////////////////////////////////////////////
    143 // Editable text
    144 
    145 async function waitForCopy(browser) {
    146  await BrowserTestUtils.waitForContentEvent(browser, "copy", false, () => {
    147    return true;
    148  });
    149 
    150  let clipboardData = await invokeContentTask(browser, [], async () => {
    151    let text = await content.navigator.clipboard.readText();
    152    return text;
    153  });
    154 
    155  return clipboardData;
    156 }
    157 
    158 async function isFinalValueCorrect(
    159  browser,
    160  acc,
    161  expectedTextLeafs,
    162  msg = "Value is correct"
    163 ) {
    164  let value =
    165    acc.role == ROLE_ENTRY
    166      ? acc.value
    167      : await invokeContentTask(browser, [], () => {
    168          return content.document.body.textContent;
    169        });
    170 
    171  let [before, text, after] = expectedTextLeafs;
    172  let finalValue =
    173    before && after && !text
    174      ? [before, after].join(" ")
    175      : [before, text, after].join("");
    176 
    177  is(value.replace("\xa0", " "), finalValue, msg);
    178 }
    179 
    180 function waitForTextChangeEvents(acc, eventSeq) {
    181  let events = eventSeq.map(eventType => {
    182    return [eventType, acc];
    183  });
    184 
    185  if (acc.role == ROLE_ENTRY) {
    186    events.push([EVENT_TEXT_VALUE_CHANGE, acc]);
    187  }
    188 
    189  return waitForEvents(events);
    190 }
    191 
    192 async function testSetTextContents(acc, text, staticContentOffset, events) {
    193  acc.QueryInterface(nsIAccessibleEditableText);
    194  let evtPromise = waitForTextChangeEvents(acc, events);
    195  acc.setTextContents(text);
    196  let evt = (await evtPromise)[0];
    197  evt.QueryInterface(nsIAccessibleTextChangeEvent);
    198  is(evt.start, staticContentOffset);
    199 }
    200 
    201 async function testInsertText(
    202  acc,
    203  textToInsert,
    204  insertOffset,
    205  staticContentOffset
    206 ) {
    207  acc.QueryInterface(nsIAccessibleEditableText);
    208 
    209  let evtPromise = waitForTextChangeEvents(acc, [EVENT_TEXT_INSERTED]);
    210  acc.insertText(textToInsert, staticContentOffset + insertOffset);
    211  let evt = (await evtPromise)[0];
    212  evt.QueryInterface(nsIAccessibleTextChangeEvent);
    213  is(evt.start, staticContentOffset + insertOffset);
    214 }
    215 
    216 async function testDeleteText(
    217  acc,
    218  startOffset,
    219  endOffset,
    220  staticContentOffset
    221 ) {
    222  acc.QueryInterface(nsIAccessibleEditableText);
    223 
    224  let evtPromise = waitForTextChangeEvents(acc, [EVENT_TEXT_REMOVED]);
    225  acc.deleteText(
    226    staticContentOffset + startOffset,
    227    staticContentOffset + endOffset
    228  );
    229  let evt = (await evtPromise)[0];
    230  evt.QueryInterface(nsIAccessibleTextChangeEvent);
    231  is(evt.start, staticContentOffset + startOffset);
    232 }
    233 
    234 async function testCopyText(
    235  acc,
    236  startOffset,
    237  endOffset,
    238  staticContentOffset,
    239  browser,
    240  aExpectedClipboard = null
    241 ) {
    242  acc.QueryInterface(nsIAccessibleEditableText);
    243  let copied = waitForCopy(browser);
    244  acc.copyText(
    245    staticContentOffset + startOffset,
    246    staticContentOffset + endOffset
    247  );
    248  let clipboardText = await copied;
    249  if (aExpectedClipboard != null) {
    250    is(clipboardText, aExpectedClipboard, "Correct text in clipboard");
    251  }
    252 }
    253 
    254 async function testPasteText(acc, insertOffset, staticContentOffset) {
    255  acc.QueryInterface(nsIAccessibleEditableText);
    256  let evtPromise = waitForTextChangeEvents(acc, [EVENT_TEXT_INSERTED]);
    257  acc.pasteText(staticContentOffset + insertOffset);
    258 
    259  let evt = (await evtPromise)[0];
    260  evt.QueryInterface(nsIAccessibleTextChangeEvent);
    261  // XXX: In non-headless mode pasting text produces several text leaves
    262  // and the offset is not what we expect.
    263  // is(evt.start, staticContentOffset + insertOffset);
    264 }
    265 
    266 async function testCutText(acc, startOffset, endOffset, staticContentOffset) {
    267  acc.QueryInterface(nsIAccessibleEditableText);
    268  let evtPromise = waitForTextChangeEvents(acc, [EVENT_TEXT_REMOVED]);
    269  acc.cutText(
    270    staticContentOffset + startOffset,
    271    staticContentOffset + endOffset
    272  );
    273 
    274  let evt = (await evtPromise)[0];
    275  evt.QueryInterface(nsIAccessibleTextChangeEvent);
    276  is(evt.start, staticContentOffset + startOffset);
    277 }
    278 
    279 /**
    280 * Check if the given ranges match the ranges in the given text accessible which
    281 * expose the given attribute names/values.
    282 */
    283 function textAttrRangesMatch(acc, ranges, attrs) {
    284  acc.QueryInterface(nsIAccessibleText);
    285  let offset = 0;
    286  let expectedRanges = [...ranges];
    287  let charCount = acc.characterCount;
    288  while (offset < charCount) {
    289    let start = {};
    290    let end = {};
    291    let attributes = acc.getTextAttributes(false, offset, start, end);
    292    offset = end.value;
    293    let matched = true;
    294    for (const attr in attrs) {
    295      try {
    296        if (attributes.getStringProperty(attr) != attrs[attr]) {
    297          // Attribute value doesn't match.
    298          matched = false;
    299        }
    300      } catch (err) {
    301        // Attribute doesn't exist.
    302        matched = false;
    303      }
    304    }
    305    if (!matched) {
    306      continue;
    307    }
    308    let expected = expectedRanges.shift();
    309    if (!expected || expected[0] != start.value || expected[1] != end.value) {
    310      return false;
    311    }
    312  }
    313 
    314  return !expectedRanges.length;
    315 }