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:
Boris Zbarsky 2009-02-16 09:11:41 -05:00
parent ac1ce67cea
commit a2d0b2c43b
8 changed files with 351 additions and 92 deletions

View File

@ -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 {

View File

@ -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] = {

View File

@ -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());

View File

@ -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)

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>