Source code
Revision control
Copy as Markdown
Other Tools
/* 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
"use strict";
/* exported createTextLeafPoint, DIRECTION_NEXT, DIRECTION_PREVIOUS,
BOUNDARY_FLAG_DEFAULT, BOUNDARY_FLAG_INCLUDE_ORIGIN,
BOUNDARY_FLAG_STOP_IN_EDITABLE, BOUNDARY_FLAG_SKIP_LIST_ITEM_MARKER,
readablePoint, testPointEqual, textBoundaryGenerator, testBoundarySequence,
isFinalValueCorrect, isFinalValueCorrect, testInsertText, testDeleteText,
testCopyText, testPasteText, testCutText, testSetTextContents,
textAttrRangesMatch */
// Load the shared-head file first.
Services.scriptloader.loadSubScript(
this
);
// Loading and common.js from accessible/tests/mochitest/ for all tests, as
// well as promisified-events.js.
/* import-globals-from ../../mochitest/role.js */
loadScripts(
{ name: "common.js", dir: MOCHITESTS_DIR },
{ name: "text.js", dir: MOCHITESTS_DIR },
{ name: "role.js", dir: MOCHITESTS_DIR },
{ name: "promisified-events.js", dir: MOCHITESTS_DIR }
);
const DIRECTION_NEXT = Ci.nsIAccessibleTextLeafPoint.DIRECTION_NEXT;
const DIRECTION_PREVIOUS = Ci.nsIAccessibleTextLeafPoint.DIRECTION_PREVIOUS;
const BOUNDARY_FLAG_DEFAULT =
Ci.nsIAccessibleTextLeafPoint.BOUNDARY_FLAG_DEFAULT;
const BOUNDARY_FLAG_INCLUDE_ORIGIN =
Ci.nsIAccessibleTextLeafPoint.BOUNDARY_FLAG_INCLUDE_ORIGIN;
const BOUNDARY_FLAG_STOP_IN_EDITABLE =
Ci.nsIAccessibleTextLeafPoint.BOUNDARY_FLAG_STOP_IN_EDITABLE;
const BOUNDARY_FLAG_SKIP_LIST_ITEM_MARKER =
Ci.nsIAccessibleTextLeafPoint.BOUNDARY_FLAG_SKIP_LIST_ITEM_MARKER;
function createTextLeafPoint(acc, offset) {
let accService = Cc["@mozilla.org/accessibilityService;1"].getService(
nsIAccessibilityService
);
return accService.createTextLeafPoint(acc, offset);
}
// Converts an nsIAccessibleTextLeafPoint into a human/machine
// readable tuple with a readable accessible and the offset within it.
// For a point text leaf it would look like this: ["hello", 2],
// For a point in an empty input it would look like this ["input#name", 0]
function readablePoint(point) {
const readableLeaf = acc => {
let tagName = getAccessibleTagName(acc);
if (tagName && !tagName.startsWith("_moz_generated")) {
let domNodeID = getAccessibleDOMNodeID(acc);
if (domNodeID) {
return `${tagName}#${domNodeID}`;
}
return tagName;
}
return acc.name;
};
return [readableLeaf(point.accessible), point.offset];
}
function sequenceEqual(val, expected, msg) {
Assert.deepEqual(val, expected, msg);
}
// eslint-disable-next-line camelcase
function sequenceEqualTodo(val, expected, msg) {
todo_is(JSON.stringify(val), JSON.stringify(expected), msg);
}
function pointsEqual(pointA, pointB) {
return (
pointA.offset == pointB.offset && pointA.accessible == pointB.accessible
);
}
function testPointEqual(pointA, pointB, msg) {
is(pointA.offset, pointB.offset, `Offset mismatch - ${msg}`);
is(pointA.accessible, pointB.accessible, `Accessible mismatch - ${msg}`);
}
function* textBoundaryGenerator(
firstPoint,
boundaryType,
direction,
flags = BOUNDARY_FLAG_DEFAULT
) {
// Our start point should be inclusive of the given point.
let nextLeafPoint = firstPoint.findBoundary(
boundaryType,
direction,
flags | BOUNDARY_FLAG_INCLUDE_ORIGIN
);
let textLeafPoint = null;
do {
textLeafPoint = nextLeafPoint;
yield textLeafPoint;
nextLeafPoint = textLeafPoint.findBoundary(boundaryType, direction, flags);
} while (!pointsEqual(textLeafPoint, nextLeafPoint));
}
// This function takes FindBoundary arguments and an expected sequence
// of boundary points formatted with readablePoint.
// For example, word starts would look like this:
// [["one two", 0], ["one two", 4], ["one two", 7]]
function testBoundarySequence(
startPoint,
boundaryType,
direction,
expectedSequence,
msg,
options = {}
) {
let sequence = [
...textBoundaryGenerator(
startPoint,
boundaryType,
direction,
options.flags ? options.flags : BOUNDARY_FLAG_DEFAULT
),
];
(options.todo ? sequenceEqualTodo : sequenceEqual)(
sequence.map(readablePoint),
expectedSequence,
msg
);
}
///////////////////////////////////////////////////////////////////////////////
// Editable text
async function waitForCopy(browser) {
await BrowserTestUtils.waitForContentEvent(browser, "copy", false, () => {
return true;
});
let clipboardData = await invokeContentTask(browser, [], async () => {
let text = await content.navigator.clipboard.readText();
return text;
});
return clipboardData;
}
async function isFinalValueCorrect(
browser,
acc,
expectedTextLeafs,
msg = "Value is correct"
) {
let value =
acc.role == ROLE_ENTRY
? acc.value
: await invokeContentTask(browser, [], () => {
return content.document.body.textContent;
});
let [before, text, after] = expectedTextLeafs;
let finalValue =
before && after && !text
? [before, after].join(" ")
: [before, text, after].join("");
is(value.replace("\xa0", " "), finalValue, msg);
}
function waitForTextChangeEvents(acc, eventSeq) {
let events = eventSeq.map(eventType => {
return [eventType, acc];
});
if (acc.role == ROLE_ENTRY) {
events.push([EVENT_TEXT_VALUE_CHANGE, acc]);
}
return waitForEvents(events);
}
async function testSetTextContents(acc, text, staticContentOffset, events) {
acc.QueryInterface(nsIAccessibleEditableText);
let evtPromise = waitForTextChangeEvents(acc, events);
acc.setTextContents(text);
let evt = (await evtPromise)[0];
evt.QueryInterface(nsIAccessibleTextChangeEvent);
is(evt.start, staticContentOffset);
}
async function testInsertText(
acc,
textToInsert,
insertOffset,
staticContentOffset
) {
acc.QueryInterface(nsIAccessibleEditableText);
let evtPromise = waitForTextChangeEvents(acc, [EVENT_TEXT_INSERTED]);
acc.insertText(textToInsert, staticContentOffset + insertOffset);
let evt = (await evtPromise)[0];
evt.QueryInterface(nsIAccessibleTextChangeEvent);
is(evt.start, staticContentOffset + insertOffset);
}
async function testDeleteText(
acc,
startOffset,
endOffset,
staticContentOffset
) {
acc.QueryInterface(nsIAccessibleEditableText);
let evtPromise = waitForTextChangeEvents(acc, [EVENT_TEXT_REMOVED]);
acc.deleteText(
staticContentOffset + startOffset,
staticContentOffset + endOffset
);
let evt = (await evtPromise)[0];
evt.QueryInterface(nsIAccessibleTextChangeEvent);
is(evt.start, staticContentOffset + startOffset);
}
async function testCopyText(
acc,
startOffset,
endOffset,
staticContentOffset,
browser,
aExpectedClipboard = null
) {
acc.QueryInterface(nsIAccessibleEditableText);
let copied = waitForCopy(browser);
acc.copyText(
staticContentOffset + startOffset,
staticContentOffset + endOffset
);
let clipboardText = await copied;
if (aExpectedClipboard != null) {
is(clipboardText, aExpectedClipboard, "Correct text in clipboard");
}
}
async function testPasteText(acc, insertOffset, staticContentOffset) {
acc.QueryInterface(nsIAccessibleEditableText);
let evtPromise = waitForTextChangeEvents(acc, [EVENT_TEXT_INSERTED]);
acc.pasteText(staticContentOffset + insertOffset);
let evt = (await evtPromise)[0];
evt.QueryInterface(nsIAccessibleTextChangeEvent);
// XXX: In non-headless mode pasting text produces several text leaves
// and the offset is not what we expect.
// is(evt.start, staticContentOffset + insertOffset);
}
async function testCutText(acc, startOffset, endOffset, staticContentOffset) {
acc.QueryInterface(nsIAccessibleEditableText);
let evtPromise = waitForTextChangeEvents(acc, [EVENT_TEXT_REMOVED]);
acc.cutText(
staticContentOffset + startOffset,
staticContentOffset + endOffset
);
let evt = (await evtPromise)[0];
evt.QueryInterface(nsIAccessibleTextChangeEvent);
is(evt.start, staticContentOffset + startOffset);
}
/**
* Check if the given ranges match the ranges in the given text accessible which
* expose the given attribute names/values.
*/
function textAttrRangesMatch(acc, ranges, attrs) {
acc.QueryInterface(nsIAccessibleText);
let offset = 0;
let expectedRanges = [...ranges];
let charCount = acc.characterCount;
while (offset < charCount) {
let start = {};
let end = {};
let attributes = acc.getTextAttributes(false, offset, start, end);
offset = end.value;
let matched = true;
for (const attr in attrs) {
try {
if (attributes.getStringProperty(attr) != attrs[attr]) {
// Attribute value doesn't match.
matched = false;
}
} catch (err) {
// Attribute doesn't exist.
matched = false;
}
}
if (!matched) {
continue;
}
let expected = expectedRanges.shift();
if (!expected || expected[0] != start.value || expected[1] != end.value) {
return false;
}
}
return !expectedRanges.length;
}