diff --git a/accessible/tests/browser/text/browser.ini b/accessible/tests/browser/text/browser.ini index 3af4c6c29671..1b0c5a303383 100644 --- a/accessible/tests/browser/text/browser.ini +++ b/accessible/tests/browser/text/browser.ini @@ -8,6 +8,7 @@ support-files = prefs = javascript.options.asyncstack_capture_debuggee_only=false +[browser_editabletext.js] [browser_text.js] [browser_text_caret.js] [browser_text_paragraph_boundary.js] diff --git a/accessible/tests/browser/text/browser_editabletext.js b/accessible/tests/browser/text/browser_editabletext.js new file mode 100644 index 000000000000..0310122debf1 --- /dev/null +++ b/accessible/tests/browser/text/browser_editabletext.js @@ -0,0 +1,173 @@ +/* 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"; + +async function testEditable(browser, acc, aBefore = "", aAfter = "") { + async function resetInput() { + if (acc.childCount <= 1) { + return; + } + + let emptyInputEvent = waitForEvent(EVENT_TEXT_VALUE_CHANGE, "input"); + await invokeContentTask(browser, [], async () => { + content.document.getElementById("input").innerHTML = ""; + }); + + await emptyInputEvent; + } + + // //////////////////////////////////////////////////////////////////////// + // insertText + await testInsertText(acc, "hello", 0, aBefore.length); + await isFinalValueCorrect(browser, acc, [aBefore, "hello", aAfter]); + await testInsertText(acc, "ma ", 0, aBefore.length); + await isFinalValueCorrect(browser, acc, [aBefore, "ma hello", aAfter]); + await testInsertText(acc, "ma", 2, aBefore.length); + await isFinalValueCorrect(browser, acc, [aBefore, "mama hello", aAfter]); + await testInsertText(acc, " hello", 10, aBefore.length); + await isFinalValueCorrect(browser, acc, [ + aBefore, + "mama hello hello", + aAfter, + ]); + + // //////////////////////////////////////////////////////////////////////// + // deleteText + await testDeleteText(acc, 0, 5, aBefore.length); + await isFinalValueCorrect(browser, acc, [aBefore, "hello hello", aAfter]); + await testDeleteText(acc, 5, 6, aBefore.length); + await isFinalValueCorrect(browser, acc, [aBefore, "hellohello", aAfter]); + await testDeleteText(acc, 5, 10, aBefore.length); + await isFinalValueCorrect(browser, acc, [aBefore, "hello", aAfter]); + await testDeleteText(acc, 0, 5, aBefore.length); + await isFinalValueCorrect(browser, acc, [aBefore, "", aAfter]); + + // XXX: clipboard operation tests don't work well with editable documents. + if (acc.role == ROLE_DOCUMENT) { + return; + } + + await resetInput(); + + // copyText and pasteText + await testInsertText(acc, "hello", 0, aBefore.length); + await isFinalValueCorrect(browser, acc, [aBefore, "hello", aAfter]); + + await testCopyText(acc, 0, 1, aBefore.length, browser, "h"); + await testPasteText(acc, 1, aBefore.length); + await isFinalValueCorrect(browser, acc, [aBefore, "hhello", aAfter]); + + await testCopyText(acc, 5, 6, aBefore.length, browser, "o"); + await testPasteText(acc, 6, aBefore.length); + await isFinalValueCorrect(browser, acc, [aBefore, "hhelloo", aAfter]); + + await testCopyText(acc, 2, 3, aBefore.length, browser, "e"); + await testPasteText(acc, 1, aBefore.length); + await isFinalValueCorrect(browser, acc, [aBefore, "hehelloo", aAfter]); + + // cut & paste + await testCutText(acc, 0, 1, aBefore.length); + await isFinalValueCorrect(browser, acc, [aBefore, "ehelloo", aAfter]); + await testPasteText(acc, 2, aBefore.length); + await isFinalValueCorrect(browser, acc, [aBefore, "ehhelloo", aAfter]); + + await testCutText(acc, 3, 4, aBefore.length); + await isFinalValueCorrect(browser, acc, [aBefore, "ehhlloo", aAfter]); + await testPasteText(acc, 6, aBefore.length); + await isFinalValueCorrect(browser, acc, [aBefore, "ehhlloeo", aAfter]); + + await testCutText(acc, 0, 8, aBefore.length); + await isFinalValueCorrect(browser, acc, [aBefore, "", aAfter]); + + await resetInput(); + + // //////////////////////////////////////////////////////////////////////// + // setTextContents + await testSetTextContents(acc, "hello", aBefore.length, [ + EVENT_TEXT_INSERTED, + ]); + await isFinalValueCorrect(browser, acc, [aBefore, "hello", aAfter]); + await testSetTextContents(acc, "katze", aBefore.length, [ + EVENT_TEXT_REMOVED, + EVENT_TEXT_INSERTED, + ]); + await isFinalValueCorrect(browser, acc, [aBefore, "katze", aAfter]); +} + +addAccessibleTask( + ``, + async function (browser, docAcc) { + await testEditable(browser, findAccessibleChildByID(docAcc, "input")); + }, + { chrome: true, topLevel: true } +); + +addAccessibleTask( + ` +
`, + async function (browser, docAcc) { + await testEditable( + browser, + findAccessibleChildByID(docAcc, "input"), + "", + "pseudo element" + ); + }, + { chrome: true, topLevel: false /* bug 1834129 */ } +); + +addAccessibleTask( + ` +
`, + async function (browser, docAcc) { + await testEditable( + browser, + findAccessibleChildByID(docAcc, "input"), + "pseudo element" + ); + }, + { chrome: true, topLevel: false /* bug 1834129 */ } +); + +addAccessibleTask( + ` +
`, + async function (browser, docAcc) { + await testEditable( + browser, + findAccessibleChildByID(docAcc, "input"), + "before", + "after" + ); + }, + { chrome: true, topLevel: false /* bug 1834129 */ } +); + +addAccessibleTask( + ``, + async function (browser, docAcc) { + await testEditable(browser, docAcc); + }, + { + chrome: true, + topLevel: true, + contentDocBodyAttrs: { contentEditable: "true" }, + } +); diff --git a/accessible/tests/browser/text/head.js b/accessible/tests/browser/text/head.js index 80868f57bf32..fa4b0958921d 100644 --- a/accessible/tests/browser/text/head.js +++ b/accessible/tests/browser/text/head.js @@ -7,7 +7,9 @@ /* 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 */ + readablePoint, testPointEqual, textBoundaryGenerator, testBoundarySequence, + isFinalValueCorrect, isFinalValueCorrect, testInsertText, testDeleteText, + testCopyText, testPasteText, testCutText, testSetTextContents */ // Load the shared-head file first. Services.scriptloader.loadSubScript( @@ -17,9 +19,13 @@ Services.scriptloader.loadSubScript( // 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 } ); @@ -131,3 +137,140 @@ function testBoundarySequence( msg ); } + +/////////////////////////////////////////////////////////////////////////////// +// Editable text + +async function waitForCopy(browser) { + await BrowserTestUtils.waitForContentEvent(browser, "copy", false, evt => { + 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); +} diff --git a/accessible/tests/mochitest/editabletext/a11y.ini b/accessible/tests/mochitest/editabletext/a11y.ini deleted file mode 100644 index 68466fdf23ec..000000000000 --- a/accessible/tests/mochitest/editabletext/a11y.ini +++ /dev/null @@ -1,7 +0,0 @@ -[DEFAULT] -support-files = - editabletext.js - !/accessible/tests/mochitest/*.js - -[test_1.html] -[test_2.html] diff --git a/accessible/tests/mochitest/editabletext/editabletext.js b/accessible/tests/mochitest/editabletext/editabletext.js deleted file mode 100644 index ef305c78427e..000000000000 --- a/accessible/tests/mochitest/editabletext/editabletext.js +++ /dev/null @@ -1,409 +0,0 @@ -/* import-globals-from ../common.js */ -/* import-globals-from ../events.js */ - -/** - * Perform all editable text tests. - */ -function editableTextTestRun() { - this.add = function add(aTest) { - this.seq.push(aTest); - }; - - this.run = function run() { - this.iterate(); - }; - - this.index = 0; - this.seq = []; - - this.iterate = function iterate() { - if (this.index < this.seq.length) { - this.seq[this.index++].startTest(this); - return; - } - - this.seq = null; - SimpleTest.finish(); - }; -} - -/** - * Used to test nsIEditableTextAccessible methods. - */ -function editableTextTest(aID) { - /** - * Schedule a test, the given function with its arguments will be executed - * when preceding test is complete. - */ - this.scheduleTest = function scheduleTest(aFunc, ...aFuncArgs) { - // A data container acts like a dummy invoker, it's never invoked but - // it's used to generate real invoker when previous invoker was handled. - var dataContainer = { - func: aFunc, - funcArgs: aFuncArgs, - }; - this.mEventQueue.push(dataContainer); - - if (!this.mEventQueueReady) { - this.unwrapNextTest(); - this.mEventQueueReady = true; - } - }; - - /** - * setTextContents test. - */ - this.setTextContents = function setTextContents(aValue, aSkipStartOffset) { - var testID = "setTextContents '" + aValue + "' for " + prettyName(aID); - - function setTextContentsInvoke() { - dump(`\nsetTextContents '${aValue}'\n`); - var acc = getAccessible(aID, nsIAccessibleEditableText); - acc.setTextContents(aValue); - } - - aSkipStartOffset = aSkipStartOffset || 0; - var insertTripple = aValue - ? [aSkipStartOffset, aSkipStartOffset + aValue.length, aValue] - : null; - var oldValue = getValue(); - var removeTripple = oldValue - ? [aSkipStartOffset, aSkipStartOffset + oldValue.length, oldValue] - : null; - - this.generateTest( - removeTripple, - insertTripple, - setTextContentsInvoke, - getValueChecker(aValue), - testID - ); - }; - - /** - * insertText test. - */ - this.insertText = function insertText(aStr, aPos, aResStr, aResPos) { - var testID = - "insertText '" + aStr + "' at " + aPos + " for " + prettyName(aID); - - function insertTextInvoke() { - dump(`\ninsertText '${aStr}' at ${aPos} pos\n`); - var acc = getAccessible(aID, nsIAccessibleEditableText); - acc.insertText(aStr, aPos); - } - - var resPos = aResPos != undefined ? aResPos : aPos; - this.generateTest( - null, - [resPos, resPos + aStr.length, aStr], - insertTextInvoke, - getValueChecker(aResStr), - testID - ); - }; - - /** - * copyText test. - */ - this.copyText = function copyText(aStartPos, aEndPos, aClipboardStr) { - var testID = - "copyText from " + - aStartPos + - " to " + - aEndPos + - " for " + - prettyName(aID); - - function copyTextInvoke() { - var acc = getAccessible(aID, nsIAccessibleEditableText); - acc.copyText(aStartPos, aEndPos); - } - - this.generateTest( - null, - null, - copyTextInvoke, - getClipboardChecker(aClipboardStr), - testID - ); - }; - - /** - * copyText and pasteText test. - */ - this.copyNPasteText = function copyNPasteText( - aStartPos, - aEndPos, - aPos, - aResStr - ) { - var testID = - "copyText from " + - aStartPos + - " to " + - aEndPos + - "and pasteText at " + - aPos + - " for " + - prettyName(aID); - - function copyNPasteTextInvoke() { - var acc = getAccessible(aID, nsIAccessibleEditableText); - acc.copyText(aStartPos, aEndPos); - acc.pasteText(aPos); - } - - this.generateTest( - null, - [aStartPos, aEndPos, getTextFromClipboard], - copyNPasteTextInvoke, - getValueChecker(aResStr), - testID - ); - }; - - /** - * cutText test. - */ - this.cutText = function cutText( - aStartPos, - aEndPos, - aResStr, - aResStartPos, - aResEndPos - ) { - var testID = - "cutText from " + - aStartPos + - " to " + - aEndPos + - " for " + - prettyName(aID); - - function cutTextInvoke() { - var acc = getAccessible(aID, nsIAccessibleEditableText); - acc.cutText(aStartPos, aEndPos); - } - - var resStartPos = aResStartPos != undefined ? aResStartPos : aStartPos; - var resEndPos = aResEndPos != undefined ? aResEndPos : aEndPos; - this.generateTest( - [resStartPos, resEndPos, getTextFromClipboard], - null, - cutTextInvoke, - getValueChecker(aResStr), - testID - ); - }; - - /** - * cutText and pasteText test. - */ - this.cutNPasteText = function copyNPasteText( - aStartPos, - aEndPos, - aPos, - aResStr - ) { - var testID = - "cutText from " + - aStartPos + - " to " + - aEndPos + - " and pasteText at " + - aPos + - " for " + - prettyName(aID); - - function cutNPasteTextInvoke() { - var acc = getAccessible(aID, nsIAccessibleEditableText); - acc.cutText(aStartPos, aEndPos); - acc.pasteText(aPos); - } - - this.generateTest( - [aStartPos, aEndPos, getTextFromClipboard], - [aPos, -1, getTextFromClipboard], - cutNPasteTextInvoke, - getValueChecker(aResStr), - testID - ); - }; - - /** - * pasteText test. - */ - this.pasteText = function pasteText(aPos, aResStr) { - var testID = "pasteText at " + aPos + " for " + prettyName(aID); - - function pasteTextInvoke() { - var acc = getAccessible(aID, nsIAccessibleEditableText); - acc.pasteText(aPos); - } - - this.generateTest( - null, - [aPos, -1, getTextFromClipboard], - pasteTextInvoke, - getValueChecker(aResStr), - testID - ); - }; - - /** - * deleteText test. - */ - this.deleteText = function deleteText(aStartPos, aEndPos, aResStr) { - var testID = - "deleteText from " + - aStartPos + - " to " + - aEndPos + - " for " + - prettyName(aID); - - var oldValue = getValue().substring(aStartPos, aEndPos); - var removeTripple = oldValue ? [aStartPos, aEndPos, oldValue] : null; - - function deleteTextInvoke() { - var acc = getAccessible(aID, [nsIAccessibleEditableText]); - acc.deleteText(aStartPos, aEndPos); - } - - this.generateTest( - removeTripple, - null, - deleteTextInvoke, - getValueChecker(aResStr), - testID - ); - }; - - // //////////////////////////////////////////////////////////////////////////// - // Implementation details. - - function getValue() { - var elm = getNode(aID); - var elmClass = ChromeUtils.getClassName(elm); - if (elmClass === "HTMLTextAreaElement" || elmClass === "HTMLInputElement") { - return elm.value; - } - - if (elmClass === "HTMLDocument") { - return elm.body.textContent; - } - - return elm.textContent; - } - - /** - * Common checkers. - */ - function getValueChecker(aValue) { - var checker = { - check: function valueChecker_check() { - is(getValue(), aValue, "Wrong value " + aValue); - }, - }; - return checker; - } - - function getClipboardChecker(aText) { - var checker = { - check: function clipboardChecker_check() { - is(getTextFromClipboard(), aText, "Wrong text in clipboard."); - }, - }; - return checker; - } - - /** - * Process next scheduled test. - */ - this.unwrapNextTest = function unwrapNextTest() { - var data = this.mEventQueue.mInvokers[this.mEventQueue.mIndex + 1]; - if (data) { - data.func.apply(this, data.funcArgs); - } - }; - - /** - * Used to generate an invoker object for the sheduled test. - */ - this.generateTest = function generateTest( - aRemoveTriple, - aInsertTriple, - aInvokeFunc, - aChecker, - aInvokerID - ) { - var et = this; - var invoker = { - eventSeq: [], - - invoke: aInvokeFunc, - finalCheck: function finalCheck() { - // dumpTree(aID, `'${aID}' tree:`); - - aChecker.check(); - et.unwrapNextTest(); // replace dummy invoker on real invoker object. - }, - getID: function getID() { - return aInvokerID; - }, - }; - - if (aRemoveTriple) { - let checker = new textChangeChecker( - aID, - aRemoveTriple[0], - aRemoveTriple[1], - aRemoveTriple[2], - false - ); - invoker.eventSeq.push(checker); - } - - if (aInsertTriple) { - let checker = new textChangeChecker( - aID, - aInsertTriple[0], - aInsertTriple[1], - aInsertTriple[2], - true - ); - invoker.eventSeq.push(checker); - } - - // Claim that we don't want to fail when no events are expected. - if (!aRemoveTriple && !aInsertTriple) { - invoker.noEventsOnAction = true; - } - - this.mEventQueue.mInvokers[this.mEventQueue.mIndex + 1] = invoker; - }; - - /** - * Run the tests. - */ - this.startTest = function startTest(aTestRun) { - var testRunObj = aTestRun; - var thisObj = this; - this.mEventQueue.onFinish = function finishCallback() { - // Notify textRun object that all tests were finished. - testRunObj.iterate(); - - // Help GC to avoid leaks (refer to aTestRun from local variable, drop - // onFinish function). - thisObj.mEventQueue.onFinish = null; - - return DO_NOT_FINISH_TEST; - }; - - this.mEventQueue.invoke(); - }; - - this.mEventQueue = new eventQueue(); - this.mEventQueueReady = false; -} diff --git a/accessible/tests/mochitest/editabletext/test_1.html b/accessible/tests/mochitest/editabletext/test_1.html deleted file mode 100644 index 693d4c8ee5cc..000000000000 --- a/accessible/tests/mochitest/editabletext/test_1.html +++ /dev/null @@ -1,140 +0,0 @@ - - - - - nsIAccessibleEditableText chrome tests - - - - - - - - - - - - - - - Bug 452161 - - - Bug 626660 - - - Bug 1105611 - -

- -
-  
- - - -
-
-
-
- -