diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /accessible/tests/browser/e10s/browser_caching_text_bounds.js | |
parent | Initial commit. (diff) | |
download | firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'accessible/tests/browser/e10s/browser_caching_text_bounds.js')
-rw-r--r-- | accessible/tests/browser/e10s/browser_caching_text_bounds.js | 696 |
1 files changed, 696 insertions, 0 deletions
diff --git a/accessible/tests/browser/e10s/browser_caching_text_bounds.js b/accessible/tests/browser/e10s/browser_caching_text_bounds.js new file mode 100644 index 0000000000..2d2a857c8f --- /dev/null +++ b/accessible/tests/browser/e10s/browser_caching_text_bounds.js @@ -0,0 +1,696 @@ +/* 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); + } +} + +/** + * 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>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 + ); + ok(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, + } +); |