Bug 1391538 - nsTextFragment for text nodes in <input> or <textarea> shouldn't store text as single byte characters even if all characters are less than U+0100 r=smaug

nsTextFrame stores text as single byte character array if all characters are
less than U+0100.  Although, this saves footprint, but retrieving and modifying
text needs converting cost.  Therefore, if it's created for a text node in
<input> or <textarea>, it should store text as char16_t array.

MozReview-Commit-ID: 9Z82rketT7g

--HG--
extra : rebase_source : 59f59ac1488c21a57d95d253cc794a011d672c95
This commit is contained in:
Masayuki Nakano 2017-08-18 16:05:16 +09:00
parent cb018b1b7f
commit a7240d8532
12 changed files with 78 additions and 17 deletions

View File

@ -5902,6 +5902,13 @@ nsDocument::CreateTextNode(const nsAString& aData, nsIDOMText** aReturn)
return NS_OK;
}
already_AddRefed<nsTextNode>
nsIDocument::CreateEmptyTextNode() const
{
RefPtr<nsTextNode> text = new nsTextNode(mNodeInfoManager);
return text.forget();
}
already_AddRefed<nsTextNode>
nsIDocument::CreateTextNode(const nsAString& aData) const
{

View File

@ -319,12 +319,18 @@ nsGenericDOMDataNode::SetTextInternal(uint32_t aOffset, uint32_t aCount,
if (aOffset == 0 && endOffset == textLength) {
// Replacing whole text or old text was empty. Don't bother to check for
// bidi in this string if the document already has bidi enabled.
bool ok = mText.SetTo(aBuffer, aLength, !document || !document->GetBidiEnabled());
// If this is marked as "maybe modified frequently", the text should be
// stored as char16_t since converting char* to char16_t* is expensive.
bool ok =
mText.SetTo(aBuffer, aLength, !document || !document->GetBidiEnabled(),
HasFlag(NS_MAYBE_MODIFIED_FREQUENTLY));
NS_ENSURE_TRUE(ok, NS_ERROR_OUT_OF_MEMORY);
}
else if (aOffset == textLength) {
// Appending to existing
bool ok = mText.Append(aBuffer, aLength, !document || !document->GetBidiEnabled());
bool ok =
mText.Append(aBuffer, aLength, !document || !document->GetBidiEnabled(),
HasFlag(NS_MAYBE_MODIFIED_FREQUENTLY));
NS_ENSURE_TRUE(ok, NS_ERROR_OUT_OF_MEMORY);
}
else {
@ -345,7 +351,11 @@ nsGenericDOMDataNode::SetTextInternal(uint32_t aOffset, uint32_t aCount,
mText.CopyTo(to + aOffset + aLength, endOffset, textLength - endOffset);
}
bool ok = mText.SetTo(to, newLength, !document || !document->GetBidiEnabled());
// If this is marked as "maybe modified frequently", the text should be
// stored as char16_t since converting char* to char16_t* is expensive.
bool ok =
mText.SetTo(to, newLength, !document || !document->GetBidiEnabled(),
HasFlag(NS_MAYBE_MODIFIED_FREQUENTLY));
delete [] to;

View File

@ -55,10 +55,14 @@ enum {
// This bit is set if there is a FlowLengthProperty attached to the node
// (used by nsTextFrame).
NS_HAS_FLOWLENGTH_PROPERTY = DATA_NODE_FLAG_BIT(5),
// This bit is set if the node may be modified frequently. This is typically
// specified if the instance is in <input> or <textarea>.
NS_MAYBE_MODIFIED_FREQUENTLY = DATA_NODE_FLAG_BIT(6),
};
// Make sure we have enough space for those bits
ASSERT_NODE_FLAGS_SPACE(NODE_TYPE_SPECIFIC_BITS_OFFSET + 6);
ASSERT_NODE_FLAGS_SPACE(NODE_TYPE_SPECIFIC_BITS_OFFSET + 7);
#undef DATA_NODE_FLAG_BIT
@ -72,6 +76,11 @@ public:
explicit nsGenericDOMDataNode(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo);
explicit nsGenericDOMDataNode(already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo);
void MarkAsMaybeModifiedFrequently()
{
SetFlags(NS_MAYBE_MODIFIED_FREQUENTLY);
}
virtual void GetNodeValueInternal(nsAString& aNodeValue) override;
virtual void SetNodeValueInternal(const nsAString& aNodeValue,
mozilla::ErrorResult& aError) override;

View File

@ -2740,6 +2740,7 @@ public:
already_AddRefed<mozilla::dom::DocumentFragment>
CreateDocumentFragment() const;
already_AddRefed<nsTextNode> CreateTextNode(const nsAString& aData) const;
already_AddRefed<nsTextNode> CreateEmptyTextNode() const;
already_AddRefed<mozilla::dom::Comment>
CreateComment(const nsAString& aData) const;
already_AddRefed<mozilla::dom::ProcessingInstruction>

View File

@ -196,7 +196,8 @@ FirstNon8Bit(const char16_t *str, const char16_t *end)
}
bool
nsTextFragment::SetTo(const char16_t* aBuffer, int32_t aLength, bool aUpdateBidi)
nsTextFragment::SetTo(const char16_t* aBuffer, int32_t aLength,
bool aUpdateBidi, bool aForce2b)
{
ReleaseText();
@ -205,7 +206,7 @@ nsTextFragment::SetTo(const char16_t* aBuffer, int32_t aLength, bool aUpdateBidi
}
char16_t firstChar = *aBuffer;
if (aLength == 1 && firstChar < 256) {
if (!aForce2b && aLength == 1 && firstChar < 256) {
m1b = sSingleCharSharedString + firstChar;
mState.mInHeap = false;
mState.mIs2b = false;
@ -218,7 +219,8 @@ nsTextFragment::SetTo(const char16_t* aBuffer, int32_t aLength, bool aUpdateBidi
const char16_t *uend = aBuffer + aLength;
// Check if we can use a shared string
if (aLength <= 1 + TEXTFRAG_WHITE_AFTER_NEWLINE + TEXTFRAG_MAX_NEWLINES &&
if (!aForce2b &&
aLength <= 1 + TEXTFRAG_WHITE_AFTER_NEWLINE + TEXTFRAG_MAX_NEWLINES &&
(firstChar == ' ' || firstChar == '\n' || firstChar == '\t')) {
if (firstChar == ' ') {
++ucp;
@ -255,7 +257,7 @@ nsTextFragment::SetTo(const char16_t* aBuffer, int32_t aLength, bool aUpdateBidi
}
// See if we need to store the data in ucs2 or not
int32_t first16bit = FirstNon8Bit(ucp, uend);
int32_t first16bit = aForce2b ? 0 : FirstNon8Bit(ucp, uend);
if (first16bit != -1) { // aBuffer contains no non-8bit character
// Use ucs2 storage because we have to
@ -324,12 +326,13 @@ nsTextFragment::CopyTo(char16_t *aDest, int32_t aOffset, int32_t aCount)
}
bool
nsTextFragment::Append(const char16_t* aBuffer, uint32_t aLength, bool aUpdateBidi)
nsTextFragment::Append(const char16_t* aBuffer, uint32_t aLength,
bool aUpdateBidi, bool aForce2b)
{
// This is a common case because some callsites create a textnode
// with a value by creating the node and then calling AppendData.
if (mState.mLength == 0) {
return SetTo(aBuffer, aLength, aUpdateBidi);
return SetTo(aBuffer, aLength, aUpdateBidi, aForce2b);
}
// Should we optimize for aData.Length() == 0?
@ -365,7 +368,7 @@ nsTextFragment::Append(const char16_t* aBuffer, uint32_t aLength, bool aUpdateBi
}
// Current string is a 1-byte string, check if the new data fits in one byte too.
int32_t first16bit = FirstNon8Bit(aBuffer, aBuffer + aLength);
int32_t first16bit = aForce2b ? 0 : FirstNon8Bit(aBuffer, aBuffer + aLength);
if (first16bit != -1) { // aBuffer contains no non-8bit character
length *= sizeof(char16_t);

View File

@ -112,15 +112,23 @@ public:
* Change the contents of this fragment to be a copy of the given
* buffer. If aUpdateBidi is true, contents of the fragment will be scanned,
* and mState.mIsBidi will be turned on if it includes any Bidi characters.
* If aForce2b is true, aBuffer will be stored as char16_t as is. Then,
* you can access the value faster but may waste memory if all characters
* are less than U+0100.
*/
bool SetTo(const char16_t* aBuffer, int32_t aLength, bool aUpdateBidi);
bool SetTo(const char16_t* aBuffer, int32_t aLength, bool aUpdateBidi,
bool aForce2b);
/**
* Append aData to the end of this fragment. If aUpdateBidi is true, contents
* of the fragment will be scanned, and mState.mIsBidi will be turned on if
* it includes any Bidi characters.
* If aForce2b is true, the string will be stored as char16_t as is. Then,
* you can access the value faster but may waste memory if all characters
* are less than U+0100.
*/
bool Append(const char16_t* aBuffer, uint32_t aLength, bool aUpdateBidi);
bool Append(const char16_t* aBuffer, uint32_t aLength, bool aUpdateBidi,
bool aForce2b);
/**
* Append the contents of this string fragment to aString

View File

@ -2359,6 +2359,7 @@ nsTextEditorState::CreateEmptyDivNode()
// Create the text node for DIV
RefPtr<nsTextNode> textNode = new nsTextNode(pNodeInfoManager);
textNode->MarkAsMaybeModifiedFrequently();
element->AppendChildTo(textNode, false);

View File

@ -2514,7 +2514,8 @@ EditorBase::InsertTextImpl(const nsAString& aStringToInsert,
CheckedInt<int32_t> newOffset;
if (!node->IsNodeOfType(nsINode::eTEXT)) {
// create a text node
RefPtr<nsTextNode> newNode = aDoc->CreateTextNode(EmptyString());
RefPtr<nsTextNode> newNode =
EditorBase::CreateTextNode(*aDoc, EmptyString());
// then we insert it into the dom tree
nsresult rv = InsertNode(*newNode, *node, offset);
NS_ENSURE_SUCCESS(rv, rv);
@ -2541,7 +2542,8 @@ EditorBase::InsertTextImpl(const nsAString& aStringToInsert,
} else {
// we are inserting text into a non-text node. first we have to create a
// textnode (this also populates it with the text)
RefPtr<nsTextNode> newNode = aDoc->CreateTextNode(aStringToInsert);
RefPtr<nsTextNode> newNode =
EditorBase::CreateTextNode(*aDoc, aStringToInsert);
// then we insert it into the dom tree
nsresult rv = InsertNode(*newNode, *node, offset);
NS_ENSURE_SUCCESS(rv, rv);
@ -4730,6 +4732,18 @@ EditorBase::CreateHTMLContent(nsIAtom* aTag)
kNameSpaceID_XHTML);
}
// static
already_AddRefed<nsTextNode>
EditorBase::CreateTextNode(nsIDocument& aDocument,
const nsAString& aData)
{
RefPtr<nsTextNode> text = aDocument.CreateEmptyTextNode();
text->MarkAsMaybeModifiedFrequently();
// Don't notify; this node is still being created.
text->SetText(aData, false);
return text.forget();
}
NS_IMETHODIMP
EditorBase::SetAttributeOrEquivalent(nsIDOMElement* aElement,
const nsAString& aAttribute,

View File

@ -348,6 +348,12 @@ public:
*/
already_AddRefed<Element> CreateHTMLContent(nsIAtom* aTag);
/**
* Creates text node which is marked as "maybe modified frequently".
*/
static already_AddRefed<nsTextNode> CreateTextNode(nsIDocument& aDocument,
const nsAString& aData);
/**
* IME event handlers.
*/

View File

@ -4613,7 +4613,8 @@ HTMLEditRules::CreateStyleForInsertText(Selection& aSelection,
if (!mHTMLEditor->IsContainer(node)) {
return NS_OK;
}
OwningNonNull<Text> newNode = aDoc.CreateTextNode(EmptyString());
OwningNonNull<Text> newNode =
EditorBase::CreateTextNode(aDoc, EmptyString());
NS_ENSURE_STATE(mHTMLEditor);
nsresult rv = mHTMLEditor->InsertNode(*newNode, *node, offset);
NS_ENSURE_SUCCESS(rv, rv);

View File

@ -878,7 +878,7 @@ TextEditRules::WillSetText(Selection& aSelection,
if (NS_WARN_IF(!doc)) {
return NS_OK;
}
RefPtr<nsTextNode> newNode = doc->CreateTextNode(tString);
RefPtr<nsTextNode> newNode = EditorBase::CreateTextNode(*doc, tString);
if (NS_WARN_IF(!newNode)) {
return NS_OK;
}

View File

@ -1171,6 +1171,7 @@ nsTextControlFrame::UpdateValueDisplay(bool aNotify,
// Set up a textnode with our value
RefPtr<nsTextNode> textNode =
new nsTextNode(mContent->NodeInfo()->NodeInfoManager());
textNode->MarkAsMaybeModifiedFrequently();
NS_ASSERTION(textNode, "Must have textcontent!\n");