Bug 1806756 - part 9: Rewrite IME state tests of text control reframing to make it possible to run in remote content r=m_kato

The remaining tests are not required to run in remote content except some of
the iframe tests.  However, iframe tests are not so important because it's
managed in remote content from point of view of parent process.  Therefore, I
don't port it in this bug.

Differential Revision: https://phabricator.services.mozilla.com/D171204
This commit is contained in:
Masayuki Nakano 2023-04-11 23:26:07 +00:00
Родитель a1af67c53e
Коммит 08bf5dcc39
8 изменённых файлов: 343 добавлений и 132 удалений

Просмотреть файл

@ -44,6 +44,10 @@ support-files =
[browser_test_ime_state_in_plugin_in_remote_content.js]
support-files =
../file_ime_state_test_helper.js
[browser_test_ime_state_in_text_control_on_reframe_in_remote_content.js]
support-files =
../file_ime_state_test_helper.js
../file_test_ime_state_in_text_control_on_reframe.js
[browser_test_InputContextURI.js]
[browser_test_swipe_gesture.js]
run-if = (os == 'mac' || os == 'win' || os == 'linux')

Просмотреть файл

@ -0,0 +1,77 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
/* import-globals-from ../file_ime_state_test_helper.js */
/* import-globals-from ../file_test_ime_state_in_text_control_on_reframe.js */
Services.scriptloader.loadSubScript(
"chrome://mochitests/content/browser/widget/tests/browser/file_ime_state_test_helper.js",
this
);
Services.scriptloader.loadSubScript(
"chrome://mochitests/content/browser/widget/tests/browser/file_test_ime_state_in_text_control_on_reframe.js",
this
);
add_task(async function() {
await BrowserTestUtils.withNewTab(
"https://example.com/browser/widget/tests/browser/file_ime_state_tests.html",
async function(browser) {
const tipWrapper = new TIPWrapper(window);
ok(
tipWrapper.isAvailable(),
"TextInputProcessor should've been initialized"
);
await (async function test_ime_state_outside_contenteditable_on_readonly_change() {
const tester = new IMEStateInTextControlOnReframeTester();
await SpecialPowers.spawn(browser, [], () => {
content.document.body.innerHTML = "<div contenteditable></div>";
content.wrappedJSObject.runner = content.wrappedJSObject.createIMEStateInTextControlOnReframeTester();
});
for (
let index = 0;
index < IMEStateInTextControlOnReframeTester.numberOfTextControlTypes;
index++
) {
tipWrapper.clearFocusBlurNotifications();
const expectedData1 = await SpecialPowers.spawn(
browser,
[index],
aIndex => {
return content.wrappedJSObject.runner.prepareToRun(
aIndex,
content.document,
content.window
);
}
);
tipWrapper.typeA();
await SpecialPowers.spawn(browser, [], () => {
return new Promise(resolve =>
content.window.requestAnimationFrame(() =>
content.window.requestAnimationFrame(resolve)
)
);
});
tester.checkResultAfterTypingA(expectedData1, window, tipWrapper);
const expectedData2 = await SpecialPowers.spawn(browser, [], () => {
return content.wrappedJSObject.runner.prepareToRun2();
});
tipWrapper.typeA();
await SpecialPowers.spawn(browser, [], () => {
return new Promise(resolve =>
content.window.requestAnimationFrame(() =>
content.window.requestAnimationFrame(resolve)
)
);
});
tester.checkResultAfterTypingA2(expectedData2);
tester.clear();
}
})();
}
);
});

Просмотреть файл

@ -4,6 +4,7 @@
<meta charset="utf-8">
<script src="file_ime_state_test_helper.js"></script>
<script src="file_test_ime_state_in_contenteditable_on_readonly_change.js"></script>
<script src="file_test_ime_state_in_text_control_on_reframe.js"></script>
<script src="file_test_ime_state_on_focus_move.js"></script>
<script src="file_test_ime_state_on_input_type_change.js"></script>
<script src="file_test_ime_state_on_readonly_change.js"></script>
@ -12,6 +13,7 @@
/* import-globals-from ../file_ime_state_test_helper.js */
/* import-globals-from ../file_test_ime_state_in_contenteditable_on_readonly_change.js */
/* import-globals-from ../file_test_ime_state_in_text_control_on_reframe.js */
/* import-globals-from ../file_test_ime_state_on_focus_move.js */
/* import-globals-from ../file_test_ime_state_on_input_type_change.js */
/* import-globals-from ../file_test_ime_state_on_readonly_change.js */
@ -25,6 +27,9 @@ function createIMEStateOfTextControlInContentEditableOnReadonlyChangeTester() {
function createIMEStateOutsideContentEditableOnReadonlyChangeTester() {
return new IMEStateOutsideContentEditableOnReadonlyChangeTester();
}
function createIMEStateInTextControlOnReframeTester() {
return new IMEStateInTextControlOnReframeTester();
}
function createIMEStateWhenNoActiveElementTester(aDescription) {
return new IMEStateWhenNoActiveElementTester(aDescription);
}

Просмотреть файл

@ -27,11 +27,13 @@ support-files = window_wheeltransaction.xhtml
support-files =
file_ime_state_test_helper.js
file_test_ime_state_in_contenteditable_on_readonly_change.js
[test_ime_state_in_parent.html]
support-files = window_imestate_iframes.html
[test_ime_state_in_plugin_in_parent.html]
support-files =
file_ime_state_test_helper.js
[test_ime_state_in_text_control_on_reframe_in_parent.html]
support-files =
file_ime_state_test_helper.js
file_test_ime_state_in_text_control_on_reframe.js
[test_ime_state_on_editable_state_change_in_parent.html]
support-files =
file_ime_state_test_helper.js
@ -48,6 +50,8 @@ support-files =
support-files =
file_ime_state_test_helper.js
file_test_ime_state_on_readonly_change.js
[test_ime_state_others_in_parent.html]
support-files = window_imestate_iframes.html
[test_composition_text_querycontent.xhtml]
support-files = window_composition_text_querycontent.xhtml
[test_input_events_on_deactive_window.xhtml]

Просмотреть файл

@ -118,6 +118,16 @@ class TIPWrapper {
);
}
typeA() {
const AKey = new this.#mWindow.KeyboardEvent("", {
key: "a",
code: "KeyA",
keyCode: this.#mWindow.KeyboardEvent.DOM_VK_A,
});
this.#mTIP.keydown(AKey);
this.#mTIP.keyup(AKey);
}
isAvailable() {
return this.#mTIP != null;
}

Просмотреть файл

@ -0,0 +1,190 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
/* import-globals-from file_ime_state_test_helper.js */
// Bug 580388 and bug 808287
class IMEStateInTextControlOnReframeTester {
static #sTextControls = [
{
tag: "input",
type: "text",
},
{
tag: "input",
type: "password",
},
{
tag: "textarea",
},
];
static get numberOfTextControlTypes() {
return IMEStateInTextControlOnReframeTester.#sTextControls.length;
}
#createElement() {
const textControl = this.#mDocument.createElement(this.#mTextControl.tag);
if (this.#mTextControl.type !== undefined) {
textControl.setAttribute("type", this.#mTextControl.type);
}
return textControl;
}
#getDescription() {
return `<${this.#mTextControl.tag}${
this.#mTextControl.type !== undefined
? ` type=${this.#mTextControl.type}`
: ""
}>`;
}
#getExpectedIMEState() {
return this.#mTextControl.type == "password"
? SpecialPowers.Ci.nsIDOMWindowUtils.IME_STATUS_PASSWORD
: SpecialPowers.Ci.nsIDOMWindowUtils.IME_STATUS_ENABLED;
}
#flushPendingIMENotifications() {
return new Promise(resolve =>
this.#mWindow.requestAnimationFrame(() =>
this.#mWindow.requestAnimationFrame(resolve)
)
);
}
// Runner only fields.
#mTextControl;
#mTextControlElement;
#mWindow;
#mDocument;
// Checker only fields.
#mWindowUtils;
#mTIPWrapper;
clear() {
this.#mTIPWrapper?.clearFocusBlurNotifications();
this.#mTIPWrapper = null;
}
/**
* @param {number} aIndex Index of the test.
* @param {Element} aDocument The document to run the test.
* @param {Window} aWindow [optional] The DOM window for aDocument.
* @returns {object} Expected result of initial state.
*/
async prepareToRun(aIndex, aDocument, aWindow = window) {
this.#mWindow = aWindow;
this.#mDocument = aDocument;
this.#mDocument.activeElement?.blur();
this.#mTextControlElement?.remove();
await this.#flushPendingIMENotifications();
this.#mTextControl =
IMEStateInTextControlOnReframeTester.#sTextControls[aIndex];
this.#mTextControlElement = this.#createElement();
this.#mDocument.body.appendChild(this.#mTextControlElement);
this.#mTextControlElement.focus();
this.#mTextControlElement.style.overflow = "visible";
this.#mTextControlElement.addEventListener(
"input",
aEvent => {
aEvent.target.style.overflow = "hidden";
},
{
capture: true,
}
);
await this.#flushPendingIMENotifications();
const expectedIMEState = this.#getExpectedIMEState();
return {
description: `when ${this.#getDescription()} has focus`,
expectedIMEState,
expectedIMEFocus:
expectedIMEState !=
SpecialPowers.Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED,
expectedNumberOfFocusNotifications: 1,
};
}
#checkResult(aExpectedResult) {
const description = "IMEStateInTextControlOnReframeTester";
is(
this.#mWindowUtils.IMEStatus,
aExpectedResult.expectedIMEState,
`${description}: IME state should be proper one for the text control ${aExpectedResult.description}`
);
is(
this.#mTIPWrapper.IMEHasFocus,
aExpectedResult.expectedIMEFocus,
`${description}: IME should ${
aExpectedResult.expectedIMEFocus ? "" : "not "
}have focus ${aExpectedResult.description}`
);
if (aExpectedResult.numberOfFocusNotifications !== undefined) {
is(
this.#mTIPWrapper.numberOfFocusNotifications,
aExpectedResult.numberOfFocusNotifications,
`${description}: focus notifications should've been received ${
this.#mTIPWrapper.numberOfFocusNotifications
} times ${aExpectedResult.description}`
);
}
if (aExpectedResult.numberOfBlurNotifications !== undefined) {
is(
this.#mTIPWrapper.numberOfBlurNotifications,
aExpectedResult.numberOfBlurNotifications,
`${description}: blur notifications should've been received ${
this.#mTIPWrapper.numberOfBlurNotifications
} times ${aExpectedResult.description}`
);
}
}
/**
* @param {object} aExpectedResult The expected result returned by prepareToRun().
* @param {Window} aWindow The window whose IME state should be checked.
* @param {TIPWrapper} aTIPWrapper The TIP wrapper of aWindow.
*/
checkResultAfterTypingA(aExpectedResult, aWindow, aTIPWrapper) {
this.#mWindowUtils = SpecialPowers.wrap(aWindow).windowUtils;
this.#mTIPWrapper = aTIPWrapper;
this.#checkResult(aExpectedResult);
this.#mTIPWrapper.clearFocusBlurNotifications();
}
async prepareToRun2() {
this.#mTextControlElement.addEventListener("focus", aEvent => {
// Perform a style change and flush it to trigger reframing.
aEvent.target.style.overflow = "visible";
aEvent.target.getBoundingClientRect();
});
this.#mTextControlElement.blur();
this.#mTextControlElement.focus();
await this.#flushPendingIMENotifications();
const expectedIMEState = this.#getExpectedIMEState();
return {
description: `when ${this.#getDescription()} is reframed by focus event listener`,
expectedIMEState,
expectedIMEFocus:
expectedIMEState !=
SpecialPowers.Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED,
expectedNumberOfFocusNotifications: 1,
expectedNumberOfBlurNotifications: 1,
};
}
/**
* @param {object} aExpectedResult The expected result returned by prepareToRun().
*/
checkResultAfterTypingA2(aExpectedResult) {
this.#checkResult(aExpectedResult);
this.#mTIPWrapper.clearFocusBlurNotifications();
}
}

Просмотреть файл

@ -0,0 +1,41 @@
<html>
<head>
<title>Test for IME state of contenteditable on readonly state change</title>
<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<script src="file_ime_state_test_helper.js"></script>
<script src="file_test_ime_state_in_text_control_on_reframe.js"></script>
<link rel="stylesheet" href="chrome://mochikit/content/tests/SimpleTest/test.css">
<script>
"use strict";
/* import-globals-from file_ime_state_test_helper.js */
/* import-globals-from file_test_ime_state_in_text_control_on_reframe.js */
SimpleTest.waitForExplicitFinish();
SimpleTest.waitForFocus(async () => {
const tipWrapper = new TIPWrapper(window);
const tester = new IMEStateInTextControlOnReframeTester();
for (let index = 0;
index < IMEStateInTextControlOnReframeTester.numberOfTextControlTypes;
index++) {
tipWrapper.clearFocusBlurNotifications();
const expectedData1 = await tester.prepareToRun(index, document);
tipWrapper.typeA();
await new Promise(resolve => requestAnimationFrame(
() => requestAnimationFrame(resolve)
)); // Flush IME content observer notifications.
tester.checkResultAfterTypingA(expectedData1, window, tipWrapper);
const expectedData2 = await tester.prepareToRun2(index, document);
tipWrapper.typeA();
await new Promise(resolve => requestAnimationFrame(
() => requestAnimationFrame(resolve)
)); // Flush IME content observer notifications.
tester.checkResultAfterTypingA2(expectedData2);
}
SimpleTest.finish();
});
</script>
<body></body>
</html>

Просмотреть файл

@ -1,34 +1,19 @@
<html style="ime-mode: disabled;">
<html>
<head>
<title>Test for IME state controlling</title>
<title>Test for IME state controlling in some special cases</title>
<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
<script src="file_ime_state_test_helper.js"></script>
<link rel="stylesheet" type="text/css"
href="chrome://mochikit/content/tests/SimpleTest/test.css" />
</head>
<body onload="setTimeout(runTests, 0);" style="ime-mode: disabled;">
<div id="display" style="ime-mode: disabled;">
<input type="text" id="text"/><br/>
</div>
<div id="content" style="display: none">
</div>
<pre id="test">
</pre>
<script class="testbody" type="application/javascript">
<body>
<div id="display"></div>
<div id="content" style="display: none"></div>
<pre id="test"></pre>
<script>
SimpleTest.waitForExplicitFinish();
function hitEventLoop(aFunc, aTimes) {
if (--aTimes) {
setTimeout(hitEventLoop, 0, aFunc, aTimes);
} else {
setTimeout(aFunc, 20);
}
}
var gUtils = window.windowUtils;
var gFM = Services.focus;
@ -147,108 +132,7 @@ function runTestPasswordFieldOnDialog() {
}
}
// Bug 580388 and bug 808287
async function runEditorReframeTests() {
if (document.activeElement) {
document.activeElement.blur();
}
var IMEFocus = 0;
var IMEBlur = 0;
var IMEHasFocus = false;
var TIPCallback = function(aTIP, aNotification) {
switch (aNotification.type) {
case "request-to-commit":
aTIP.commitComposition();
break;
case "request-to-cancel":
aTIP.cancelComposition();
break;
case "notify-focus":
IMEFocus++;
IMEHasFocus = true;
break;
case "notify-blur":
IMEBlur++;
IMEHasFocus = false;
break;
}
return true;
};
var TIP = Cc["@mozilla.org/text-input-processor;1"]
.createInstance(Ci.nsITextInputProcessor);
if (!TIP.beginInputTransactionForTests(window, TIPCallback)) {
ok(false, "runEditorReframeTests(): failed to begin input transaction");
return;
}
var input = document.getElementById("text");
input.focus();
is(IMEFocus, 1, "runEditorReframeTests(): IME should receive a focus notification by a call of <input>.focus()");
is(IMEBlur, 0, "runEditorReframeTests(): IME shouldn't receive a blur notification by a call of <input>.focus()");
ok(IMEHasFocus, "runEditorReframeTests(): IME should have focus because <input>.focus() is called");
IMEFocus = IMEBlur = 0;
input.style.overflow = "visible";
var onInput = function(aEvent) {
aEvent.target.style.overflow = "hidden";
};
input.addEventListener("input", onInput, true);
var AKey = new KeyboardEvent("", { key: "a", code: "KeyA", keyCode: KeyboardEvent.DOM_VK_A });
TIP.keydown(AKey);
TIP.keyup(AKey);
await new Promise(r => hitEventLoop(r, 20));
is(IMEFocus, 0, "runEditorReframeTests(): IME shouldn't receive a focus notification during reframing");
is(IMEBlur, 0, "runEditorReframeTests(): IME shouldn't receive a blur notification during reframing");
ok(IMEHasFocus, "runEditorReframeTests(): IME must have focus even after reframing");
var onFocus = function(aEvent) {
// Perform a style change and query during focus to trigger reframing
input.style.overflow = "visible";
synthesizeQuerySelectedText();
};
input.addEventListener("focus", onFocus);
IMEFocus = IMEBlur = 0;
input.blur();
input.focus();
await new Promise(
resolve => requestAnimationFrame(
() => requestAnimationFrame(resolve)
)
); // wait for notify-focus
TIP.keydown(AKey);
TIP.keyup(AKey);
await new Promise(r => hitEventLoop(r, 20));
is(IMEFocus, 1, "runEditorReframeTests(): IME should receive a focus notification at focus but shouldn't receive it during reframing");
is(IMEBlur, 1, "runEditorReframeTests(): IME should receive a blur notification at blur but shouldn't receive it during reframing");
ok(IMEHasFocus, "runEditorReframeTests(): IME sould have focus after reframing during focus");
input.removeEventListener("input", onInput, true);
input.removeEventListener("focus", onFocus);
input.style.overflow = "visible";
input.value = "";
TIP = null;
await new Promise(r => hitEventLoop(r, 20));
}
async function runTests() {
await SpecialPowers.pushPrefEnv({
set: [["dom.forms.always_allow_key_and_focus_events.enabled", true]],
});
SimpleTest.waitForFocus(async () => {
// test whether the IME state and composition are not changed unexpectedly
runEditorFlagChangeTests();
@ -256,18 +140,14 @@ async function runTests() {
// XXX temporary disable against failure
// runTestPasswordFieldOnDialog();
// Asynchronous tests
await runEditorReframeTests();
// This will call onFinish(), so, this test must be the last.
// TODO: Make this test run with remote content too.
runEditableSubframeTests();
}
});
function onFinish() {
SimpleTest.finish();
}
</script>
</body>
</html>