diff --git a/dom/base/nsRange.cpp b/dom/base/nsRange.cpp index 1b420cd887..27d48aa6aa 100644 --- a/dom/base/nsRange.cpp +++ b/dom/base/nsRange.cpp @@ -33,6 +33,9 @@ #include "mozilla/Telemetry.h" #include "mozilla/Likely.h" #include "nsCSSFrameConstructor.h" +#include "nsStyleStruct.h" +#include "nsStyleStructInlines.h" +#include "nsComputedDOMStyle.h" using namespace mozilla; using namespace mozilla::dom; @@ -3131,3 +3134,244 @@ nsRange::ExcludeNonSelectableNodes(nsTArray>* aOutRanges) } } } + + +struct InnerTextAccumulator +{ + explicit InnerTextAccumulator(mozilla::dom::DOMString& aValue) + : mString(aValue.AsAString()), mRequiredLineBreakCount(0) {} + void FlushLineBreaks() + { + while (mRequiredLineBreakCount > 0) { + // Required line breaks at the start of the text are suppressed. + if (!mString.IsEmpty()) { + mString.Append('\n'); + } + --mRequiredLineBreakCount; + } + } + void Append(char aCh) + { + Append(nsAutoString(aCh)); + } + void Append(const nsAString& aString) + { + if (aString.IsEmpty()) { + return; + } + FlushLineBreaks(); + mString.Append(aString); + } + void AddRequiredLineBreakCount(int8_t aCount) + { + mRequiredLineBreakCount = std::max(mRequiredLineBreakCount, aCount); + } + + nsAString& mString; + int8_t mRequiredLineBreakCount; +}; + +static bool +IsVisibleAndNotInReplacedElement(nsIFrame* aFrame) +{ + if (!aFrame || !aFrame->StyleVisibility()->IsVisible()) { + return false; + } + for (nsIFrame* f = aFrame->GetParent(); f; f = f->GetParent()) { + if (f->IsFrameOfType(nsIFrame::eReplaced) && + !f->GetContent()->IsHTML(nsGkAtoms::button)) { + return false; + } + } + return true; +} + +static bool +ElementIsVisible(Element* aElement) +{ + if (!aElement) { + return false; + } + nsRefPtr sc = nsComputedDOMStyle::GetStyleContextForElement( + aElement, nullptr, nullptr); + return sc && sc->StyleVisibility()->IsVisible(); +} + +static void +AppendTransformedText(InnerTextAccumulator& aResult, + nsGenericDOMDataNode* aTextNode, + int32_t aStart, int32_t aEnd) +{ + nsIFrame* frame = aTextNode->GetPrimaryFrame(); + if (!IsVisibleAndNotInReplacedElement(frame)) { + return; + } + nsIFrame::RenderedText text = frame->GetRenderedText(aStart, aEnd); + aResult.Append(text.mString); +} + +/** + * States for tree traversal. AT_NODE means that we are about to enter + * the current DOM node. AFTER_NODE means that we have just finished traversing + * the children of the current DOM node and are about to apply any + * "after processing the node's children" steps before we finish visiting + * the node. + */ +enum TreeTraversalState { + AT_NODE, + AFTER_NODE +}; + +static int8_t +GetRequiredInnerTextLineBreakCount(nsIFrame* aFrame) +{ + if (aFrame->GetContent()->IsHTML(nsGkAtoms::p)) { + return 2; + } + const nsStyleDisplay* styleDisplay = aFrame->StyleDisplay(); + if (styleDisplay->IsBlockOutside(aFrame) || + styleDisplay->mDisplay == NS_STYLE_DISPLAY_TABLE_CAPTION) { + return 1; + } + return 0; +} + +static bool +IsLastCellOfRow(nsIFrame* aFrame) +{ + nsIAtom* type = aFrame->GetType(); + if (type != nsGkAtoms::tableCellFrame && + type != nsGkAtoms::bcTableCellFrame) { + return true; + } + for (nsIFrame* c = aFrame; c; c = c->GetNextContinuation()) { + if (c->GetNextSibling()) { + return false; + } + } + return true; +} + +static bool +IsLastRowOfRowGroup(nsIFrame* aFrame) +{ + if (aFrame->GetType() != nsGkAtoms::tableRowFrame) { + return true; + } + for (nsIFrame* c = aFrame; c; c = c->GetNextContinuation()) { + if (c->GetNextSibling()) { + return false; + } + } + return true; +} + +static bool +IsLastNonemptyRowGroupOfTable(nsIFrame* aFrame) +{ + if (aFrame->GetType() != nsGkAtoms::tableRowGroupFrame) { + return true; + } + for (nsIFrame* c = aFrame; c; c = c->GetNextContinuation()) { + for (nsIFrame* next = c->GetNextSibling(); next; next = next->GetNextSibling()) { + if (next->GetFirstPrincipalChild()) { + return false; + } + } + } + return true; +} + +void +nsRange::GetInnerTextNoFlush(DOMString& aValue, ErrorResult& aError, + nsIContent* aStartParent, uint32_t aStartOffset, + nsIContent* aEndParent, uint32_t aEndOffset) +{ + InnerTextAccumulator result(aValue); + nsIContent* currentNode = aStartParent; + TreeTraversalState currentState = AFTER_NODE; + if (aStartParent->IsNodeOfType(nsINode::eTEXT)) { + auto t = static_cast(aStartParent); + if (aStartParent == aEndParent) { + AppendTransformedText(result, t, aStartOffset, aEndOffset); + return; + } + AppendTransformedText(result, t, aStartOffset, t->TextLength()); + } else { + if (uint32_t(aStartOffset) < aStartParent->GetChildCount()) { + currentNode = aStartParent->GetChildAt(aStartOffset); + currentState = AT_NODE; + } + } + + nsIContent* endNode = aEndParent; + TreeTraversalState endState = AFTER_NODE; + if (aEndParent->IsNodeOfType(nsINode::eTEXT)) { + endState = AT_NODE; + } else { + if (uint32_t(aEndOffset) < aEndParent->GetChildCount()) { + endNode = aEndParent->GetChildAt(aEndOffset); + endState = AT_NODE; + } + } + + while (currentNode != endNode || currentState != endState) { + nsIFrame* f = currentNode->GetPrimaryFrame(); + bool isVisibleAndNotReplaced = IsVisibleAndNotInReplacedElement(f); + if (currentState == AT_NODE) { + bool isText = currentNode->IsNodeOfType(nsINode::eTEXT); + if (isText && currentNode->GetParent()->IsHTML(nsGkAtoms::rp) && + ElementIsVisible(currentNode->GetParent()->AsElement())) { + nsAutoString str; + currentNode->GetTextContent(str, aError); + result.Append(str); + } else if (isVisibleAndNotReplaced) { + result.AddRequiredLineBreakCount(GetRequiredInnerTextLineBreakCount(f)); + if (isText) { + nsIFrame::RenderedText text = f->GetRenderedText(); + result.Append(text.mString); + } + } + nsIContent* child = currentNode->GetFirstChild(); + if (child) { + currentNode = child; + } else { + currentState = AFTER_NODE; + } + } else { + if (isVisibleAndNotReplaced) { + if (currentNode->IsHTML(nsGkAtoms::br)) { + result.Append('\n'); + } + switch (f->StyleDisplay()->mDisplay) { + case NS_STYLE_DISPLAY_TABLE_CELL: + if (!IsLastCellOfRow(f)) { + result.Append('\t'); + } + break; + case NS_STYLE_DISPLAY_TABLE_ROW: + if (!IsLastRowOfRowGroup(f) || + !IsLastNonemptyRowGroupOfTable(f->GetParent())) { + result.Append('\n'); + } + break; + } + result.AddRequiredLineBreakCount(GetRequiredInnerTextLineBreakCount(f)); + } + nsIContent* next = currentNode->GetNextSibling(); + if (next) { + currentNode = next; + currentState = AT_NODE; + } else { + currentNode = currentNode->GetParent(); + } + } + } + + if (aEndParent->IsNodeOfType(nsINode::eTEXT)) { + nsGenericDOMDataNode* t = static_cast(aEndParent); + AppendTransformedText(result, t, 0, aEndOffset); + } + // Do not flush trailing line breaks! Required breaks at the end of the text + // are suppressed. +} \ No newline at end of file diff --git a/dom/base/nsRange.h b/dom/base/nsRange.h index 0be4a903cd..e82628b3a5 100644 --- a/dom/base/nsRange.h +++ b/dom/base/nsRange.h @@ -242,6 +242,12 @@ public: bool aFlushLayout = true); already_AddRefed GetClientRects(bool aClampToEdge = true, bool aFlushLayout = true); + static void GetInnerTextNoFlush(mozilla::dom::DOMString& aValue, + mozilla::ErrorResult& aError, + nsIContent* aStartParent, + uint32_t aStartOffset, + nsIContent* aEndParent, + uint32_t aEndOffset); nsINode* GetParentObject() const { return mOwner; } virtual JSObject* WrapObject(JSContext* cx) override final; diff --git a/dom/html/nsGenericHTMLElement.cpp b/dom/html/nsGenericHTMLElement.cpp index 966ed54db8..2c13e953e4 100644 --- a/dom/html/nsGenericHTMLElement.cpp +++ b/dom/html/nsGenericHTMLElement.cpp @@ -84,6 +84,8 @@ #include "nsITextControlElement.h" #include "mozilla/dom/Element.h" #include "HTMLFieldSetElement.h" +#include "nsTextNode.h" +#include "HTMLBRElement.h" #include "HTMLMenuElement.h" #include "nsDOMMutationObserver.h" #include "mozilla/Preferences.h" @@ -104,6 +106,7 @@ #include "nsGlobalWindow.h" #include "mozilla/dom/HTMLBodyElement.h" #include "imgIContainer.h" +#include "nsComputedDOMStyle.h" using namespace mozilla; using namespace mozilla::dom; @@ -3220,3 +3223,77 @@ nsGenericHTMLElement::NewURIFromString(const nsAutoString& aURISpec, return NS_OK; } + +void +nsGenericHTMLElement::GetInnerText(mozilla::dom::DOMString& aValue, + mozilla::ErrorResult& aError) +{ + if (!GetPrimaryFrame(Flush_Layout)) { + nsRefPtr sc = + nsComputedDOMStyle::GetStyleContextForElementNoFlush(this, nullptr, nullptr); + if (!sc || sc->StyleDisplay()->mDisplay == NS_STYLE_DISPLAY_NONE || + !IsInComposedDoc()) { + GetTextContentInternal(aValue, aError); + return; + } + } + + nsRange::GetInnerTextNoFlush(aValue, aError, this, 0, this, GetChildCount()); +} + +void +nsGenericHTMLElement::SetInnerText(const nsAString& aValue) +{ + // Fire DOMNodeRemoved mutation events before we do anything else. + nsCOMPtr kungFuDeathGrip; + + // Batch possible DOMSubtreeModified events. + mozAutoSubtreeModified subtree(OwnerDoc(), nullptr); + FireNodeRemovedForChildren(); + + // Might as well stick a batch around this since we're performing several + // mutations. + mozAutoDocUpdate updateBatch(GetComposedDoc(), + UPDATE_CONTENT_MODEL, true); + nsAutoMutationBatch mb; + + uint32_t childCount = GetChildCount(); + + mb.Init(this, true, false); + for (uint32_t i = 0; i < childCount; ++i) { + RemoveChildAt(0, true); + } + mb.RemovalDone(); + + nsString str; + const char16_t* s = aValue.BeginReading(); + const char16_t* end = aValue.EndReading(); + while (true) { + if (s != end && *s == '\r' && s + 1 != end && s[1] == '\n') { + // a \r\n pair should only generate one
, so just skip the \r + ++s; + } + if (s == end || *s == '\r' || *s == '\n') { + if (!str.IsEmpty()) { + nsRefPtr textContent = + new nsTextNode(NodeInfo()->NodeInfoManager()); + textContent->SetText(str, true); + AppendChildTo(textContent, true); + } + if (s == end) { + break; + } + str.Truncate(); + already_AddRefed ni = + NodeInfo()->NodeInfoManager()->GetNodeInfo(nsGkAtoms::br, + nullptr, kNameSpaceID_XHTML, nsIDOMNode::ELEMENT_NODE); + nsRefPtr br = new HTMLBRElement(ni); + AppendChildTo(br, true); + } else { + str.Append(*s); + } + ++s; + } + + mb.NodesAdded(); +} diff --git a/dom/html/nsGenericHTMLElement.h b/dom/html/nsGenericHTMLElement.h index 35181ef284..6028612e70 100644 --- a/dom/html/nsGenericHTMLElement.h +++ b/dom/html/nsGenericHTMLElement.h @@ -233,6 +233,9 @@ public: mScrollgrab = aValue; } + void GetInnerText(mozilla::dom::DOMString& aValue, mozilla::ErrorResult& aError); + void SetInnerText(const nsAString& aValue); + /** * Determine whether an attribute is an event (onclick, etc.) * @param aName the attribute diff --git a/dom/webidl/HTMLElement.webidl b/dom/webidl/HTMLElement.webidl index 8399c1e7cb..7caf687854 100644 --- a/dom/webidl/HTMLElement.webidl +++ b/dom/webidl/HTMLElement.webidl @@ -22,6 +22,9 @@ interface HTMLElement : Element { [Constant] readonly attribute DOMStringMap dataset; + [GetterThrows, Pure] + attribute DOMString innerText; + // microdata [SetterThrows, Pure] attribute boolean itemScope;