mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 264412. Implement HTMLElement.innerText. r=smaug,mats
This commit is contained in:
parent
c9cf62c475
commit
1f544fabc4
@ -1159,7 +1159,7 @@ void
|
||||
FragmentOrElement::GetTextContentInternal(nsAString& aTextContent,
|
||||
ErrorResult& aError)
|
||||
{
|
||||
if(!nsContentUtils::GetNodeTextContent(this, true, aTextContent, fallible)) {
|
||||
if (!nsContentUtils::GetNodeTextContent(this, true, aTextContent, fallible)) {
|
||||
aError.Throw(NS_ERROR_OUT_OF_MEMORY);
|
||||
}
|
||||
}
|
||||
|
@ -35,6 +35,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;
|
||||
@ -3193,3 +3196,243 @@ nsRange::ExcludeNonSelectableNodes(nsTArray<RefPtr<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()->IsHTMLElement(nsGkAtoms::button)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
ElementIsVisible(Element* aElement)
|
||||
{
|
||||
if (!aElement) {
|
||||
return false;
|
||||
}
|
||||
RefPtr<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()->IsHTMLElement(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()->IsHTMLElement(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->IsHTMLElement(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.
|
||||
}
|
||||
|
@ -212,6 +212,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, JS::Handle<JSObject*> aGivenProto) override final;
|
||||
|
@ -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;
|
||||
@ -3292,3 +3295,77 @@ nsGenericHTMLElement::NewURIFromString(const nsAString& aURISpec,
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void
|
||||
nsGenericHTMLElement::GetInnerText(mozilla::dom::DOMString& aValue,
|
||||
mozilla::ErrorResult& aError)
|
||||
{
|
||||
if (!GetPrimaryFrame(Flush_Layout)) {
|
||||
RefPtr<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()) {
|
||||
RefPtr<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);
|
||||
RefPtr<HTMLBRElement> br = new HTMLBRElement(ni);
|
||||
AppendChildTo(br, true);
|
||||
} else {
|
||||
str.Append(*s);
|
||||
}
|
||||
++s;
|
||||
}
|
||||
|
||||
mb.NodesAdded();
|
||||
}
|
||||
|
@ -241,6 +241,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
|
||||
|
@ -22,6 +22,9 @@ interface HTMLElement : Element {
|
||||
[Constant]
|
||||
readonly attribute DOMStringMap dataset;
|
||||
|
||||
[GetterThrows, Pure]
|
||||
attribute DOMString innerText;
|
||||
|
||||
// microdata
|
||||
[SetterThrows, Pure]
|
||||
attribute boolean itemScope;
|
||||
|
@ -18701,6 +18701,14 @@
|
||||
"path": "infrastructure/failing-test.html",
|
||||
"url": "/infrastructure/failing-test.html"
|
||||
},
|
||||
{
|
||||
"path": "innerText/getter.html",
|
||||
"url": "/innerText/getter.html"
|
||||
},
|
||||
{
|
||||
"path": "innerText/setter.html",
|
||||
"url": "/innerText/setter.html"
|
||||
},
|
||||
{
|
||||
"path": "js/behaviours/SetPrototypeOf-window.html",
|
||||
"url": "/js/behaviours/SetPrototypeOf-window.html"
|
||||
|
324
testing/web-platform/tests/innerText/getter-tests.js
Normal file
324
testing/web-platform/tests/innerText/getter-tests.js
Normal file
@ -0,0 +1,324 @@
|
||||
testText("<div>abc", "abc", "Simplest possible test");
|
||||
|
||||
/**** white-space:normal ****/
|
||||
|
||||
testText("<div> abc", "abc", "Leading whitespace removed");
|
||||
testText("<div>abc ", "abc", "Trailing whitespace removed");
|
||||
testText("<div>abc def", "abc def", "Internal whitespace compressed");
|
||||
testText("<div>abc\ndef", "abc def", "\\n converted to space");
|
||||
testText("<div>abc\rdef", "abc def", "\\r converted to space");
|
||||
testText("<div>abc\tdef", "abc def", "\\t converted to space");
|
||||
testText("<div>abc <br>def", "abc\ndef", "Trailing whitespace before hard line break removed");
|
||||
|
||||
/**** <pre> ****/
|
||||
|
||||
testText("<pre> abc", " abc", "Leading whitespace preserved");
|
||||
testText("<pre>abc ", "abc ", "Trailing whitespace preserved");
|
||||
testText("<pre>abc def", "abc def", "Internal whitespace preserved");
|
||||
testText("<pre>abc\ndef", "abc\ndef", "\\n preserved");
|
||||
testText("<pre>abc\rdef", "abc\ndef", "\\r converted to newline");
|
||||
testText("<pre>abc\tdef", "abc\tdef", "\\t preserved");
|
||||
|
||||
/**** <div style="white-space:pre"> ****/
|
||||
|
||||
testText("<div style='white-space:pre'> abc", " abc", "Leading whitespace preserved");
|
||||
testText("<div style='white-space:pre'>abc ", "abc ", "Trailing whitespace preserved");
|
||||
testText("<div style='white-space:pre'>abc def", "abc def", "Internal whitespace preserved");
|
||||
testText("<div style='white-space:pre'>abc\ndef", "abc\ndef", "\\n preserved");
|
||||
testText("<div style='white-space:pre'>abc\rdef", "abc\ndef", "\\r converted to newline");
|
||||
testText("<div style='white-space:pre'>abc\tdef", "abc\tdef", "\\t preserved");
|
||||
|
||||
/**** <span style="white-space:pre"> ****/
|
||||
|
||||
testText("<span style='white-space:pre'> abc", " abc", "Leading whitespace preserved");
|
||||
testText("<span style='white-space:pre'>abc ", "abc ", "Trailing whitespace preserved");
|
||||
testText("<span style='white-space:pre'>abc def", "abc def", "Internal whitespace preserved");
|
||||
testText("<span style='white-space:pre'>abc\ndef", "abc\ndef", "\\n preserved");
|
||||
testText("<span style='white-space:pre'>abc\rdef", "abc\ndef", "\\r converted to newline");
|
||||
testText("<span style='white-space:pre'>abc\tdef", "abc\tdef", "\\t preserved");
|
||||
|
||||
/**** <div style="white-space:pre-line"> ****/
|
||||
|
||||
testText("<div style='white-space:pre-line'> abc", "abc", "Leading whitespace removed");
|
||||
testText("<div style='white-space:pre-line'>abc ", "abc", "Trailing whitespace removed");
|
||||
testText("<div style='white-space:pre-line'>abc def", "abc def", "Internal whitespace collapsed");
|
||||
testText("<div style='white-space:pre-line'>abc\ndef", "abc\ndef", "\\n preserved");
|
||||
testText("<div style='white-space:pre-line'>abc\rdef", "abc\ndef", "\\r converted to newline");
|
||||
testText("<div style='white-space:pre-line'>abc\tdef", "abc def", "\\t converted to space");
|
||||
|
||||
/**** Collapsing whitespace across element boundaries ****/
|
||||
|
||||
testText("<div><span>abc </span> def", "abc def", "Whitespace collapses across element boundaries");
|
||||
testText("<div><span>abc </span><span></span> def", "abc def", "Whitespace collapses across element boundaries");
|
||||
testText("<div><span>abc </span><span style='white-space:pre'></span> def", "abc def", "Whitespace collapses across element boundaries");
|
||||
|
||||
/**** Soft line breaks ****/
|
||||
|
||||
testText("<div style='width:0'>abc def", "abc def", "Soft line breaks ignored");
|
||||
|
||||
/**** first-line/first-letter ****/
|
||||
|
||||
testText("<div class='first-line-uppercase' style='width:0'>abc def", "ABC def", "::first-line styles applied");
|
||||
testText("<div class='first-letter-uppercase' style='width:0'>abc def", "Abc def", "::first-letter styles applied");
|
||||
testText("<div class='first-letter-float' style='width:0'>abc def", "abc def", "::first-letter float ignored");
|
||||
|
||||
/**** ****/
|
||||
|
||||
testText("<div> ", "\xA0", " preserved");
|
||||
|
||||
/**** display:none ****/
|
||||
|
||||
testText("<div style='display:none'>abc", "abc", "display:none container");
|
||||
testText("<div style='display:none'>abc def", "abc def", "No whitespace compression in display:none container");
|
||||
testText("<div style='display:none'> abc def ", " abc def ", "No removal of leading/trailing whitespace in display:none container");
|
||||
testText("<div>123<span style='display:none'>abc", "123", "display:none child not rendered");
|
||||
|
||||
/**** display:contents ****/
|
||||
|
||||
if (CSS.supports("display", "contents")) {
|
||||
testText("<div style='display:contents'>abc", "abc", "display:contents container");
|
||||
testText("<div><div style='display:contents'>abc", "abc", "display:contents container");
|
||||
testText("<div>123<span style='display:contents'>abc", "123abc", "display:contents rendered");
|
||||
testText("<div>123<span style='display:contents'>abc", "123abc", "display:contents rendered");
|
||||
testText("<div style='display:contents'> ", "", "display:contents not processed via textContent");
|
||||
testText("<div><div style='display:contents'> ", "", "display:contents not processed via textContent");
|
||||
}
|
||||
|
||||
/**** visibility:hidden ****/
|
||||
|
||||
testText("<div style='visibility:hidden'>abc", "", "visibility:hidden container");
|
||||
testText("<div>123<span style='visibility:hidden'>abc", "123", "visibility:hidden child not rendered");
|
||||
testText("<div style='visibility:hidden'>123<span style='visibility:visible'>abc", "abc", "visibility:visible child rendered");
|
||||
|
||||
/**** visibility:collapse ****/
|
||||
|
||||
testText("<table><tbody style='visibility:collapse'><tr><td>abc", "", "visibility:collapse row-group");
|
||||
testText("<table><tr style='visibility:collapse'><td>abc", "", "visibility:collapse row");
|
||||
testText("<table><tr><td style='visibility:collapse'>abc", "", "visibility:collapse cell");
|
||||
testText("<table><tbody style='visibility:collapse'><tr><td style='visibility:visible'>abc", "abc",
|
||||
"visibility:collapse row-group with visible cell");
|
||||
testText("<table><tr style='visibility:collapse'><td style='visibility:visible'>abc", "abc",
|
||||
"visibility:collapse row with visible cell");
|
||||
testText("<div style='display:flex'><span style='visibility:collapse'>1</span><span>2</span></div>",
|
||||
"2", "visibility:collapse honored on flex item");
|
||||
testText("<div style='display:grid'><span style='visibility:collapse'>1</span><span>2</span></div>",
|
||||
"2", "visibility:collapse honored on grid item");
|
||||
|
||||
/**** opacity:0 ****/
|
||||
|
||||
testText("<div style='opacity:0'>abc", "abc", "opacity:0 container");
|
||||
testText("<div style='opacity:0'>abc def", "abc def", "Whitespace compression in opacity:0 container");
|
||||
testText("<div style='opacity:0'> abc def ", "abc def", "Remove leading/trailing whitespace in opacity:0 container");
|
||||
testText("<div>123<span style='opacity:0'>abc", "123abc", "opacity:0 child rendered");
|
||||
|
||||
/**** generated content ****/
|
||||
|
||||
testText("<div class='before'>", "", "Generated content not included");
|
||||
testText("<div><div class='before'>", "", "Generated content on child not included");
|
||||
|
||||
/**** innerText on replaced elements ****/
|
||||
|
||||
testText("<button>abc", "abc", "<button> contents preserved");
|
||||
testText("<fieldset>abc", "abc", "<fieldset> contents preserved");
|
||||
testText("<fieldset><legend>abc", "abc", "<fieldset> <legend> contents preserved");
|
||||
testText("<input type='text' value='abc'>", "", "<input> contents ignored");
|
||||
testText("<textarea>abc", "", "<textarea> contents ignored");
|
||||
testText("<select size='1'><option>abc</option><option>def", "", "<select size='1'> contents ignored");
|
||||
testText("<select size='2'><option>abc</option><option>def", "", "<select size='2'> contents ignored");
|
||||
testText("<select size='1'><option id='target'>abc</option><option>def", "", "<select size='1'> contents ignored");
|
||||
testText("<select size='2'><option id='target'>abc</option><option>def", "", "<select size='2'> contents ignored");
|
||||
testText("<iframe>abc", "", "<iframe> contents ignored");
|
||||
testText("<iframe><div id='target'>abc", "", "<iframe> contents ignored");
|
||||
testText("<iframe src='data:text/html,abc'>", "","<iframe> subdocument ignored");
|
||||
testText("<audio style='display:block'>abc", "", "<audio> contents ignored");
|
||||
testText("<audio style='display:block'><source id='target' class='poke' style='display:block'>", "", "<audio> contents ignored");
|
||||
testText("<audio style='display:block'><source id='target' class='poke' style='display:none'>", "abc", "<audio> contents ok if display:none");
|
||||
testText("<video>abc", "", "<video> contents ignored");
|
||||
testText("<video style='display:block'><source id='target' class='poke' style='display:block'>", "", "<video> contents ignored");
|
||||
testText("<video style='display:block'><source id='target' class='poke' style='display:none'>", "abc", "<video> contents ok if display:none");
|
||||
testText("<canvas>abc", "", "<canvas> contents ignored");
|
||||
testText("<canvas><div id='target'>abc", "", "<canvas><div id='target'> contents ignored");
|
||||
testText("<img alt='abc'>", "", "<img> alt text ignored");
|
||||
testText("<img src='about:blank' class='poke'>", "", "<img> contents ignored");
|
||||
|
||||
/**** innerText on replaced element children ****/
|
||||
|
||||
testText("<div><button>abc", "abc", "<button> contents preserved");
|
||||
testText("<div><fieldset>abc", "abc", "<fieldset> contents preserved");
|
||||
testText("<div><fieldset><legend>abc", "abc", "<fieldset> <legend> contents preserved");
|
||||
testText("<div><input type='text' value='abc'>", "", "<input> contents ignored");
|
||||
testText("<div><textarea>abc", "", "<textarea> contents ignored");
|
||||
testText("<div><select size='1'><option>abc</option><option>def", "", "<select size='1'> contents ignored");
|
||||
testText("<div><select size='2'><option>abc</option><option>def", "", "<select size='2'> contents ignored");
|
||||
testText("<div><iframe>abc", "", "<iframe> contents ignored");
|
||||
testText("<div><iframe src='data:text/html,abc'>", ""," <iframe> subdocument ignored");
|
||||
testText("<div><audio>abc", "", "<audio> contents ignored");
|
||||
testText("<div><video>abc", "", "<video> contents ignored");
|
||||
testText("<div><canvas>abc", "", "<canvas> contents ignored");
|
||||
testText("<div><img alt='abc'>", "", "<img> alt text ignored");
|
||||
|
||||
/**** Lines around blocks ****/
|
||||
|
||||
testText("<div>123<div>abc</div>def", "123\nabc\ndef", "Newline at block boundary");
|
||||
testText("<div>123<span style='display:block'>abc</span>def", "123\nabc\ndef", "Newline at display:block boundary");
|
||||
testText("<div>abc<div></div>def", "abc\ndef", "Empty block induces single line break");
|
||||
testText("<div>abc<div></div><div></div>def", "abc\ndef", "Consecutive empty blocks ignored");
|
||||
testText("<div><p>abc", "abc", "No blank lines around <p> alone");
|
||||
testText("<div><p>abc</p> ", "abc", "No blank lines around <p> followed by only collapsible whitespace");
|
||||
testText("<div> <p>abc</p>", "abc", "No blank lines around <p> preceded by only collapsible whitespace");
|
||||
testText("<div><p>abc<p>def", "abc\n\ndef", "Blank line between consecutive <p>s");
|
||||
testText("<div><p>abc</p> <p>def", "abc\n\ndef", "Blank line between consecutive <p>s separated only by collapsible whitespace");
|
||||
testText("<div><p>abc</p><div></div><p>def", "abc\n\ndef", "Blank line between consecutive <p>s separated only by empty block");
|
||||
testText("<div><p>abc</p><div>123</div><p>def", "abc\n\n123\n\ndef", "Blank lines between <p>s separated by non-empty block");
|
||||
testText("<div>abc<div><p>123</p></div>def", "abc\n\n123\n\ndef", "Blank lines around a <p> in its own block");
|
||||
testText("<div>abc<p>def", "abc\n\ndef", "Blank line before <p>");
|
||||
testText("<div><p>abc</p>def", "abc\n\ndef", "Blank line after <p>");
|
||||
testText("<div><p>abc<p></p><p></p><p>def", "abc\n\ndef", "One blank line between <p>s, ignoring empty <p>s");
|
||||
testText("<div style='visibility:hidden'><p><span style='visibility:visible'>abc</span></p>\n<div style='visibility:visible'>def</div>",
|
||||
"abc\ndef", "Invisible <p> doesn't induce extra line breaks");
|
||||
testText("<div>abc<div style='margin:2em'>def", "abc\ndef", "No blank lines around <div> with margin");
|
||||
testText("<div>123<span style='display:inline-block'>abc</span>def", "123abcdef", "No newlines at display:inline-block boundary");
|
||||
testText("<div>123<span style='display:inline-block'> abc </span>def", "123abcdef", "Leading/trailing space removal at display:inline-block boundary");
|
||||
|
||||
/**** Spans ****/
|
||||
|
||||
testText("<div>123<span>abc</span>def", "123abcdef", "<span> boundaries are irrelevant");
|
||||
testText("<div>123 <span>abc</span> def", "123 abc def", "<span> boundaries are irrelevant");
|
||||
testText("<div style='width:0'>123 <span>abc</span> def", "123 abc def", "<span> boundaries are irrelevant");
|
||||
testText("<div>123<em>abc</em>def", "123abcdef", "<em> gets no special treatment");
|
||||
testText("<div>123<b>abc</b>def", "123abcdef", "<b> gets no special treatment");
|
||||
testText("<div>123<i>abc</i>def", "123abcdef", "<i> gets no special treatment");
|
||||
testText("<div>123<strong>abc</strong>def", "123abcdef", "<strong> gets no special treatment");
|
||||
testText("<div>123<tt>abc</tt>def", "123abcdef", "<tt> gets no special treatment");
|
||||
testText("<div>123<code>abc</code>def", "123abcdef", "<code> gets no special treatment");
|
||||
|
||||
/**** Soft hyphen ****/
|
||||
|
||||
testText("<div>abc­def", "abc\xADdef", "soft hyphen preserved");
|
||||
testText("<div style='width:0'>abc­def", "abc\xADdef", "soft hyphen preserved");
|
||||
|
||||
/**** Tables ****/
|
||||
|
||||
testText("<div><table style='white-space:pre'> <td>abc</td> </table>", "abc", "Ignoring non-rendered table whitespace");
|
||||
testText("<div><table><tr><td>abc<td>def</table>", "abc\tdef", "Tab-separated table cells");
|
||||
testText("<div><table><tr><td>abc<td><td>def</table>", "abc\t\tdef", "Tab-separated table cells including empty cells");
|
||||
testText("<div><table><tr><td>abc<td><td></table>", "abc\t\t", "Tab-separated table cells including trailing empty cells");
|
||||
testText("<div><table><tr><td>abc<tr><td>def</table>", "abc\ndef", "Newline-separated table rows");
|
||||
testText("<div>abc<table><td>def</table>ghi", "abc\ndef\nghi", "Newlines around table");
|
||||
testText("<div><table style='border-collapse:collapse'><tr><td>abc<td>def</table>", "abc\tdef",
|
||||
"Tab-separated table cells in a border-collapse table");
|
||||
testText("<div><table><tfoot>x</tfoot><tbody>y</tbody></table>", "xy", "tfoot not reordered");
|
||||
testText("<table><tfoot><tr><td>footer</tfoot><thead><tr><td style='visibility:collapse'>thead</thead><tbody><tr><td>tbody</tbody></table>",
|
||||
"footer\n\ntbody", "");
|
||||
|
||||
/**** Table captions ****/
|
||||
|
||||
testText("<div><table><tr><td>abc<caption>def</caption></table>", "abc\ndef", "Newline between cells and caption");
|
||||
|
||||
/**** display:table ****/
|
||||
|
||||
testText("<div><div class='table'><span class='cell'>abc</span>\n<span class='cell'>def</span></div>",
|
||||
"abc\tdef", "Tab-separated table cells");
|
||||
testText("<div><div class='table'><span class='row'><span class='cell'>abc</span></span>\n<span class='row'><span class='cell'>def</span></span></div>",
|
||||
"abc\ndef", "Newline-separated table rows");
|
||||
testText("<div>abc<div class='table'><span class='cell'>def</span></div>ghi", "abc\ndef\nghi", "Newlines around table");
|
||||
|
||||
/**** display:inline-table ****/
|
||||
|
||||
testText("<div><div class='itable'><span class='cell'>abc</span>\n<span class='cell'>def</span></div>", "abc\tdef", "Tab-separated table cells");
|
||||
testText("<div><div class='itable'><span class='row'><span class='cell'>abc</span></span>\n<span class='row'><span class='cell'>def</span></span></div>",
|
||||
"abc\ndef", "Newline-separated table rows");
|
||||
testText("<div>abc<div class='itable'><span class='cell'>def</span></div>ghi", "abcdefghi", "No newlines around inline-table");
|
||||
testText("<div>abc<div class='itable'><span class='row'><span class='cell'>def</span></span>\n<span class='row'><span class='cell'>123</span></span></div>ghi",
|
||||
"abcdef\n123ghi", "Single newline in two-row inline-table");
|
||||
|
||||
/**** Lists ****/
|
||||
|
||||
testText("<div><ol><li>abc", "abc", "<ol> list items get no special treatment");
|
||||
testText("<div><ul><li>abc", "abc", "<ul> list items get no special treatment");
|
||||
|
||||
/**** Misc elements ****/
|
||||
|
||||
testText("<div><script style='display:block'>abc", "abc", "display:block <script> is rendered");
|
||||
testText("<div><style style='display:block'>abc", "abc", "display:block <style> is rendered");
|
||||
testText("<div><noscript style='display:block'>abc", "", "display:block <noscript> is not rendered (it's not parsed!)");
|
||||
testText("<div><template style='display:block'>abc", "",
|
||||
"display:block <template> contents are not rendered (the contents are in a different document)");
|
||||
testText("<div>abc<br>def", "abc\ndef", "<br> induces line break");
|
||||
testText("<div>abc<br>", "abc\n", "<br> induces line break even at end of block");
|
||||
testText("<div><br class='poke'>", "\n", "<br> content ignored");
|
||||
testText("<div>abc<hr>def", "abc\ndef", "<hr> induces line break");
|
||||
testText("<div>abc<hr><hr>def", "abc\ndef", "<hr><hr> induces just one line break");
|
||||
testText("<div>abc<hr><hr><hr>def", "abc\ndef", "<hr><hr><hr> induces just one line break");
|
||||
testText("<div><hr class='poke'>", "abc", "<hr> content rendered");
|
||||
testText("<div>abc<!--comment-->def", "abcdef", "comment ignored");
|
||||
|
||||
/**** text-transform ****/
|
||||
|
||||
testText("<div><div style='text-transform:uppercase'>abc", "ABC", "text-transform is applied");
|
||||
|
||||
/**** block-in-inline ****/
|
||||
|
||||
testText("<div>abc<span>123<div>456</div>789</span>def", "abc123\n456\n789def", "block-in-inline doesn't add unnecessary newlines");
|
||||
|
||||
/**** floats ****/
|
||||
|
||||
testText("<div>abc<div style='float:left'>123</div>def", "abc\n123\ndef", "floats induce a block boundary");
|
||||
testText("<div>abc<span style='float:left'>123</span>def", "abc\n123\ndef", "floats induce a block boundary");
|
||||
|
||||
/**** position ****/
|
||||
|
||||
testText("<div>abc<div style='position:absolute'>123</div>def", "abc\n123\ndef", "position:absolute induces a block boundary");
|
||||
testText("<div>abc<span style='position:absolute'>123</span>def", "abc\n123\ndef", "position:absolute induces a block boundary");
|
||||
testText("<div>abc<div style='position:relative'>123</div>def", "abc\n123\ndef", "position:relative has no effect");
|
||||
testText("<div>abc<span style='position:relative'>123</span>def", "abc123def", "position:relative has no effect");
|
||||
|
||||
/**** text-overflow:ellipsis ****/
|
||||
|
||||
testText("<div style='overflow:hidden'>abc", "abc", "overflow:hidden ignored");
|
||||
// XXX Chrome skips content with width:0 or height:0 and overflow:hidden;
|
||||
// should we spec that?
|
||||
testText("<div style='width:0; overflow:hidden'>abc", "abc", "overflow:hidden ignored even with zero width");
|
||||
testText("<div style='height:0; overflow:hidden'>abc", "abc", "overflow:hidden ignored even with zero height");
|
||||
testText("<div style='width:0; overflow:hidden; text-overflow:ellipsis'>abc", "abc", "text-overflow:ellipsis ignored");
|
||||
|
||||
/**** Support on non-HTML elements ****/
|
||||
|
||||
testText("<svg>abc", undefined, "innerText not supported on SVG elements");
|
||||
testText("<math>abc", undefined, "innerText not supported on MathML elements");
|
||||
|
||||
/**** Ruby ****/
|
||||
|
||||
testText("<div><ruby>abc<rp>(</rp><rt>def</rt><rp>)</rp></ruby>", "abc(def)", "<rp> rendered");
|
||||
testText("<div><rp>abc</rp>", "abc", "Lone <rp> rendered");
|
||||
testText("<div><rp style='visibility:hidden'>abc</rp>", "", "visibility:hidden <rp> not rendered");
|
||||
testText("<div><rp> abc </rp>", " abc ", "Lone <rp> rendered without whitespace trimming");
|
||||
testText("<div><rp style='display:block'>abc</rp>def", "abc\ndef", "display:block <rp> induces line breaks");
|
||||
testText("<div><rp style='display:block'> abc </rp>def", " abc \ndef", "display:block <rp> induces line breaks but doesn't trim whitespace");
|
||||
// XXX this is not desirable but the spec currently requires it.
|
||||
testText("<div><select class='poke-rp'></select>", "abc", "<rp> in a replaced element still renders");
|
||||
|
||||
/**** Shadow DOM ****/
|
||||
|
||||
if ("createShadowRoot" in document.body) {
|
||||
testText("<div class='shadow'>", "", "Shadow DOM contents ignored");
|
||||
testText("<div><div class='shadow'>", "", "Shadow DOM contents ignored");
|
||||
}
|
||||
|
||||
/**** Flexbox ****/
|
||||
|
||||
if (CSS.supports('display', 'flex')) {
|
||||
testText("<div style='display:flex'><div style='order:1'>1</div><div>2</div></div>",
|
||||
"1\n2", "CSS 'order' property ignored");
|
||||
testText("<div style='display:flex'><span>1</span><span>2</span></div>",
|
||||
"1\n2", "Flex items blockified");
|
||||
}
|
||||
|
||||
/**** Grid ****/
|
||||
|
||||
if (CSS.supports('display', 'grid')) {
|
||||
testText("<div style='display:grid'><div style='order:1'>1</div><div>2</div></div>",
|
||||
"1\n2", "CSS 'order' property ignored");
|
||||
testText("<div style='display:grid'><span>1</span><span>2</span></div>",
|
||||
"1\n2", "Grid items blockified");
|
||||
}
|
46
testing/web-platform/tests/innerText/getter.html
Normal file
46
testing/web-platform/tests/innerText/getter.html
Normal file
@ -0,0 +1,46 @@
|
||||
<!DOCTYPE html>
|
||||
<title>innerText getter test</title>
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<style>
|
||||
.before::before { content:'abc'; }
|
||||
.table { display:table; }
|
||||
.itable { display:inline-table; }
|
||||
.row { display:table-row; }
|
||||
.cell { display:table-cell; }
|
||||
.first-line-uppercase::first-line { text-transform:uppercase; }
|
||||
.first-letter-uppercase::first-letter { text-transform:uppercase; }
|
||||
.first-letter-float::first-letter { float:left; }
|
||||
</style>
|
||||
<div id="container"></div>
|
||||
<script>
|
||||
function testText(html, expectedPlain, msg) {
|
||||
test(function() {
|
||||
container.innerHTML = html;
|
||||
var e = document.getElementById('target');
|
||||
if (!e) {
|
||||
e = container.firstChild;
|
||||
}
|
||||
var pokes = document.getElementsByClassName('poke');
|
||||
for (var i = 0; i < pokes.length; ++i) {
|
||||
pokes[i].textContent = 'abc';
|
||||
}
|
||||
pokes = document.getElementsByClassName('poke-rp');
|
||||
for (var i = 0; i < pokes.length; ++i) {
|
||||
var rp = document.createElement("rp");
|
||||
rp.textContent = "abc";
|
||||
pokes[i].appendChild(rp);
|
||||
}
|
||||
var shadows = document.getElementsByClassName('shadow');
|
||||
for (var i = 0; i < shadows.length; ++i) {
|
||||
var s = shadows[i].createShadowRoot();
|
||||
s.textContent = 'abc';
|
||||
}
|
||||
while (e && e.nodeType != Node.ELEMENT_NODE) {
|
||||
e = e.nextSibling;
|
||||
}
|
||||
assert_equals(e.innerText, expectedPlain);
|
||||
}, msg);
|
||||
}
|
||||
</script>
|
||||
<script src="getter-tests.js"></script>
|
24
testing/web-platform/tests/innerText/setter-tests.js
Normal file
24
testing/web-platform/tests/innerText/setter-tests.js
Normal file
@ -0,0 +1,24 @@
|
||||
testText("<div>", "abc", "abc", "Simplest possible test");
|
||||
testHTML("<div>", "abc\ndef", "abc<br>def", "Newlines convert to <br> in non-white-space:pre elements");
|
||||
testHTML("<pre>", "abc\ndef", "abc<br>def", "Newlines convert to <br> in <pre> element");
|
||||
testHTML("<div style='white-space:pre'>", "abc\ndef", "abc<br>def", "Newlines convert to <br> in white-space:pre element");
|
||||
testHTML("<div>", "abc\rdef", "abc<br>def", "CRs convert to <br> in non-white-space:pre elements");
|
||||
testHTML("<pre>", "abc\rdef", "abc<br>def", "CRs convert to <br> in <pre> element");
|
||||
testHTML("<div>", "abc\r\ndef", "abc<br>def", "Newline/CR pair converts to <br> in non-white-space:pre element");
|
||||
testHTML("<div>", "abc\n\ndef", "abc<br><br>def", "Newline/newline pair converts to two <br>s in non-white-space:pre element");
|
||||
testHTML("<div>", "abc\r\rdef", "abc<br><br>def", "CR/CR pair converts to two <br>s in non-white-space:pre element");
|
||||
testHTML("<div style='white-space:pre'>", "abc\rdef", "abc<br>def", "CRs convert to <br> in white-space:pre element");
|
||||
testText("<div>", "abc<def", "abc<def", "< preserved");
|
||||
testText("<div>", "abc>def", "abc>def", "> preserved");
|
||||
testText("<div>", "abc&", "abc&", "& preserved");
|
||||
testText("<div>", "abc\"def", "abc\"def", "\" preserved");
|
||||
testText("<div>", "abc\'def", "abc\'def", "\' preserved");
|
||||
testHTML("<svg>", "abc", "", "innerText not supported on SVG elements");
|
||||
testHTML("<math>", "abc", "", "innerText not supported on MathML elements");
|
||||
testText("<div>", "abc\0def", "abc\0def", "Null characters preserved");
|
||||
testText("<div>", "abc\tdef", "abc\tdef", "Tabs preserved");
|
||||
testText("<div>", " abc", " abc", "Leading whitespace preserved");
|
||||
testText("<div>", "abc ", "abc ", "Trailing whitespace preserved");
|
||||
testText("<div>", "abc def", "abc def", "Whitespace not compressed");
|
||||
testHTML("<div>abc\n\n", "abc", "abc", "Existing text deleted");
|
||||
testHTML("<div><br>", "abc", "abc", "Existing <br> deleted");
|
32
testing/web-platform/tests/innerText/setter.html
Normal file
32
testing/web-platform/tests/innerText/setter.html
Normal file
@ -0,0 +1,32 @@
|
||||
<!DOCTYPE html>
|
||||
<title>innerText setter test</title>
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<div id="container"></div>
|
||||
<script>
|
||||
function setupTest(context, plain) {
|
||||
container.innerHTML = context;
|
||||
var e = container.firstChild;
|
||||
while (e && e.nodeType != Node.ELEMENT_NODE) {
|
||||
e = e.nextSibling;
|
||||
}
|
||||
e.innerText = plain;
|
||||
return e;
|
||||
}
|
||||
function testText(context, plain, expectedText, msg) {
|
||||
test(function(){
|
||||
var e = setupTest(context, plain);
|
||||
assert_not_equals(e.firstChild, null, "Should have a child");
|
||||
assert_equals(e.firstChild.nodeType, Node.TEXT_NODE, "Child should be a text node");
|
||||
assert_equals(e.firstChild.nextSibling, null, "Should have only one child");
|
||||
assert_equals(e.firstChild.data, expectedText);
|
||||
}, msg);
|
||||
}
|
||||
function testHTML(context, plain, expectedHTML, msg) {
|
||||
test(function(){
|
||||
var e = setupTest(context, plain);
|
||||
assert_equals(e.innerHTML, expectedHTML);
|
||||
}, msg);
|
||||
}
|
||||
</script>
|
||||
<script src="setter-tests.js"></script>
|
Loading…
Reference in New Issue
Block a user