/* 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"; /* import-globals-from ../../mochitest/text.js */ function waitForSelectionChange(selectionAcc, caretAcc) { if (!caretAcc) { caretAcc = selectionAcc; } return waitForEvents( [ [EVENT_TEXT_SELECTION_CHANGED, selectionAcc], // We must swallow the caret events as well to avoid confusion with later, // unrelated caret events. [EVENT_TEXT_CARET_MOVED, caretAcc], ], true ); } function changeDomSelection( browser, anchorId, anchorOffset, focusId, focusOffset ) { return invokeContentTask( browser, [anchorId, anchorOffset, focusId, focusOffset], ( contentAnchorId, contentAnchorOffset, contentFocusId, contentFocusOffset ) => { // We want the text node, so we use firstChild. content.window .getSelection() .setBaseAndExtent( content.document.getElementById(contentAnchorId).firstChild, contentAnchorOffset, content.document.getElementById(contentFocusId).firstChild, contentFocusOffset ); } ); } function testSelectionRange( browser, root, startContainer, startOffset, endContainer, endOffset ) { let selRange = root.selectionRanges.queryElementAt(0, nsIAccessibleTextRange); testTextRange( selRange, getAccessibleDOMNodeID(root), startContainer, startOffset, endContainer, endOffset ); } /** * Test text selection via keyboard. */ addAccessibleTask( `

a

bc

`, async function (browser, docAcc) { queryInterfaces(docAcc, [nsIAccessibleText]); const textarea = findAccessibleChildByID(docAcc, "textarea", [ nsIAccessibleText, ]); info("Focusing textarea"); let caretMoved = waitForEvent(EVENT_TEXT_CARET_MOVED, textarea); textarea.takeFocus(); await caretMoved; testSelectionRange(browser, textarea, textarea, 0, textarea, 0); is(textarea.selectionCount, 0, "textarea selectionCount is 0"); is(docAcc.selectionCount, 0, "document selectionCount is 0"); info("Selecting a in textarea"); let selChanged = waitForSelectionChange(textarea); EventUtils.synthesizeKey("KEY_ArrowRight", { shiftKey: true }); await selChanged; testSelectionRange(browser, textarea, textarea, 0, textarea, 1); testTextGetSelection(textarea, 0, 1, 0); info("Selecting b in textarea"); selChanged = waitForSelectionChange(textarea); EventUtils.synthesizeKey("KEY_ArrowRight", { shiftKey: true }); await selChanged; testSelectionRange(browser, textarea, textarea, 0, textarea, 2); testTextGetSelection(textarea, 0, 2, 0); info("Unselecting b in textarea"); selChanged = waitForSelectionChange(textarea); EventUtils.synthesizeKey("KEY_ArrowLeft", { shiftKey: true }); await selChanged; testSelectionRange(browser, textarea, textarea, 0, textarea, 1); testTextGetSelection(textarea, 0, 1, 0); info("Unselecting a in textarea"); // We don't fire selection changed when the selection collapses. caretMoved = waitForEvent(EVENT_TEXT_CARET_MOVED, textarea); EventUtils.synthesizeKey("KEY_ArrowLeft", { shiftKey: true }); await caretMoved; testSelectionRange(browser, textarea, textarea, 0, textarea, 0); is(textarea.selectionCount, 0, "textarea selectionCount is 0"); const editable = findAccessibleChildByID(docAcc, "editable", [ nsIAccessibleText, ]); const p1 = findAccessibleChildByID(docAcc, "p1", [nsIAccessibleText]); info("Focusing editable, caret to start"); caretMoved = waitForEvent(EVENT_TEXT_CARET_MOVED, p1); await changeDomSelection(browser, "p1", 0, "p1", 0); await caretMoved; testSelectionRange(browser, editable, p1, 0, p1, 0); is(editable.selectionCount, 0, "editable selectionCount is 0"); is(p1.selectionCount, 0, "p1 selectionCount is 0"); is(docAcc.selectionCount, 0, "document selectionCount is 0"); info("Selecting a in editable"); selChanged = waitForSelectionChange(p1); await changeDomSelection(browser, "p1", 0, "p1", 1); await selChanged; testSelectionRange(browser, editable, p1, 0, p1, 1); testTextGetSelection(editable, 0, 1, 0); testTextGetSelection(p1, 0, 1, 0); const p2 = findAccessibleChildByID(docAcc, "p2", [nsIAccessibleText]); if (browser.isRemoteBrowser) { is(p2.selectionCount, 0, "p2 selectionCount is 0"); } else { todo( false, "Siblings report wrong selection in non-cache implementation" ); } // Selecting across two Accessibles with only a partial selection in the // second. info("Selecting ab in editable"); selChanged = waitForSelectionChange(editable, p2); await changeDomSelection(browser, "p1", 0, "p2", 1); await selChanged; testSelectionRange(browser, editable, p1, 0, p2, 1); testTextGetSelection(editable, 0, 2, 0); testTextGetSelection(p1, 0, 1, 0); testTextGetSelection(p2, 0, 1, 0); const pWithLink = findAccessibleChildByID(docAcc, "pWithLink", [ nsIAccessibleText, ]); const link = findAccessibleChildByID(docAcc, "link", [nsIAccessibleText]); // Selecting both text and a link. info("Selecting de in editable"); selChanged = waitForSelectionChange(pWithLink, link); await changeDomSelection(browser, "pWithLink", 0, "link", 1); await selChanged; testSelectionRange(browser, editable, pWithLink, 0, link, 1); testTextGetSelection(editable, 2, 3, 0); testTextGetSelection(pWithLink, 0, 2, 0); testTextGetSelection(link, 0, 1, 0); // Selecting a link and text on either side. info("Selecting def in editable"); selChanged = waitForSelectionChange(pWithLink, pWithLink); await changeDomSelection(browser, "pWithLink", 0, "textAfterLink", 1); await selChanged; testSelectionRange(browser, editable, pWithLink, 0, pWithLink, 3); testTextGetSelection(editable, 2, 3, 0); testTextGetSelection(pWithLink, 0, 3, 0); testTextGetSelection(link, 0, 1, 0); // Noncontiguous selection. info("Selecting a in editable"); selChanged = waitForSelectionChange(p1); await changeDomSelection(browser, "p1", 0, "p1", 1); await selChanged; info("Adding c to selection in editable"); selChanged = waitForSelectionChange(p2); await invokeContentTask(browser, [], () => { const r = content.document.createRange(); const p2text = content.document.getElementById("p2").firstChild; r.setStart(p2text, 0); r.setEnd(p2text, 1); content.window.getSelection().addRange(r); }); await selChanged; let selRanges = editable.selectionRanges; is(selRanges.length, 2, "2 selection ranges"); testTextRange( selRanges.queryElementAt(0, nsIAccessibleTextRange), "range 0", p1, 0, p1, 1 ); testTextRange( selRanges.queryElementAt(1, nsIAccessibleTextRange), "range 1", p2, 0, p2, 1 ); is(editable.selectionCount, 2, "editable selectionCount is 2"); testTextGetSelection(editable, 0, 1, 0); testTextGetSelection(editable, 1, 2, 1); if (browser.isRemoteBrowser) { is(p1.selectionCount, 1, "p1 selectionCount is 1"); testTextGetSelection(p1, 0, 1, 0); is(p2.selectionCount, 1, "p2 selectionCount is 1"); testTextGetSelection(p2, 0, 1, 0); } else { todo( false, "Siblings report wrong selection in non-cache implementation" ); } }, { chrome: true, topLevel: true, iframe: true, remoteIframe: true, } ); /** * Tabbing to an input selects all its text. Test that the cached selection *reflects this. This has to be done separately from the other selection tests * because prior contentEditable selection changes the events that get fired. */ addAccessibleTask( ` `, async function (browser, docAcc) { // The tab order is different when there's an iframe, so focus a control // before the input to make tab consistent. info("Focusing before"); const before = findAccessibleChildByID(docAcc, "before"); // Focusing a button fires a selection event. We must swallow this to // avoid confusing the later test. let events = waitForOrderedEvents([ [EVENT_FOCUS, before], [EVENT_TEXT_SELECTION_CHANGED, docAcc], ]); before.takeFocus(); await events; const input = findAccessibleChildByID(docAcc, "input", [nsIAccessibleText]); info("Tabbing to input"); events = waitForEvents( { expected: [ [EVENT_FOCUS, input], [EVENT_TEXT_SELECTION_CHANGED, input], ], unexpected: [[EVENT_TEXT_SELECTION_CHANGED, docAcc]], }, "input", false, (args, task) => invokeContentTask(browser, args, task) ); EventUtils.synthesizeKey("KEY_Tab"); await events; testSelectionRange(browser, input, input, 0, input, 4); testTextGetSelection(input, 0, 4, 0); }, { chrome: true, topLevel: true, iframe: true, remoteIframe: true, } ); /** * Test text selection via API. */ addAccessibleTask( `

hello world

  1. Number one
`, async function (browser, docAcc) { const paragraph = findAccessibleChildByID(docAcc, "paragraph", [ nsIAccessibleText, ]); let selChanged = waitForSelectionChange(paragraph); paragraph.setSelectionBounds(0, 2, 4); await selChanged; testTextGetSelection(paragraph, 2, 4, 0); selChanged = waitForSelectionChange(paragraph); paragraph.addSelection(6, 10); await selChanged; testTextGetSelection(paragraph, 6, 10, 1); is(paragraph.selectionCount, 2, "paragraph selectionCount is 2"); selChanged = waitForSelectionChange(paragraph); paragraph.removeSelection(0); await selChanged; testTextGetSelection(paragraph, 6, 10, 0); is(paragraph.selectionCount, 1, "paragraph selectionCount is 1"); const li = findAccessibleChildByID(docAcc, "li", [nsIAccessibleText]); selChanged = waitForSelectionChange(li); li.setSelectionBounds(0, 1, 8); await selChanged; testTextGetSelection(li, 3, 8, 0); }, { chrome: true, topLevel: true, iframe: true, remoteIframe: true, } );