1 /* Any copyright is dedicated to the Public Domain.
2 http://creativecommons.org/publicdomain/zero/1.0/ */
6 /* import-globals-from file_ime_state_test_helper.js */
8 class IMEStateInContentEditableOnReadonlyChangeTester {
14 // Tester only fields.
19 this.#mTIPWrapper?.clearFocusBlurNotifications();
20 this.#mTIPWrapper = null;
23 #flushPendingIMENotifications() {
24 return new Promise(resolve =>
25 this.#mWindow.requestAnimationFrame(() =>
26 this.#mWindow.requestAnimationFrame(resolve)
31 #getExpectedIMEState() {
32 // Although if this.#mFocusElement is a <button>, its `.focus()` call
33 // focus it, but caret is not set into it and following typing is handled
34 // outside the <button>. Therefore, anyway the enabled state should be
36 return SpecialPowers.Ci.nsIDOMWindowUtils.IME_STATUS_ENABLED;
40 * @param {Element} aEditingHost The editing host.
41 * @param {Element} aFocusElement Element which should have focus. This must
42 * be an inclusive descendant of the editing host and editable element.
43 * @param {Window} aWindow [optional] The window.
44 * @returns {object} Expected result of initial state.
46 async prepareToRun(aEditingHost, aFocusElement, aWindow = window) {
47 this.#mWindow = aWindow;
48 this.#mEditingHost = aEditingHost;
49 this.#mFocusElement = aFocusElement;
51 if (this.#mEditingHost.ownerDocument.activeElement) {
52 this.#mEditingHost.ownerDocument.activeElement.blur();
53 await this.#flushPendingIMENotifications();
56 this.#mWindow.focus();
57 this.#mEditingHost.setAttribute("contenteditable", "");
58 this.#mFocusElement.focus();
60 await this.#flushPendingIMENotifications();
62 const expectedIMEState = this.#getExpectedIMEState();
64 description: `when initialized with setting focus to ${
65 this.#mFocusElement == this.#mEditingHost
67 : `<${this.#mFocusElement.tagName.toLowerCase()}>`
72 SpecialPowers.Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED,
77 * @param {object} aExpectedResult The expected result of the test.
79 #checkResult(aExpectedResult) {
80 const description = `IMEStateInContentEditableOnReadonlyChangeTester`;
82 this.#mWindowUtils.IMEStatus,
83 aExpectedResult.expectedIMEState,
84 `${description}: IME enabled state should be expected one ${aExpectedResult.description}`
87 this.#mTIPWrapper.IMEHasFocus,
88 aExpectedResult.expectedIMEFocus,
89 `${description}: IME should ${
90 aExpectedResult.expectedIMEFocus ? "" : "not "
91 }have focus ${aExpectedResult.description}`
96 * @param {object} aExpectedResult The expected result of prepareToRun().
97 * @param {Window} aWindow The window to check IME state.
98 * @param {TIPWrapper} aTIPWrapper The TIPWrapper for aWindow.
100 checkResultOfPreparation(aExpectedResult, aWindow, aTIPWrapper) {
101 this.#mWindowUtils = SpecialPowers.wrap(aWindow).windowUtils;
102 this.#mTIPWrapper = aTIPWrapper;
103 this.#checkResult(aExpectedResult);
107 * @returns {object} The expected result.
109 async runToMakeHTMLEditorReadonly() {
110 const htmlEditor = SpecialPowers.wrap(this.#mWindow).docShell.editor;
111 htmlEditor.flags |= SpecialPowers.Ci.nsIEditor.eEditorReadonlyMask;
113 await this.#flushPendingIMENotifications();
117 this.#mFocusElement == this.#mEditingHost
118 ? "when the editing host has focus"
119 : `when <${this.#mFocusElement.tagName.toLowerCase()}> has focus`,
120 expectedIMEState: SpecialPowers.Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED,
121 expectedIMEFocus: false,
126 * @param {object} aExpectedResult The expected result of runToMakeHTMLEditorReadonly().
128 checkResultOfMakingHTMLEditorReadonly(aExpectedResult) {
129 this.#checkResult(aExpectedResult);
133 * @returns {object} The expected result.
135 async runToMakeHTMLEditorEditable() {
136 const htmlEditor = SpecialPowers.wrap(this.#mWindow).docShell.editor;
137 htmlEditor.flags &= ~SpecialPowers.Ci.nsIEditor.eEditorReadonlyMask;
139 await this.#flushPendingIMENotifications();
141 const expectedIMEState = this.#getExpectedIMEState();
144 this.#mFocusElement == this.#mEditingHost
145 ? "when the editing host has focus"
146 : `when <${this.#mFocusElement.tagName.toLowerCase()}> has focus`,
150 SpecialPowers.Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED,
155 * @param {object} aExpectedResult The expected result of runToMakeHTMLEditorEditable().
157 checkResultOfMakingHTMLEditorEditable(aExpectedResult) {
158 this.#checkResult(aExpectedResult);
161 async runToRemoveContentEditableAttribute() {
162 this.#mEditingHost.removeAttribute("contenteditable");
164 await this.#flushPendingIMENotifications();
168 this.#mFocusElement == this.#mEditingHost
169 ? "after removing contenteditable attribute when the editing host has focus"
170 : `after removing contenteditable attribute when <${this.#mFocusElement.tagName.toLowerCase()}> has focus`,
171 expectedIMEState: SpecialPowers.Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED,
172 expectedIMEFocus: false,
177 * @param {object} aExpectedResult The expected result of runToRemoveContentEditableAttribute().
179 checkResultOfRemovingContentEditableAttribute(aExpectedResult) {
180 this.#checkResult(aExpectedResult);
184 class IMEStateOfTextControlInContentEditableOnReadonlyChangeTester {
185 static #sTextControls = [
206 static get numberOfTextControlTypes() {
207 return IMEStateOfTextControlInContentEditableOnReadonlyChangeTester
208 .#sTextControls.length;
211 static #createElement(aDocument, aTextControl) {
212 const textControl = aDocument.createElement(aTextControl.tag);
213 if (aTextControl.type !== undefined) {
214 textControl.setAttribute("type", aTextControl.type);
216 if (aTextControl.readonly) {
217 textControl.setAttribute("readonly", "");
223 return `<${this.#mTextControl.tag}${
224 this.#mTextControl.type !== undefined
225 ? ` type=${this.#mTextControl.type}`
227 }${this.#mTextControl.readonly ? " readonly" : ""}>`;
230 #getExpectedIMEState() {
231 return this.#mTextControl.readonly
232 ? SpecialPowers.Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED
233 : SpecialPowers.Ci.nsIDOMWindowUtils.IME_STATUS_ENABLED;
236 #flushPendingIMENotifications() {
237 return new Promise(resolve =>
238 this.#mWindow.requestAnimationFrame(() =>
239 this.#mWindow.requestAnimationFrame(resolve)
244 // Runner only fields.
247 #mTextControlElement;
250 // Checker only fields.
255 this.#mTIPWrapper?.clearFocusBlurNotifications();
256 this.#mTIPWrapper = null;
260 * @param {number} aIndex Index of the test.
261 * @param {Element} aEditingHost The editing host which will have a text control.
262 * @param {Window} aWindow [optional] The DOM window containing aEditingHost.
263 * @returns {object} Expected result of initial state.
265 async prepareToRun(aIndex, aEditingHost, aWindow = window) {
266 this.#mWindow = aWindow;
267 this.#mEditingHost = aEditingHost;
268 this.#mEditingHost.ownerDocument.activeElement?.blur();
269 this.#mEditingHost.removeAttribute("contenteditable");
270 this.#mTextControlElement?.remove();
271 await this.#flushPendingIMENotifications();
273 IMEStateOfTextControlInContentEditableOnReadonlyChangeTester.#sTextControls[
276 this.#mTextControlElement =
277 IMEStateOfTextControlInContentEditableOnReadonlyChangeTester.#createElement(
278 this.#mEditingHost.ownerDocument,
281 this.#mEditingHost.appendChild(this.#mTextControlElement);
282 this.#mTextControlElement.focus();
283 await this.#flushPendingIMENotifications();
284 const expectedIMEState = this.#getExpectedIMEState();
286 description: `when ${this.#getDescription()} simply has focus`,
290 SpecialPowers.Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED,
294 #checkResult(aExpectedResult) {
296 "IMEStateOfTextControlInContentEditableOnReadonlyChangeTester";
298 this.#mWindowUtils.IMEStatus,
299 aExpectedResult.expectedIMEState,
300 `${description}: IME state should be proper one for the text control ${aExpectedResult.description}`
303 this.#mTIPWrapper.IMEHasFocus,
304 aExpectedResult.expectedIMEFocus,
305 `${description}: IME should ${
306 aExpectedResult.expectedIMEFocus ? "" : "not "
307 }have focus ${aExpectedResult.description}`
312 * @param {object} aExpectedResult The expected result returned by prepareToRun().
313 * @param {Window} aWindow The window whose IME state should be checked.
314 * @param {TIPWrapper} aTIPWrapper The TIP wrapper of aWindow.
316 checkResultOfPreparation(aExpectedResult, aWindow, aTIPWrapper) {
317 this.#mWindowUtils = SpecialPowers.wrap(aWindow).windowUtils;
318 this.#mTIPWrapper = aTIPWrapper;
319 this.#checkResult(aExpectedResult);
322 async runToMakeParentEditingHost() {
323 this.#mEditingHost.setAttribute("contenteditable", "");
324 await this.#flushPendingIMENotifications();
325 const expectedIMEState = this.#getExpectedIMEState();
327 description: `when parent of ${this.#getDescription()} becomes contenteditable`,
331 SpecialPowers.Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED,
335 checkResultOfMakingParentEditingHost(aExpectedResult) {
336 this.#checkResult(aExpectedResult);
339 async runToMakeHTMLEditorReadonly() {
340 const editor = SpecialPowers.wrap(this.#mWindow).docShell.editor;
341 editor.flags |= SpecialPowers.Ci.nsIEditor.eEditorReadonlyMask;
342 await this.#flushPendingIMENotifications();
343 const expectedIMEState = this.#getExpectedIMEState();
345 description: `when HTMLEditor for parent of ${this.#getDescription()} becomes readonly`,
349 SpecialPowers.Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED,
353 checkResultOfMakingHTMLEditorReadonly(aExpectedResult) {
354 this.#checkResult(aExpectedResult);
357 async runToMakeHTMLEditorEditable() {
358 const editor = SpecialPowers.wrap(this.#mWindow).docShell.editor;
359 editor.flags &= ~SpecialPowers.Ci.nsIEditor.eEditorReadonlyMask;
360 await this.#flushPendingIMENotifications();
361 const expectedIMEState = this.#getExpectedIMEState();
363 description: `when HTMLEditor for parent of ${this.#getDescription()} becomes editable`,
367 SpecialPowers.Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED,
371 checkResultOfMakingHTMLEditorEditable(aExpectedResult) {
372 this.#checkResult(aExpectedResult);
375 async runToMakeParentNonEditingHost() {
376 this.#mEditingHost.removeAttribute("contenteditable");
377 await this.#flushPendingIMENotifications();
378 const expectedIMEState = this.#getExpectedIMEState();
380 description: `when parent of ${this.#getDescription()} becomes non-editable`,
384 SpecialPowers.Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED,
388 checkResultOfMakingParentNonEditable(aExpectedResult) {
389 this.#checkResult(aExpectedResult);
393 class IMEStateOutsideContentEditableOnReadonlyChangeTester {
394 static #sFocusTargets = [
421 static get numberOfFocusTargets() {
422 return IMEStateOutsideContentEditableOnReadonlyChangeTester.#sFocusTargets
426 static #maybeCreateElement(aDocument, aFocusTarget) {
427 if (aFocusTarget.tag == "body") {
430 const element = aDocument.createElement(aFocusTarget.tag);
431 if (aFocusTarget.type !== undefined) {
432 element.setAttribute("type", aFocusTarget.type);
434 if (aFocusTarget.readonly) {
435 element.setAttribute("readonly", "");
441 return `<${this.#mFocusTarget.tag}${
442 this.#mFocusTarget.type !== undefined
443 ? ` type=${this.#mFocusTarget.type}`
445 }${this.#mFocusTarget.readonly ? " readonly" : ""}>`;
448 #getExpectedIMEState() {
449 return this.#mFocusTarget.readonly ||
450 this.#mFocusTarget.tag == "button" ||
451 this.#mFocusTarget.tag == "body"
452 ? SpecialPowers.Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED
453 : SpecialPowers.Ci.nsIDOMWindowUtils.IME_STATUS_ENABLED;
456 #flushPendingIMENotifications() {
457 return new Promise(resolve =>
458 this.#mWindow.requestAnimationFrame(() =>
459 this.#mWindow.requestAnimationFrame(resolve)
464 // Runner only fields.
468 #mFocusTargetElement;
471 // Checker only fields.
476 this.#mTIPWrapper?.clearFocusBlurNotifications();
477 this.#mTIPWrapper = null;
481 * @param {number} aIndex Index of the test.
482 * @param {Element} aEditingHost The editing host.
483 * @param {Window} aWindow [optional] The DOM window containing aEditingHost.
484 * @returns {object} Expected result of initial state.
486 async prepareToRun(aIndex, aEditingHost, aWindow = window) {
487 this.#mWindow = aWindow;
488 this.#mEditingHost = aEditingHost;
489 this.#mEditingHost.removeAttribute("contenteditable");
490 this.#mBody = this.#mEditingHost.ownerDocument.body;
491 this.#mBody.ownerDocument.activeElement?.blur();
492 if (this.#mFocusTargetElement != this.#mBody) {
493 this.#mFocusTargetElement?.remove();
495 await this.#flushPendingIMENotifications();
497 IMEStateOutsideContentEditableOnReadonlyChangeTester.#sFocusTargets[
500 this.#mFocusTargetElement =
501 IMEStateOutsideContentEditableOnReadonlyChangeTester.#maybeCreateElement(
502 this.#mBody.ownerDocument,
505 if (this.#mFocusTargetElement) {
506 this.#mBody.appendChild(this.#mFocusTargetElement);
507 this.#mFocusTargetElement.focus();
509 await this.#flushPendingIMENotifications();
510 const expectedIMEState = this.#getExpectedIMEState();
512 description: `when ${this.#getDescription()} simply has focus`,
516 SpecialPowers.Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED,
520 #checkResult(aExpectedResult) {
521 const description = "IMEStateOutsideContentEditableOnReadonlyChangeTester";
523 this.#mWindowUtils.IMEStatus,
524 aExpectedResult.expectedIMEState,
525 `${description}: IME state should be proper one for the focused element ${aExpectedResult.description}`
528 this.#mTIPWrapper.IMEHasFocus,
529 aExpectedResult.expectedIMEFocus,
530 `${description}: IME should ${
531 aExpectedResult.expectedIMEFocus ? "" : "not "
532 }have focus ${aExpectedResult.description}`
537 * @param {object} aExpectedResult The expected result returned by prepareToRun().
538 * @param {Window} aWindow The window whose IME state should be checked.
539 * @param {TIPWrapper} aTIPWrapper The TIP wrapper of aWindow.
541 checkResultOfPreparation(aExpectedResult, aWindow, aTIPWrapper) {
542 this.#mWindowUtils = SpecialPowers.wrap(aWindow).windowUtils;
543 this.#mTIPWrapper = aTIPWrapper;
544 this.#checkResult(aExpectedResult);
547 async runToMakeParentEditingHost() {
548 this.#mEditingHost.setAttribute("contenteditable", "");
549 await this.#flushPendingIMENotifications();
550 const expectedIMEState = this.#getExpectedIMEState();
552 description: `when parent of ${this.#getDescription()} becomes contenteditable`,
556 SpecialPowers.Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED,
560 checkResultOfMakingParentEditingHost(aExpectedResult) {
561 this.#checkResult(aExpectedResult);
564 async runToMakeHTMLEditorReadonly() {
565 const editor = SpecialPowers.wrap(this.#mWindow).docShell.editor;
566 editor.flags |= SpecialPowers.Ci.nsIEditor.eEditorReadonlyMask;
567 await this.#flushPendingIMENotifications();
568 const expectedIMEState = this.#getExpectedIMEState();
570 description: `when HTMLEditor for parent of ${this.#getDescription()} becomes readonly`,
574 SpecialPowers.Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED,
578 checkResultOfMakingHTMLEditorReadonly(aExpectedResult) {
579 this.#checkResult(aExpectedResult);
582 async runToMakeHTMLEditorEditable() {
583 const editor = SpecialPowers.wrap(this.#mWindow).docShell.editor;
584 editor.flags &= ~SpecialPowers.Ci.nsIEditor.eEditorReadonlyMask;
585 await this.#flushPendingIMENotifications();
586 const expectedIMEState = this.#getExpectedIMEState();
588 description: `when HTMLEditor for parent of ${this.#getDescription()} becomes editable`,
592 SpecialPowers.Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED,
596 checkResultOfMakingHTMLEditorEditable(aExpectedResult) {
597 this.#checkResult(aExpectedResult);
600 async runToMakeParentNonEditingHost() {
601 this.#mEditingHost.removeAttribute("contenteditable");
602 await this.#flushPendingIMENotifications();
603 const expectedIMEState = this.#getExpectedIMEState();
605 description: `when parent of ${this.#getDescription()} becomes non-editable`,
609 SpecialPowers.Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED,
613 checkResultOfMakingParentNonEditable(aExpectedResult) {
614 this.#checkResult(aExpectedResult);