Fix bug 277724 -- make <select>, <textarea>, <button> restore disabled state. r+sr=jst

This commit is contained in:
bzbarsky%mit.edu 2006-11-28 03:17:03 +00:00
parent 195cfecbbe
commit 91f9d8f94c
8 changed files with 366 additions and 11 deletions

View File

@ -58,6 +58,8 @@
#include "nsUnicharUtils.h"
#include "nsLayoutUtils.h"
#include "nsEventDispatcher.h"
#include "nsPresState.h"
#include "nsLayoutErrors.h"
#define NS_IN_SUBMIT_CLICK (1 << 0)
@ -92,12 +94,20 @@ public:
NS_IMETHOD Click();
NS_IMETHOD SetType(const nsAString& aType);
// overrided nsIFormControl method
// overriden nsIFormControl methods
NS_IMETHOD_(PRInt32) GetType() const { return mType; }
NS_IMETHOD Reset();
NS_IMETHOD SubmitNamesValues(nsIFormSubmission* aFormSubmission,
nsIContent* aSubmitElement);
NS_IMETHOD SaveState();
PRBool RestoreState(nsPresState* aState);
/**
* Called when an attribute is about to be changed
*/
virtual nsresult BeforeSetAttr(PRInt32 aNameSpaceID, nsIAtom* aName,
const nsAString* aValue, PRBool aNotify);
// nsIContent overrides...
virtual void SetFocus(nsPresContext* aPresContext);
virtual PRBool IsFocusable(PRInt32 *aTabIndex = nsnull);
@ -109,10 +119,12 @@ public:
virtual nsresult PostHandleEvent(nsEventChainPostVisitor& aVisitor);
virtual nsresult Clone(nsINodeInfo *aNodeInfo, nsINode **aResult) const;
virtual void DoneCreatingElement();
protected:
PRInt8 mType;
PRPackedBool mHandlingClick;
PRPackedBool mDisabledChanged;
private:
// The analogue of defaultValue in the DOM for input and textarea
@ -128,10 +140,11 @@ NS_IMPL_NS_NEW_HTML_ELEMENT(Button)
nsHTMLButtonElement::nsHTMLButtonElement(nsINodeInfo *aNodeInfo)
: nsGenericHTMLFormElement(aNodeInfo)
: nsGenericHTMLFormElement(aNodeInfo),
mType(NS_FORM_BUTTON_SUBMIT), // default
mHandlingClick(PR_FALSE),
mDisabledChanged(PR_FALSE)
{
mType = NS_FORM_BUTTON_SUBMIT; // default
mHandlingClick = PR_FALSE;
}
nsHTMLButtonElement::~nsHTMLButtonElement()
@ -565,3 +578,62 @@ nsHTMLButtonElement::SubmitNamesValues(nsIFormSubmission* aFormSubmission,
return rv;
}
void
nsHTMLButtonElement::DoneCreatingElement()
{
// Restore state as needed.
RestoreFormControlState(this, this);
}
nsresult
nsHTMLButtonElement::BeforeSetAttr(PRInt32 aNameSpaceID, nsIAtom* aName,
const nsAString* aValue, PRBool aNotify)
{
if (aNotify && aName == nsHTMLAtoms::disabled &&
aNameSpaceID == kNameSpaceID_None) {
mDisabledChanged = PR_TRUE;
}
return nsGenericHTMLFormElement::BeforeSetAttr(aNameSpaceID, aName,
aValue, aNotify);
}
NS_IMETHODIMP
nsHTMLButtonElement::SaveState()
{
if (!mDisabledChanged) {
return NS_OK;
}
nsPresState *state = nsnull;
nsresult rv = GetPrimaryPresState(this, &state);
if (state) {
PRBool disabled;
GetDisabled(&disabled);
if (disabled) {
rv |= state->SetStateProperty(NS_LITERAL_STRING("disabled"),
NS_LITERAL_STRING("t"));
} else {
rv |= state->SetStateProperty(NS_LITERAL_STRING("disabled"),
NS_LITERAL_STRING("f"));
}
NS_ASSERTION(NS_SUCCEEDED(rv), "disabled save failed!");
}
return rv;
}
PRBool
nsHTMLButtonElement::RestoreState(nsPresState* aState)
{
nsAutoString disabled;
nsresult rv =
aState->GetStateProperty(NS_LITERAL_STRING("disabled"), disabled);
NS_ASSERTION(NS_SUCCEEDED(rv), "disabled restore failed!");
if (rv == NS_STATE_PROPERTY_EXISTS) {
SetDisabled(disabled.EqualsLiteral("t"));
}
return PR_FALSE;
}

View File

@ -73,6 +73,7 @@
#include "nsPresState.h"
#include "nsIComponentManager.h"
#include "nsCheapSets.h"
#include "nsLayoutErrors.h"
// Notify/query select frame for selectedIndex
#include "nsIDocument.h"
@ -268,6 +269,11 @@ public:
// nsISelectElement
NS_DECL_NSISELECTELEMENT
/**
* Called when an attribute is about to be changed
*/
virtual nsresult BeforeSetAttr(PRInt32 aNameSpaceID, nsIAtom* aName,
const nsAString* aValue, PRBool aNotify);
virtual nsresult UnsetAttr(PRInt32 aNameSpaceID, nsIAtom* aAttribute,
PRBool aNotify);
@ -455,7 +461,9 @@ protected:
/** The options[] array */
nsRefPtr<nsHTMLOptionCollection> mOptions;
/** false if the parser is in the middle of adding children. */
PRBool mIsDoneAddingChildren;
PRPackedBool mIsDoneAddingChildren;
/** true if our disabled state has changed from the default **/
PRPackedBool mDisabledChanged;
/** The number of non-options as children of the select */
PRUint32 mNonOptionChildren;
/** The number of optgroups anywhere under the select */
@ -488,6 +496,7 @@ nsHTMLSelectElement::nsHTMLSelectElement(nsINodeInfo *aNodeInfo,
: nsGenericHTMLFormElement(aNodeInfo),
mOptions(new nsHTMLOptionCollection(this)),
mIsDoneAddingChildren(!aFromParser),
mDisabledChanged(PR_FALSE),
mNonOptionChildren(0),
mOptGroupCount(0),
mSelectedIndex(-1)
@ -1677,6 +1686,19 @@ nsHTMLSelectElement::SelectSomething()
return PR_FALSE;
}
nsresult
nsHTMLSelectElement::BeforeSetAttr(PRInt32 aNameSpaceID, nsIAtom* aName,
const nsAString* aValue, PRBool aNotify)
{
if (aNotify && aName == nsHTMLAtoms::disabled &&
aNameSpaceID == kNameSpaceID_None) {
mDisabledChanged = PR_TRUE;
}
return nsGenericHTMLFormElement::BeforeSetAttr(aNameSpaceID, aName,
aValue, aNotify);
}
nsresult
nsHTMLSelectElement::UnsetAttr(PRInt32 aNameSpaceID, nsIAtom* aAttribute,
PRBool aNotify)
@ -1867,6 +1889,19 @@ nsHTMLSelectElement::SaveState()
rv = presState->SetStatePropertyAsSupports(NS_LITERAL_STRING("selecteditems"),
state);
NS_ASSERTION(NS_SUCCEEDED(rv), "selecteditems set failed!");
if (mDisabledChanged) {
PRBool disabled;
GetDisabled(&disabled);
if (disabled) {
rv |= presState->SetStateProperty(NS_LITERAL_STRING("disabled"),
NS_LITERAL_STRING("t"));
} else {
rv |= presState->SetStateProperty(NS_LITERAL_STRING("disabled"),
NS_LITERAL_STRING("f"));
}
NS_ASSERTION(NS_SUCCEEDED(rv), "disabled save failed!");
}
}
return rv;
@ -1887,6 +1922,13 @@ nsHTMLSelectElement::RestoreState(nsPresState* aState)
DispatchContentReset();
}
nsAutoString disabled;
rv = aState->GetStateProperty(NS_LITERAL_STRING("disabled"), disabled);
NS_ASSERTION(NS_SUCCEEDED(rv), "disabled restore failed!");
if (rv == NS_STATE_PROPERTY_EXISTS) {
SetDisabled(disabled.EqualsLiteral("t"));
}
return PR_FALSE;
}

View File

@ -72,6 +72,7 @@
#include "nsReadableUtils.h"
#include "nsEventDispatcher.h"
#include "nsLayoutUtils.h"
#include "nsLayoutErrors.h"
static NS_DEFINE_CID(kXULControllersCID, NS_XULCONTROLLERS_CID);
@ -143,6 +144,11 @@ public:
virtual nsresult Clone(nsINodeInfo *aNodeInfo, nsINode **aResult) const;
/**
* Called when an attribute is about to be changed
*/
virtual nsresult BeforeSetAttr(PRInt32 aNameSpaceID, nsIAtom* aName,
const nsAString* aValue, PRBool aNotify);
protected:
nsCOMPtr<nsIControllers> mControllers;
/** The current value. This is null if the frame owns the value. */
@ -154,7 +160,9 @@ protected:
/** Whether or not we are done adding children (always PR_TRUE if not
created by a parser */
PRPackedBool mDoneAddingChildren;
/** Whether our disabled state has changed from the default **/
PRPackedBool mDisabledChanged;
NS_IMETHOD SelectAll(nsPresContext* aPresContext);
/**
* Get the value, whether it is from the content or the frame.
@ -180,7 +188,8 @@ nsHTMLTextAreaElement::nsHTMLTextAreaElement(nsINodeInfo *aNodeInfo,
mValue(nsnull),
mValueChanged(PR_FALSE),
mHandlingSelect(PR_FALSE),
mDoneAddingChildren(!aFromParser)
mDoneAddingChildren(!aFromParser),
mDisabledChanged(PR_FALSE)
{
}
@ -852,8 +861,8 @@ nsHTMLTextAreaElement::SaveState()
nsresult rv = NS_OK;
// Only save if value != defaultValue (bug 62713)
nsPresState *state = nsnull;
if (mValueChanged) {
nsPresState *state = nsnull;
rv = GetPrimaryPresState(this, &state);
if (state) {
nsAutoString value;
@ -869,6 +878,23 @@ nsHTMLTextAreaElement::SaveState()
}
}
if (mDisabledChanged) {
if (!state) {
rv = GetPrimaryPresState(this, &state);
}
if (state) {
PRBool disabled;
GetDisabled(&disabled);
if (disabled) {
rv |= state->SetStateProperty(NS_LITERAL_STRING("disabled"),
NS_LITERAL_STRING("t"));
} else {
rv |= state->SetStateProperty(NS_LITERAL_STRING("disabled"),
NS_LITERAL_STRING("f"));
}
NS_ASSERTION(NS_SUCCEEDED(rv), "disabled save failed!");
}
}
return rv;
}
@ -876,12 +902,30 @@ PRBool
nsHTMLTextAreaElement::RestoreState(nsPresState* aState)
{
nsAutoString value;
#ifdef DEBUG
nsresult rv =
#endif
aState->GetStateProperty(NS_LITERAL_STRING("value"), value);
NS_ASSERTION(NS_SUCCEEDED(rv), "value restore failed!");
SetValue(value);
nsAutoString disabled;
rv = aState->GetStateProperty(NS_LITERAL_STRING("disabled"), disabled);
NS_ASSERTION(NS_SUCCEEDED(rv), "disabled restore failed!");
if (rv == NS_STATE_PROPERTY_EXISTS) {
SetDisabled(disabled.EqualsLiteral("t"));
}
return PR_FALSE;
}
nsresult
nsHTMLTextAreaElement::BeforeSetAttr(PRInt32 aNameSpaceID, nsIAtom* aName,
const nsAString* aValue, PRBool aNotify)
{
if (aNotify && aName == nsHTMLAtoms::disabled &&
aNameSpaceID == kNameSpaceID_None) {
mDisabledChanged = PR_TRUE;
}
return nsGenericHTMLFormElement::BeforeSetAttr(aNameSpaceID, aName,
aValue, aNotify);
}

View File

@ -1126,6 +1126,10 @@ SinkContext::OpenContainer(const nsIParserNode& aNode)
}
break;
case eHTMLTag_button:
content->DoneCreatingElement();
break;
default:
break;
}
@ -1349,7 +1353,6 @@ SinkContext::AddLeaf(const nsIParserNode& aNode)
break;
case eHTMLTag_input:
case eHTMLTag_button:
content->DoneCreatingElement();
break;

View File

@ -0,0 +1,28 @@
<!DOCTYPE HTML>
<html>
<!-- Use an unload handler to prevent bfcache from messing with us -->
<body onunload="parent.childUnloaded = true;">
<select id="select">
<option>aaa</option>
<option>bbbb</option>
</select>
<textarea id="textarea">
</textarea>
<input type="text" id="text">
<input type="password" id="password">
<input type="checkbox" id="checkbox">
<input type="radio" id="radio">
<input type="image" id="image">
<input type="submit" id="submit">
<input type="reset" id="reset">
<input type="button" id="button input">
<input type="hidden" id="hidden">
<input type="file" id="file">
<button type="submit" id="submit button"></button>
<button type="reset" id="reset button"></button>
<button type="button" id="button"></button>
</body>
</html>

View File

@ -0,0 +1,27 @@
<html xmlns="http://www.w3.org/1999/xhtml">
<!-- Use an unload handler to prevent bfcache from messing with us -->
<body onunload="parent.childUnloaded = true;">
<select id="select">
<option>aaa</option>
<option>bbbb</option>
</select>
<textarea id="textarea">
</textarea>
<input type="text" id="text" />
<input type="password" id="password" />
<input type="checkbox" id="checkbox" />
<input type="radio" id="radio" />
<input type="image" id="image" />
<input type="submit" id="submit" />
<input type="reset" id="reset" />
<input type="button" id="button input" />
<input type="hidden" id="hidden" />
<input type="file" id="file" />
<button type="submit" id="submit button"></button>
<button type="reset" id="reset button"></button>
<button type="button" id="button"></button>
</body>
</html>

View File

@ -62,6 +62,7 @@ RunSet.runall = function() {
'test_bug100533.html',
'test_bug218277.html',
'test_bug237071.html',
'test_bug277724.html',
'test_bug302186.html',
'test_bug308856.html',
'test_bug333983.html',

View File

@ -0,0 +1,138 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=277724
-->
<head>
<title>Test for Bug 277724</title>
<script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
<script type="text/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=277724">Mozilla Bug 277724</a>
<p id="display"></p>
<pre id="test">
<script class="testbody" type="text/javascript">
/** Test for Bug 277724 **/
var childUnloaded = false;
var nodes = [
[ "select", HTMLSelectElement ],
[ "textarea", HTMLTextAreaElement ],
[ "text", HTMLInputElement ],
[ "password", HTMLInputElement ],
[ "checkbox", HTMLInputElement ],
[ "radio", HTMLInputElement ],
[ "image", HTMLInputElement ],
[ "submit", HTMLInputElement ],
[ "reset", HTMLInputElement ],
[ "button input", HTMLInputElement ],
[ "hidden", HTMLInputElement ],
[ "file", HTMLInputElement ],
[ "submit button", HTMLButtonElement ],
[ "reset button", HTMLButtonElement ],
[ "button", HTMLButtonElement ]
];
function startTest(frameid) {
is(childUnloaded, false, "Child not unloaded yet");
var doc = $(frameid).contentDocument;
ok(doc instanceof Document, "Check for doc", "doc should be a document");
for (var i = 0; i < nodes.length; ++i) {
var id = nodes[i][0];
var node = doc.getElementById(id);
ok(node instanceof nodes[i][1],
"Check for " + id, id + " should be a " + nodes[i][1]);
is(node.disabled, false, "check for " + id + " state");
node.disabled = true;
is(node.disabled, true, "check for " + id + " state change");
}
$(frameid).onload = function () { continueTest(frameid) };
// Do this off a timeout so it's not treated like a replace load.
function loadBlank() {
$(frameid).contentWindow.location = "about:blank";
}
setTimeout(loadBlank, 0);
}
function continueTest(frameid) {
is(childUnloaded, true, "Unload handler should have fired");
var doc = $(frameid).contentDocument;
ok(doc instanceof Document, "Check for doc", "doc should be a document");
for (var i = 0; i < nodes.length; ++i) {
var id = nodes[i][0];
var node = doc.getElementById(id);
ok(node === null,
"Check for " + id, id + " should be null");
}
$(frameid).onload = function() { finishTest(frameid) };
// Do this off a timeout too. Why, I'm not sure. Something in session
// history creates another history state if we don't. :(
function goBack() {
$(frameid).contentWindow.history.back();
}
setTimeout(goBack, 0);
}
// XXXbz this is a nasty hack to work around the XML content sink not being
// incremental, so that the _first_ control we test is ok but others are not.
var testIs = is;
var once = false;
function flipper(a, b, c) {
if (once) {
todo(a == b, c);
} else {
once = true;
is(a, b, c);
}
}
function finishTest(frameid) {
var doc = $(frameid).contentDocument;
ok(doc instanceof Document, "Check for doc", "doc should be a document");
for (var i = 0; i < nodes.length; ++i) {
var id = nodes[i][0];
var node = doc.getElementById(id);
ok(node instanceof nodes[i][1],
"Check for " + id, id + " should be a " + nodes[i][1]);
testIs(node.disabled, true, "check for " + id + " state restore");
}
if (frameid == "frame2") {
SimpleTest.finish();
} else {
childUnloaded = false;
// XXXbz this is a nasty hack to deal with the content sink. See above.
testIs = flipper;
$("frame2").onload = function () { startTest("frame2") };
$("frame2").src = "/static/bug277724_iframe2.xhtml";
}
}
SimpleTest.waitForExplicitFinish();
</script>
</pre>
<!-- Don't use display:none, since we don't support framestate restoration
without a frame tree -->
<div id="content" style="visibility: hidden">
<iframe src="/static/bug277724_iframe1.html" id="frame1"
onload="startTest('frame1')"></iframe>
<iframe src="" id="frame2"></iframe>
</div>
</body>
</html>