mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-10 03:45:46 +00:00
Bug 722961 Add tests for autocomplete with composition r=m_kato
This commit is contained in:
parent
9db2c07418
commit
0582e6722b
@ -65,7 +65,6 @@ _TEST_FILES = \
|
|||||||
test_bug645914.html \
|
test_bug645914.html \
|
||||||
test_bug681229.html \
|
test_bug681229.html \
|
||||||
test_bug692520.html \
|
test_bug692520.html \
|
||||||
test_bug717147.html \
|
|
||||||
test_dom_input_event_on_texteditor.html \
|
test_dom_input_event_on_texteditor.html \
|
||||||
$(NULL)
|
$(NULL)
|
||||||
|
|
||||||
|
@ -1,99 +0,0 @@
|
|||||||
<!DOCTYPE HTML>
|
|
||||||
<html>
|
|
||||||
<!--
|
|
||||||
https://bugzilla.mozilla.org/show_bug.cgi?id=717147
|
|
||||||
-->
|
|
||||||
<head>
|
|
||||||
<title>Test for Bug 717147</title>
|
|
||||||
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
|
||||||
<script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
|
|
||||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=717147">Mozilla Bug 717147</a>
|
|
||||||
<p id="display"></p>
|
|
||||||
<div id="content">
|
|
||||||
<iframe id="formTarget" name="formTarget"></iframe>
|
|
||||||
<form action="data:text/html," target="formTarget">
|
|
||||||
<input name="test" id="initValue"><input type="submit">
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
<pre id="test">
|
|
||||||
<script type="application/javascript">
|
|
||||||
|
|
||||||
/** Test for Bug 717147 **/
|
|
||||||
SimpleTest.waitForExplicitFinish();
|
|
||||||
SimpleTest.waitForFocus(function() {
|
|
||||||
var formTarget = document.getElementById("formTarget");
|
|
||||||
var initValue = document.getElementById("initValue");
|
|
||||||
|
|
||||||
formTarget.addEventListener("load", function() {
|
|
||||||
var newInput = document.createElement("input");
|
|
||||||
newInput.setAttribute("name", "test");
|
|
||||||
document.body.appendChild(newInput);
|
|
||||||
|
|
||||||
setTimeout(function() {
|
|
||||||
var popupShown = false;
|
|
||||||
function listener() {
|
|
||||||
popupShown = true;
|
|
||||||
}
|
|
||||||
SpecialPowers.addAutoCompletePopupEventListener(window, listener);
|
|
||||||
|
|
||||||
newInput.value = "";
|
|
||||||
newInput.focus();
|
|
||||||
|
|
||||||
synthesizeComposition({ type: "compositionstart" });
|
|
||||||
synthesizeComposition({ type: "compositionupdate", data: "f" });
|
|
||||||
synthesizeText(
|
|
||||||
{ "composition":
|
|
||||||
{ "string": "f",
|
|
||||||
"clauses":
|
|
||||||
[
|
|
||||||
{ "length": 1, "attr": COMPOSITION_ATTR_RAWINPUT }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"caret": { "start": 1, "length": 0 }
|
|
||||||
});
|
|
||||||
|
|
||||||
hitEventLoop(function() {
|
|
||||||
ok(!popupShown, "Popup must not be opened during composition");
|
|
||||||
|
|
||||||
synthesizeText(
|
|
||||||
{ "composition":
|
|
||||||
{ "string": "f",
|
|
||||||
"clauses":
|
|
||||||
[
|
|
||||||
{ "length": 0, "attr": 0 }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"caret": { "start": 1, "length": 0 }
|
|
||||||
});
|
|
||||||
synthesizeComposition({ type: "compositionend", data: "f" });
|
|
||||||
|
|
||||||
hitEventLoop(function () {
|
|
||||||
ok(popupShown, "Popup must be opened after compositionend");
|
|
||||||
|
|
||||||
SpecialPowers.removeAutoCompletePopupEventListener(window, listener);
|
|
||||||
SimpleTest.finish();
|
|
||||||
}, 100);
|
|
||||||
}, 100);
|
|
||||||
}, 0);
|
|
||||||
}, false);
|
|
||||||
|
|
||||||
initValue.focus();
|
|
||||||
initValue.value = "foo";
|
|
||||||
synthesizeKey("VK_ENTER", {});
|
|
||||||
});
|
|
||||||
|
|
||||||
function hitEventLoop(func, times) {
|
|
||||||
if (times > 0) {
|
|
||||||
setTimeout(hitEventLoop, 0, func, times - 1);
|
|
||||||
} else {
|
|
||||||
setTimeout(func, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
</script>
|
|
||||||
</pre>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -704,6 +704,18 @@ SpecialPowersAPI.prototype = {
|
|||||||
listener,
|
listener,
|
||||||
false);
|
false);
|
||||||
},
|
},
|
||||||
|
getFormFillController: function(window) {
|
||||||
|
return Components.classes["@mozilla.org/satchel/form-fill-controller;1"]
|
||||||
|
.getService(Components.interfaces.nsIFormFillController);
|
||||||
|
},
|
||||||
|
attachFormFillControllerTo: function(window) {
|
||||||
|
this.getFormFillController()
|
||||||
|
.attachToBrowser(this._getDocShell(window),
|
||||||
|
this._getAutoCompletePopup(window));
|
||||||
|
},
|
||||||
|
detachFormFillControllerFrom: function(window) {
|
||||||
|
this.getFormFillController().detachFromBrowser(this._getDocShell(window));
|
||||||
|
},
|
||||||
isBackButtonEnabled: function(window) {
|
isBackButtonEnabled: function(window) {
|
||||||
return !this._getTopChromeWindow(window).document
|
return !this._getTopChromeWindow(window).document
|
||||||
.getElementById("Browser:Back")
|
.getElementById("Browser:Back")
|
||||||
|
@ -89,6 +89,9 @@ _TEST_FILES = findbar_window.xul \
|
|||||||
test_autocomplete4.xul \
|
test_autocomplete4.xul \
|
||||||
test_autocomplete5.xul \
|
test_autocomplete5.xul \
|
||||||
test_autocomplete_delayOnPaste.xul \
|
test_autocomplete_delayOnPaste.xul \
|
||||||
|
file_autocomplete_with_composition.js \
|
||||||
|
test_autocomplete_with_composition_on_input.html \
|
||||||
|
test_autocomplete_with_composition_on_textbox.xul \
|
||||||
test_keys.xul \
|
test_keys.xul \
|
||||||
window_keys.xul \
|
window_keys.xul \
|
||||||
test_showcaret.xul \
|
test_showcaret.xul \
|
||||||
|
@ -0,0 +1,633 @@
|
|||||||
|
// nsDoTestsForAutoCompleteWithComposition tests autocomplete with composition.
|
||||||
|
// Users must include SimpleTest.js and EventUtils.js.
|
||||||
|
|
||||||
|
function nsDoTestsForAutoCompleteWithComposition(aDescription,
|
||||||
|
aWindow,
|
||||||
|
aTarget,
|
||||||
|
aAutoCompleteController,
|
||||||
|
aIsFunc,
|
||||||
|
aGetTargetValueFunc,
|
||||||
|
aOnFinishFunc)
|
||||||
|
{
|
||||||
|
this._description = aDescription;
|
||||||
|
this._window = aWindow;
|
||||||
|
this._target = aTarget;
|
||||||
|
this._controller = aAutoCompleteController;
|
||||||
|
|
||||||
|
this._is = aIsFunc;
|
||||||
|
this._getTargetValue = aGetTargetValueFunc;
|
||||||
|
this._onFinish = aOnFinishFunc;
|
||||||
|
|
||||||
|
this._target.focus();
|
||||||
|
|
||||||
|
this._DefaultCompleteDefaultIndex =
|
||||||
|
this._controller.input.completeDefaultIndex;
|
||||||
|
|
||||||
|
this._doTests();
|
||||||
|
}
|
||||||
|
|
||||||
|
nsDoTestsForAutoCompleteWithComposition.prototype = {
|
||||||
|
_window: null,
|
||||||
|
_target: null,
|
||||||
|
_controller: null,
|
||||||
|
_DefaultCompleteDefaultIndex: false,
|
||||||
|
_description: "",
|
||||||
|
|
||||||
|
_is: null,
|
||||||
|
_getTargetValue: function () { return "not initialized"; },
|
||||||
|
_onFinish: null,
|
||||||
|
|
||||||
|
_doTests: function ()
|
||||||
|
{
|
||||||
|
if (++this._testingIndex == this._tests.length) {
|
||||||
|
this._controller.input.completeDefaultIndex =
|
||||||
|
this._DefaultCompleteDefaultIndex;
|
||||||
|
this._onFinish();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var test = this._tests[this._testingIndex];
|
||||||
|
if (this._controller.input.completeDefaultIndex != test.completeDefaultIndex) {
|
||||||
|
this._controller.input.completeDefaultIndex = test.completeDefaultIndex;
|
||||||
|
}
|
||||||
|
test.execute(this._window);
|
||||||
|
|
||||||
|
var timeout = this._controller.input.timeout + 10;
|
||||||
|
this._waitResult(timeout);
|
||||||
|
},
|
||||||
|
|
||||||
|
_waitResult: function (aTimes)
|
||||||
|
{
|
||||||
|
var obj = this;
|
||||||
|
if (aTimes-- > 0) {
|
||||||
|
setTimeout(function () { obj._waitResult(aTimes); }, 0);
|
||||||
|
} else {
|
||||||
|
setTimeout(function () { obj._checkResult(); }, 0);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_checkResult: function ()
|
||||||
|
{
|
||||||
|
var test = this._tests[this._testingIndex];
|
||||||
|
this._is(this._getTargetValue(), test.value,
|
||||||
|
this._description + ", " + test.description + ": value");
|
||||||
|
this._is(this._controller.searchString, test.searchString,
|
||||||
|
this._description + ", " + test.description +": searchString");
|
||||||
|
this._is(this._controller.input.popupOpen, test.popup,
|
||||||
|
this._description + ", " + test.description + ": popupOpen");
|
||||||
|
this._doTests();
|
||||||
|
},
|
||||||
|
|
||||||
|
_testingIndex: -1,
|
||||||
|
_tests: [
|
||||||
|
// Simple composition when popup hasn't been shown.
|
||||||
|
// The autocomplete popup should not be shown during composition, but
|
||||||
|
// after compositionend, the popup should be shown.
|
||||||
|
{ description: "compositionstart shouldn't open the popup",
|
||||||
|
completeDefaultIndex: false,
|
||||||
|
execute: function (aWindow) {
|
||||||
|
synthesizeKey("m", { type: "keydown", shiftKey: true }, aWindow);
|
||||||
|
synthesizeComposition({ type: "compositionstart" }, aWindow);
|
||||||
|
synthesizeComposition({ type: "compositionupdate", data: "M" }, aWindow);
|
||||||
|
synthesizeText(
|
||||||
|
{ "composition":
|
||||||
|
{ "string": "M",
|
||||||
|
"clauses":
|
||||||
|
[
|
||||||
|
{ "length": 1, "attr": COMPOSITION_ATTR_RAWINPUT }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"caret": { "start": 1, "length": 0 }
|
||||||
|
}, aWindow);
|
||||||
|
}, popup: false, value: "M", searchString: ""
|
||||||
|
},
|
||||||
|
{ description: "compositionupdate shouldn't open the popup",
|
||||||
|
completeDefaultIndex: false,
|
||||||
|
execute: function (aWindow) {
|
||||||
|
synthesizeComposition({ type: "compositionupdate", data: "Mo" }, aWindow);
|
||||||
|
synthesizeText(
|
||||||
|
{ "composition":
|
||||||
|
{ "string": "Mo",
|
||||||
|
"clauses":
|
||||||
|
[
|
||||||
|
{ "length": 2, "attr": COMPOSITION_ATTR_RAWINPUT }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"caret": { "start": 2, "length": 0 }
|
||||||
|
}, aWindow);
|
||||||
|
}, popup: false, value: "Mo", searchString: ""
|
||||||
|
},
|
||||||
|
{ description: "compositionend should open the popup",
|
||||||
|
completeDefaultIndex: false,
|
||||||
|
execute: function (aWindow) {
|
||||||
|
synthesizeText(
|
||||||
|
{ "composition":
|
||||||
|
{ "string": "Mo",
|
||||||
|
"clauses":
|
||||||
|
[
|
||||||
|
{ "length": 0, "attr": 0 }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"caret": { "start": 2, "length": 0 }
|
||||||
|
}, aWindow);
|
||||||
|
synthesizeComposition({ type: "compositionend", data: "Mo" }, aWindow);
|
||||||
|
synthesizeKey("VK_ENTER", { type: "keyup" }, aWindow);
|
||||||
|
}, popup: true, value: "Mo", searchString: "Mo"
|
||||||
|
},
|
||||||
|
// If composition starts when popup is shown, the compositionstart event
|
||||||
|
// should cause closing the popup.
|
||||||
|
{ description: "compositionstart should close the popup",
|
||||||
|
completeDefaultIndex: false,
|
||||||
|
execute: function (aWindow) {
|
||||||
|
synthesizeKey("z", { type: "keydown" }, aWindow);
|
||||||
|
synthesizeComposition({ type: "compositionstart" }, aWindow);
|
||||||
|
synthesizeComposition({ type: "compositionupdate", data: "z" }, aWindow);
|
||||||
|
synthesizeText(
|
||||||
|
{ "composition":
|
||||||
|
{ "string": "z",
|
||||||
|
"clauses":
|
||||||
|
[
|
||||||
|
{ "length": 1, "attr": COMPOSITION_ATTR_RAWINPUT }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"caret": { "start": 1, "length": 0 }
|
||||||
|
}, aWindow);
|
||||||
|
}, popup: false, value: "Moz", searchString: "Mo"
|
||||||
|
},
|
||||||
|
{ description: "compositionupdate shouldn't reopen the popup",
|
||||||
|
completeDefaultIndex: false,
|
||||||
|
execute: function (aWindow) {
|
||||||
|
synthesizeComposition({ type: "compositionupdate", data: "zi" }, aWindow);
|
||||||
|
synthesizeText(
|
||||||
|
{ "composition":
|
||||||
|
{ "string": "zi",
|
||||||
|
"clauses":
|
||||||
|
[
|
||||||
|
{ "length": 2, "attr": COMPOSITION_ATTR_RAWINPUT }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"caret": { "start": 2, "length": 0 }
|
||||||
|
}, aWindow);
|
||||||
|
}, popup: false, value: "Mozi", searchString: "Mo"
|
||||||
|
},
|
||||||
|
{ description: "compositionend should research the result and open the popup",
|
||||||
|
completeDefaultIndex: false,
|
||||||
|
execute: function (aWindow) {
|
||||||
|
synthesizeText(
|
||||||
|
{ "composition":
|
||||||
|
{ "string": "zi",
|
||||||
|
"clauses":
|
||||||
|
[
|
||||||
|
{ "length": 0, "attr": 0 }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"caret": { "start": 2, "length": 0 }
|
||||||
|
});
|
||||||
|
synthesizeComposition({ type: "compositionend", data: "zi" }, aWindow);
|
||||||
|
synthesizeKey("VK_ENTER", { type: "keyup" }, aWindow);
|
||||||
|
}, popup: true, value: "Mozi", searchString: "Mozi"
|
||||||
|
},
|
||||||
|
// If composition is cancelled, the value shouldn't be changed.
|
||||||
|
{ description: "compositionstart should reclose the popup",
|
||||||
|
completeDefaultIndex: false,
|
||||||
|
execute: function (aWindow) {
|
||||||
|
synthesizeKey("l", { type: "keydown" }, aWindow);
|
||||||
|
synthesizeComposition({ type: "compositionstart" }, aWindow);
|
||||||
|
synthesizeComposition({ type: "compositionupdate", data: "l" }, aWindow);
|
||||||
|
synthesizeText(
|
||||||
|
{ "composition":
|
||||||
|
{ "string": "l",
|
||||||
|
"clauses":
|
||||||
|
[
|
||||||
|
{ "length": 1, "attr": COMPOSITION_ATTR_RAWINPUT }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"caret": { "start": 1, "length": 0 }
|
||||||
|
}, aWindow);
|
||||||
|
}, popup: false, value: "Mozil", searchString: "Mozi"
|
||||||
|
},
|
||||||
|
{ description: "compositionupdate shouldn't reopen the popup",
|
||||||
|
completeDefaultIndex: false,
|
||||||
|
execute: function (aWindow) {
|
||||||
|
synthesizeComposition({ type: "compositionupdate", data: "ll" }, aWindow);
|
||||||
|
synthesizeText(
|
||||||
|
{ "composition":
|
||||||
|
{ "string": "ll",
|
||||||
|
"clauses":
|
||||||
|
[
|
||||||
|
{ "length": 2, "attr": COMPOSITION_ATTR_RAWINPUT }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"caret": { "start": 2, "length": 0 }
|
||||||
|
}, aWindow);
|
||||||
|
}, popup: false, value: "Mozill", searchString: "Mozi"
|
||||||
|
},
|
||||||
|
{ description: "empty compositionupdate shouldn't reopen the popup",
|
||||||
|
completeDefaultIndex: false,
|
||||||
|
execute: function (aWindow) {
|
||||||
|
synthesizeComposition({ type: "compositionupdate", data: "" }, aWindow);
|
||||||
|
synthesizeText(
|
||||||
|
{ "composition":
|
||||||
|
{ "string": "",
|
||||||
|
"clauses":
|
||||||
|
[
|
||||||
|
{ "length": 0, "attr": 0 }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"caret": { "start": 0, "length": 0 }
|
||||||
|
}, aWindow);
|
||||||
|
}, popup: false, value: "Mozi", searchString: "Mozi"
|
||||||
|
},
|
||||||
|
{ description: "cancled compositionend should reopen the popup",
|
||||||
|
completeDefaultIndex: false,
|
||||||
|
execute: function (aWindow) {
|
||||||
|
synthesizeText(
|
||||||
|
{ "composition":
|
||||||
|
{ "string": "",
|
||||||
|
"clauses":
|
||||||
|
[
|
||||||
|
{ "length": 0, "attr": 0 }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"caret": { "start": 0, "length": 0 }
|
||||||
|
}, aWindow);
|
||||||
|
synthesizeComposition({ type: "compositionend", data: "" }, aWindow);
|
||||||
|
synthesizeKey("VK_ESCAPE", { type: "keyup" }, aWindow);
|
||||||
|
}, popup: true, value: "Mozi", searchString: "Mozi"
|
||||||
|
},
|
||||||
|
// But if composition replaces some characters and canceled, the search
|
||||||
|
// string should be the latest value.
|
||||||
|
{ description: "compositionstart with selected string should close the popup",
|
||||||
|
completeDefaultIndex: false,
|
||||||
|
execute: function (aWindow) {
|
||||||
|
synthesizeKey("VK_LEFT", { shiftKey: true }, aWindow);
|
||||||
|
synthesizeKey("VK_LEFT", { shiftKey: true }, aWindow);
|
||||||
|
synthesizeKey("z", { type: "keydown" }, aWindow);
|
||||||
|
synthesizeComposition({ type: "compositionstart" }, aWindow);
|
||||||
|
synthesizeComposition({ type: "compositionupdate", data: "z" }, aWindow);
|
||||||
|
synthesizeText(
|
||||||
|
{ "composition":
|
||||||
|
{ "string": "z",
|
||||||
|
"clauses":
|
||||||
|
[
|
||||||
|
{ "length": 1, "attr": COMPOSITION_ATTR_RAWINPUT }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"caret": { "start": 1, "length": 0 }
|
||||||
|
}, aWindow);
|
||||||
|
}, popup: false, value: "Moz", searchString: "Mozi"
|
||||||
|
},
|
||||||
|
{ description: "compositionupdate shouldn't reopen the popup",
|
||||||
|
completeDefaultIndex: false,
|
||||||
|
execute: function (aWindow) {
|
||||||
|
synthesizeComposition({ type: "compositionupdate", data: "ll" }, aWindow);
|
||||||
|
synthesizeText(
|
||||||
|
{ "composition":
|
||||||
|
{ "string": "zi",
|
||||||
|
"clauses":
|
||||||
|
[
|
||||||
|
{ "length": 2, "attr": COMPOSITION_ATTR_RAWINPUT }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"caret": { "start": 2, "length": 0 }
|
||||||
|
}, aWindow);
|
||||||
|
}, popup: false, value: "Mozi", searchString: "Mozi"
|
||||||
|
},
|
||||||
|
{ description: "empty compositionupdate shouldn't reopen the popup",
|
||||||
|
completeDefaultIndex: false,
|
||||||
|
execute: function (aWindow) {
|
||||||
|
synthesizeComposition({ type: "compositionupdate", data: "" }, aWindow);
|
||||||
|
synthesizeText(
|
||||||
|
{ "composition":
|
||||||
|
{ "string": "",
|
||||||
|
"clauses":
|
||||||
|
[
|
||||||
|
{ "length": 0, "attr": 0 }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"caret": { "start": 0, "length": 0 }
|
||||||
|
}, aWindow);
|
||||||
|
}, popup: false, value: "Mo", searchString: "Mozi"
|
||||||
|
},
|
||||||
|
{ description: "canceled compositionend should seach the result with the latest value",
|
||||||
|
completeDefaultIndex: false,
|
||||||
|
execute: function (aWindow) {
|
||||||
|
synthesizeText(
|
||||||
|
{ "composition":
|
||||||
|
{ "string": "",
|
||||||
|
"clauses":
|
||||||
|
[
|
||||||
|
{ "length": 0, "attr": 0 }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"caret": { "start": 0, "length": 0 }
|
||||||
|
}, aWindow);
|
||||||
|
synthesizeComposition({ type: "compositionend", data: "" }, aWindow);
|
||||||
|
synthesizeKey("VK_ESCAPE", { type: "keyup" }, aWindow);
|
||||||
|
}, popup: true, value: "Mo", searchString: "Mo"
|
||||||
|
},
|
||||||
|
//If all characters are removed, the popup should be closed.
|
||||||
|
{ description: "the value becomes empty by backspace, the popup should be closed",
|
||||||
|
completeDefaultIndex: false,
|
||||||
|
execute: function (aWindow) {
|
||||||
|
synthesizeKey("VK_BACK_SPACE", {}, aWindow);
|
||||||
|
synthesizeKey("VK_BACK_SPACE", {}, aWindow);
|
||||||
|
}, popup: false, value: "", searchString: ""
|
||||||
|
},
|
||||||
|
// composition which is canceled shouldn't cause opening the popup.
|
||||||
|
{ description: "compositionstart shouldn't open the popup",
|
||||||
|
completeDefaultIndex: false,
|
||||||
|
execute: function (aWindow) {
|
||||||
|
synthesizeKey("m", { type: "keydown", shiftKey: true }, aWindow);
|
||||||
|
synthesizeComposition({ type: "compositionstart" }, aWindow);
|
||||||
|
synthesizeComposition({ type: "compositionupdate", data: "M" }, aWindow);
|
||||||
|
synthesizeText(
|
||||||
|
{ "composition":
|
||||||
|
{ "string": "M",
|
||||||
|
"clauses":
|
||||||
|
[
|
||||||
|
{ "length": 1, "attr": COMPOSITION_ATTR_RAWINPUT }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"caret": { "start": 1, "length": 0 }
|
||||||
|
}, aWindow);
|
||||||
|
}, popup: false, value: "M", searchString: ""
|
||||||
|
},
|
||||||
|
{ description: "compositionupdate shouldn't open the popup",
|
||||||
|
completeDefaultIndex: false,
|
||||||
|
execute: function (aWindow) {
|
||||||
|
synthesizeComposition({ type: "compositionupdate", data: "Mo" }, aWindow);
|
||||||
|
synthesizeText(
|
||||||
|
{ "composition":
|
||||||
|
{ "string": "Mo",
|
||||||
|
"clauses":
|
||||||
|
[
|
||||||
|
{ "length": 2, "attr": COMPOSITION_ATTR_RAWINPUT }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"caret": { "start": 2, "length": 0 }
|
||||||
|
}, aWindow);
|
||||||
|
}, popup: false, value: "Mo", searchString: ""
|
||||||
|
},
|
||||||
|
{ description: "empty compositionupdate shouldn't open the popup",
|
||||||
|
completeDefaultIndex: false,
|
||||||
|
execute: function (aWindow) {
|
||||||
|
synthesizeComposition({ type: "compositionupdate", data: "" }, aWindow);
|
||||||
|
synthesizeText(
|
||||||
|
{ "composition":
|
||||||
|
{ "string": "",
|
||||||
|
"clauses":
|
||||||
|
[
|
||||||
|
{ "length": 0, "attr": 0 }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"caret": { "start": 0, "length": 0 }
|
||||||
|
}, aWindow);
|
||||||
|
}, popup: false, value: "", searchString: ""
|
||||||
|
},
|
||||||
|
{ description: "canceled compositionend shouldn't open the popup if it was closed",
|
||||||
|
completeDefaultIndex: false,
|
||||||
|
execute: function (aWindow) {
|
||||||
|
synthesizeText(
|
||||||
|
{ "composition":
|
||||||
|
{ "string": "",
|
||||||
|
"clauses":
|
||||||
|
[
|
||||||
|
{ "length": 0, "attr": 0 }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"caret": { "start": 0, "length": 0 }
|
||||||
|
}, aWindow);
|
||||||
|
synthesizeComposition({ type: "compositionend", data: "" }, aWindow);
|
||||||
|
synthesizeKey("VK_ESCAPE", { type: "keyup" }, aWindow);
|
||||||
|
}, popup: false, value: "", searchString: ""
|
||||||
|
},
|
||||||
|
// Down key should open the popup even if the editor is empty.
|
||||||
|
{ description: "DOWN key should open the popup even if the value is empty",
|
||||||
|
completeDefaultIndex: false,
|
||||||
|
execute: function (aWindow) {
|
||||||
|
synthesizeKey("VK_DOWN", {}, aWindow);
|
||||||
|
}, popup: true, value: "", searchString: ""
|
||||||
|
},
|
||||||
|
// If popup is open at starting composition, the popup should be reopened
|
||||||
|
// after composition anyway.
|
||||||
|
{ description: "compositionstart shouldn't open the popup",
|
||||||
|
completeDefaultIndex: false,
|
||||||
|
execute: function (aWindow) {
|
||||||
|
synthesizeKey("m", { type: "keydown", shiftKey: true }, aWindow);
|
||||||
|
synthesizeComposition({ type: "compositionstart" }, aWindow);
|
||||||
|
synthesizeComposition({ type: "compositionupdate", data: "M" }, aWindow);
|
||||||
|
synthesizeText(
|
||||||
|
{ "composition":
|
||||||
|
{ "string": "M",
|
||||||
|
"clauses":
|
||||||
|
[
|
||||||
|
{ "length": 1, "attr": COMPOSITION_ATTR_RAWINPUT }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"caret": { "start": 1, "length": 0 }
|
||||||
|
}, aWindow);
|
||||||
|
}, popup: false, value: "M", searchString: ""
|
||||||
|
},
|
||||||
|
{ description: "compositionupdate shouldn't open the popup",
|
||||||
|
completeDefaultIndex: false,
|
||||||
|
execute: function (aWindow) {
|
||||||
|
synthesizeComposition({ type: "compositionupdate", data: "Mo" }, aWindow);
|
||||||
|
synthesizeText(
|
||||||
|
{ "composition":
|
||||||
|
{ "string": "Mo",
|
||||||
|
"clauses":
|
||||||
|
[
|
||||||
|
{ "length": 2, "attr": COMPOSITION_ATTR_RAWINPUT }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"caret": { "start": 2, "length": 0 }
|
||||||
|
}, aWindow);
|
||||||
|
}, popup: false, value: "Mo", searchString: ""
|
||||||
|
},
|
||||||
|
{ description: "empty compositionupdate shouldn't open the popup",
|
||||||
|
completeDefaultIndex: false,
|
||||||
|
execute: function (aWindow) {
|
||||||
|
synthesizeComposition({ type: "compositionupdate", data: "" }, aWindow);
|
||||||
|
synthesizeText(
|
||||||
|
{ "composition":
|
||||||
|
{ "string": "",
|
||||||
|
"clauses":
|
||||||
|
[
|
||||||
|
{ "length": 0, "attr": 0 }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"caret": { "start": 0, "length": 0 }
|
||||||
|
}, aWindow);
|
||||||
|
}, popup: false, value: "", searchString: ""
|
||||||
|
},
|
||||||
|
{ description: "canceled compositionend should open the popup if it was opened",
|
||||||
|
completeDefaultIndex: false,
|
||||||
|
execute: function (aWindow) {
|
||||||
|
synthesizeText(
|
||||||
|
{ "composition":
|
||||||
|
{ "string": "",
|
||||||
|
"clauses":
|
||||||
|
[
|
||||||
|
{ "length": 0, "attr": 0 }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"caret": { "start": 0, "length": 0 }
|
||||||
|
}, aWindow);
|
||||||
|
synthesizeComposition({ type: "compositionend", data: "" }, aWindow);
|
||||||
|
synthesizeKey("VK_ESCAPE", { type: "keyup" }, aWindow);
|
||||||
|
}, popup: true, value: "", searchString: ""
|
||||||
|
},
|
||||||
|
// Type normally, and hit escape, the popup should be closed.
|
||||||
|
{ description: "ESCAPE should close the popup after typing something",
|
||||||
|
completeDefaultIndex: false,
|
||||||
|
execute: function (aWindow) {
|
||||||
|
synthesizeKey("M", { shiftKey: true }, aWindow);
|
||||||
|
synthesizeKey("o", { shiftKey: true }, aWindow);
|
||||||
|
synthesizeKey("VK_ESCAPE", {}, aWindow);
|
||||||
|
}, popup: false, value: "Mo", searchString: "Mo"
|
||||||
|
},
|
||||||
|
// Even if the popup is closed, composition which is canceled should open
|
||||||
|
// the popup if the value isn't empty.
|
||||||
|
// XXX This might not be good behavior, but anyway, this is minor issue...
|
||||||
|
{ description: "compositionstart shouldn't open the popup",
|
||||||
|
completeDefaultIndex: false,
|
||||||
|
execute: function (aWindow) {
|
||||||
|
synthesizeKey("z", { type: "keydown", shiftKey: true }, aWindow);
|
||||||
|
synthesizeComposition({ type: "compositionstart" }, aWindow);
|
||||||
|
synthesizeComposition({ type: "compositionupdate", data: "z" }, aWindow);
|
||||||
|
synthesizeText(
|
||||||
|
{ "composition":
|
||||||
|
{ "string": "z",
|
||||||
|
"clauses":
|
||||||
|
[
|
||||||
|
{ "length": 1, "attr": COMPOSITION_ATTR_RAWINPUT }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"caret": { "start": 1, "length": 0 }
|
||||||
|
}, aWindow);
|
||||||
|
}, popup: false, value: "Moz", searchString: "Mo"
|
||||||
|
},
|
||||||
|
{ description: "compositionupdate shouldn't open the popup",
|
||||||
|
completeDefaultIndex: false,
|
||||||
|
execute: function (aWindow) {
|
||||||
|
synthesizeComposition({ type: "compositionupdate", data: "zi" }, aWindow);
|
||||||
|
synthesizeText(
|
||||||
|
{ "composition":
|
||||||
|
{ "string": "zi",
|
||||||
|
"clauses":
|
||||||
|
[
|
||||||
|
{ "length": 2, "attr": COMPOSITION_ATTR_RAWINPUT }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"caret": { "start": 2, "length": 0 }
|
||||||
|
}, aWindow);
|
||||||
|
}, popup: false, value: "Mozi", searchString: "Mo"
|
||||||
|
},
|
||||||
|
{ description: "empty compositionupdate shouldn't open the popup",
|
||||||
|
completeDefaultIndex: false,
|
||||||
|
execute: function (aWindow) {
|
||||||
|
synthesizeComposition({ type: "compositionupdate", data: "" }, aWindow);
|
||||||
|
synthesizeText(
|
||||||
|
{ "composition":
|
||||||
|
{ "string": "",
|
||||||
|
"clauses":
|
||||||
|
[
|
||||||
|
{ "length": 0, "attr": 0 }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"caret": { "start": 0, "length": 0 }
|
||||||
|
}, aWindow);
|
||||||
|
}, popup: false, value: "Mo", searchString: "Mo"
|
||||||
|
},
|
||||||
|
{ description: "canceled compositionend shouldn't open the popup if the popup was closed",
|
||||||
|
completeDefaultIndex: false,
|
||||||
|
execute: function (aWindow) {
|
||||||
|
synthesizeText(
|
||||||
|
{ "composition":
|
||||||
|
{ "string": "",
|
||||||
|
"clauses":
|
||||||
|
[
|
||||||
|
{ "length": 0, "attr": 0 }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"caret": { "start": 0, "length": 0 }
|
||||||
|
}, aWindow);
|
||||||
|
synthesizeComposition({ type: "compositionend", data: "" }, aWindow);
|
||||||
|
synthesizeKey("VK_ESCAPE", { type: "keyup" }, aWindow);
|
||||||
|
}, popup: true, value: "Mo", searchString: "Mo"
|
||||||
|
},
|
||||||
|
// House keeping...
|
||||||
|
{ description: "house keeping for next tests",
|
||||||
|
completeDefaultIndex: false,
|
||||||
|
execute: function (aWindow) {
|
||||||
|
synthesizeKey("VK_BACK_SPACE", {}, aWindow);
|
||||||
|
synthesizeKey("VK_BACK_SPACE", {}, aWindow);
|
||||||
|
}, popup: false, value: "", searchString: ""
|
||||||
|
},
|
||||||
|
// Testing for nsIAutoCompleteInput.completeDefaultIndex being true.
|
||||||
|
{ description: "compositionstart shouldn't open the popup (completeDefaultIndex is true)",
|
||||||
|
completeDefaultIndex: true,
|
||||||
|
execute: function (aWindow) {
|
||||||
|
synthesizeKey("m", { type: "keydown", shiftKey: true }, aWindow);
|
||||||
|
synthesizeComposition({ type: "compositionstart" }, aWindow);
|
||||||
|
synthesizeComposition({ type: "compositionupdate", data: "M" }, aWindow);
|
||||||
|
synthesizeText(
|
||||||
|
{ "composition":
|
||||||
|
{ "string": "M",
|
||||||
|
"clauses":
|
||||||
|
[
|
||||||
|
{ "length": 1, "attr": COMPOSITION_ATTR_RAWINPUT }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"caret": { "start": 1, "length": 0 }
|
||||||
|
}, aWindow);
|
||||||
|
}, popup: false, value: "M", searchString: ""
|
||||||
|
},
|
||||||
|
{ description: "compositionupdate shouldn't open the popup (completeDefaultIndex is true)",
|
||||||
|
completeDefaultIndex: true,
|
||||||
|
execute: function (aWindow) {
|
||||||
|
synthesizeComposition({ type: "compositionupdate", data: "Mo" }, aWindow);
|
||||||
|
synthesizeText(
|
||||||
|
{ "composition":
|
||||||
|
{ "string": "Mo",
|
||||||
|
"clauses":
|
||||||
|
[
|
||||||
|
{ "length": 2, "attr": COMPOSITION_ATTR_RAWINPUT }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"caret": { "start": 2, "length": 0 }
|
||||||
|
}, aWindow);
|
||||||
|
}, popup: false, value: "Mo", searchString: ""
|
||||||
|
},
|
||||||
|
{ description: "compositionend should open the popup (completeDefaultIndex is true)",
|
||||||
|
completeDefaultIndex: true,
|
||||||
|
execute: function (aWindow) {
|
||||||
|
synthesizeText(
|
||||||
|
{ "composition":
|
||||||
|
{ "string": "Mo",
|
||||||
|
"clauses":
|
||||||
|
[
|
||||||
|
{ "length": 0, "attr": 0 }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"caret": { "start": 2, "length": 0 }
|
||||||
|
}, aWindow);
|
||||||
|
synthesizeComposition({ type: "compositionend", data: "Mo" }, aWindow);
|
||||||
|
synthesizeKey("VK_ENTER", { type: "keyup" }, aWindow);
|
||||||
|
}, popup: true, value: "Mozilla", searchString: "Mo"
|
||||||
|
},
|
||||||
|
// House keeping...
|
||||||
|
{ description: "house keeping for next tests",
|
||||||
|
completeDefaultIndex: false,
|
||||||
|
execute: function (aWindow) {
|
||||||
|
synthesizeKey("VK_BACK_SPACE", {}, aWindow);
|
||||||
|
synthesizeKey("VK_BACK_SPACE", {}, aWindow);
|
||||||
|
synthesizeKey("VK_BACK_SPACE", {}, aWindow);
|
||||||
|
synthesizeKey("VK_BACK_SPACE", {}, aWindow);
|
||||||
|
synthesizeKey("VK_BACK_SPACE", {}, aWindow);
|
||||||
|
synthesizeKey("VK_BACK_SPACE", {}, aWindow);
|
||||||
|
}, popup: false, value: "", searchString: ""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
@ -0,0 +1,65 @@
|
|||||||
|
<!DOCTYPE HTML>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>autocomplete with composition tests on HTML input element</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="chrome://mochikit/content/tests/SimpleTest/ChromeUtils.js"></script>
|
||||||
|
<script type="text/javascript" src="file_autocomplete_with_composition.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();
|
||||||
|
|
||||||
|
function runTests()
|
||||||
|
{
|
||||||
|
var formFillController =
|
||||||
|
SpecialPowers.getFormFillController()
|
||||||
|
.QueryInterface(Components.interfaces.nsIAutoCompleteInput);
|
||||||
|
var originalFormFillTimeout = formFillController.timeout;
|
||||||
|
|
||||||
|
SpecialPowers.attachFormFillControllerTo(window);
|
||||||
|
var target = document.getElementById("input");
|
||||||
|
|
||||||
|
// Register a word to the form history.
|
||||||
|
target.focus();
|
||||||
|
target.value = "Mozilla";
|
||||||
|
synthesizeKey("VK_ENTER", {});
|
||||||
|
target.value = "";
|
||||||
|
|
||||||
|
var test1 = new nsDoTestsForAutoCompleteWithComposition(
|
||||||
|
"Testing on HTML input (asynchronously search)",
|
||||||
|
window, target, formFillController.controller, is,
|
||||||
|
function () { return target.value; },
|
||||||
|
function () {
|
||||||
|
target.setAttribute("timeout", 0);
|
||||||
|
var test2 = new nsDoTestsForAutoCompleteWithComposition(
|
||||||
|
"Testing on HTML input (synchronously search)",
|
||||||
|
window, target, formFillController.controller, is,
|
||||||
|
function () { return target.value; },
|
||||||
|
function () {
|
||||||
|
formFillController.timeout = originalFormFillTimeout;
|
||||||
|
SpecialPowers.detachFormFillControllerFrom(window);
|
||||||
|
SimpleTest.finish();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
SimpleTest.waitForFocus(runTests);
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</pre>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -0,0 +1,125 @@
|
|||||||
|
<?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="Testing autocomplete with composition"
|
||||||
|
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="chrome://mochikit/content/tests/SimpleTest/ChromeUtils.js" />
|
||||||
|
<script type="text/javascript"
|
||||||
|
src="file_autocomplete_with_composition.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 = Components.interfaces.nsIAutoCompleteResult;
|
||||||
|
|
||||||
|
// This result can't be constructed in-line, because otherwise we leak memory.
|
||||||
|
function nsAutoCompleteSimpleResult(aString)
|
||||||
|
{
|
||||||
|
this.searchString = aString;
|
||||||
|
if (aString == "" || aString == "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; },
|
||||||
|
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(Components.interfaces.nsISupports) ||
|
||||||
|
iid.equals(Components.interfaces.nsIFactory) ||
|
||||||
|
iid.equals(Components.interfaces.nsIAutoCompleteSearch))
|
||||||
|
return this;
|
||||||
|
|
||||||
|
throw Components.results.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(Components.interfaces.nsIComponentRegistrar);
|
||||||
|
componentManager.registerFactory(autoCompleteSimpleID,
|
||||||
|
"Test Simple Autocomplete for composition",
|
||||||
|
autoCompleteSimpleName, autoCompleteSimple);
|
||||||
|
|
||||||
|
function runTests()
|
||||||
|
{
|
||||||
|
var target = document.getElementById("textbox");
|
||||||
|
target.setAttribute("timeout", 1);
|
||||||
|
var test1 = new nsDoTestsForAutoCompleteWithComposition(
|
||||||
|
"Testing on XUL textbox (asynchronously search)",
|
||||||
|
window, target, target.controller, is,
|
||||||
|
function () { return target.value; },
|
||||||
|
function () {
|
||||||
|
target.setAttribute("timeout", 0);
|
||||||
|
var test2 = new nsDoTestsForAutoCompleteWithComposition(
|
||||||
|
"Testing on XUL textbox (synchronously search)",
|
||||||
|
window, target, target.controller, is,
|
||||||
|
function () { return target.value; },
|
||||||
|
function () {
|
||||||
|
// 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