Bug 518122, improve textarea.value+= performance, r=bz

This commit is contained in:
Olli Pettay 2010-02-07 21:44:32 +02:00
Родитель 082e12c87c
Коммит a229f20863
5 изменённых файлов: 255 добавлений и 33 удалений

Просмотреть файл

@ -799,6 +799,9 @@ nsHTMLTextAreaElement::Reset()
// If the frame is there, we have to set the value so that it will show up.
nsIFormControlFrame* formControlFrame = GetFormControlFrame(PR_FALSE);
if (formControlFrame) {
// To get the initial spellchecking, reset value to
// empty string before setting the default value.
SetValue(EmptyString());
nsAutoString resetVal;
GetDefaultValue(resetVal);
rv = SetValue(resetVal);

Просмотреть файл

@ -140,6 +140,7 @@ _TEST_FILES = test_bug589.html \
test_bug500885.html \
test_bug514856.html \
bug514856_iframe.html \
test_bug518122.html \
test_bug519987.html \
test_bug523771.html \
form_submit_server.sjs \

Просмотреть файл

@ -0,0 +1,122 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=518122
-->
<head>
<title>Test for Bug 518122</title>
<script type="application/javascript" src="/MochiKit/packed.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 onload="runTests()">
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=518122">Mozilla Bug 518122</a>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<pre id="test">
<script type="application/javascript">
/** Test for Bug 518122 **/
SimpleTest.waitForExplicitFinish();
var simple_tests = [ ["foo", "foo"],
["", ""],
[null, ""],
[undefined , "undefined"],
["\n", "\n"],
["\r", "\n"],
["\rfoo", "\nfoo"],
["foo\r", "foo\n"],
["foo\rbar", "foo\nbar"],
["foo\rbar\r", "foo\nbar\n"],
["\r\n", "\n"],
["\r\nfoo", "\nfoo"],
["foo\r\n", "foo\n"],
["foo\r\nbar", "foo\nbar"],
["foo\r\nbar\r\n", "foo\nbar\n"] ];
var value_append_tests = [ ["foo", "bar", "foobar"],
["foo", "foo", "foofoo"],
["foobar", "bar", "foobarbar"],
["foobar", "foo", "foobarfoo"],
["foo\n", "foo", "foo\nfoo"],
["foo\r", "foo", "foo\nfoo"],
["foo\r\n", "foo", "foo\nfoo"],
["\n", "\n", "\n\n"],
["\r", "\r", "\n\n"],
["\r\n", "\r\n", "\n\n"],
["\r", "\r\n", "\n\n"],
["\r\n", "\r", "\n\n"],
[null, null, "null"],
[null, undefined, "undefined"],
["", "", ""]
];
var simple_tests_for_input = [ ["foo", "foo"],
["", ""],
[null, ""],
[undefined , "undefined"],
["\n", ""],
["\r", ""],
["\rfoo", " foo"],
["foo\r", "foo"],
["foo\rbar", "foo bar"],
["foo\rbar\r", "foo bar"],
["\r\n", ""],
["\r\nfoo", " foo"],
["foo\r\n", "foo"],
["foo\r\nbar", "foo bar"],
["foo\r\nbar\r\n", "foo bar"] ];
var value_append_tests_for_input = [ ["foo", "bar", "foobar"],
["foo", "foo", "foofoo"],
["foobar", "bar", "foobarbar"],
["foobar", "foo", "foobarfoo"],
["foo\n", "foo", "foofoo"],
["foo\r", "foo", "foofoo"],
["foo\r\n", "foo", "foofoo"],
["\n", "\n", ""],
["\r", "\r", ""],
["\r\n", "\r\n", ""],
["\r", "\r\n", ""],
["\r\n", "\r", ""],
[null, null, "null"],
[null, undefined, "undefined"],
["", "", ""]
];
function runTestsFor(el, simpleTests, appendTests) {
for(var i = 0; i < simpleTests.length; ++i) {
el.value = simpleTests[i][0];
is(el.value, simpleTests[i][1], "Wrong value (wrap=" + el.getAttribute('wrap') + ", simple_test=" + i + ")");
}
for (var j = 0; j < appendTests.length; ++j) {
el.value = appendTests[j][0];
el.value += appendTests[j][1];
is(el.value, appendTests[j][2], "Wrong value (wrap=" + el.getAttribute('wrap') + ", value_append_test=" + j + ")");
}
}
function runTests() {
var textareas = document.getElementsByTagName("textarea");
for (var i = 0; i < textareas.length; ++i) {
runTestsFor(textareas[i], simple_tests, value_append_tests);
}
runTestsFor(document.getElementsByTagName("input")[0],
simple_tests_for_input, value_append_tests_for_input);
SimpleTest.finish();
}
</script>
</pre>
<textarea cols="30" rows="7" wrap="none"></textarea>
<textarea cols="30" rows="7" wrap="off"></textarea><br>
<textarea cols="30" rows="7" wrap="soft"></textarea>
<textarea cols="30" rows="7" wrap="hard"></textarea>
<input type="text">
</body>
</html>

Просмотреть файл

@ -1099,6 +1099,9 @@ nsTextControlFrame::DestroyFrom(nsIFrame* aDestructRoot)
if (!mDidPreDestroy) {
PreDestroy();
}
if (mAnonymousDiv && mMutationObserver) {
mAnonymousDiv->RemoveMutationObserver(mMutationObserver);
}
nsContentUtils::DestroyAnonymousContent(&mAnonymousDiv);
nsBoxFrame::DestroyFrom(aDestructRoot);
}
@ -1605,6 +1608,10 @@ nsTextControlFrame::CreateAnonymousContent(nsTArray<nsIContent*>& aElements)
disp->mOverflowX != NS_STYLE_OVERFLOW_CLIP) {
classValue.AppendLiteral(" inherit-overflow");
}
mMutationObserver = new nsAnonDivObserver(this);
NS_ENSURE_TRUE(mMutationObserver, NS_ERROR_OUT_OF_MEMORY);
mAnonymousDiv->AddMutationObserver(mMutationObserver);
}
rv = mAnonymousDiv->SetAttr(kNameSpaceID_None, nsGkAtoms::_class,
classValue, PR_FALSE);
@ -1882,7 +1889,7 @@ nsresult nsTextControlFrame::SetFormProperty(nsIAtom* aName, const nsAString& aV
// of select all which merely builds a range that selects
// all of the content and adds that to the selection.
SelectAllContents();
SelectAllOrCollapseToEndOfText(PR_TRUE);
}
mIsProcessing = PR_FALSE;
}
@ -1964,7 +1971,7 @@ nsTextControlFrame::SetSelectionInternal(nsIDOMNode *aStartNode,
}
nsresult
nsTextControlFrame::SelectAllContents()
nsTextControlFrame::SelectAllOrCollapseToEndOfText(PRBool aSelect)
{
if (!mEditor)
return NS_OK;
@ -1974,6 +1981,7 @@ nsTextControlFrame::SelectAllContents()
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIContent> rootContent = do_QueryInterface(rootElement);
nsCOMPtr<nsIDOMNode> rootNode(do_QueryInterface(rootElement));
PRInt32 numChildren = rootContent->GetChildCount();
if (numChildren > 0) {
@ -1984,11 +1992,18 @@ nsTextControlFrame::SelectAllContents()
if (child->Tag() == nsGkAtoms::br)
--numChildren;
}
if (!aSelect && numChildren) {
child = rootContent->GetChildAt(numChildren - 1);
if (child && child->IsNodeOfType(nsINode::eTEXT)) {
rootNode = do_QueryInterface(child);
const nsTextFragment* fragment = child->GetText();
numChildren = fragment ? fragment->GetLength() : 0;
}
}
}
nsCOMPtr<nsIDOMNode> rootNode(do_QueryInterface(rootElement));
return SetSelectionInternal(rootNode, 0, rootNode, numChildren);
return SetSelectionInternal(rootNode, aSelect ? 0 : numChildren,
rootNode, numChildren);
}
nsresult
@ -2394,18 +2409,18 @@ nsTextControlFrame::AttributeChanged(PRInt32 aNameSpaceID,
}
NS_IMETHODIMP
nsTextControlFrame::GetText(nsString* aText)
nsresult
nsTextControlFrame::GetText(nsString& aText)
{
nsresult rv = NS_OK;
if (IsSingleLineTextControl()) {
// If we're going to remove newlines anyway, ignore the wrap property
GetValue(*aText, PR_TRUE);
RemoveNewlines(*aText);
GetValue(aText, PR_TRUE);
RemoveNewlines(aText);
} else {
nsCOMPtr<nsIDOMHTMLTextAreaElement> textArea = do_QueryInterface(mContent);
if (textArea) {
rv = textArea->GetValue(*aText);
rv = textArea->GetValue(aText);
}
}
return rv;
@ -2475,14 +2490,14 @@ nsTextControlFrame::FireOnInput()
nsresult
nsTextControlFrame::InitFocusedValue()
{
return GetText(&mFocusedValue);
return GetText(mFocusedValue);
}
NS_IMETHODIMP
nsTextControlFrame::CheckFireOnChange()
{
nsString value;
GetText(&value);
GetText(value);
if (!mFocusedValue.Equals(value))
{
mFocusedValue = value;
@ -2506,6 +2521,12 @@ nsTextControlFrame::GetValue(nsAString& aValue, PRBool aIgnoreWrap) const
if (mEditor && mUseEditor)
{
PRBool canCache = aIgnoreWrap && !IsSingleLineTextControl();
if (canCache && !mCachedValue.IsEmpty()) {
aValue = mCachedValue;
return NS_OK;
}
PRUint32 flags = (nsIDocumentEncoder::OutputLFLineBreak |
nsIDocumentEncoder::OutputPreformatted |
nsIDocumentEncoder::OutputPersistNBSP);
@ -2542,6 +2563,11 @@ nsTextControlFrame::GetValue(nsAString& aValue, PRBool aIgnoreWrap) const
rv = mEditor->OutputToString(NS_LITERAL_STRING("text/plain"), flags,
aValue);
}
if (canCache) {
const_cast<nsTextControlFrame*>(this)->mCachedValue = aValue;
} else {
const_cast<nsTextControlFrame*>(this)->mCachedValue.Truncate();
}
}
else
{
@ -2580,19 +2606,16 @@ nsTextControlFrame::SetValue(const nsAString& aValue)
// restores it afterwards (ie. we want 'change' events for those changes).
// Focused value must be updated to prevent incorrect 'change' events,
// but only if user hasn't changed the value.
nsString val;
GetText(&val);
// GetText removes newlines from single line control.
nsString currentValue;
GetText(currentValue);
PRBool focusValueInit = !mFireChangeEventState &&
mFocusedValue.Equals(val);
mFocusedValue.Equals(currentValue);
nsCOMPtr<nsIEditor> editor = mEditor;
nsWeakFrame weakFrame(this);
nsAutoString currentValue;
GetValue(currentValue, PR_FALSE);
if (IsSingleLineTextControl())
{
RemoveNewlines(currentValue);
}
// this is necessary to avoid infinite recursion
if (!currentValue.Equals(aValue))
{
@ -2600,12 +2623,13 @@ nsTextControlFrame::SetValue(const nsAString& aValue)
// so convert windows and mac platform linebreaks to \n:
// Unfortunately aValue is declared const, so we have to copy
// in order to do this substitution.
currentValue.Assign(aValue);
::PlatformToDOMLineBreaks(currentValue);
nsString newValue(aValue);
if (aValue.FindChar(PRUnichar('\r')) != -1) {
::PlatformToDOMLineBreaks(newValue);
}
nsCOMPtr<nsIDOMDocument>domDoc;
nsresult rv = editor->GetDocument(getter_AddRefs(domDoc));
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIDOMDocument> domDoc;
editor->GetDocument(getter_AddRefs(domDoc));
NS_ENSURE_STATE(domDoc);
PRBool outerTransaction;
@ -2628,7 +2652,19 @@ nsTextControlFrame::SetValue(const nsAString& aValue)
}
nsCOMPtr<nsISelectionController> kungFuDeathGrip = mSelCon.get();
mSelCon->SelectAll();
PRUint32 currentLength = currentValue.Length();
PRUint32 newlength = newValue.Length();
if (!currentLength ||
!StringBeginsWith(newValue, currentValue)) {
// Replace the whole text.
currentLength = 0;
mSelCon->SelectAll();
} else {
// Collapse selection to the end so that we can append data.
SelectAllOrCollapseToEndOfText(PR_FALSE);
}
const nsAString& insertValue =
StringTail(newValue, newlength - currentLength);
nsCOMPtr<nsIPlaintextEditor> plaintextEditor = do_QueryInterface(editor);
if (!plaintextEditor || !weakFrame.IsAlive()) {
NS_WARNING("Somehow not a plaintext editor?");
@ -2662,11 +2698,14 @@ nsTextControlFrame::SetValue(const nsAString& aValue)
plaintextEditor->GetMaxTextLength(&savedMaxLength);
plaintextEditor->SetMaxTextLength(-1);
if (currentValue.Length() < 1)
if (insertValue.IsEmpty()) {
editor->DeleteSelection(nsIEditor::eNone);
else {
if (plaintextEditor)
plaintextEditor->InsertText(currentValue);
} else {
plaintextEditor->InsertText(insertValue);
}
if (!IsSingleLineTextControl()) {
mCachedValue = newValue;
}
plaintextEditor->SetMaxTextLength(savedMaxLength);
@ -2762,3 +2801,40 @@ nsTextControlFrame::ShutDown()
NS_IF_RELEASE(sNativeTextAreaBindings);
NS_IF_RELEASE(sNativeInputBindings);
}
NS_IMPL_ISUPPORTS1(nsAnonDivObserver, nsIMutationObserver)
void
nsAnonDivObserver::CharacterDataChanged(nsIDocument* aDocument,
nsIContent* aContent,
CharacterDataChangeInfo* aInfo)
{
mTextControl->ClearValueCache();
}
void
nsAnonDivObserver::ContentAppended(nsIDocument* aDocument,
nsIContent* aContainer,
PRInt32 aNewIndexInContainer)
{
mTextControl->ClearValueCache();
}
void
nsAnonDivObserver::ContentInserted(nsIDocument* aDocument,
nsIContent* aContainer,
nsIContent* aChild,
PRInt32 aIndexInContainer)
{
mTextControl->ClearValueCache();
}
void
nsAnonDivObserver::ContentRemoved(nsIDocument* aDocument,
nsIContent* aContainer,
nsIContent* aChild,
PRInt32 aIndexInContainer)
{
mTextControl->ClearValueCache();
}

Просмотреть файл

@ -50,6 +50,7 @@
#include "nsContentUtils.h"
#include "nsDisplayList.h"
#include "nsIScrollableFrame.h"
#include "nsStubMutationObserver.h"
class nsIEditor;
class nsISelectionController;
@ -60,6 +61,22 @@ class nsIDOMCharacterData;
class nsIAccessible;
#endif
class nsTextInputSelectionImpl;
class nsTextControlFrame;
class nsAnonDivObserver : public nsStubMutationObserver
{
public:
nsAnonDivObserver(nsTextControlFrame* aTextControl)
: mTextControl(aTextControl) {}
NS_DECL_ISUPPORTS
NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATACHANGED
NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED
NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED
NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED
private:
nsTextControlFrame* mTextControl;
};
class nsTextControlFrame : public nsStackFrame,
public nsIAnonymousContentCreator,
@ -166,7 +183,7 @@ public:
nsIAtom* aAttribute,
PRInt32 aModType);
NS_IMETHOD GetText(nsString* aText);
nsresult GetText(nsString& aText);
NS_DECL_QUERYFRAME
@ -213,6 +230,7 @@ public: //for methods who access nsTextControlFrame directly
nsresult MaybeBeginSecureKeyboardInput();
void MaybeEndSecureKeyboardInput();
void ClearValueCache() { mCachedValue.Truncate(); }
protected:
class EditorInitializer;
friend class EditorInitializer;
@ -313,7 +331,7 @@ private:
//helper methods
nsresult SetSelectionInternal(nsIDOMNode *aStartNode, PRInt32 aStartOffset,
nsIDOMNode *aEndNode, PRInt32 aEndOffset);
nsresult SelectAllContents();
nsresult SelectAllOrCollapseToEndOfText(PRBool aSelect);
nsresult SetSelectionEndPoints(PRInt32 aSelStart, PRInt32 aSelEnd);
private:
@ -335,6 +353,8 @@ private:
nsCOMPtr<nsFrameSelection> mFrameSel;
nsTextInputListener* mTextListener;
nsString mFocusedValue;
nsString mCachedValue; // Caches non-hard-wrapped value on a multiline control.
nsRefPtr<nsAnonDivObserver> mMutationObserver;
};
#endif