mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-02-26 12:20:56 +00:00
Bug 1806756 - part 8: Rewrite IME state tests of an element outside editing host to make it possible to run in remote content r=m_kato
Differential Revision: https://phabricator.services.mozilla.com/D171203
This commit is contained in:
parent
6883374a12
commit
a1af67c53e
@ -201,6 +201,79 @@ add_task(async function() {
|
||||
tester.clear();
|
||||
}
|
||||
})();
|
||||
|
||||
await (async function test_ime_state_outside_contenteditable_on_readonly_change() {
|
||||
const tester = new IMEStateOutsideContentEditableOnReadonlyChangeTester();
|
||||
await SpecialPowers.spawn(browser, [], () => {
|
||||
content.document.body.innerHTML = "<div contenteditable></div>";
|
||||
content.wrappedJSObject.runner = content.wrappedJSObject.createIMEStateOutsideContentEditableOnReadonlyChangeTester();
|
||||
});
|
||||
for (
|
||||
let index = 0;
|
||||
index <
|
||||
IMEStateOutsideContentEditableOnReadonlyChangeTester.numberOfFocusTargets;
|
||||
index++
|
||||
) {
|
||||
const expectedDataOfInitialization = await SpecialPowers.spawn(
|
||||
browser,
|
||||
[index],
|
||||
aIndex => {
|
||||
const editingHost = content.document.querySelector("div");
|
||||
return content.wrappedJSObject.runner.prepareToRun(
|
||||
aIndex,
|
||||
editingHost,
|
||||
content.window
|
||||
);
|
||||
}
|
||||
);
|
||||
tester.checkResultOfPreparation(
|
||||
expectedDataOfInitialization,
|
||||
window,
|
||||
tipWrapper
|
||||
);
|
||||
const expectedDataOfMakingParentEditingHost = await SpecialPowers.spawn(
|
||||
browser,
|
||||
[],
|
||||
() => {
|
||||
return content.wrappedJSObject.runner.runToMakeParentEditingHost();
|
||||
}
|
||||
);
|
||||
tester.checkResultOfMakingParentEditingHost(
|
||||
expectedDataOfMakingParentEditingHost
|
||||
);
|
||||
const expectedDataOfMakingHTMLEditorReadonly = await SpecialPowers.spawn(
|
||||
browser,
|
||||
[],
|
||||
() => {
|
||||
return content.wrappedJSObject.runner.runToMakeHTMLEditorReadonly();
|
||||
}
|
||||
);
|
||||
tester.checkResultOfMakingHTMLEditorReadonly(
|
||||
expectedDataOfMakingHTMLEditorReadonly
|
||||
);
|
||||
const expectedDataOfMakingHTMLEditorEditable = await SpecialPowers.spawn(
|
||||
browser,
|
||||
[],
|
||||
() => {
|
||||
return content.wrappedJSObject.runner.runToMakeHTMLEditorEditable();
|
||||
}
|
||||
);
|
||||
tester.checkResultOfMakingHTMLEditorEditable(
|
||||
expectedDataOfMakingHTMLEditorEditable
|
||||
);
|
||||
const expectedDataOfMakingParentNonEditable = await SpecialPowers.spawn(
|
||||
browser,
|
||||
[],
|
||||
() => {
|
||||
return content.wrappedJSObject.runner.runToMakeParentNonEditingHost();
|
||||
}
|
||||
);
|
||||
tester.checkResultOfMakingParentNonEditable(
|
||||
expectedDataOfMakingParentNonEditable
|
||||
);
|
||||
tester.clear();
|
||||
}
|
||||
})();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
@ -22,6 +22,9 @@ function createIMEStateInContentEditableOnReadonlyChangeTester() {
|
||||
function createIMEStateOfTextControlInContentEditableOnReadonlyChangeTester() {
|
||||
return new IMEStateOfTextControlInContentEditableOnReadonlyChangeTester();
|
||||
}
|
||||
function createIMEStateOutsideContentEditableOnReadonlyChangeTester() {
|
||||
return new IMEStateOutsideContentEditableOnReadonlyChangeTester();
|
||||
}
|
||||
function createIMEStateWhenNoActiveElementTester(aDescription) {
|
||||
return new IMEStateWhenNoActiveElementTester(aDescription);
|
||||
}
|
||||
|
@ -388,3 +388,227 @@ class IMEStateOfTextControlInContentEditableOnReadonlyChangeTester {
|
||||
this.#checkResult(aExpectedResult);
|
||||
}
|
||||
}
|
||||
|
||||
class IMEStateOutsideContentEditableOnReadonlyChangeTester {
|
||||
static #sFocusTargets = [
|
||||
{
|
||||
tag: "input",
|
||||
type: "text",
|
||||
readonly: false,
|
||||
},
|
||||
{
|
||||
tag: "input",
|
||||
type: "text",
|
||||
readonly: true,
|
||||
},
|
||||
{
|
||||
tag: "textarea",
|
||||
readonly: false,
|
||||
},
|
||||
{
|
||||
tag: "textarea",
|
||||
readonly: true,
|
||||
},
|
||||
{
|
||||
tag: "button",
|
||||
},
|
||||
{
|
||||
tag: "body",
|
||||
},
|
||||
];
|
||||
|
||||
static get numberOfFocusTargets() {
|
||||
return IMEStateOutsideContentEditableOnReadonlyChangeTester.#sFocusTargets
|
||||
.length;
|
||||
}
|
||||
|
||||
static #maybeCreateElement(aDocument, aFocusTarget) {
|
||||
if (aFocusTarget.tag == "body") {
|
||||
return null;
|
||||
}
|
||||
const element = aDocument.createElement(aFocusTarget.tag);
|
||||
if (aFocusTarget.type !== undefined) {
|
||||
element.setAttribute("type", aFocusTarget.type);
|
||||
}
|
||||
if (aFocusTarget.readonly) {
|
||||
element.setAttribute("readonly", "");
|
||||
}
|
||||
return element;
|
||||
}
|
||||
|
||||
#getDescription() {
|
||||
return `<${this.#mFocusTarget.tag}${
|
||||
this.#mFocusTarget.type !== undefined
|
||||
? ` type=${this.#mFocusTarget.type}`
|
||||
: ""
|
||||
}${this.#mFocusTarget.readonly ? " readonly" : ""}>`;
|
||||
}
|
||||
|
||||
#getExpectedIMEState() {
|
||||
return this.#mFocusTarget.readonly ||
|
||||
this.#mFocusTarget.tag == "button" ||
|
||||
this.#mFocusTarget.tag == "body"
|
||||
? SpecialPowers.Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED
|
||||
: SpecialPowers.Ci.nsIDOMWindowUtils.IME_STATUS_ENABLED;
|
||||
}
|
||||
|
||||
#flushPendingIMENotifications() {
|
||||
return new Promise(resolve =>
|
||||
this.#mWindow.requestAnimationFrame(() =>
|
||||
this.#mWindow.requestAnimationFrame(resolve)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Runner only fields.
|
||||
#mBody;
|
||||
#mEditingHost;
|
||||
#mFocusTarget;
|
||||
#mFocusTargetElement;
|
||||
#mWindow;
|
||||
|
||||
// Checker only fields.
|
||||
#mWindowUtils;
|
||||
#mTIPWrapper;
|
||||
|
||||
clear() {
|
||||
this.#mTIPWrapper?.clearFocusBlurNotifications();
|
||||
this.#mTIPWrapper = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} aIndex Index of the test.
|
||||
* @param {Element} aEditingHost The editing host.
|
||||
* @param {Window} aWindow [optional] The DOM window containing aEditingHost.
|
||||
* @returns {object} Expected result of initial state.
|
||||
*/
|
||||
async prepareToRun(aIndex, aEditingHost, aWindow = window) {
|
||||
this.#mWindow = aWindow;
|
||||
this.#mEditingHost = aEditingHost;
|
||||
this.#mEditingHost.removeAttribute("contenteditable");
|
||||
this.#mBody = this.#mEditingHost.ownerDocument.body;
|
||||
this.#mBody.ownerDocument.activeElement?.blur();
|
||||
if (this.#mFocusTargetElement != this.#mBody) {
|
||||
this.#mFocusTargetElement?.remove();
|
||||
}
|
||||
await this.#flushPendingIMENotifications();
|
||||
this.#mFocusTarget =
|
||||
IMEStateOutsideContentEditableOnReadonlyChangeTester.#sFocusTargets[
|
||||
aIndex
|
||||
];
|
||||
this.#mFocusTargetElement = IMEStateOutsideContentEditableOnReadonlyChangeTester.#maybeCreateElement(
|
||||
this.#mBody.ownerDocument,
|
||||
this.#mFocusTarget
|
||||
);
|
||||
if (this.#mFocusTargetElement) {
|
||||
this.#mBody.appendChild(this.#mFocusTargetElement);
|
||||
this.#mFocusTargetElement.focus();
|
||||
}
|
||||
await this.#flushPendingIMENotifications();
|
||||
const expectedIMEState = this.#getExpectedIMEState();
|
||||
return {
|
||||
description: `when ${this.#getDescription()} simply has focus`,
|
||||
expectedIMEState,
|
||||
expectedIMEFocus:
|
||||
expectedIMEState !=
|
||||
SpecialPowers.Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED,
|
||||
};
|
||||
}
|
||||
|
||||
#checkResult(aExpectedResult) {
|
||||
const description = "IMEStateOutsideContentEditableOnReadonlyChangeTester";
|
||||
is(
|
||||
this.#mWindowUtils.IMEStatus,
|
||||
aExpectedResult.expectedIMEState,
|
||||
`${description}: IME state should be proper one for the focused element ${aExpectedResult.description}`
|
||||
);
|
||||
is(
|
||||
this.#mTIPWrapper.IMEHasFocus,
|
||||
aExpectedResult.expectedIMEFocus,
|
||||
`${description}: IME should ${
|
||||
aExpectedResult.expectedIMEFocus ? "" : "not "
|
||||
}have focus ${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.
|
||||
*/
|
||||
checkResultOfPreparation(aExpectedResult, aWindow, aTIPWrapper) {
|
||||
this.#mWindowUtils = SpecialPowers.wrap(aWindow).windowUtils;
|
||||
this.#mTIPWrapper = aTIPWrapper;
|
||||
this.#checkResult(aExpectedResult);
|
||||
}
|
||||
|
||||
async runToMakeParentEditingHost() {
|
||||
this.#mEditingHost.setAttribute("contenteditable", "");
|
||||
await this.#flushPendingIMENotifications();
|
||||
const expectedIMEState = this.#getExpectedIMEState();
|
||||
return {
|
||||
description: `when parent of ${this.#getDescription()} becomes contenteditable`,
|
||||
expectedIMEState,
|
||||
expectedIMEFocus:
|
||||
expectedIMEState !=
|
||||
SpecialPowers.Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED,
|
||||
};
|
||||
}
|
||||
|
||||
checkResultOfMakingParentEditingHost(aExpectedResult) {
|
||||
this.#checkResult(aExpectedResult);
|
||||
}
|
||||
|
||||
async runToMakeHTMLEditorReadonly() {
|
||||
const editor = SpecialPowers.wrap(this.#mWindow).docShell.editor;
|
||||
editor.flags |= SpecialPowers.Ci.nsIEditor.eEditorReadonlyMask;
|
||||
await this.#flushPendingIMENotifications();
|
||||
const expectedIMEState = this.#getExpectedIMEState();
|
||||
return {
|
||||
description: `when HTMLEditor for parent of ${this.#getDescription()} becomes readonly`,
|
||||
expectedIMEState,
|
||||
expectedIMEFocus:
|
||||
expectedIMEState !=
|
||||
SpecialPowers.Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED,
|
||||
};
|
||||
}
|
||||
|
||||
checkResultOfMakingHTMLEditorReadonly(aExpectedResult) {
|
||||
this.#checkResult(aExpectedResult);
|
||||
}
|
||||
|
||||
async runToMakeHTMLEditorEditable() {
|
||||
const editor = SpecialPowers.wrap(this.#mWindow).docShell.editor;
|
||||
editor.flags &= ~SpecialPowers.Ci.nsIEditor.eEditorReadonlyMask;
|
||||
await this.#flushPendingIMENotifications();
|
||||
const expectedIMEState = this.#getExpectedIMEState();
|
||||
return {
|
||||
description: `when HTMLEditor for parent of ${this.#getDescription()} becomes editable`,
|
||||
expectedIMEState,
|
||||
expectedIMEFocus:
|
||||
expectedIMEState !=
|
||||
SpecialPowers.Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED,
|
||||
};
|
||||
}
|
||||
|
||||
checkResultOfMakingHTMLEditorEditable(aExpectedResult) {
|
||||
this.#checkResult(aExpectedResult);
|
||||
}
|
||||
|
||||
async runToMakeParentNonEditingHost() {
|
||||
this.#mEditingHost.removeAttribute("contenteditable");
|
||||
await this.#flushPendingIMENotifications();
|
||||
const expectedIMEState = this.#getExpectedIMEState();
|
||||
return {
|
||||
description: `when parent of ${this.#getDescription()} becomes non-editable`,
|
||||
expectedIMEState,
|
||||
expectedIMEFocus:
|
||||
expectedIMEState !=
|
||||
SpecialPowers.Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED,
|
||||
};
|
||||
}
|
||||
|
||||
checkResultOfMakingParentNonEditable(aExpectedResult) {
|
||||
this.#checkResult(aExpectedResult);
|
||||
}
|
||||
}
|
||||
|
@ -49,6 +49,21 @@ SimpleTest.waitForFocus(async () => {
|
||||
editingHost.setAttribute("contenteditable", "");
|
||||
})();
|
||||
|
||||
await (async function test_ime_state_outside_contenteditable_on_readonly_change() {
|
||||
const tester = new IMEStateOutsideContentEditableOnReadonlyChangeTester();
|
||||
for (let index = 0;
|
||||
index < IMEStateOutsideContentEditableOnReadonlyChangeTester.numberOfFocusTargets;
|
||||
index++) {
|
||||
tester.checkResultOfPreparation(await tester.prepareToRun(index, editingHost), window, tipWrapper);
|
||||
tester.checkResultOfMakingParentEditingHost(await tester.runToMakeParentEditingHost());
|
||||
tester.checkResultOfMakingHTMLEditorReadonly(await tester.runToMakeHTMLEditorReadonly());
|
||||
tester.checkResultOfMakingHTMLEditorEditable(await tester.runToMakeHTMLEditorEditable());
|
||||
tester.checkResultOfMakingParentNonEditable(await tester.runToMakeParentNonEditingHost());
|
||||
tester.clear();
|
||||
}
|
||||
editingHost.setAttribute("contenteditable", "");
|
||||
})();
|
||||
|
||||
SimpleTest.finish();
|
||||
});
|
||||
</script>
|
||||
|
@ -9,17 +9,7 @@
|
||||
</head>
|
||||
<body onload="setTimeout(runTests, 0);" style="ime-mode: disabled;">
|
||||
<div id="display" style="ime-mode: disabled;">
|
||||
<!-- input elements -->
|
||||
<input type="text" id="text"/><br/>
|
||||
<input type="text" id="text_readonly" readonly="readonly"/><br/>
|
||||
|
||||
<!-- form controls -->
|
||||
<button id="button">button</button><br/>
|
||||
<textarea id="textarea">textarea</textarea><br/>
|
||||
<textarea id="textarea_readonly" readonly="readonly">textarea[readonly]</textarea><br/>
|
||||
|
||||
<!-- contenteditable editor -->
|
||||
<div id="contenteditableEditor" contenteditable="true"></div>
|
||||
<input type="text" id="text"/><br/>
|
||||
</div>
|
||||
<div id="content" style="display: none">
|
||||
|
||||
@ -42,79 +32,6 @@ function hitEventLoop(aFunc, aTimes) {
|
||||
var gUtils = window.windowUtils;
|
||||
var gFM = Services.focus;
|
||||
|
||||
function runComplexContenteditableTests() {
|
||||
const container = document.getElementById("display");
|
||||
const kReadonly = Ci.nsIEditor.eEditorReadonlyMask;
|
||||
|
||||
function testOnOutsideOfEditor(aFocusNode, aFocusNodeDescription, aEditor) {
|
||||
const description = "testOnOutsideOfEditor: ";
|
||||
if (aFocusNode) {
|
||||
aFocusNode.focus();
|
||||
is(gFM.focusedElement, aFocusNode,
|
||||
description + "The " + aFocusNodeDescription + " doesn't get focus");
|
||||
} else {
|
||||
if (document.activeElement) {
|
||||
document.activeElement.blur();
|
||||
}
|
||||
is(gFM.focusedElement, null,
|
||||
description + "Unexpected element has focus");
|
||||
}
|
||||
var expectedState =
|
||||
aFocusNode ? gUtils.IMEStatus : gUtils.IME_STATUS_DISABLED;
|
||||
var unexpectedStateDescription =
|
||||
expectedState != gUtils.IME_STATUS_ENABLED ? "enabled" : "disabled";
|
||||
|
||||
aEditor.setAttribute("contenteditable", "true");
|
||||
is(gFM.focusedElement, aFocusNode,
|
||||
description + "The " + aFocusNodeDescription +
|
||||
" loses focus, a HTML editor is editable now");
|
||||
is(gUtils.IMEStatus, expectedState,
|
||||
description + "IME becomes " + unexpectedStateDescription +
|
||||
" on the " + aFocusNodeDescription +
|
||||
", the HTML editor is editable now");
|
||||
const editor = window.docShell.editor;
|
||||
const flags = editor.flags;
|
||||
editor.flags = flags | kReadonly;
|
||||
is(gFM.focusedElement, aFocusNode,
|
||||
description + aFocusNodeDescription +
|
||||
" loses focus by changing HTML editor flags");
|
||||
is(gUtils.IMEStatus, expectedState,
|
||||
description + "IME becomes " + unexpectedStateDescription + " on " +
|
||||
aFocusNodeDescription + ", the HTML editor is readonly now");
|
||||
editor.flags = flags;
|
||||
is(gFM.focusedElement, aFocusNode,
|
||||
description + aFocusNodeDescription +
|
||||
" loses focus by changing HTML editor flags #2");
|
||||
is(gUtils.IMEStatus, expectedState,
|
||||
description + "IME becomes " + unexpectedStateDescription + " on " +
|
||||
aFocusNodeDescription + ", the HTML editor isn't readonly now");
|
||||
container.removeAttribute("contenteditable");
|
||||
is(gFM.focusedElement, aFocusNode,
|
||||
description + aFocusNodeDescription +
|
||||
" loses focus, the HTML editor has been no editable");
|
||||
is(gUtils.IMEStatus, expectedState,
|
||||
description + "IME becomes " + unexpectedStateDescription + " on " +
|
||||
aFocusNodeDescription + ", the HTML editor has been no editable");
|
||||
}
|
||||
|
||||
var div = document.getElementById("contenteditableEditor");
|
||||
// a textarea which is outside of the editor has focus
|
||||
testOnOutsideOfEditor(document.getElementById("textarea"), "textarea", div);
|
||||
// a readonly textarea which is outside of the editor has focus
|
||||
testOnOutsideOfEditor(document.getElementById("textarea_readonly"),
|
||||
"textarea[readonly]", div);
|
||||
// an input field which is outside of the editor has focus
|
||||
testOnOutsideOfEditor(document.getElementById("text"),
|
||||
"input[type=\"text\"]", div);
|
||||
// a readonly input field which outside of the editor has focus
|
||||
testOnOutsideOfEditor(document.getElementById("text_readonly"),
|
||||
"input[type=\"text\"][readonly]", div);
|
||||
// a readonly input field which outside of the editor has focus
|
||||
testOnOutsideOfEditor(document.getElementById("button"), "button", div);
|
||||
// nobody has focus.
|
||||
testOnOutsideOfEditor(null, "nobody", div);
|
||||
}
|
||||
|
||||
function runEditorFlagChangeTests() {
|
||||
var description = "runEditorFlagChangeTests: ";
|
||||
|
||||
@ -332,9 +249,6 @@ async function runTests() {
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [["dom.forms.always_allow_key_and_focus_events.enabled", true]],
|
||||
});
|
||||
// complex contenteditable editor's tests
|
||||
runComplexContenteditableTests();
|
||||
|
||||
// test whether the IME state and composition are not changed unexpectedly
|
||||
runEditorFlagChangeTests();
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user