/* Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/ */ "use strict"; add_task(async function () { const TIP = Cc["@mozilla.org/text-input-processor;1"].createInstance( Ci.nsITextInputProcessor ); let notifications = []; const observer = (aTIP, aNotification) => { switch (aNotification.type) { case "request-to-commit": aTIP.commitComposition(); break; case "request-to-cancel": aTIP.cancelComposition(); break; case "notify-end-input-transaction": case "notify-focus": case "notify-blur": case "notify-text-change": case "notify-selection-change": notifications.push(aNotification); break; } return true; }; function checkNotifications(aExpectedNotifications, aDescription) { for (const expectedNotification of aExpectedNotifications) { const notification = notifications.find( element => element.type == expectedNotification.type ); if (expectedNotification.expected) { isnot( notification, undefined, `"${expectedNotification.type}" should be notified ${aDescription}` ); } else { is( notification, undefined, `"${expectedNotification.type}" should not be notified ${aDescription}` ); } } } ok( TIP.beginInputTransaction(window, observer), "nsITextInputProcessor.beingInputTransaction should return true" ); ok( TIP.beginInputTransactionForTests(window, observer), "nsITextInputProcessor.beginInputTransactionForTests should return true" ); await BrowserTestUtils.withNewTab( "https://example.com/browser/toolkit/content/tests/browser/file_empty.html", async function (browser) { ok(browser.isRemoteBrowser, "This test passes only in e10s mode"); // IMEContentObserver flushes pending IME notifications at next vsync // after something happens. Therefore, after doing something in content // process, we need to guarantee that IMEContentObserver has a change to // send IME notifications to the main process with calling this function. function waitForSendingIMENotificationsInContent() { return SpecialPowers.spawn(browser, [], async () => { await new Promise(resolve => content.requestAnimationFrame(() => content.requestAnimationFrame(resolve) ) ); }); } /** * Test when empty editor gets focus */ notifications = []; await SpecialPowers.spawn(browser, [], () => { content.document.body.innerHTML = "

"; const editor = content.document.querySelector("div[contenteditable]"); editor.focus(); }); await waitForSendingIMENotificationsInContent(); (function () { checkNotifications( [ { type: "notify-focus", expected: true }, { type: "notify-blur", expected: false }, { type: "notify-end-input-transaction", expected: false }, { type: "notify-text-change", expected: false }, { type: "notify-selection-change", expected: false }, ], "after empty editor gets focus" ); const text = EventUtils.synthesizeQueryTextContent(0, 1000); ok( text?.succeeded, "query text content should succeed after empty editor gets focus" ); if (text?.succeeded) { is( text.text.replace(/[\r\n]/g, ""), "", "text should be only line breaks after empty editor gets focus" ); } const selection = EventUtils.synthesizeQuerySelectedText(); ok( selection?.succeeded, "query selected text should succeed after empty editor gets focus" ); if (selection?.succeeded) { ok( !selection.notFound, "query selected text should find a selection range after empty editor gets focus" ); if (!selection.notFound) { is( selection.text, "", "selection should be collapsed after empty editor gets focus" ); } } })(); /** * Test when there is non-collapsed selection */ notifications = []; await SpecialPowers.spawn(browser, [], () => { const editor = content.document.querySelector("div[contenteditable]"); editor.innerHTML = "

abc

def

"; content .getSelection() .setBaseAndExtent( editor.querySelector("p").firstChild, 2, editor.querySelector("p + p").firstChild, 1 ); }); await waitForSendingIMENotificationsInContent(); (function () { checkNotifications( [ { type: "notify-focus", expected: false }, { type: "notify-blur", expected: false }, { type: "notify-end-input-transaction", expected: false }, { type: "notify-text-change", expected: true }, { type: "notify-selection-change", expected: true }, ], "after modifying focused editor" ); const text = EventUtils.synthesizeQueryTextContent(0, 1000); ok( text?.succeeded, "query text content should succeed after modifying focused editor" ); if (text?.succeeded) { is( text.text.trim().replace(/\r\n/g, "\n").replace(/\n\n+/g, "\n"), "abc\ndef", "text should include the both paragraph's text after modifying focused editor" ); } const selection = EventUtils.synthesizeQuerySelectedText(); ok( selection?.succeeded, "query selected text should succeed after modifying focused editor" ); if (selection?.succeeded) { ok( !selection.notFound, "query selected text should find a selection range after modifying focused editor" ); if (!selection.notFound) { is( selection.text .trim() .replace(/\r\n/g, "\n") .replace(/\n\n+/g, "\n"), "c\nd", "selection should have the selected characters in the both paragraphs after modifying focused editor" ); } } })(); /** * Test when there is no selection ranges */ notifications = []; await SpecialPowers.spawn(browser, [], () => { content.getSelection().removeAllRanges(); }); await waitForSendingIMENotificationsInContent(); (function () { checkNotifications( [ { type: "notify-focus", expected: false }, { type: "notify-blur", expected: false }, { type: "notify-end-input-transaction", expected: false }, { type: "notify-text-change", expected: false }, { type: "notify-selection-change", expected: true }, ], "after removing all selection ranges from the focused editor" ); const text = EventUtils.synthesizeQueryTextContent(0, 1000); ok( text?.succeeded, "query text content should succeed after removing all selection ranges from the focused editor" ); if (text?.succeeded) { is( text.text.trim().replace(/\r\n/g, "\n").replace(/\n\n+/g, "\n"), "abc\ndef", "text should include the both paragraph's text after removing all selection ranges from the focused editor" ); } const selection = EventUtils.synthesizeQuerySelectedText(); ok( selection?.succeeded, "query selected text should succeed after removing all selection ranges from the focused editor" ); if (selection?.succeeded) { ok( selection.notFound, "query selected text should find no selection range after removing all selection ranges from the focused editor" ); } })(); /** * Test when no editable element has focus. */ notifications = []; await SpecialPowers.spawn(browser, [], () => { content.document.body.innerHTML = "abcdef"; }); await waitForSendingIMENotificationsInContent(); (function () { checkNotifications( [ { type: "notify-focus", expected: false }, { type: "notify-blur", expected: true }, ], "removing editor should make ContentCacheInParent not have any data" ); const text = EventUtils.synthesizeQueryTextContent(0, 1000); ok( !text?.succeeded, "query text content should fail because no editable element has focus" ); const selection = EventUtils.synthesizeQuerySelectedText(); ok( !selection?.succeeded, "query selected text should fail because no editable element has focus" ); const caret = EventUtils.synthesizeQueryCaretRect(0); ok( !caret?.succeeded, "query caret rect should fail because no editable element has focus" ); const textRect = EventUtils.synthesizeQueryTextRect(0, 5, false); ok( !textRect?.succeeded, "query text rect should fail because no editable element has focus" ); const textRectArray = EventUtils.synthesizeQueryTextRectArray(0, 5); ok( !textRectArray?.succeeded, "query text rect array should fail because no editable element has focus" ); const editorRect = EventUtils.synthesizeQueryEditorRect(); todo( !editorRect?.succeeded, "query editor rect should fail because no editable element has focus" ); })(); } ); });