зеркало из https://github.com/mozilla/gecko-dev.git
Bug 518122, improve textarea.value+= performance, r=bz
This commit is contained in:
Родитель
082e12c87c
Коммит
a229f20863
|
@ -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
|
||||
|
|
Загрузка…
Ссылка в новой задаче