mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-23 21:01:08 +00:00
Bug 1467796 - part 2: Make autocomplete use new method TextEditor::ReplaceTextAsAction() which replaces all text with specified text r=m_kato
InputEvent.inputType needs to distinguish whether inserting text is caused by insertText command or replaced by autocomplete or spellchecker. Therefore, nsTextEditorState::SetValue() cannot use TextEditor::InsertTextAsAction() nor TextEditor::DeleteSelectionAsAction(). This patch reuses TextEditor::SetText()'s slow path for the new method. Note that the new method uses EditSubAction::eInsertText as top level edit sub- action because specifying this improves undo/redo behavior. And also this patch modifies test_bug1368544.html. Oddly, only on Android, we get different result. After removing all text with setUserInput(""), TextEditor::DeleteSelectionAsSubAction() removes both text node and non-bogus <br> element from the anonymous-div element. However, only on Android, new <br> element is recreated. I've not understood where this difference comes from yet. MozReview-Commit-ID: GKNksctGik --HG-- rename : toolkit/content/tests/chrome/file_autocomplete_with_composition.js => toolkit/content/tests/chrome/file_editor_with_autocomplete.js rename : toolkit/content/tests/chrome/test_autocomplete_with_composition_on_input.html => toolkit/content/tests/chrome/test_editor_for_input_with_autocomplete.html rename : toolkit/content/tests/chrome/test_autocomplete_with_composition_on_textbox.xul => toolkit/content/tests/chrome/test_editor_for_textbox_with_autocomplete.xul extra : rebase_source : b90419d9e5a01e86f6e6418f8df002c91416acae
This commit is contained in:
parent
efd8891532
commit
08f4c56c7e
@ -2425,10 +2425,24 @@ nsTextEditorState::SetValue(const nsAString& aValue, const nsAString* aOldValue,
|
||||
bool notifyValueChanged = !!(aFlags & eSetValue_Notify);
|
||||
mTextListener->SetValueChanged(notifyValueChanged);
|
||||
|
||||
// We preserve the undo history if we are explicitly setting the
|
||||
// value for the user's input, or if we are setting the value for a
|
||||
// XUL text control.
|
||||
if (aFlags & (eSetValue_BySetUserInput | eSetValue_ForXUL)) {
|
||||
if (aFlags & eSetValue_BySetUserInput) {
|
||||
// If the caller inserts text as part of user input, for example,
|
||||
// autocomplete, we need to replace the text as "insert string"
|
||||
// because undo should cancel only this operation (i.e., previous
|
||||
// transactions typed by user shouldn't be merged with this).
|
||||
DebugOnly<nsresult> rv = textEditor->ReplaceTextAsAction(newValue);
|
||||
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
|
||||
"Failed to set the new value");
|
||||
} else if (aFlags & eSetValue_ForXUL) {
|
||||
// On XUL <textbox> element, we need to preserve existing undo
|
||||
// transactions.
|
||||
// XXX Do we really need to do such complicated optimization?
|
||||
// This was landed for web pages which set <textarea> value
|
||||
// per line (bug 518122). For example:
|
||||
// for (;;) {
|
||||
// textarea.value += oneLineText + "\n";
|
||||
// }
|
||||
// However, this path won't be used in web content anymore.
|
||||
nsCOMPtr<nsISelectionController> kungFuDeathGrip = mSelCon.get();
|
||||
uint32_t currentLength = currentValue.Length();
|
||||
uint32_t newlength = newValue.Length();
|
||||
@ -2457,6 +2471,9 @@ nsTextEditorState::SetValue(const nsAString& aValue, const nsAString* aOldValue,
|
||||
"Failed to insert the new value");
|
||||
}
|
||||
} else {
|
||||
// On <input> or <textarea>, we shouldn't preserve existing undo
|
||||
// transactions because other browsers do not preserve them too
|
||||
// and not preserving transactions makes setting value faster.
|
||||
AutoDisableUndo disableUndo(textEditor);
|
||||
if (selection) {
|
||||
// Since we don't use undo transaction, we don't need to store
|
||||
|
@ -905,9 +905,12 @@ TextEditRules::WillSetText(bool* aCancel,
|
||||
*aHandled = false;
|
||||
*aCancel = false;
|
||||
|
||||
if (!IsPlaintextEditor() || TextEditorRef().IsIMEComposing() ||
|
||||
if (!IsPlaintextEditor() ||
|
||||
TextEditorRef().IsIMEComposing() ||
|
||||
TextEditorRef().IsUndoRedoEnabled() ||
|
||||
aMaxLength != -1) {
|
||||
// SetTextImpl only supports plain text editor without IME.
|
||||
// SetTextImpl only supports plain text editor without IME and
|
||||
// when we don't need to make it undoable.
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
@ -1136,6 +1136,36 @@ TextEditor::SetText(const nsAString& aString)
|
||||
{
|
||||
MOZ_ASSERT(aString.FindChar(static_cast<char16_t>('\r')) == kNotFound);
|
||||
|
||||
AutoPlaceholderBatch batch(this, nullptr);
|
||||
nsresult rv = SetTextAsSubAction(aString);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
TextEditor::ReplaceTextAsAction(const nsAString& aString)
|
||||
{
|
||||
AutoPlaceholderBatch batch(this, nullptr);
|
||||
|
||||
// This should emulates inserting text for better undo/redo behavior.
|
||||
AutoTopLevelEditSubActionNotifier maybeTopLevelEditSubAction(
|
||||
*this, EditSubAction::eInsertText,
|
||||
nsIEditor::eNext);
|
||||
|
||||
nsresult rv = SetTextAsSubAction(aString);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
TextEditor::SetTextAsSubAction(const nsAString& aString)
|
||||
{
|
||||
MOZ_ASSERT(mPlaceholderBatch);
|
||||
|
||||
if (NS_WARN_IF(!mRules)) {
|
||||
return NS_ERROR_NOT_INITIALIZED;
|
||||
}
|
||||
@ -1143,8 +1173,6 @@ TextEditor::SetText(const nsAString& aString)
|
||||
// Protect the edit rules object from dying
|
||||
RefPtr<TextEditRules> rules(mRules);
|
||||
|
||||
// delete placeholder txns merge.
|
||||
AutoPlaceholderBatch batch(this, nullptr);
|
||||
AutoTopLevelEditSubActionNotifier maybeTopLevelEditSubAction(
|
||||
*this, EditSubAction::eSetText,
|
||||
nsIEditor::eNext);
|
||||
@ -1169,6 +1197,11 @@ TextEditor::SetText(const nsAString& aString)
|
||||
return NS_OK;
|
||||
}
|
||||
if (!handled) {
|
||||
// Note that do not notify selectionchange caused by selecting all text
|
||||
// because it's preparation of our delete implementation so web apps
|
||||
// shouldn't receive such selectionchange before the first mutation.
|
||||
AutoUpdateViewBatch preventSelectionChangeEvent(this);
|
||||
|
||||
// We want to select trailing BR node to remove all nodes to replace all,
|
||||
// but TextEditor::SelectEntireDocument doesn't select that BR node.
|
||||
if (rules->DocumentIsEmpty()) {
|
||||
|
@ -160,6 +160,14 @@ public:
|
||||
*/
|
||||
nsresult SetText(const nsAString& aString);
|
||||
|
||||
/**
|
||||
* Replace all text in this editor with aString and treat the change as
|
||||
* inserting the string.
|
||||
*
|
||||
* @param aString The string to set.
|
||||
*/
|
||||
nsresult ReplaceTextAsAction(const nsAString& aString);
|
||||
|
||||
/**
|
||||
* OnInputParagraphSeparator() is called when user tries to separate current
|
||||
* paragraph with Enter key press or something.
|
||||
@ -263,6 +271,14 @@ protected: // May be called by friends.
|
||||
DeleteSelectionWithTransaction(EDirection aAction,
|
||||
EStripWrappers aStripWrappers);
|
||||
|
||||
/**
|
||||
* Replace existed string with aString. Caller must guarantee that there
|
||||
* is a placeholder transaction which will have the transaction.
|
||||
*
|
||||
* @ param aString The string to be set.
|
||||
*/
|
||||
nsresult SetTextAsSubAction(const nsAString& aString);
|
||||
|
||||
/**
|
||||
* InsertBrElementWithTransaction() creates a <br> element and inserts it
|
||||
* before aPointToInsert. Then, tries to collapse selection at or after the
|
||||
|
@ -52,9 +52,20 @@ SimpleTest.waitForFocus(() => {
|
||||
|
||||
textarea.value = "ABC";
|
||||
SpecialPowers.wrap(textarea).setUserInput("");
|
||||
ok(editor.rootElement.hasChildNodes(),
|
||||
"editor of textarea has child node even if value is empty");
|
||||
|
||||
is(textarea.value, "",
|
||||
"textarea should become empty when setUserInput() is called with empty string");
|
||||
if (navigator.appVersion.includes("Android")) {
|
||||
todo(!editor.rootElement.hasChildNodes(),
|
||||
"editor of textarea should have no children when user input emulation set the value to empty");
|
||||
if (editor.rootElement.childNodes.length > 0) {
|
||||
is(editor.rootElement.childNodes.length, 1, "There should be only one <br> node");
|
||||
is(editor.rootElement.firstChild.tagName.toLowerCase(), "br", "The node should be a <br> element node");
|
||||
is(editor.rootElement.firstChild.getAttribute("_moz_editor_bogus_node"), null, "The <br> should not be a bogus node");
|
||||
}
|
||||
} else {
|
||||
ok(!editor.rootElement.hasChildNodes(),
|
||||
"editor of textarea should have no children when user input emulation set the value to empty");
|
||||
}
|
||||
textarea.value = "ABC";
|
||||
synthesizeKey("KEY_Enter", {repeat: 2});
|
||||
textarea.value = "";
|
||||
|
@ -16,6 +16,7 @@ support-files =
|
||||
dialog_dialogfocus2.xul
|
||||
file_about_networking_wsh.py
|
||||
file_autocomplete_with_composition.js
|
||||
file_editor_with_autocomplete.js
|
||||
findbar_entireword_window.xul
|
||||
findbar_events_window.xul
|
||||
findbar_window.xul
|
||||
@ -106,6 +107,8 @@ skip-if = toolkit == "cocoa"
|
||||
[test_custom_element_base.xul]
|
||||
[test_deck.xul]
|
||||
[test_dialogfocus.xul]
|
||||
[test_editor_for_input_with_autocomplete.html]
|
||||
[test_editor_for_textbox_with_autocomplete.xul]
|
||||
[test_findbar.xul]
|
||||
subsuite = clipboard
|
||||
[test_findbar_entireword.xul]
|
||||
|
377
toolkit/content/tests/chrome/file_editor_with_autocomplete.js
Normal file
377
toolkit/content/tests/chrome/file_editor_with_autocomplete.js
Normal file
@ -0,0 +1,377 @@
|
||||
// nsDoTestsForEditorWithAutoComplete tests basic functions of editor with autocomplete.
|
||||
// Users must include SimpleTest.js and EventUtils.js, and register "Mozilla" to the autocomplete for the target.
|
||||
|
||||
ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
async function waitForCondition(condition) {
|
||||
return new Promise(resolve => {
|
||||
var tries = 0;
|
||||
var interval = setInterval(function() {
|
||||
if (condition() || tries >= 60) {
|
||||
moveOn();
|
||||
}
|
||||
tries++;
|
||||
}, 100);
|
||||
var moveOn = function() { clearInterval(interval); resolve(); };
|
||||
});
|
||||
}
|
||||
|
||||
function nsDoTestsForEditorWithAutoComplete(aDescription,
|
||||
aWindow,
|
||||
aTarget,
|
||||
aAutoCompleteController,
|
||||
aIsFunc,
|
||||
aGetTargetValueFunc) {
|
||||
this._description = aDescription;
|
||||
this._window = aWindow;
|
||||
this._target = aTarget;
|
||||
this._controller = aAutoCompleteController;
|
||||
|
||||
this._is = aIsFunc;
|
||||
this._getTargetValue = aGetTargetValueFunc;
|
||||
|
||||
this._target.focus();
|
||||
|
||||
this._DefaultCompleteDefaultIndex =
|
||||
this._controller.input.completeDefaultIndex;
|
||||
}
|
||||
|
||||
nsDoTestsForEditorWithAutoComplete.prototype = {
|
||||
_window: null,
|
||||
_target: null,
|
||||
_controller: null,
|
||||
_DefaultCompleteDefaultIndex: false,
|
||||
_description: "",
|
||||
|
||||
_is: null,
|
||||
_getTargetValue() { return "not initialized"; },
|
||||
|
||||
run: async function runTestsImpl() {
|
||||
for (let test of this._tests) {
|
||||
if (this._controller.input.completeDefaultIndex != test.completeDefaultIndex) {
|
||||
this._controller.input.completeDefaultIndex = test.completeDefaultIndex;
|
||||
}
|
||||
|
||||
if (test.execute(this._window, this._target) === false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
await waitForCondition(() => {
|
||||
return this._controller.searchStatus >=
|
||||
Ci.nsIAutoCompleteController.STATUS_COMPLETE_NO_MATCH;
|
||||
});
|
||||
this._checkResult(test);
|
||||
}
|
||||
this._controller.input.completeDefaultIndex = this._DefaultCompleteDefaultIndex;
|
||||
},
|
||||
|
||||
_checkResult(aTest) {
|
||||
this._is(this._getTargetValue(), aTest.value,
|
||||
this._description + ", " + aTest.description + ": value");
|
||||
this._is(this._controller.searchString, aTest.searchString,
|
||||
this._description + ", " + aTest.description + ": searchString");
|
||||
this._is(this._controller.input.popupOpen, aTest.popup,
|
||||
this._description + ", " + aTest.description + ": popupOpen");
|
||||
this._is(this._controller.searchStatus, Ci.nsIAutoCompleteController.STATUS_COMPLETE_MATCH,
|
||||
this._description + ", " + aTest.description + ": status");
|
||||
},
|
||||
|
||||
_tests: [
|
||||
{ description: "Undo/Redo behavior check when typed text exactly matches the case: type 'Mo'",
|
||||
completeDefaultIndex: false,
|
||||
execute(aWindow, aTarget) {
|
||||
synthesizeKey("M", { shiftKey: true }, aWindow);
|
||||
synthesizeKey("o", {}, aWindow);
|
||||
return true;
|
||||
}, popup: true, value: "Mo", searchString: "Mo"
|
||||
},
|
||||
{ description: "Undo/Redo behavior check when typed text exactly matches the case: select 'Mozilla' to complete the word",
|
||||
completeDefaultIndex: false,
|
||||
execute(aWindow, aTarget) {
|
||||
synthesizeKey("KEY_ArrowDown", {}, aWindow);
|
||||
synthesizeKey("KEY_Enter", {}, aWindow);
|
||||
return true;
|
||||
}, popup: false, value: "Mozilla", searchString: "Mozilla"
|
||||
},
|
||||
{ description: "Undo/Redo behavior check when typed text exactly matches the case: undo the word, but typed text shouldn't be canceled",
|
||||
completeDefaultIndex: false,
|
||||
execute(aWindow, aTarget) {
|
||||
synthesizeKey("z", { accelKey: true }, aWindow);
|
||||
return true;
|
||||
}, popup: true, value: "Mo", searchString: "Mo"
|
||||
},
|
||||
{ description: "Undo/Redo behavior check when typed text exactly matches the case: undo the typed text",
|
||||
completeDefaultIndex: false,
|
||||
execute(aWindow, aTarget) {
|
||||
synthesizeKey("z", { accelKey: true }, aWindow);
|
||||
return true;
|
||||
}, popup: false, value: "", searchString: ""
|
||||
},
|
||||
{ description: "Undo/Redo behavior check when typed text exactly matches the case: redo the typed text",
|
||||
completeDefaultIndex: false,
|
||||
execute(aWindow, aTarget) {
|
||||
synthesizeKey("Z", { accelKey: true, shiftKey: true }, aWindow);
|
||||
return true;
|
||||
}, popup: true, value: "Mo", searchString: "Mo"
|
||||
},
|
||||
{ description: "Undo/Redo behavior check when typed text exactly matches the case: redo the word",
|
||||
completeDefaultIndex: false,
|
||||
execute(aWindow, aTarget) {
|
||||
synthesizeKey("Z", { accelKey: true, shiftKey: true }, aWindow);
|
||||
return true;
|
||||
}, popup: true, value: "Mozilla", searchString: "Mozilla"
|
||||
},
|
||||
{ description: "Undo/Redo behavior check when typed text exactly matches the case: removing all text for next test...",
|
||||
completeDefaultIndex: false,
|
||||
execute(aWindow, aTarget) {
|
||||
synthesizeKey("a", { accelKey: true }, aWindow);
|
||||
synthesizeKey("KEY_Backspace", {}, aWindow);
|
||||
return true;
|
||||
}, popup: false, value: "", searchString: ""
|
||||
},
|
||||
|
||||
{ description: "Undo/Redo behavior check when typed text does not match the case: type 'mo'",
|
||||
completeDefaultIndex: false,
|
||||
execute(aWindow, aTarget) {
|
||||
synthesizeKey("m", {}, aWindow);
|
||||
synthesizeKey("o", {}, aWindow);
|
||||
return true;
|
||||
}, popup: true, value: "mo", searchString: "mo"
|
||||
},
|
||||
{ description: "Undo/Redo behavior check when typed text does not match the case: select 'Mozilla' to complete the word",
|
||||
completeDefaultIndex: false,
|
||||
execute(aWindow, aTarget) {
|
||||
synthesizeKey("KEY_ArrowDown", {}, aWindow);
|
||||
synthesizeKey("KEY_Enter", {}, aWindow);
|
||||
return true;
|
||||
}, popup: false, value: "Mozilla", searchString: "Mozilla"
|
||||
},
|
||||
{ description: "Undo/Redo behavior check when typed text does not match the case: undo the word, but typed text shouldn't be canceled",
|
||||
completeDefaultIndex: false,
|
||||
execute(aWindow, aTarget) {
|
||||
synthesizeKey("z", { accelKey: true }, aWindow);
|
||||
return true;
|
||||
}, popup: true, value: "mo", searchString: "mo"
|
||||
},
|
||||
{ description: "Undo/Redo behavior check when typed text does not match the case: undo the typed text",
|
||||
completeDefaultIndex: false,
|
||||
execute(aWindow, aTarget) {
|
||||
synthesizeKey("z", { accelKey: true }, aWindow);
|
||||
return true;
|
||||
}, popup: false, value: "", searchString: ""
|
||||
},
|
||||
{ description: "Undo/Redo behavior check when typed text does not match the case: redo the typed text",
|
||||
completeDefaultIndex: false,
|
||||
execute(aWindow, aTarget) {
|
||||
synthesizeKey("Z", { accelKey: true, shiftKey: true }, aWindow);
|
||||
return true;
|
||||
}, popup: true, value: "mo", searchString: "mo"
|
||||
},
|
||||
{ description: "Undo/Redo behavior check when typed text does not match the case: redo the word",
|
||||
completeDefaultIndex: false,
|
||||
execute(aWindow, aTarget) {
|
||||
synthesizeKey("Z", { accelKey: true, shiftKey: true }, aWindow);
|
||||
return true;
|
||||
}, popup: true, value: "Mozilla", searchString: "Mozilla"
|
||||
},
|
||||
{ description: "Undo/Redo behavior check when typed text does not match the case: removing all text for next test...",
|
||||
completeDefaultIndex: false,
|
||||
execute(aWindow, aTarget) {
|
||||
synthesizeKey("a", { accelKey: true }, aWindow);
|
||||
synthesizeKey("KEY_Backspace", {}, aWindow);
|
||||
return true;
|
||||
}, popup: false, value: "", searchString: ""
|
||||
},
|
||||
|
||||
// Testing for nsIAutoCompleteInput.completeDefaultIndex being true.
|
||||
{ description: "Undo/Redo behavior check when typed text exactly matches the case (completeDefaultIndex is true): type 'Mo'",
|
||||
completeDefaultIndex: true,
|
||||
execute(aWindow, aTarget) {
|
||||
// Undo/Redo behavior on XUL <textbox> with completeDefaultIndex is set to true is unstable. Skip it now.
|
||||
if (aTarget.tagName === "textbox") {
|
||||
return false;
|
||||
}
|
||||
synthesizeKey("M", { shiftKey: true }, aWindow);
|
||||
synthesizeKey("o", {}, aWindow);
|
||||
return true;
|
||||
}, popup: true, value: "Mozilla", searchString: "Mo"
|
||||
},
|
||||
{ description: "Undo/Redo behavior check when typed text exactly matches the case (completeDefaultIndex is true): select 'Mozilla' to complete the word",
|
||||
completeDefaultIndex: true,
|
||||
execute(aWindow, aTarget) {
|
||||
// Undo/Redo behavior on XUL <textbox> with completeDefaultIndex is set to true is unstable. Skip it now.
|
||||
if (aTarget.tagName === "textbox") {
|
||||
return false;
|
||||
}
|
||||
synthesizeKey("KEY_ArrowDown", {}, aWindow);
|
||||
synthesizeKey("KEY_Enter", {}, aWindow);
|
||||
return true;
|
||||
}, popup: false, value: "Mozilla", searchString: "Mozilla"
|
||||
},
|
||||
{ description: "Undo/Redo behavior check when typed text exactly matches the case (completeDefaultIndex is true): undo the word, but typed text shouldn't be canceled",
|
||||
completeDefaultIndex: true,
|
||||
execute(aWindow, aTarget) {
|
||||
// Undo/Redo behavior on XUL <textbox> with completeDefaultIndex is set to true is unstable. Skip it now.
|
||||
if (aTarget.tagName === "textbox") {
|
||||
return false;
|
||||
}
|
||||
synthesizeKey("z", { accelKey: true }, aWindow);
|
||||
return true;
|
||||
}, popup: true, value: "Mo", searchString: "Mo"
|
||||
},
|
||||
{ description: "Undo/Redo behavior check when typed text exactly matches the case (completeDefaultIndex is true): undo the typed text",
|
||||
completeDefaultIndex: true,
|
||||
execute(aWindow, aTarget) {
|
||||
// Undo/Redo behavior on XUL <textbox> with completeDefaultIndex is set to true is unstable. Skip it now.
|
||||
if (aTarget.tagName === "textbox") {
|
||||
return false;
|
||||
}
|
||||
synthesizeKey("z", { accelKey: true }, aWindow);
|
||||
return true;
|
||||
}, popup: false, value: "", searchString: ""
|
||||
},
|
||||
{ description: "Undo/Redo behavior check when typed text exactly matches the case (completeDefaultIndex is true): redo the typed text",
|
||||
completeDefaultIndex: true,
|
||||
execute(aWindow, aTarget) {
|
||||
// Undo/Redo behavior on XUL <textbox> with completeDefaultIndex is set to true is unstable. Skip it now.
|
||||
if (aTarget.tagName === "textbox") {
|
||||
return false;
|
||||
}
|
||||
synthesizeKey("Z", { accelKey: true, shiftKey: true }, aWindow);
|
||||
return true;
|
||||
}, popup: true, value: "Mozilla", searchString: "Mo"
|
||||
},
|
||||
{ description: "Undo/Redo behavior check when typed text exactly matches the case (completeDefaultIndex is true): redo the word",
|
||||
completeDefaultIndex: true,
|
||||
execute(aWindow, aTarget) {
|
||||
// Undo/Redo behavior on XUL <textbox> with completeDefaultIndex is set to true is unstable. Skip it now.
|
||||
if (aTarget.tagName === "textbox") {
|
||||
return false;
|
||||
}
|
||||
synthesizeKey("Z", { accelKey: true, shiftKey: true }, aWindow);
|
||||
return true;
|
||||
}, popup: true, value: "Mozilla", searchString: "Mo"
|
||||
},
|
||||
{ description: "Undo/Redo behavior check when typed text exactly matches the case (completeDefaultIndex is true): removing all text for next test...",
|
||||
completeDefaultIndex: true,
|
||||
execute(aWindow, aTarget) {
|
||||
// Undo/Redo behavior on XUL <textbox> with completeDefaultIndex is set to true is unstable. Skip it now.
|
||||
if (aTarget.tagName === "textbox") {
|
||||
return false;
|
||||
}
|
||||
synthesizeKey("a", { accelKey: true }, aWindow);
|
||||
synthesizeKey("KEY_Backspace", {}, aWindow);
|
||||
return true;
|
||||
}, popup: false, value: "", searchString: ""
|
||||
},
|
||||
|
||||
{ description: "Undo/Redo behavior check when typed text does not match the case (completeDefaultIndex is true): type 'mo'",
|
||||
completeDefaultIndex: true,
|
||||
execute(aWindow, aTarget) {
|
||||
// Undo/Redo behavior on XUL <textbox> with completeDefaultIndex is set to true is unstable. Skip it now.
|
||||
if (aTarget.tagName === "textbox") {
|
||||
return false;
|
||||
}
|
||||
synthesizeKey("m", {}, aWindow);
|
||||
synthesizeKey("o", {}, aWindow);
|
||||
return true;
|
||||
}, popup: true, value: "mozilla", searchString: "mo"
|
||||
},
|
||||
{ description: "Undo/Redo behavior check when typed text does not match the case (completeDefaultIndex is true): select 'Mozilla' to complete the word",
|
||||
completeDefaultIndex: true,
|
||||
execute(aWindow, aTarget) {
|
||||
// Undo/Redo behavior on XUL <textbox> with completeDefaultIndex is set to true is unstable. Skip it now.
|
||||
if (aTarget.tagName === "textbox") {
|
||||
return false;
|
||||
}
|
||||
synthesizeKey("KEY_ArrowDown", {}, aWindow);
|
||||
synthesizeKey("KEY_Enter", {}, aWindow);
|
||||
return true;
|
||||
}, popup: false, value: "Mozilla", searchString: "Mozilla"
|
||||
},
|
||||
// Different from "exactly matches the case" case, modifying the case causes one additional transaction.
|
||||
// Although we could make this transaction ignored.
|
||||
{ description: "Undo/Redo behavior check when typed text does not match the case (completeDefaultIndex is true): undo the selected word, but typed text shouldn't be canceled",
|
||||
completeDefaultIndex: true,
|
||||
execute(aWindow, aTarget) {
|
||||
// Undo/Redo behavior on XUL <textbox> with completeDefaultIndex is set to true is unstable. Skip it now.
|
||||
if (aTarget.tagName === "textbox") {
|
||||
return false;
|
||||
}
|
||||
synthesizeKey("z", { accelKey: true }, aWindow);
|
||||
return true;
|
||||
}, popup: true, value: "mozilla", searchString: "mozilla"
|
||||
},
|
||||
{ description: "Undo/Redo behavior check when typed text does not match the case (completeDefaultIndex is true): undo the word, but typed text shouldn't be canceled",
|
||||
completeDefaultIndex: true,
|
||||
execute(aWindow, aTarget) {
|
||||
// Undo/Redo behavior on XUL <textbox> with completeDefaultIndex is set to true is unstable. Skip it now.
|
||||
if (aTarget.tagName === "textbox") {
|
||||
return false;
|
||||
}
|
||||
synthesizeKey("z", { accelKey: true }, aWindow);
|
||||
return true;
|
||||
}, popup: true, value: "mo", searchString: "mo"
|
||||
},
|
||||
{ description: "Undo/Redo behavior check when typed text does not match the case (completeDefaultIndex is true): undo the typed text",
|
||||
completeDefaultIndex: true,
|
||||
execute(aWindow, aTarget) {
|
||||
// Undo/Redo behavior on XUL <textbox> with completeDefaultIndex is set to true is unstable. Skip it now.
|
||||
if (aTarget.tagName === "textbox") {
|
||||
return false;
|
||||
}
|
||||
synthesizeKey("z", { accelKey: true }, aWindow);
|
||||
return true;
|
||||
}, popup: false, value: "", searchString: ""
|
||||
},
|
||||
// XXX This is odd case. Consistency with undo behavior, this should restore "mo".
|
||||
// However, looks like that autocomplete automatically restores "mozilla".
|
||||
// Additionally, looks like that it causes clearing the redo stack.
|
||||
// Therefore, the following redo operations do nothing.
|
||||
{ description: "Undo/Redo behavior check when typed text does not match the case (completeDefaultIndex is true): redo the typed text",
|
||||
completeDefaultIndex: true,
|
||||
execute(aWindow, aTarget) {
|
||||
// Undo/Redo behavior on XUL <textbox> with completeDefaultIndex is set to true is unstable. Skip it now.
|
||||
if (aTarget.tagName === "textbox") {
|
||||
return false;
|
||||
}
|
||||
synthesizeKey("Z", { accelKey: true, shiftKey: true }, aWindow);
|
||||
return true;
|
||||
}, popup: true, value: "mozilla", searchString: "mo"
|
||||
},
|
||||
{ description: "Undo/Redo behavior check when typed text does not match the case (completeDefaultIndex is true): redo the default index word",
|
||||
completeDefaultIndex: true,
|
||||
execute(aWindow, aTarget) {
|
||||
// Undo/Redo behavior on XUL <textbox> with completeDefaultIndex is set to true is unstable. Skip it now.
|
||||
if (aTarget.tagName === "textbox") {
|
||||
return false;
|
||||
}
|
||||
synthesizeKey("Z", { accelKey: true, shiftKey: true }, aWindow);
|
||||
return true;
|
||||
}, popup: true, value: "mozilla", searchString: "mo"
|
||||
},
|
||||
{ description: "Undo/Redo behavior check when typed text does not match the case (completeDefaultIndex is true): redo the word",
|
||||
completeDefaultIndex: true,
|
||||
execute(aWindow, aTarget) {
|
||||
// Undo/Redo behavior on XUL <textbox> with completeDefaultIndex is set to true is unstable. Skip it now.
|
||||
if (aTarget.tagName === "textbox") {
|
||||
return false;
|
||||
}
|
||||
synthesizeKey("Z", { accelKey: true, shiftKey: true }, aWindow);
|
||||
return true;
|
||||
}, popup: true, value: "mozilla", searchString: "mo"
|
||||
},
|
||||
{ description: "Undo/Redo behavior check when typed text does not match the case (completeDefaultIndex is true): removing all text for next test...",
|
||||
completeDefaultIndex: true,
|
||||
execute(aWindow, aTarget) {
|
||||
// Undo/Redo behavior on XUL <textbox> with completeDefaultIndex is set to true is unstable. Skip it now.
|
||||
if (aTarget.tagName === "textbox") {
|
||||
return false;
|
||||
}
|
||||
synthesizeKey("a", { accelKey: true }, aWindow);
|
||||
synthesizeKey("KEY_Backspace", {}, aWindow);
|
||||
return true;
|
||||
}, popup: false, value: "", searchString: ""
|
||||
},
|
||||
]
|
||||
};
|
@ -0,0 +1,75 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>Basic editor behavior for HTML input element with autocomplete</title>
|
||||
<script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
|
||||
<script type="text/javascript" src="file_editor_with_autocomplete.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
|
||||
</head>
|
||||
<body>
|
||||
<p id="display"></p>
|
||||
|
||||
<div id="content">
|
||||
<iframe id="formTarget" name="formTarget"></iframe>
|
||||
<form action="data:text/html," target="formTarget">
|
||||
<input name="test" id="input"><input type="submit">
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<pre id="test">
|
||||
<script class="testbody" type="text/javascript">
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
async function registerWord(aTarget, aAutoCompleteController) {
|
||||
// Register a word to the form history.
|
||||
aTarget.focus();
|
||||
aTarget.value = "Mozilla";
|
||||
synthesizeKey("KEY_Enter");
|
||||
await waitForCondition(() => {
|
||||
if (aAutoCompleteController.searchStatus == aAutoCompleteController.STATUS_NONE ||
|
||||
aAutoCompleteController.searchStatus == aAutoCompleteController.STATUS_COMPLETE_NO_MATCH) {
|
||||
aAutoCompleteController.startSearch("Mozilla");
|
||||
}
|
||||
return aAutoCompleteController.matchCount > 0;
|
||||
});
|
||||
aTarget.value = "";
|
||||
synthesizeKey("KEY_Escape");
|
||||
}
|
||||
|
||||
async function runTests() {
|
||||
var formFillController =
|
||||
SpecialPowers.getFormFillController()
|
||||
.QueryInterface(Ci.nsIAutoCompleteInput);
|
||||
var originalFormFillTimeout = formFillController.timeout;
|
||||
|
||||
SpecialPowers.attachFormFillControllerTo(window);
|
||||
var target = document.getElementById("input");
|
||||
|
||||
// Register a word to the form history.
|
||||
await registerWord(target, formFillController.controller);
|
||||
|
||||
let tests1 = new nsDoTestsForEditorWithAutoComplete(
|
||||
"Testing on HTML input (asynchronously search)",
|
||||
window, target, formFillController.controller, is,
|
||||
function() { return target.value; });
|
||||
await tests1.run();
|
||||
|
||||
target.setAttribute("timeout", 0);
|
||||
let tests2 = new nsDoTestsForEditorWithAutoComplete(
|
||||
"Testing on HTML input (synchronously search)",
|
||||
window, target, formFillController.controller, is,
|
||||
function() { return target.value; });
|
||||
await tests2.run();
|
||||
|
||||
formFillController.timeout = originalFormFillTimeout;
|
||||
SpecialPowers.detachFormFillControllerFrom(window);
|
||||
SimpleTest.finish();
|
||||
}
|
||||
|
||||
SimpleTest.waitForFocus(runTests);
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,126 @@
|
||||
<?xml version="1.0"?>
|
||||
<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
|
||||
<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
|
||||
type="text/css"?>
|
||||
<window title="Basic editor behavior for XUL textbox element with autocomplete"
|
||||
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
||||
|
||||
<script type="application/javascript"
|
||||
src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
|
||||
<script type="application/javascript"
|
||||
src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js" />
|
||||
<script type="text/javascript"
|
||||
src="file_editor_with_autocomplete.js" />
|
||||
|
||||
<textbox id="textbox" type="autocomplete"
|
||||
autocompletesearch="simpleForComposition"/>
|
||||
|
||||
<body xmlns="http://www.w3.org/1999/xhtml">
|
||||
<div id="content" style="display: none">
|
||||
</div>
|
||||
<pre id="test">
|
||||
</pre>
|
||||
</body>
|
||||
|
||||
<script class="testbody" type="application/javascript">
|
||||
<![CDATA[
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
const nsIAutoCompleteResult = Ci.nsIAutoCompleteResult;
|
||||
|
||||
// This result can't be constructed in-line, because otherwise we leak memory.
|
||||
function nsAutoCompleteSimpleResult(aString)
|
||||
{
|
||||
this.searchString = aString;
|
||||
if (aString == "" ||
|
||||
aString.toLowerCase() == "mozilla".substr(0, aString.length)) {
|
||||
this.searchResult = nsIAutoCompleteResult.RESULT_SUCCESS;
|
||||
this.matchCount = 1;
|
||||
this._value = "Mozilla";
|
||||
} else {
|
||||
this.searchResult = nsIAutoCompleteResult.RESULT_NOMATCH;
|
||||
this.matchCount = 0;
|
||||
this._value = "";
|
||||
}
|
||||
}
|
||||
|
||||
nsAutoCompleteSimpleResult.prototype = {
|
||||
_value: "",
|
||||
searchString: null,
|
||||
searchResult: nsIAutoCompleteResult.RESULT_FAILURE,
|
||||
defaultIndex: 0,
|
||||
errorDescription: null,
|
||||
matchCount: 0,
|
||||
getValueAt: function(aIndex) { return aIndex == 0 ? this._value : null; },
|
||||
getCommentAt: function() { return null; },
|
||||
getStyleAt: function() { return null; },
|
||||
getImageAt: function() { return null; },
|
||||
getFinalCompleteValueAt: function(aIndex) { return this.getValueAt(aIndex); },
|
||||
getLabelAt: function() { return null; },
|
||||
removeValueAt: function() {}
|
||||
};
|
||||
|
||||
// A basic autocomplete implementation that either returns one result or none
|
||||
var autoCompleteSimpleID =
|
||||
Components.ID("0a2afbdb-f30e-47d1-9cb1-0cd160240aca");
|
||||
var autoCompleteSimpleName =
|
||||
"@mozilla.org/autocomplete/search;1?name=simpleForComposition"
|
||||
var autoCompleteSimple = {
|
||||
QueryInterface: function(iid) {
|
||||
if (iid.equals(Ci.nsISupports) ||
|
||||
iid.equals(Ci.nsIFactory) ||
|
||||
iid.equals(Ci.nsIAutoCompleteSearch))
|
||||
return this;
|
||||
|
||||
throw Cr.NS_ERROR_NO_INTERFACE;
|
||||
},
|
||||
|
||||
createInstance: function(outer, iid) {
|
||||
return this.QueryInterface(iid);
|
||||
},
|
||||
|
||||
startSearch: function(aString, aParam, aResult, aListener) {
|
||||
var result = new nsAutoCompleteSimpleResult(aString);
|
||||
aListener.onSearchResult(this, result);
|
||||
},
|
||||
|
||||
stopSearch: function() {}
|
||||
};
|
||||
|
||||
var componentManager =
|
||||
Components.manager
|
||||
.QueryInterface(Ci.nsIComponentRegistrar);
|
||||
componentManager.registerFactory(autoCompleteSimpleID,
|
||||
"Test Simple Autocomplete for composition",
|
||||
autoCompleteSimpleName, autoCompleteSimple);
|
||||
|
||||
async function runTests()
|
||||
{
|
||||
var target = document.getElementById("textbox");
|
||||
|
||||
target.setAttribute("timeout", 1);
|
||||
let tests1 = new nsDoTestsForEditorWithAutoComplete(
|
||||
"Testing on XUL textbox (asynchronously search)",
|
||||
window, target, target.controller, is,
|
||||
function() { return target.value; });
|
||||
await tests1.run();
|
||||
|
||||
target.setAttribute("timeout", 0);
|
||||
let tests2 = new nsDoTestsForEditorWithAutoComplete(
|
||||
"Testing on XUL textbox (synchronously search)",
|
||||
window, target, target.controller, is,
|
||||
function() { return target.value; });
|
||||
await tests2.run();
|
||||
|
||||
// Unregister the factory so that we don't get in the way of other
|
||||
// tests
|
||||
componentManager.unregisterFactory(autoCompleteSimpleID,
|
||||
autoCompleteSimple);
|
||||
SimpleTest.finish();
|
||||
}
|
||||
|
||||
SimpleTest.waitForFocus(runTests);
|
||||
]]>
|
||||
</script>
|
||||
</window>
|
Loading…
Reference in New Issue
Block a user