Add support for element.innerText (getter, setter)

This commit is contained in:
Pale Moon
2017-02-24 00:11:14 +01:00
committed by roytam1
parent 63dabe18cd
commit 38fe669f6e
5 changed files with 333 additions and 0 deletions
+244
View File
@@ -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<nsRefPtr<nsRange>>* 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<nsStyleContext> 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<nsGenericDOMDataNode*>(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<nsGenericDOMDataNode*>(aEndParent);
AppendTransformedText(result, t, 0, aEndOffset);
}
// Do not flush trailing line breaks! Required breaks at the end of the text
// are suppressed.
}
+6
View File
@@ -242,6 +242,12 @@ public:
bool aFlushLayout = true);
already_AddRefed<DOMRectList> 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;
+77
View File
@@ -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<nsStyleContext> 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<nsIContent> 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 <br>, so just skip the \r
++s;
}
if (s == end || *s == '\r' || *s == '\n') {
if (!str.IsEmpty()) {
nsRefPtr<nsTextNode> textContent =
new nsTextNode(NodeInfo()->NodeInfoManager());
textContent->SetText(str, true);
AppendChildTo(textContent, true);
}
if (s == end) {
break;
}
str.Truncate();
already_AddRefed<mozilla::dom::NodeInfo> ni =
NodeInfo()->NodeInfoManager()->GetNodeInfo(nsGkAtoms::br,
nullptr, kNameSpaceID_XHTML, nsIDOMNode::ELEMENT_NODE);
nsRefPtr<HTMLBRElement> br = new HTMLBRElement(ni);
AppendChildTo(br, true);
} else {
str.Append(*s);
}
++s;
}
mb.NodesAdded();
}
+3
View File
@@ -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
+3
View File
@@ -22,6 +22,9 @@ interface HTMLElement : Element {
[Constant]
readonly attribute DOMStringMap dataset;
[GetterThrows, Pure]
attribute DOMString innerText;
// microdata
[SetterThrows, Pure]
attribute boolean itemScope;