зеркало из https://github.com/mozilla/gecko-dev.git
Bug 477700. Push a null JSContext around editor init, so it'll work no matter what JS is on the stack. r+sr=smaug
This commit is contained in:
Родитель
d9761e7aaf
Коммит
f5a290d916
|
@ -1476,12 +1476,25 @@ public:
|
|||
|
||||
// Returns PR_FALSE if something erroneous happened.
|
||||
PRBool Push(nsPIDOMEventTarget *aCurrentTarget);
|
||||
// If a null JSContext is passed to Push(), that will cause no
|
||||
// Push() to happen and an error to be returned.
|
||||
PRBool Push(JSContext *cx);
|
||||
// Explicitly push a null JSContext on the the stack
|
||||
PRBool PushNull();
|
||||
|
||||
// Pop() will be a no-op if Push() or PushNull() fail
|
||||
void Pop();
|
||||
|
||||
private:
|
||||
// Combined code for PushNull() and Push(JSContext*)
|
||||
PRBool DoPush(JSContext* cx);
|
||||
|
||||
nsCOMPtr<nsIScriptContext> mScx;
|
||||
PRBool mScriptIsRunning;
|
||||
PRBool mPushedSomething;
|
||||
#ifdef DEBUG
|
||||
JSContext* mPushedContext;
|
||||
#endif
|
||||
};
|
||||
|
||||
class nsAutoGCRoot {
|
||||
|
|
|
@ -2656,7 +2656,8 @@ nsContentUtils::GetEventArgNames(PRInt32 aNameSpaceID,
|
|||
}
|
||||
|
||||
nsCxPusher::nsCxPusher()
|
||||
: mScriptIsRunning(PR_FALSE)
|
||||
: mScriptIsRunning(PR_FALSE),
|
||||
mPushedSomething(PR_FALSE)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -2701,7 +2702,7 @@ IsContextOnStack(nsIJSContextStack *aStack, JSContext *aContext)
|
|||
PRBool
|
||||
nsCxPusher::Push(nsPIDOMEventTarget *aCurrentTarget)
|
||||
{
|
||||
if (mScx) {
|
||||
if (mPushedSomething) {
|
||||
NS_ERROR("Whaaa! No double pushing with nsCxPusher::Push()!");
|
||||
|
||||
return PR_FALSE;
|
||||
|
@ -2729,39 +2730,68 @@ nsCxPusher::Push(nsPIDOMEventTarget *aCurrentTarget)
|
|||
PRBool
|
||||
nsCxPusher::Push(JSContext *cx)
|
||||
{
|
||||
if (mScx) {
|
||||
if (mPushedSomething) {
|
||||
NS_ERROR("Whaaa! No double pushing with nsCxPusher::Push()!");
|
||||
|
||||
return PR_FALSE;
|
||||
}
|
||||
|
||||
if (cx) {
|
||||
mScx = GetScriptContextFromJSContext(cx);
|
||||
if (!mScx) {
|
||||
// Should probably return PR_FALSE. See bug 416916.
|
||||
return PR_TRUE;
|
||||
}
|
||||
|
||||
nsIThreadJSContextStack* stack = nsContentUtils::ThreadJSContextStack();
|
||||
if (stack) {
|
||||
if (IsContextOnStack(stack, cx)) {
|
||||
// If the context is on the stack, that means that a script
|
||||
// is running at the moment in the context.
|
||||
mScriptIsRunning = PR_TRUE;
|
||||
}
|
||||
|
||||
stack->Push(cx);
|
||||
}
|
||||
if (!cx) {
|
||||
return PR_FALSE;
|
||||
}
|
||||
|
||||
// Hold a strong ref to the nsIScriptContext, just in case
|
||||
// XXXbz do we really need to? If we don't get one of these in Pop(), is
|
||||
// that really a problem? Or do we need to do this to effectively root |cx|?
|
||||
mScx = GetScriptContextFromJSContext(cx);
|
||||
if (!mScx) {
|
||||
// Should probably return PR_FALSE. See bug 416916.
|
||||
return PR_TRUE;
|
||||
}
|
||||
|
||||
return DoPush(cx);
|
||||
}
|
||||
|
||||
PRBool
|
||||
nsCxPusher::DoPush(JSContext* cx)
|
||||
{
|
||||
nsIThreadJSContextStack* stack = nsContentUtils::ThreadJSContextStack();
|
||||
if (!stack) {
|
||||
return PR_TRUE;
|
||||
}
|
||||
|
||||
if (cx && IsContextOnStack(stack, cx)) {
|
||||
// If the context is on the stack, that means that a script
|
||||
// is running at the moment in the context.
|
||||
mScriptIsRunning = PR_TRUE;
|
||||
}
|
||||
|
||||
if (NS_FAILED(stack->Push(cx))) {
|
||||
mScriptIsRunning = PR_FALSE;
|
||||
mScx = nsnull;
|
||||
return PR_FALSE;
|
||||
}
|
||||
|
||||
mPushedSomething = PR_TRUE;
|
||||
#ifdef DEBUG
|
||||
mPushedContext = cx;
|
||||
#endif
|
||||
return PR_TRUE;
|
||||
}
|
||||
|
||||
PRBool
|
||||
nsCxPusher::PushNull()
|
||||
{
|
||||
return DoPush(nsnull);
|
||||
}
|
||||
|
||||
void
|
||||
nsCxPusher::Pop()
|
||||
{
|
||||
nsIThreadJSContextStack* stack = nsContentUtils::ThreadJSContextStack();
|
||||
if (!mScx || !stack) {
|
||||
if (!mPushedSomething || !stack) {
|
||||
mScx = nsnull;
|
||||
mPushedSomething = PR_FALSE;
|
||||
|
||||
NS_ASSERTION(!mScriptIsRunning, "Huh, this can't be happening, "
|
||||
"mScriptIsRunning can't be set here!");
|
||||
|
@ -2772,7 +2802,9 @@ nsCxPusher::Pop()
|
|||
JSContext *unused;
|
||||
stack->Pop(&unused);
|
||||
|
||||
if (!mScriptIsRunning) {
|
||||
NS_ASSERTION(unused == mPushedContext, "Unexpected context popped");
|
||||
|
||||
if (!mScriptIsRunning && mScx) {
|
||||
// No JS is running in the context, but executing the event handler might have
|
||||
// caused some JS to run. Tell the script context that it's done.
|
||||
|
||||
|
@ -2781,6 +2813,7 @@ nsCxPusher::Pop()
|
|||
|
||||
mScx = nsnull;
|
||||
mScriptIsRunning = PR_FALSE;
|
||||
mPushedSomething = PR_FALSE;
|
||||
}
|
||||
|
||||
static const char gPropertiesFiles[nsContentUtils::PropertiesFile_COUNT][56] = {
|
||||
|
|
|
@ -1386,6 +1386,11 @@ nsTextControlFrame::CalcIntrinsicSize(nsIRenderingContext* aRenderingContext,
|
|||
void
|
||||
nsTextControlFrame::DelayedEditorInit()
|
||||
{
|
||||
// Time to mess with our security context... See comments in GetValue()
|
||||
// for why this is needed.
|
||||
nsCxPusher pusher;
|
||||
pusher.PushNull();
|
||||
|
||||
InitEditor();
|
||||
// Notify the text listener we have focus and setup the caret etc (bug 446663).
|
||||
if (IsFocusedContent(PresContext(), GetContent())) {
|
||||
|
@ -2569,17 +2574,12 @@ nsTextControlFrame::GetValue(nsAString& aValue, PRBool aIgnoreWrap) const
|
|||
// XXXbz if we could just get the textContent of our anonymous content (eg
|
||||
// if plaintext editor didn't create <br> nodes all over), we wouldn't need
|
||||
// this.
|
||||
nsCOMPtr<nsIJSContextStack> stack =
|
||||
do_GetService("@mozilla.org/js/xpc/ContextStack;1");
|
||||
PRBool pushed = stack && NS_SUCCEEDED(stack->Push(nsnull));
|
||||
{ /* Scope for context pusher */
|
||||
nsCxPusher pusher;
|
||||
pusher.PushNull();
|
||||
|
||||
rv = mEditor->OutputToString(NS_LITERAL_STRING("text/plain"), flags,
|
||||
aValue);
|
||||
|
||||
if (pushed) {
|
||||
JSContext* cx;
|
||||
stack->Pop(&cx);
|
||||
NS_ASSERTION(!cx, "Unexpected JSContext popped!");
|
||||
rv = mEditor->OutputToString(NS_LITERAL_STRING("text/plain"), flags,
|
||||
aValue);
|
||||
}
|
||||
}
|
||||
else
|
||||
|
@ -2647,77 +2647,69 @@ nsTextControlFrame::SetValue(const nsAString& aValue)
|
|||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
NS_ENSURE_STATE(domDoc);
|
||||
|
||||
PRBool outerTransaction;
|
||||
// Time to mess with our security context... See comments in GetValue()
|
||||
// for why this is needed. Note that we have to do this up here, because
|
||||
// otherwise SelectAll() will fail.
|
||||
nsCOMPtr<nsIJSContextStack> stack =
|
||||
do_GetService("@mozilla.org/js/xpc/ContextStack;1");
|
||||
PRBool pushed = stack && NS_SUCCEEDED(stack->Push(nsnull));
|
||||
{ /* Scope for context pusher */
|
||||
nsCxPusher pusher;
|
||||
pusher.PushNull();
|
||||
|
||||
nsCOMPtr<nsISelection> domSel;
|
||||
nsCOMPtr<nsISelectionPrivate> selPriv;
|
||||
mSelCon->GetSelection(nsISelectionController::SELECTION_NORMAL, getter_AddRefs(domSel));
|
||||
if (domSel)
|
||||
{
|
||||
selPriv = do_QueryInterface(domSel);
|
||||
if (selPriv)
|
||||
selPriv->StartBatchChanges();
|
||||
}
|
||||
|
||||
nsCOMPtr<nsISelectionController> kungFuDeathGrip = mSelCon;
|
||||
mSelCon->SelectAll();
|
||||
nsCOMPtr<nsIPlaintextEditor> plaintextEditor = do_QueryInterface(editor);
|
||||
if (!plaintextEditor || !weakFrame.IsAlive()) {
|
||||
NS_WARNING("Somehow not a plaintext editor?");
|
||||
if (pushed) {
|
||||
JSContext* cx;
|
||||
stack->Pop(&cx);
|
||||
NS_ASSERTION(!cx, "Unexpected JSContext popped!");
|
||||
nsCOMPtr<nsISelection> domSel;
|
||||
nsCOMPtr<nsISelectionPrivate> selPriv;
|
||||
mSelCon->GetSelection(nsISelectionController::SELECTION_NORMAL,
|
||||
getter_AddRefs(domSel));
|
||||
if (domSel)
|
||||
{
|
||||
selPriv = do_QueryInterface(domSel);
|
||||
if (selPriv)
|
||||
selPriv->StartBatchChanges();
|
||||
}
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
// Since this code does not handle user-generated changes to the text,
|
||||
// make sure we don't fire oninput when the editor notifies us.
|
||||
// (mNotifyOnInput must be reset before we return).
|
||||
nsCOMPtr<nsISelectionController> kungFuDeathGrip = mSelCon;
|
||||
mSelCon->SelectAll();
|
||||
nsCOMPtr<nsIPlaintextEditor> plaintextEditor = do_QueryInterface(editor);
|
||||
if (!plaintextEditor || !weakFrame.IsAlive()) {
|
||||
NS_WARNING("Somehow not a plaintext editor?");
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
// To protect against a reentrant call to SetValue, we check whether
|
||||
// another SetValue is already happening for this frame. If it is,
|
||||
// we must wait until we unwind to re-enable oninput events.
|
||||
PRBool outerTransaction = mNotifyOnInput;
|
||||
if (outerTransaction)
|
||||
mNotifyOnInput = PR_FALSE;
|
||||
// Since this code does not handle user-generated changes to the text,
|
||||
// make sure we don't fire oninput when the editor notifies us.
|
||||
// (mNotifyOnInput must be reset before we return).
|
||||
|
||||
// get the flags, remove readonly and disabled, set the value,
|
||||
// restore flags
|
||||
PRUint32 flags, savedFlags;
|
||||
editor->GetFlags(&savedFlags);
|
||||
flags = savedFlags;
|
||||
flags &= ~(nsIPlaintextEditor::eEditorDisabledMask);
|
||||
flags &= ~(nsIPlaintextEditor::eEditorReadonlyMask);
|
||||
editor->SetFlags(flags);
|
||||
// To protect against a reentrant call to SetValue, we check whether
|
||||
// another SetValue is already happening for this frame. If it is,
|
||||
// we must wait until we unwind to re-enable oninput events.
|
||||
outerTransaction = mNotifyOnInput;
|
||||
if (outerTransaction)
|
||||
mNotifyOnInput = PR_FALSE;
|
||||
|
||||
// Also don't enforce max-length here
|
||||
PRInt32 savedMaxLength;
|
||||
plaintextEditor->GetMaxTextLength(&savedMaxLength);
|
||||
plaintextEditor->SetMaxTextLength(-1);
|
||||
// get the flags, remove readonly and disabled, set the value,
|
||||
// restore flags
|
||||
PRUint32 flags, savedFlags;
|
||||
editor->GetFlags(&savedFlags);
|
||||
flags = savedFlags;
|
||||
flags &= ~(nsIPlaintextEditor::eEditorDisabledMask);
|
||||
flags &= ~(nsIPlaintextEditor::eEditorReadonlyMask);
|
||||
editor->SetFlags(flags);
|
||||
|
||||
if (currentValue.Length() < 1)
|
||||
editor->DeleteSelection(nsIEditor::eNone);
|
||||
else {
|
||||
if (plaintextEditor)
|
||||
plaintextEditor->InsertText(currentValue);
|
||||
}
|
||||
// Also don't enforce max-length here
|
||||
PRInt32 savedMaxLength;
|
||||
plaintextEditor->GetMaxTextLength(&savedMaxLength);
|
||||
plaintextEditor->SetMaxTextLength(-1);
|
||||
|
||||
plaintextEditor->SetMaxTextLength(savedMaxLength);
|
||||
editor->SetFlags(savedFlags);
|
||||
if (selPriv)
|
||||
selPriv->EndBatchChanges();
|
||||
if (currentValue.Length() < 1)
|
||||
editor->DeleteSelection(nsIEditor::eNone);
|
||||
else {
|
||||
if (plaintextEditor)
|
||||
plaintextEditor->InsertText(currentValue);
|
||||
}
|
||||
|
||||
if (pushed) {
|
||||
JSContext* cx;
|
||||
stack->Pop(&cx);
|
||||
NS_ASSERTION(!cx, "Unexpected JSContext popped!");
|
||||
plaintextEditor->SetMaxTextLength(savedMaxLength);
|
||||
editor->SetFlags(savedFlags);
|
||||
if (selPriv)
|
||||
selPriv->EndBatchChanges();
|
||||
}
|
||||
|
||||
NS_ENSURE_STATE(weakFrame.IsAlive());
|
||||
|
|
|
@ -44,13 +44,17 @@ relativesrcdir = layout/forms/test
|
|||
include $(DEPTH)/config/autoconf.mk
|
||||
include $(topsrcdir)/config/rules.mk
|
||||
|
||||
_TEST_FILES = test_bug345267.html \
|
||||
_TEST_FILES = test_bug287446.html \
|
||||
bug287446_subframe.html \
|
||||
test_bug345267.html \
|
||||
test_bug348236.html \
|
||||
test_bug402198.html \
|
||||
test_bug411236.html \
|
||||
test_bug446663.html \
|
||||
test_bug476308.html \
|
||||
test_bug477531.html \
|
||||
test_bug477700.html \
|
||||
bug477700_subframe.html \
|
||||
$(NULL)
|
||||
|
||||
libs:: $(_TEST_FILES)
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
|
||||
<script>
|
||||
function doIs(arg1, arg2, arg3) {
|
||||
window.parent.postMessage("t " + encodeURIComponent(arg1) + " " +
|
||||
encodeURIComponent(arg2) + " " +
|
||||
encodeURIComponent(arg3), "*");
|
||||
}
|
||||
|
||||
function $(arg) { return document.getElementById(arg); }
|
||||
|
||||
window.addEventListener("message",
|
||||
function(evt) {
|
||||
var t = $("target");
|
||||
if (evt.data == "start") {
|
||||
doIs(t.value, "Test", "Shouldn't have lost our initial value");
|
||||
t.focus();
|
||||
sendString("Foo");
|
||||
doIs(t.value, "TestFoo", "Typing should work");
|
||||
window.parent.postMessage("c", "*");
|
||||
} else {
|
||||
doIs(evt.data, "continue", "Unexpected message");
|
||||
doIs(t.value, "TestFoo", "Shouldn't have lost our typed value");
|
||||
sendString("Bar");
|
||||
doIs(t.value, "TestFooBar", "Typing should still work");
|
||||
window.parent.postMessage("f", "*");
|
||||
}
|
||||
},
|
||||
"false");
|
||||
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<input id="target" value="Test">
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
|
||||
<script>
|
||||
function doIs(arg1, arg2, arg3) {
|
||||
window.parent.postMessage("t " + encodeURIComponent(arg1) + " " +
|
||||
encodeURIComponent(arg2) + " " +
|
||||
encodeURIComponent(arg3), "*");
|
||||
}
|
||||
|
||||
function $(arg) { return document.getElementById(arg); }
|
||||
|
||||
window.addEventListener("message",
|
||||
function(evt) {
|
||||
doIs(evt.data, "start", "Unexpected message");
|
||||
sendString("Test");
|
||||
var t = $("target");
|
||||
doIs(t.value, "Test", "Typing should work");
|
||||
(function() {
|
||||
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
|
||||
t.QueryInterface(Components.interfaces.nsIDOMNSEditableElement).editor.undo(1);
|
||||
})()
|
||||
doIs(t.value, "", "Undo should work");
|
||||
(function() {
|
||||
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
|
||||
t.QueryInterface(Components.interfaces.nsIDOMNSEditableElement).editor.redo(1);
|
||||
})()
|
||||
doIs(t.value, "Test", "Redo should work");
|
||||
window.parent.postMessage("f", "*");
|
||||
},
|
||||
"false");
|
||||
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<input id="target">
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=287446
|
||||
-->
|
||||
<head>
|
||||
<title>Test for Bug 287446</title>
|
||||
<script type="application/javascript" src="/MochiKit/MochiKit.js"></script>
|
||||
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.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=287446">Mozilla Bug 287446</a>
|
||||
<p id="display">
|
||||
<iframe id="i"
|
||||
src="http://example.com/tests/layout/forms/test/bug287446_subframe.html"></iframe>
|
||||
</p>
|
||||
<div id="content" style="display: none">
|
||||
|
||||
</div>
|
||||
<pre id="test">
|
||||
<script type="application/javascript">
|
||||
|
||||
/** Test for Bug 287446 **/
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
addLoadEvent(function() {
|
||||
isnot(window.location.host, "example.com", "test is not testing cross-site");
|
||||
var accessed = false;
|
||||
try {
|
||||
$("i").contentDocument.documentElement;
|
||||
accessed = true;
|
||||
} catch(e) {}
|
||||
is(accessed, false, "Shouldn't be able to access cross-site");
|
||||
|
||||
$("i").style.display = "none";
|
||||
document.body.offsetWidth;
|
||||
is(document.defaultView.getComputedStyle($("i"), "").display, "none",
|
||||
"toggling display failed");
|
||||
$("i").style.display = "";
|
||||
document.body.offsetWidth;
|
||||
is(document.defaultView.getComputedStyle($("i"), "").display, "inline",
|
||||
"toggling display back failed");
|
||||
|
||||
$("i").contentWindow.postMessage("start", "*");
|
||||
});
|
||||
|
||||
function continueTest() {
|
||||
dump('aaa');
|
||||
$("i").style.display = "none";
|
||||
document.body.offsetWidth;
|
||||
is(document.defaultView.getComputedStyle($("i"), "").display, "none",
|
||||
"toggling display second time failed");
|
||||
$("i").style.display = "";
|
||||
document.body.offsetWidth;
|
||||
is(document.defaultView.getComputedStyle($("i"), "").display, "inline",
|
||||
"toggling display back second time failed");
|
||||
|
||||
$("i").contentWindow.postMessage("continue", "*");
|
||||
}
|
||||
|
||||
window.addEventListener("message",
|
||||
function(evt) {
|
||||
var arr = evt.data.split(/ /).map(decodeURIComponent);
|
||||
if (arr[0] == 't') {
|
||||
is(arr[1], arr[2], arr[3]);
|
||||
} else if (arr[0] == 'c') {
|
||||
continueTest();
|
||||
} else if (arr[0] == 'f') {
|
||||
SimpleTest.finish();
|
||||
}
|
||||
},
|
||||
false);
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,61 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=477700
|
||||
-->
|
||||
<head>
|
||||
<title>Test for Bug 477700</title>
|
||||
<script type="application/javascript" src="/MochiKit/MochiKit.js"></script>
|
||||
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.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=477700">Mozilla Bug 477700</a>
|
||||
<p id="display">
|
||||
<iframe id="i"
|
||||
src="http://example.com/tests/layout/forms/test/bug477700_subframe.html"></iframe>
|
||||
</p>
|
||||
<div id="content" style="display: none">
|
||||
|
||||
</div>
|
||||
<pre id="test">
|
||||
<script type="application/javascript">
|
||||
|
||||
/** Test for Bug 477700 **/
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
addLoadEvent(function() {
|
||||
isnot(window.location.host, "example.com", "test is not testing cross-site");
|
||||
var accessed = false;
|
||||
try {
|
||||
$("i").contentDocument.documentElement;
|
||||
accessed = true;
|
||||
} catch(e) {}
|
||||
is(accessed, false, "Shouldn't be able to access cross-site");
|
||||
|
||||
$("i").style.display = "none";
|
||||
document.body.offsetWidth;
|
||||
is(document.defaultView.getComputedStyle($("i"), "").display, "none",
|
||||
"toggling display failed");
|
||||
$("i").style.display = "";
|
||||
document.body.offsetWidth;
|
||||
is(document.defaultView.getComputedStyle($("i"), "").display, "inline",
|
||||
"toggling display back failed");
|
||||
|
||||
$("i").contentWindow.postMessage("start", "*");
|
||||
});
|
||||
|
||||
window.addEventListener("message",
|
||||
function(evt) {
|
||||
var arr = evt.data.split(/ /).map(decodeURIComponent);
|
||||
if (arr[0] == 't') {
|
||||
is(arr[1], arr[2], arr[3]);
|
||||
} else if (arr[0] == 'f') {
|
||||
SimpleTest.finish();
|
||||
}
|
||||
},
|
||||
false);
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
Загрузка…
Ссылка в новой задаче