850 lines
24 KiB
JavaScript
850 lines
24 KiB
JavaScript
/* 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(3);
|
|
|
|
/* import-globals-from ../../mochitest/layout.js */
|
|
loadScripts({ name: "layout.js", dir: MOCHITESTS_DIR });
|
|
|
|
// Note that testTextNode, testChar and testTextRange currently don't handle
|
|
// white space in the code that doesn't get rendered on screen. To work around
|
|
// this, ensure that containers you want to test are all on a single line in the
|
|
// test snippet.
|
|
|
|
async function testTextNode(accDoc, browser, id) {
|
|
await testTextRange(accDoc, browser, id, 0, -1);
|
|
}
|
|
|
|
async function testChar(accDoc, browser, id, idx) {
|
|
await testTextRange(accDoc, browser, id, idx, idx + 1);
|
|
}
|
|
|
|
async function testTextRange(accDoc, browser, id, start, end) {
|
|
const r = await invokeContentTask(
|
|
browser,
|
|
[id, start, end],
|
|
(_id, _start, _end) => {
|
|
const htNode = content.document.getElementById(_id);
|
|
let [eX, eY, eW, eH] = [
|
|
Number.MAX_SAFE_INTEGER,
|
|
Number.MAX_SAFE_INTEGER,
|
|
0,
|
|
0,
|
|
];
|
|
let traversed = 0;
|
|
let localStart = _start;
|
|
let endTraversal = false;
|
|
for (let element of htNode.childNodes) {
|
|
// ignore whitespace, but not embedded elements
|
|
let isEmbeddedElement = false;
|
|
if (element.length == undefined) {
|
|
let potentialTextContainer = element;
|
|
while (
|
|
potentialTextContainer &&
|
|
potentialTextContainer.length == undefined
|
|
) {
|
|
potentialTextContainer = element.firstChild;
|
|
}
|
|
if (potentialTextContainer && potentialTextContainer.length) {
|
|
// If we can reach some text from this container, use that as part
|
|
// of our range. This is important when testing with intervening inline
|
|
// elements. ie. <pre><code>ab%0acd
|
|
element = potentialTextContainer;
|
|
} else if (element.firstChild) {
|
|
isEmbeddedElement = true;
|
|
} else {
|
|
continue;
|
|
}
|
|
}
|
|
if (element.length + traversed < _start) {
|
|
// If our start index is not within this
|
|
// node, keep looking.
|
|
traversed += element.length;
|
|
localStart -= element.length;
|
|
continue;
|
|
}
|
|
|
|
let rect;
|
|
if (isEmbeddedElement) {
|
|
rect = element.getBoundingClientRect();
|
|
} else {
|
|
const range = content.document.createRange();
|
|
range.setStart(element, localStart);
|
|
|
|
if (_end != -1 && _end - traversed <= element.length) {
|
|
// If the current node contains
|
|
// our end index, stop here.
|
|
endTraversal = true;
|
|
range.setEnd(element, _end - traversed);
|
|
} else {
|
|
range.setEnd(element, element.length);
|
|
}
|
|
|
|
rect = range.getBoundingClientRect();
|
|
}
|
|
|
|
const oldX = eX == Number.MAX_SAFE_INTEGER ? 0 : eX;
|
|
const oldY = eY == Number.MAX_SAFE_INTEGER ? 0 : eY;
|
|
eX = Math.min(eX, rect.x);
|
|
eY = Math.min(eY, rect.y);
|
|
eW = Math.abs(Math.max(oldX + eW, rect.x + rect.width) - eX);
|
|
eH = Math.abs(Math.max(oldY + eH, rect.y + rect.height) - eY);
|
|
|
|
if (endTraversal) {
|
|
break;
|
|
}
|
|
localStart = 0;
|
|
traversed += element.length;
|
|
}
|
|
return [Math.round(eX), Math.round(eY), Math.round(eW), Math.round(eH)];
|
|
}
|
|
);
|
|
let hyperTextNode = findAccessibleChildByID(accDoc, id);
|
|
|
|
// Add in the doc's screen coords because getBoundingClientRect
|
|
// is relative to the document, not the screen. This assumes the doc's
|
|
// screen coords are correct. We use getBoundsInCSSPixels to avoid factoring
|
|
// in the DPR ourselves.
|
|
let x = {};
|
|
let y = {};
|
|
let w = {};
|
|
let h = {};
|
|
accDoc.getBoundsInCSSPixels(x, y, w, h);
|
|
r[0] += x.value;
|
|
r[1] += y.value;
|
|
if (end != -1 && end - start == 1) {
|
|
// If we're only testing a character, use this function because it calls
|
|
// CharBounds() directly instead of TextBounds().
|
|
testTextPos(hyperTextNode, start, [r[0], r[1]], COORDTYPE_SCREEN_RELATIVE);
|
|
} else {
|
|
testTextBounds(hyperTextNode, start, end, r, COORDTYPE_SCREEN_RELATIVE);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Since testTextChar can't handle non-rendered white space, this function first
|
|
* uses testTextChar to verify the first character and then ensures all
|
|
* characters thereafter have an incrementing x and a non-0 width.
|
|
*/
|
|
async function testLineWithNonRenderedSpace(docAcc, browser, id, length) {
|
|
await testChar(docAcc, browser, id, 0);
|
|
const acc = findAccessibleChildByID(docAcc, id, [nsIAccessibleText]);
|
|
let prevX = -1;
|
|
for (let offset = 0; offset < length; ++offset) {
|
|
const x = {};
|
|
const y = {};
|
|
const w = {};
|
|
const h = {};
|
|
acc.getCharacterExtents(offset, x, y, w, h, COORDTYPE_SCREEN_RELATIVE);
|
|
Assert.greater(
|
|
x.value,
|
|
prevX,
|
|
`${id}: offset ${offset} x is larger (${x.value})`
|
|
);
|
|
prevX = x.value;
|
|
Assert.greater(w.value, 0, `${id}: offset ${offset} width > 0`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Test the text range boundary for simple LtR text
|
|
*/
|
|
addAccessibleTask(
|
|
`
|
|
<p id='p1' style='font-family: monospace;'>Tilimilitryamdiya</p>
|
|
<p id='p2' style='font-family: monospace;'>ل</p>
|
|
<p id='p3' dir='ltr' style='font-family: monospace;'>Привіт Світ</p>
|
|
<pre id='p4' style='font-family: monospace;'>a%0abcdef</pre>
|
|
`,
|
|
async function (browser, accDoc) {
|
|
info("Testing simple LtR text");
|
|
await testTextNode(accDoc, browser, "p1");
|
|
await testTextNode(accDoc, browser, "p2");
|
|
await testTextNode(accDoc, browser, "p3");
|
|
await testTextNode(accDoc, browser, "p4");
|
|
},
|
|
{
|
|
iframe: true,
|
|
}
|
|
);
|
|
|
|
/**
|
|
* Test the partial text range boundary for LtR text
|
|
*/
|
|
addAccessibleTask(
|
|
`
|
|
<p id='p1' style='font-family: monospace;'>Tilimilitryamdiya</p>
|
|
<p id='p2' dir='ltr' style='font-family: monospace;'>Привіт Світ</p>
|
|
`,
|
|
async function (browser, accDoc) {
|
|
info("Testing partial ranges in LtR text");
|
|
await testTextRange(accDoc, browser, "p1", 0, 4);
|
|
await testTextRange(accDoc, browser, "p1", 2, 8);
|
|
await testTextRange(accDoc, browser, "p1", 12, 17);
|
|
await testTextRange(accDoc, browser, "p2", 0, 4);
|
|
await testTextRange(accDoc, browser, "p2", 2, 8);
|
|
await testTextRange(accDoc, browser, "p2", 6, 11);
|
|
},
|
|
{
|
|
topLevel: true,
|
|
iframe: true,
|
|
}
|
|
);
|
|
|
|
/**
|
|
* Test the text boundary for multiline LtR text
|
|
*/
|
|
addAccessibleTask(
|
|
`
|
|
<p id='p4' dir='ltr' style='font-family: monospace;'>Привіт Світ<br>Привіт Світ</p>
|
|
<p id='p5' dir='ltr' style='font-family: monospace;'>Привіт Світ<br> Я ще трохи тексту в другому рядку</p>
|
|
<p id='p6' style='font-family: monospace;'>hello world I'm on line one<br> and I'm a separate line two with slightly more text</p>
|
|
<p id='p7' style='font-family: monospace;'>hello world<br>hello world</p>
|
|
`,
|
|
async function (browser, accDoc) {
|
|
info("Testing multiline LtR text");
|
|
await testTextNode(accDoc, browser, "p4");
|
|
await testTextNode(accDoc, browser, "p5");
|
|
// await testTextNode(accDoc, browser, "p6"); // w/o cache, fails width (a 259, e 250), w/ cache wrong w, h in iframe (line wrapping)
|
|
await testTextNode(accDoc, browser, "p7");
|
|
},
|
|
{
|
|
topLevel: true,
|
|
iframe: true,
|
|
}
|
|
);
|
|
|
|
/**
|
|
* Test the text boundary for simple RtL text
|
|
*/
|
|
addAccessibleTask(
|
|
`
|
|
<p id='p1' dir='rtl' style='font-family: monospace;'>Tilimilitryamdiya</p>
|
|
<p id='p2' dir='rtl' style='font-family: monospace;'>ل</p>
|
|
<p id='p3' dir='rtl' style='font-family: monospace;'>لل لللل لل</p>
|
|
<pre id='p4' dir='rtl' style='font-family: monospace;'>a%0abcdef</pre>
|
|
`,
|
|
async function (browser, accDoc) {
|
|
info("Testing simple RtL text");
|
|
await testTextNode(accDoc, browser, "p1");
|
|
await testTextNode(accDoc, browser, "p2");
|
|
await testTextNode(accDoc, browser, "p3");
|
|
await testTextNode(accDoc, browser, "p4");
|
|
},
|
|
{
|
|
topLevel: true,
|
|
iframe: true,
|
|
}
|
|
);
|
|
|
|
/**
|
|
* Test the text boundary for multiline RtL text
|
|
*/
|
|
addAccessibleTask(
|
|
`
|
|
<p id='p4' dir='rtl' style='font-family: monospace;'>لل لللل لل<br>لل لللل لل</p>
|
|
<p id='p5' dir='rtl' style='font-family: monospace;'>لل لللل لل<br> لل لل لل لل ل لل لل لل</p>
|
|
<p id='p6' dir='rtl' style='font-family: monospace;'>hello world I'm on line one<br> and I'm a separate line two with slightly more text</p>
|
|
<p id='p7' dir='rtl' style='font-family: monospace;'>hello world<br>hello world</p>
|
|
`,
|
|
async function (browser, accDoc) {
|
|
info("Testing multiline RtL text");
|
|
await testTextNode(accDoc, browser, "p4");
|
|
//await testTextNode(accDoc, browser, "p5"); // w/ cache fails x, w - off by one char
|
|
// await testTextNode(accDoc, browser, "p6"); // w/o cache, fails width (a 259, e 250), w/ cache fails w, h in iframe (line wrapping)
|
|
await testTextNode(accDoc, browser, "p7");
|
|
},
|
|
{
|
|
topLevel: true,
|
|
iframe: true,
|
|
}
|
|
);
|
|
|
|
/**
|
|
* Test the partial text range boundary for RtL text
|
|
*/
|
|
addAccessibleTask(
|
|
`
|
|
<p id='p1' dir='rtl' style='font-family: monospace;'>Tilimilitryamdiya</p>
|
|
<p id='p2' dir='rtl' style='font-family: monospace;'>لل لللل لل</p>
|
|
`,
|
|
async function (browser, accDoc) {
|
|
info("Testing partial ranges in RtL text");
|
|
await testTextRange(accDoc, browser, "p1", 0, 4);
|
|
await testTextRange(accDoc, browser, "p1", 2, 8);
|
|
await testTextRange(accDoc, browser, "p1", 12, 17);
|
|
await testTextRange(accDoc, browser, "p2", 0, 4);
|
|
await testTextRange(accDoc, browser, "p2", 2, 8);
|
|
await testTextRange(accDoc, browser, "p2", 6, 10);
|
|
},
|
|
{
|
|
topLevel: true,
|
|
iframe: true,
|
|
}
|
|
);
|
|
|
|
/**
|
|
* Test simple vertical text in rl and lr layouts
|
|
*/
|
|
addAccessibleTask(
|
|
`
|
|
<div style="writing-mode: vertical-rl;">
|
|
<p id='p1'>你好世界</p>
|
|
<p id='p2'>hello world</p>
|
|
<br>
|
|
<p id='p3'>こんにちは世界</p>
|
|
</div>
|
|
<div style="writing-mode: vertical-lr;">
|
|
<p id='p4'>你好世界</p>
|
|
<p id='p5'>hello world</p>
|
|
<br>
|
|
<p id='p6'>こんにちは世界</p>
|
|
</div>
|
|
`,
|
|
async function (browser, accDoc) {
|
|
info("Testing vertical-rl");
|
|
await testTextNode(accDoc, browser, "p1");
|
|
await testTextNode(accDoc, browser, "p2");
|
|
await testTextNode(accDoc, browser, "p3");
|
|
info("Testing vertical-lr");
|
|
await testTextNode(accDoc, browser, "p4");
|
|
await testTextNode(accDoc, browser, "p5");
|
|
await testTextNode(accDoc, browser, "p6");
|
|
},
|
|
{
|
|
topLevel: true,
|
|
iframe: true,
|
|
}
|
|
);
|
|
|
|
/**
|
|
* Test multiline vertical-rl text
|
|
*/
|
|
addAccessibleTask(
|
|
`
|
|
<p id='p1' style='writing-mode: vertical-rl;'>你好世界<br>你好世界</p>
|
|
<p id='p2' style='writing-mode: vertical-rl;'>hello world<br>hello world</p>
|
|
<br>
|
|
<p id='p3' style='writing-mode: vertical-rl;'>你好世界<br> 你好世界 你好世界</p>
|
|
<p id='p4' style='writing-mode: vertical-rl;'>hello world<br> hello world hello world</p>
|
|
`,
|
|
async function (browser, accDoc) {
|
|
info("Testing vertical-rl multiline");
|
|
await testTextNode(accDoc, browser, "p1");
|
|
await testTextNode(accDoc, browser, "p2");
|
|
await testTextNode(accDoc, browser, "p3");
|
|
// await testTextNode(accDoc, browser, "p4"); // off by 4 with caching, iframe
|
|
},
|
|
{
|
|
topLevel: true,
|
|
iframe: true,
|
|
}
|
|
);
|
|
|
|
/**
|
|
* Test text with embedded chars
|
|
*/
|
|
addAccessibleTask(
|
|
`<p id='p1' style='font-family: monospace;'>hello <a href="google.com">world</a></p>
|
|
<p id='p2' style='font-family: monospace;'>hello<br><a href="google.com">world</a></p>
|
|
<div id='d3'><p></p>hello world</div>
|
|
<div id='d4'>hello world<p></p></div>
|
|
<div id='d5'>oh<p></p>hello world</div>`,
|
|
async function (browser, accDoc) {
|
|
info("Testing embedded chars");
|
|
await testTextNode(accDoc, browser, "p1");
|
|
await testTextNode(accDoc, browser, "p2");
|
|
await testTextNode(accDoc, browser, "d3");
|
|
await testTextNode(accDoc, browser, "d4");
|
|
await testTextNode(accDoc, browser, "d5");
|
|
},
|
|
{
|
|
topLevel: true,
|
|
iframe: true,
|
|
}
|
|
);
|
|
|
|
/**
|
|
* Test bounds after text mutations.
|
|
*/
|
|
addAccessibleTask(
|
|
`<p id="p">a</p>`,
|
|
async function (browser, docAcc) {
|
|
await testTextNode(docAcc, browser, "p");
|
|
const p = findAccessibleChildByID(docAcc, "p");
|
|
info("Appending a character to text leaf");
|
|
let textInserted = waitForEvent(EVENT_TEXT_INSERTED, p);
|
|
await invokeContentTask(browser, [], () => {
|
|
content.document.getElementById("p").firstChild.data = "ab";
|
|
});
|
|
await textInserted;
|
|
await testTextNode(docAcc, browser, "p");
|
|
},
|
|
{
|
|
chrome: true,
|
|
topLevel: true,
|
|
iframe: true,
|
|
}
|
|
);
|
|
|
|
/**
|
|
* Test character bounds on the insertion point at the end of a text box.
|
|
*/
|
|
addAccessibleTask(
|
|
`<input id="input" value="a">`,
|
|
async function (browser, docAcc) {
|
|
const input = findAccessibleChildByID(docAcc, "input");
|
|
testTextPos(input, 1, [0, 0], COORDTYPE_SCREEN_RELATIVE);
|
|
},
|
|
{
|
|
chrome: true,
|
|
topLevel: true,
|
|
iframe: true,
|
|
}
|
|
);
|
|
|
|
/**
|
|
* Test character bounds after non-br line break.
|
|
*/
|
|
addAccessibleTask(
|
|
`
|
|
<style>
|
|
@font-face {
|
|
font-family: Ahem;
|
|
src: url(${CURRENT_CONTENT_DIR}e10s/fonts/Ahem.sjs);
|
|
}
|
|
pre {
|
|
font: 20px/20px Ahem;
|
|
}
|
|
</style>
|
|
<pre id="t">XX
|
|
XXX</pre>`,
|
|
async function (browser, docAcc) {
|
|
await testChar(docAcc, browser, "t", 3);
|
|
},
|
|
{
|
|
chrome: true,
|
|
topLevel: true,
|
|
iframe: true,
|
|
}
|
|
);
|
|
|
|
/**
|
|
* Test character bounds in a pre with padding.
|
|
*/
|
|
addAccessibleTask(
|
|
`
|
|
<style>
|
|
@font-face {
|
|
font-family: Ahem;
|
|
src: url(${CURRENT_CONTENT_DIR}e10s/fonts/Ahem.sjs);
|
|
}
|
|
pre {
|
|
font: 20px/20px Ahem;
|
|
padding: 20px;
|
|
}
|
|
</style>
|
|
<pre id="t">XX
|
|
XXX</pre>`,
|
|
async function (browser, docAcc) {
|
|
await testTextNode(docAcc, browser, "t");
|
|
await testChar(docAcc, browser, "t", 3);
|
|
},
|
|
{
|
|
chrome: true,
|
|
topLevel: true,
|
|
iframe: true,
|
|
}
|
|
);
|
|
|
|
/**
|
|
* Test text bounds with an invalid end offset.
|
|
*/
|
|
addAccessibleTask(
|
|
`<p id="p">a</p>`,
|
|
async function (browser, docAcc) {
|
|
const p = findAccessibleChildByID(docAcc, "p");
|
|
testTextBounds(p, 0, 2, [0, 0, 0, 0], COORDTYPE_SCREEN_RELATIVE);
|
|
},
|
|
{ chrome: true, topLevel: !true }
|
|
);
|
|
|
|
/**
|
|
* Test character bounds in an intervening inline element with non-br line breaks
|
|
*/
|
|
addAccessibleTask(
|
|
`
|
|
<style>
|
|
@font-face {
|
|
font-family: Ahem;
|
|
src: url(${CURRENT_CONTENT_DIR}e10s/fonts/Ahem.sjs);
|
|
}
|
|
pre {
|
|
font: 20px/20px Ahem;
|
|
}
|
|
</style>
|
|
<pre id="t"><code role="none">XX
|
|
XXX
|
|
XX
|
|
X</pre>`,
|
|
async function (browser, docAcc) {
|
|
await testChar(docAcc, browser, "t", 0);
|
|
await testChar(docAcc, browser, "t", 3);
|
|
await testChar(docAcc, browser, "t", 7);
|
|
await testChar(docAcc, browser, "t", 10);
|
|
},
|
|
{
|
|
chrome: true,
|
|
topLevel: true,
|
|
iframe: true,
|
|
}
|
|
);
|
|
|
|
/**
|
|
* Test character bounds in an intervening inline element with margins
|
|
* and with non-br line breaks
|
|
*/
|
|
addAccessibleTask(
|
|
`
|
|
<style>
|
|
@font-face {
|
|
font-family: Ahem;
|
|
src: url(${CURRENT_CONTENT_DIR}e10s/fonts/Ahem.sjs);
|
|
}
|
|
</style>
|
|
<div>hello<pre id="t" style="margin-left:100px;margin-top:30px;background-color:blue;">XX
|
|
XXX
|
|
XX
|
|
X</pre></div>`,
|
|
async function (browser, docAcc) {
|
|
await testChar(docAcc, browser, "t", 0);
|
|
await testChar(docAcc, browser, "t", 3);
|
|
await testChar(docAcc, browser, "t", 7);
|
|
await testChar(docAcc, browser, "t", 10);
|
|
},
|
|
{
|
|
chrome: true,
|
|
topLevel: true,
|
|
iframe: true,
|
|
}
|
|
);
|
|
|
|
/**
|
|
* Test text bounds in a textarea after scrolling.
|
|
*/
|
|
addAccessibleTask(
|
|
`
|
|
<textarea id="textarea" rows="1">a
|
|
b
|
|
c</textarea>
|
|
`,
|
|
async function (browser, docAcc) {
|
|
// We can't use testChar because Range.getBoundingClientRect isn't supported
|
|
// inside textareas.
|
|
const textarea = findAccessibleChildByID(docAcc, "textarea");
|
|
textarea.QueryInterface(nsIAccessibleText);
|
|
const oldY = {};
|
|
textarea.getCharacterExtents(
|
|
4,
|
|
{},
|
|
oldY,
|
|
{},
|
|
{},
|
|
COORDTYPE_SCREEN_RELATIVE
|
|
);
|
|
info("Moving textarea caret to c");
|
|
await invokeContentTask(browser, [], () => {
|
|
const textareaDom = content.document.getElementById("textarea");
|
|
textareaDom.focus();
|
|
textareaDom.selectionStart = 4;
|
|
});
|
|
await waitForContentPaint(browser);
|
|
const newY = {};
|
|
textarea.getCharacterExtents(
|
|
4,
|
|
{},
|
|
newY,
|
|
{},
|
|
{},
|
|
COORDTYPE_SCREEN_RELATIVE
|
|
);
|
|
Assert.less(
|
|
newY.value,
|
|
oldY.value,
|
|
"y coordinate smaller after scrolling down"
|
|
);
|
|
},
|
|
{ chrome: true, topLevel: true, iframe: !true }
|
|
);
|
|
|
|
/**
|
|
* Test magic offsets with GetCharacter/RangeExtents.
|
|
*/
|
|
addAccessibleTask(
|
|
`<input id="input" value="abc">`,
|
|
async function (browser, docAcc) {
|
|
const input = findAccessibleChildByID(docAcc, "input", [nsIAccessibleText]);
|
|
info("Setting caret and focusing input");
|
|
let caretMoved = waitForEvent(EVENT_TEXT_CARET_MOVED, input);
|
|
await invokeContentTask(browser, [], () => {
|
|
const inputDom = content.document.getElementById("input");
|
|
inputDom.selectionStart = inputDom.selectionEnd = 1;
|
|
inputDom.focus();
|
|
});
|
|
await caretMoved;
|
|
is(input.caretOffset, 1, "input caretOffset is 1");
|
|
let expectedX = {};
|
|
let expectedY = {};
|
|
let expectedW = {};
|
|
let expectedH = {};
|
|
let magicX = {};
|
|
let magicY = {};
|
|
let magicW = {};
|
|
let magicH = {};
|
|
input.getCharacterExtents(
|
|
1,
|
|
expectedX,
|
|
expectedY,
|
|
expectedW,
|
|
expectedH,
|
|
COORDTYPE_SCREEN_RELATIVE
|
|
);
|
|
input.getCharacterExtents(
|
|
nsIAccessibleText.TEXT_OFFSET_CARET,
|
|
magicX,
|
|
magicY,
|
|
magicW,
|
|
magicH,
|
|
COORDTYPE_SCREEN_RELATIVE
|
|
);
|
|
Assert.deepEqual(
|
|
[magicX.value, magicY.value, magicW.value, magicH.value],
|
|
[expectedX.value, expectedY.value, expectedW.value, expectedH.value],
|
|
"GetCharacterExtents correct with TEXT_OFFSET_CARET"
|
|
);
|
|
input.getRangeExtents(
|
|
1,
|
|
3,
|
|
expectedX,
|
|
expectedY,
|
|
expectedW,
|
|
expectedH,
|
|
COORDTYPE_SCREEN_RELATIVE
|
|
);
|
|
input.getRangeExtents(
|
|
nsIAccessibleText.TEXT_OFFSET_CARET,
|
|
nsIAccessibleText.TEXT_OFFSET_END_OF_TEXT,
|
|
magicX,
|
|
magicY,
|
|
magicW,
|
|
magicH,
|
|
COORDTYPE_SCREEN_RELATIVE
|
|
);
|
|
Assert.deepEqual(
|
|
[magicX.value, magicY.value, magicW.value, magicH.value],
|
|
[expectedX.value, expectedY.value, expectedW.value, expectedH.value],
|
|
"GetRangeExtents correct with TEXT_OFFSET_CARET/END_OF_TEXT"
|
|
);
|
|
},
|
|
{ chrome: true, topLevel: true, remoteIframe: !true }
|
|
);
|
|
|
|
/**
|
|
* Test wrapped text and pre-formatted text beginning with an empty line.
|
|
*/
|
|
addAccessibleTask(
|
|
`
|
|
<style>
|
|
#wrappedText {
|
|
width: 3ch;
|
|
font-family: monospace;
|
|
}
|
|
</style>
|
|
<p id="wrappedText"><a href="https://example.com/">a</a>b cd</p>
|
|
<p id="emptyFirstLine" style="white-space: pre-line;">
|
|
foo</p>
|
|
`,
|
|
async function (browser, docAcc) {
|
|
await testChar(docAcc, browser, "wrappedText", 0);
|
|
await testChar(docAcc, browser, "wrappedText", 1);
|
|
await testChar(docAcc, browser, "wrappedText", 2);
|
|
await testChar(docAcc, browser, "wrappedText", 3);
|
|
await testChar(docAcc, browser, "wrappedText", 4);
|
|
|
|
// We can't use testChar for emptyFirstLine because it doesn't handle white
|
|
// space properly. Instead, verify that the first character is at the top
|
|
// left of the text leaf.
|
|
const emptyFirstLine = findAccessibleChildByID(docAcc, "emptyFirstLine", [
|
|
nsIAccessibleText,
|
|
]);
|
|
const emptyFirstLineLeaf = emptyFirstLine.firstChild;
|
|
const leafX = {};
|
|
const leafY = {};
|
|
emptyFirstLineLeaf.getBounds(leafX, leafY, {}, {});
|
|
testTextPos(
|
|
emptyFirstLine,
|
|
0,
|
|
[leafX.value, leafY.value],
|
|
COORDTYPE_SCREEN_RELATIVE
|
|
);
|
|
},
|
|
{ chrome: true, topLevel: true, remoteIframe: !true }
|
|
);
|
|
|
|
/**
|
|
* Test character bounds in an intervening inline element with non-br line breaks
|
|
*/
|
|
addAccessibleTask(
|
|
`
|
|
<style>
|
|
@font-face {
|
|
font-family: Ahem;
|
|
src: url(${CURRENT_CONTENT_DIR}e10s/fonts/Ahem.sjs);
|
|
}
|
|
pre {
|
|
font: 20px/20px Ahem;
|
|
}
|
|
</style>
|
|
<pre><code id="t" role="group">XX
|
|
XXX
|
|
XX
|
|
X</pre>`,
|
|
async function (browser, docAcc) {
|
|
await testChar(docAcc, browser, "t", 0);
|
|
await testChar(docAcc, browser, "t", 3);
|
|
await testChar(docAcc, browser, "t", 7);
|
|
await testChar(docAcc, browser, "t", 10);
|
|
},
|
|
{
|
|
chrome: true,
|
|
topLevel: true,
|
|
iframe: true,
|
|
}
|
|
);
|
|
|
|
/**
|
|
* Test character bounds where content white space isn't rendered.
|
|
*/
|
|
addAccessibleTask(
|
|
`
|
|
<p id="single">a b</p>
|
|
<p id="multi"><ins>a </ins>
|
|
b</p>
|
|
<pre id="pre">a b</pre>
|
|
`,
|
|
async function (browser, docAcc) {
|
|
await testLineWithNonRenderedSpace(docAcc, browser, "single", 3);
|
|
await testLineWithNonRenderedSpace(docAcc, browser, "multi", 2);
|
|
for (let offset = 0; offset < 4; ++offset) {
|
|
await testChar(docAcc, browser, "pre", offset);
|
|
}
|
|
},
|
|
{ chrome: true, topLevel: true }
|
|
);
|
|
|
|
function getCharacterExtents(acc, offset) {
|
|
const x = {};
|
|
const y = {};
|
|
const w = {};
|
|
const h = {};
|
|
acc.getCharacterExtents(offset, x, y, w, h, COORDTYPE_SCREEN_RELATIVE);
|
|
return [x.value, y.value, w.value, h.value];
|
|
}
|
|
|
|
/**
|
|
* Test character bounds of line feed characters in a textarea.
|
|
*/
|
|
addAccessibleTask(
|
|
`
|
|
<textarea id="textarea">a
|
|
|
|
b</textarea>
|
|
`,
|
|
async function testLineFeedTextarea(browser, docAcc) {
|
|
// We can't use testChar because it doesn't know how to handle line feeds.
|
|
// We check relative to other characters instead.
|
|
const textarea = findAccessibleChildByID(docAcc, "textarea", [
|
|
nsIAccessibleText,
|
|
]);
|
|
const [x0, y0, ,] = getCharacterExtents(textarea, 0);
|
|
const [x1, y1, w1, h1] = getCharacterExtents(textarea, 1);
|
|
const [x2, y2, w2, h2] = getCharacterExtents(textarea, 2);
|
|
const [x3, y3, ,] = getCharacterExtents(textarea, 3);
|
|
// Character 0 is a letter on the first line.
|
|
// Character 1 is a line feed at the end of the first line.
|
|
Assert.greater(x1, x0, "x1 > x0");
|
|
is(y1, y0, "y1 == y0");
|
|
Assert.greater(w1, 0, "w1 > 0");
|
|
Assert.greater(h1, 0, "h1 > 0");
|
|
// Character 2 is a line feed on a blank line.
|
|
is(x2, x0, "x2 == x0");
|
|
Assert.greaterOrEqual(y2, y1 + h1, "y2 >= y1 + h1");
|
|
Assert.greater(w2, 0, "w2 > 0");
|
|
Assert.greater(h2, 0, "h2 > 0");
|
|
// Character 3 is a letter on the final line.
|
|
is(x3, x0, "x3 == x0");
|
|
Assert.greaterOrEqual(y3, y2 + h2, "y3 >= y2 + h2");
|
|
},
|
|
{ chrome: true, topLevel: true }
|
|
);
|
|
|
|
/**
|
|
* Test line feed characters in a contentEditable.
|
|
*/
|
|
addAccessibleTask(
|
|
`
|
|
<div contenteditable role="textbox">
|
|
<div id="ce0">a</div>
|
|
<div id="ce1"><br></div>
|
|
<div id="ce2">b</div>
|
|
</div>
|
|
`,
|
|
async function testLineFeedEditable(browser, docAcc) {
|
|
// We can't use testChar because it doesn't know how to handle line feeds.
|
|
// We check relative to other characters instead.
|
|
const ce0 = findAccessibleChildByID(docAcc, "ce0", [nsIAccessibleText]);
|
|
const [x0, y0, ,] = getCharacterExtents(ce0, 0);
|
|
const ce1 = findAccessibleChildByID(docAcc, "ce1", [nsIAccessibleText]);
|
|
const [x1, y1, w1, h1] = getCharacterExtents(ce1, 0);
|
|
const ce2 = findAccessibleChildByID(docAcc, "ce2", [nsIAccessibleText]);
|
|
const [x2, y2, ,] = getCharacterExtents(ce2, 0);
|
|
// Character 0 is a letter on the first line.
|
|
// Character 1 is a line feed on a blank line.
|
|
is(x1, x0, "x1 == x0");
|
|
Assert.greater(y1, y0, "y1 > y0");
|
|
Assert.greater(w1, 0, "w1 > 0");
|
|
Assert.greater(h1, 0, "h1 > 0");
|
|
// Character 2 is a letter on the final line.
|
|
is(x2, x0, "x2 == x0");
|
|
Assert.greaterOrEqual(y2, y1 + h1, "y2 >= y1 + h1");
|
|
},
|
|
{ chrome: true, topLevel: true }
|
|
);
|
|
|
|
/**
|
|
* Test list bullets.
|
|
*/
|
|
addAccessibleTask(
|
|
`<ul><li id="li" style="font-family: monospace;">a</li></ul>`,
|
|
async function testBullet(browser, docAcc) {
|
|
// We can't use testChar because it doesn't know how to handle list bullets.
|
|
// We check relative to other characters instead.
|
|
const li = findAccessibleChildByID(docAcc, "li", [nsIAccessibleText]);
|
|
const [x0, y0, w0, h0] = getCharacterExtents(li, 0);
|
|
const [x1, y1, w1, h1] = getCharacterExtents(li, 1);
|
|
const [x2, y2, ,] = getCharacterExtents(li, 2);
|
|
// Characters 0 and 1 are the bullet.
|
|
// Character 2 is a letter.
|
|
Assert.less(x0, x2, "x0 < x2");
|
|
isWithin(y0, y2, 5, "y0 ~ y2");
|
|
Assert.greater(w0, 0, "w0 > 0");
|
|
Assert.greater(h0, 0, "h0 > 0");
|
|
Assert.less(x1, x2, "x1 < x2");
|
|
isWithin(y1, y2, 5, "y1 ~ y2");
|
|
Assert.greater(w1, 0, "w1 > 0");
|
|
Assert.greater(h1, 0, "h1 > 0");
|
|
},
|
|
{ chrome: true, topLevel: true }
|
|
);
|