Bug 806506 - Part 2: Implement innerHTML on ShadowRoot. r=mrbkap

This commit is contained in:
William Chen 2013-12-02 02:26:12 -08:00
parent e1156f34cd
commit 257e018b19
10 changed files with 831 additions and 783 deletions

View File

@ -1133,8 +1133,6 @@ private:
nsIScrollableFrame* GetScrollFrame(nsIFrame **aStyledFrame = nullptr,
bool aFlushLayout = true);
void GetMarkup(bool aIncludeSelf, nsAString& aMarkup);
// Data members
nsEventStates mState;
};

View File

@ -380,6 +380,9 @@ public:
};
protected:
void GetMarkup(bool aIncludeSelf, nsAString& aMarkup);
void SetInnerHTMLInternal(const nsAString& aInnerHTML, ErrorResult& aError);
// Override from nsINode
virtual nsINode::nsSlots* CreateSlots() MOZ_OVERRIDE;

View File

@ -1845,6 +1845,18 @@ public:
*/
static bool HasPluginWithUncontrolledEventDispatch(nsIDocument* aDoc);
/**
* Fire mutation events for changes caused by parsing directly into a
* context node.
*
* @param aDoc the document of the node
* @param aDest the destination node that got stuff appended to it
* @param aOldChildCount the number of children the node had before parsing
*/
static void FireMutationEventsForDirectParsing(nsIDocument* aDoc,
nsIContent* aDest,
int32_t aOldChildCount);
/**
* Returns true if the content is in a document and contains a plugin
* which we don't control event dispatch for, i.e. do any plugins in this

View File

@ -18,7 +18,6 @@
#include "nsIAtom.h"
#include "nsIContentInlines.h"
#include "nsINodeInfo.h"
#include "nsIDocumentEncoder.h"
#include "nsIDocumentInlines.h"
#include "nsIDOMNodeList.h"
#include "nsIDOMDocument.h"
@ -102,7 +101,6 @@
#include "nsRuleProcessorData.h"
#include "nsAsyncDOMEvent.h"
#include "nsTextNode.h"
#include "mozilla/dom/HTMLTemplateElement.h"
#ifdef MOZ_XUL
#include "nsIXULDocument.h"
@ -127,7 +125,6 @@
#include "nsStyledElement.h"
#include "nsXBLService.h"
#include "nsContentCID.h"
#include "nsITextControlElement.h"
#include "nsISupportsImpl.h"
#include "mozilla/dom/DocumentFragment.h"
@ -2573,726 +2570,6 @@ Element::MozRequestFullScreen()
return;
}
// Try to keep the size of StringBuilder close to a jemalloc bucket size.
#define STRING_BUFFER_UNITS 1020
namespace {
// We put StringBuilder in the anonymous namespace to prevent anything outside
// this file from accidentally being linked against it.
class StringBuilder
{
private:
class Unit
{
public:
Unit() : mAtom(nullptr), mType(eUnknown), mLength(0)
{
MOZ_COUNT_CTOR(StringBuilder::Unit);
}
~Unit()
{
if (mType == eString || mType == eStringWithEncode) {
delete mString;
}
MOZ_COUNT_DTOR(StringBuilder::Unit);
}
enum Type
{
eUnknown,
eAtom,
eString,
eStringWithEncode,
eLiteral,
eTextFragment,
eTextFragmentWithEncode,
};
union
{
nsIAtom* mAtom;
const char* mLiteral;
nsAutoString* mString;
const nsTextFragment* mTextFragment;
};
Type mType;
uint32_t mLength;
};
public:
StringBuilder() : mLast(MOZ_THIS_IN_INITIALIZER_LIST()), mLength(0)
{
MOZ_COUNT_CTOR(StringBuilder);
}
~StringBuilder()
{
MOZ_COUNT_DTOR(StringBuilder);
}
void Append(nsIAtom* aAtom)
{
Unit* u = AddUnit();
u->mAtom = aAtom;
u->mType = Unit::eAtom;
uint32_t len = aAtom->GetLength();
u->mLength = len;
mLength += len;
}
template<int N>
void Append(const char (&aLiteral)[N])
{
Unit* u = AddUnit();
u->mLiteral = aLiteral;
u->mType = Unit::eLiteral;
uint32_t len = N - 1;
u->mLength = len;
mLength += len;
}
template<int N>
void Append(char (&aLiteral)[N])
{
Unit* u = AddUnit();
u->mLiteral = aLiteral;
u->mType = Unit::eLiteral;
uint32_t len = N - 1;
u->mLength = len;
mLength += len;
}
void Append(const nsAString& aString)
{
Unit* u = AddUnit();
u->mString = new nsAutoString(aString);
u->mType = Unit::eString;
uint32_t len = aString.Length();
u->mLength = len;
mLength += len;
}
void Append(nsAutoString* aString)
{
Unit* u = AddUnit();
u->mString = aString;
u->mType = Unit::eString;
uint32_t len = aString->Length();
u->mLength = len;
mLength += len;
}
void AppendWithAttrEncode(nsAutoString* aString, uint32_t aLen)
{
Unit* u = AddUnit();
u->mString = aString;
u->mType = Unit::eStringWithEncode;
u->mLength = aLen;
mLength += aLen;
}
void Append(const nsTextFragment* aTextFragment)
{
Unit* u = AddUnit();
u->mTextFragment = aTextFragment;
u->mType = Unit::eTextFragment;
uint32_t len = aTextFragment->GetLength();
u->mLength = len;
mLength += len;
}
void AppendWithEncode(const nsTextFragment* aTextFragment, uint32_t aLen)
{
Unit* u = AddUnit();
u->mTextFragment = aTextFragment;
u->mType = Unit::eTextFragmentWithEncode;
u->mLength = aLen;
mLength += aLen;
}
bool ToString(nsAString& aOut)
{
if (!aOut.SetCapacity(mLength, fallible_t())) {
return false;
}
for (StringBuilder* current = this; current; current = current->mNext) {
uint32_t len = current->mUnits.Length();
for (uint32_t i = 0; i < len; ++i) {
Unit& u = current->mUnits[i];
switch (u.mType) {
case Unit::eAtom:
aOut.Append(nsDependentAtomString(u.mAtom));
break;
case Unit::eString:
aOut.Append(*(u.mString));
break;
case Unit::eStringWithEncode:
EncodeAttrString(*(u.mString), aOut);
break;
case Unit::eLiteral:
aOut.AppendASCII(u.mLiteral, u.mLength);
break;
case Unit::eTextFragment:
u.mTextFragment->AppendTo(aOut);
break;
case Unit::eTextFragmentWithEncode:
EncodeTextFragment(u.mTextFragment, aOut);
break;
default:
MOZ_CRASH("Unknown unit type?");
}
}
}
return true;
}
private:
Unit* AddUnit()
{
if (mLast->mUnits.Length() == STRING_BUFFER_UNITS) {
new StringBuilder(this);
}
return mLast->mUnits.AppendElement();
}
StringBuilder(StringBuilder* aFirst)
: mLast(nullptr), mLength(0)
{
MOZ_COUNT_CTOR(StringBuilder);
aFirst->mLast->mNext = this;
aFirst->mLast = this;
}
void EncodeAttrString(const nsAutoString& aValue, nsAString& aOut)
{
const PRUnichar* c = aValue.BeginReading();
const PRUnichar* end = aValue.EndReading();
while (c < end) {
switch (*c) {
case '"':
aOut.AppendLiteral("&quot;");
break;
case '&':
aOut.AppendLiteral("&amp;");
break;
case 0x00A0:
aOut.AppendLiteral("&nbsp;");
break;
default:
aOut.Append(*c);
break;
}
++c;
}
}
void EncodeTextFragment(const nsTextFragment* aValue, nsAString& aOut)
{
uint32_t len = aValue->GetLength();
if (aValue->Is2b()) {
const PRUnichar* data = aValue->Get2b();
for (uint32_t i = 0; i < len; ++i) {
const PRUnichar c = data[i];
switch (c) {
case '<':
aOut.AppendLiteral("&lt;");
break;
case '>':
aOut.AppendLiteral("&gt;");
break;
case '&':
aOut.AppendLiteral("&amp;");
break;
case 0x00A0:
aOut.AppendLiteral("&nbsp;");
break;
default:
aOut.Append(c);
break;
}
}
} else {
const char* data = aValue->Get1b();
for (uint32_t i = 0; i < len; ++i) {
const unsigned char c = data[i];
switch (c) {
case '<':
aOut.AppendLiteral("&lt;");
break;
case '>':
aOut.AppendLiteral("&gt;");
break;
case '&':
aOut.AppendLiteral("&amp;");
break;
case 0x00A0:
aOut.AppendLiteral("&nbsp;");
break;
default:
aOut.Append(c);
break;
}
}
}
}
nsAutoTArray<Unit, STRING_BUFFER_UNITS> mUnits;
nsAutoPtr<StringBuilder> mNext;
StringBuilder* mLast;
// mLength is used only in the first StringBuilder object in the linked list.
uint32_t mLength;
};
} // anonymous namespace
static void
AppendEncodedCharacters(const nsTextFragment* aText, StringBuilder& aBuilder)
{
uint32_t extraSpaceNeeded = 0;
uint32_t len = aText->GetLength();
if (aText->Is2b()) {
const PRUnichar* data = aText->Get2b();
for (uint32_t i = 0; i < len; ++i) {
const PRUnichar c = data[i];
switch (c) {
case '<':
extraSpaceNeeded += ArrayLength("&lt;") - 2;
break;
case '>':
extraSpaceNeeded += ArrayLength("&gt;") - 2;
break;
case '&':
extraSpaceNeeded += ArrayLength("&amp;") - 2;
break;
case 0x00A0:
extraSpaceNeeded += ArrayLength("&nbsp;") - 2;
break;
default:
break;
}
}
} else {
const char* data = aText->Get1b();
for (uint32_t i = 0; i < len; ++i) {
const unsigned char c = data[i];
switch (c) {
case '<':
extraSpaceNeeded += ArrayLength("&lt;") - 2;
break;
case '>':
extraSpaceNeeded += ArrayLength("&gt;") - 2;
break;
case '&':
extraSpaceNeeded += ArrayLength("&amp;") - 2;
break;
case 0x00A0:
extraSpaceNeeded += ArrayLength("&nbsp;") - 2;
break;
default:
break;
}
}
}
if (extraSpaceNeeded) {
aBuilder.AppendWithEncode(aText, len + extraSpaceNeeded);
} else {
aBuilder.Append(aText);
}
}
static void
AppendEncodedAttributeValue(nsAutoString* aValue, StringBuilder& aBuilder)
{
const PRUnichar* c = aValue->BeginReading();
const PRUnichar* end = aValue->EndReading();
uint32_t extraSpaceNeeded = 0;
while (c < end) {
switch (*c) {
case '"':
extraSpaceNeeded += ArrayLength("&quot;") - 2;
break;
case '&':
extraSpaceNeeded += ArrayLength("&amp;") - 2;
break;
case 0x00A0:
extraSpaceNeeded += ArrayLength("&nbsp;") - 2;
break;
default:
break;
}
++c;
}
if (extraSpaceNeeded) {
aBuilder.AppendWithAttrEncode(aValue, aValue->Length() + extraSpaceNeeded);
} else {
aBuilder.Append(aValue);
}
}
static void
StartElement(Element* aContent, StringBuilder& aBuilder)
{
nsIAtom* localName = aContent->Tag();
int32_t tagNS = aContent->GetNameSpaceID();
aBuilder.Append("<");
if (aContent->IsHTML() || aContent->IsSVG() || aContent->IsMathML()) {
aBuilder.Append(localName);
} else {
aBuilder.Append(aContent->NodeName());
}
int32_t count = aContent->GetAttrCount();
for (int32_t i = count; i > 0;) {
--i;
const nsAttrName* name = aContent->GetAttrNameAt(i);
int32_t attNs = name->NamespaceID();
nsIAtom* attName = name->LocalName();
// Filter out any attribute starting with [-|_]moz
nsDependentAtomString attrNameStr(attName);
if (StringBeginsWith(attrNameStr, NS_LITERAL_STRING("_moz")) ||
StringBeginsWith(attrNameStr, NS_LITERAL_STRING("-moz"))) {
continue;
}
nsAutoString* attValue = new nsAutoString();
aContent->GetAttr(attNs, attName, *attValue);
// Filter out special case of <br type="_moz*"> used by the editor.
// Bug 16988. Yuck.
if (localName == nsGkAtoms::br && tagNS == kNameSpaceID_XHTML &&
attName == nsGkAtoms::type && attNs == kNameSpaceID_None &&
StringBeginsWith(*attValue, NS_LITERAL_STRING("_moz"))) {
delete attValue;
continue;
}
if (MOZ_LIKELY(attNs == kNameSpaceID_None) ||
(attNs == kNameSpaceID_XMLNS &&
attName == nsGkAtoms::xmlns)) {
aBuilder.Append(" ");
} else if (attNs == kNameSpaceID_XML) {
aBuilder.Append(" xml:");
} else if (attNs == kNameSpaceID_XMLNS) {
aBuilder.Append(" xmlns:");
} else if (attNs == kNameSpaceID_XLink) {
aBuilder.Append(" xlink:");
} else {
nsIAtom* prefix = name->GetPrefix();
if (prefix) {
aBuilder.Append(" ");
aBuilder.Append(prefix);
aBuilder.Append(":");
}
}
aBuilder.Append(attName);
aBuilder.Append("=\"");
AppendEncodedAttributeValue(attValue, aBuilder);
aBuilder.Append("\"");
}
aBuilder.Append(">");
/*
// Per HTML spec we should append one \n if the first child of
// pre/textarea/listing is a textnode and starts with a \n.
// But because browsers haven't traditionally had that behavior,
// we're not changing our behavior either - yet.
if (aContent->IsHTML()) {
if (localName == nsGkAtoms::pre || localName == nsGkAtoms::textarea ||
localName == nsGkAtoms::listing) {
nsIContent* fc = aContent->GetFirstChild();
if (fc &&
(fc->NodeType() == nsIDOMNode::TEXT_NODE ||
fc->NodeType() == nsIDOMNode::CDATA_SECTION_NODE)) {
const nsTextFragment* text = fc->GetText();
if (text && text->GetLength() && text->CharAt(0) == PRUnichar('\n')) {
aBuilder.Append("\n");
}
}
}
}*/
}
static inline bool
ShouldEscape(nsIContent* aParent)
{
if (!aParent || !aParent->IsHTML()) {
return true;
}
static const nsIAtom* nonEscapingElements[] = {
nsGkAtoms::style, nsGkAtoms::script, nsGkAtoms::xmp,
nsGkAtoms::iframe, nsGkAtoms::noembed, nsGkAtoms::noframes,
nsGkAtoms::plaintext,
// Per the current spec noscript should be escaped in case
// scripts are disabled or if document doesn't have
// browsing context. However the latter seems to be a spec bug
// and Gecko hasn't traditionally done the former.
nsGkAtoms::noscript
};
static mozilla::BloomFilter<12, nsIAtom> sFilter;
static bool sInitialized = false;
if (!sInitialized) {
sInitialized = true;
for (uint32_t i = 0; i < ArrayLength(nonEscapingElements); ++i) {
sFilter.add(nonEscapingElements[i]);
}
}
nsIAtom* tag = aParent->Tag();
if (sFilter.mightContain(tag)) {
for (uint32_t i = 0; i < ArrayLength(nonEscapingElements); ++i) {
if (tag == nonEscapingElements[i]) {
return false;
}
}
}
return true;
}
static inline bool
IsVoidTag(Element* aElement)
{
if (!aElement->IsHTML()) {
return false;
}
static const nsIAtom* voidElements[] = {
nsGkAtoms::area, nsGkAtoms::base, nsGkAtoms::basefont,
nsGkAtoms::bgsound, nsGkAtoms::br, nsGkAtoms::col,
nsGkAtoms::command, nsGkAtoms::embed, nsGkAtoms::frame,
nsGkAtoms::hr, nsGkAtoms::img, nsGkAtoms::input,
nsGkAtoms::keygen, nsGkAtoms::link, nsGkAtoms::meta,
nsGkAtoms::param, nsGkAtoms::source, nsGkAtoms::track,
nsGkAtoms::wbr
};
static mozilla::BloomFilter<12, nsIAtom> sFilter;
static bool sInitialized = false;
if (!sInitialized) {
sInitialized = true;
for (uint32_t i = 0; i < ArrayLength(voidElements); ++i) {
sFilter.add(voidElements[i]);
}
}
nsIAtom* tag = aElement->Tag();
if (sFilter.mightContain(tag)) {
for (uint32_t i = 0; i < ArrayLength(voidElements); ++i) {
if (tag == voidElements[i]) {
return true;
}
}
}
return false;
}
static bool
Serialize(Element* aRoot, bool aDescendentsOnly, nsAString& aOut)
{
nsINode* current = aDescendentsOnly ?
nsNodeUtils::GetFirstChildOfTemplateOrNode(aRoot) : aRoot;
if (!current) {
return true;
}
StringBuilder builder;
nsIContent* next;
while (true) {
bool isVoid = false;
switch (current->NodeType()) {
case nsIDOMNode::ELEMENT_NODE: {
Element* elem = current->AsElement();
StartElement(elem, builder);
isVoid = IsVoidTag(elem);
if (!isVoid &&
(next = nsNodeUtils::GetFirstChildOfTemplateOrNode(current))) {
current = next;
continue;
}
break;
}
case nsIDOMNode::TEXT_NODE:
case nsIDOMNode::CDATA_SECTION_NODE: {
const nsTextFragment* text = static_cast<nsIContent*>(current)->GetText();
nsIContent* parent = current->GetParent();
if (ShouldEscape(parent)) {
AppendEncodedCharacters(text, builder);
} else {
builder.Append(text);
}
break;
}
case nsIDOMNode::COMMENT_NODE: {
builder.Append("<!--");
builder.Append(static_cast<nsIContent*>(current)->GetText());
builder.Append("-->");
break;
}
case nsIDOMNode::DOCUMENT_TYPE_NODE: {
builder.Append("<!DOCTYPE ");
builder.Append(current->NodeName());
builder.Append(">");
break;
}
case nsIDOMNode::PROCESSING_INSTRUCTION_NODE: {
builder.Append("<?");
builder.Append(current->NodeName());
builder.Append(" ");
builder.Append(static_cast<nsIContent*>(current)->GetText());
builder.Append(">");
break;
}
}
while (true) {
if (!isVoid && current->NodeType() == nsIDOMNode::ELEMENT_NODE) {
builder.Append("</");
nsIContent* elem = static_cast<nsIContent*>(current);
if (elem->IsHTML() || elem->IsSVG() || elem->IsMathML()) {
builder.Append(elem->Tag());
} else {
builder.Append(current->NodeName());
}
builder.Append(">");
}
isVoid = false;
if (current == aRoot) {
return builder.ToString(aOut);
}
if ((next = current->GetNextSibling())) {
current = next;
break;
}
current = current->GetParentNode();
// Template case, if we are in a template's content, then the parent
// should be the host template element.
if (current->NodeType() == nsIDOMNode::DOCUMENT_FRAGMENT_NODE) {
DocumentFragment* frag = static_cast<DocumentFragment*>(current);
nsIContent* fragHost = frag->GetHost();
if (fragHost && nsNodeUtils::IsTemplateElement(fragHost)) {
current = fragHost;
}
}
if (aDescendentsOnly && current == aRoot) {
return builder.ToString(aOut);
}
}
}
}
void
Element::GetMarkup(bool aIncludeSelf, nsAString& aMarkup)
{
aMarkup.Truncate();
nsIDocument* doc = OwnerDoc();
if (IsInHTMLDocument()) {
Serialize(this, !aIncludeSelf, aMarkup);
return;
}
nsAutoString contentType;
doc->GetContentType(contentType);
bool tryToCacheEncoder = !aIncludeSelf;
nsCOMPtr<nsIDocumentEncoder> docEncoder = doc->GetCachedEncoder();
if (!docEncoder) {
docEncoder =
do_CreateInstance(PromiseFlatCString(
nsDependentCString(NS_DOC_ENCODER_CONTRACTID_BASE) +
NS_ConvertUTF16toUTF8(contentType)
).get());
}
if (!docEncoder) {
// This could be some type for which we create a synthetic document. Try
// again as XML
contentType.AssignLiteral("application/xml");
docEncoder = do_CreateInstance(NS_DOC_ENCODER_CONTRACTID_BASE "application/xml");
// Don't try to cache the encoder since it would point to a different
// contentType once it has been reinitialized.
tryToCacheEncoder = false;
}
NS_ENSURE_TRUE_VOID(docEncoder);
uint32_t flags = nsIDocumentEncoder::OutputEncodeBasicEntities |
// Output DOM-standard newlines
nsIDocumentEncoder::OutputLFLineBreak |
// Don't do linebreaking that's not present in
// the source
nsIDocumentEncoder::OutputRaw |
// Only check for mozdirty when necessary (bug 599983)
nsIDocumentEncoder::OutputIgnoreMozDirty;
if (IsEditable()) {
nsIEditor* editor = GetEditorInternal();
if (editor && editor->OutputsMozDirty()) {
flags &= ~nsIDocumentEncoder::OutputIgnoreMozDirty;
}
}
DebugOnly<nsresult> rv = docEncoder->NativeInit(doc, contentType, flags);
MOZ_ASSERT(NS_SUCCEEDED(rv));
if (aIncludeSelf) {
docEncoder->SetNativeNode(this);
} else {
docEncoder->SetNativeContainerNode(this);
}
rv = docEncoder->EncodeToString(aMarkup);
MOZ_ASSERT(NS_SUCCEEDED(rv));
if (tryToCacheEncoder) {
doc->SetCachedEncoder(docEncoder.forget());
}
}
/**
* Fire mutation events for changes caused by parsing directly into a
* context node.
*
* @param aDoc the document of the node
* @param aDest the destination node that got stuff appended to it
* @param aOldChildCount the number of children the node had before parsing
*/
static void
FireMutationEventsForDirectParsing(nsIDocument* aDoc, nsIContent* aDest,
int32_t aOldChildCount)
{
// Fire mutation events. Optimize for the case when there are no listeners
int32_t newChildCount = aDest->GetChildCount();
if (newChildCount && nsContentUtils::
HasMutationListeners(aDoc, NS_EVENT_BITS_MUTATION_NODEINSERTED)) {
nsAutoTArray<nsCOMPtr<nsIContent>, 50> childNodes;
NS_ASSERTION(newChildCount - aOldChildCount >= 0,
"What, some unexpected dom mutation has happened?");
childNodes.SetCapacity(newChildCount - aOldChildCount);
for (nsIContent* child = aDest->GetFirstChild();
child;
child = child->GetNextSibling()) {
childNodes.AppendElement(child);
}
Element::FireNodeInserted(aDoc, aDest, childNodes);
}
}
NS_IMETHODIMP
Element::GetInnerHTML(nsAString& aInnerHTML)
{
@ -3303,63 +2580,7 @@ Element::GetInnerHTML(nsAString& aInnerHTML)
void
Element::SetInnerHTML(const nsAString& aInnerHTML, ErrorResult& aError)
{
FragmentOrElement* target = this;
// Handle template case.
if (nsNodeUtils::IsTemplateElement(target)) {
DocumentFragment* frag =
static_cast<HTMLTemplateElement*>(target)->Content();
MOZ_ASSERT(frag);
target = frag;
}
nsIDocument* doc = target->OwnerDoc();
// Batch possible DOMSubtreeModified events.
mozAutoSubtreeModified subtree(doc, nullptr);
target->FireNodeRemovedForChildren();
// Needed when innerHTML is used in combination with contenteditable
mozAutoDocUpdate updateBatch(doc, UPDATE_CONTENT_MODEL, true);
// Remove childnodes.
uint32_t childCount = target->GetChildCount();
nsAutoMutationBatch mb(target, true, false);
for (uint32_t i = 0; i < childCount; ++i) {
target->RemoveChildAt(0, true);
}
mb.RemovalDone();
nsAutoScriptLoaderDisabler sld(doc);
if (doc->IsHTML()) {
int32_t oldChildCount = target->GetChildCount();
aError = nsContentUtils::ParseFragmentHTML(aInnerHTML,
target,
Tag(),
GetNameSpaceID(),
doc->GetCompatibilityMode() ==
eCompatibility_NavQuirks,
true);
mb.NodesAdded();
// HTML5 parser has notified, but not fired mutation events.
FireMutationEventsForDirectParsing(doc, target, oldChildCount);
} else {
nsCOMPtr<nsIDOMDocumentFragment> df;
aError = nsContentUtils::CreateContextualFragment(target, aInnerHTML,
true,
getter_AddRefs(df));
nsCOMPtr<nsINode> fragment = do_QueryInterface(df);
if (!aError.Failed()) {
// Suppress assertion about node removal mutation events that can't have
// listeners anyway, because no one has had the chance to register mutation
// listeners on the fragment that comes from the parser.
nsAutoScriptBlockerSuppressNodeRemoved scriptBlocker;
static_cast<nsINode*>(target)->AppendChild(*fragment, aError);
mb.NodesAdded();
}
}
SetInnerHTMLInternal(aInnerHTML, aError);
}
void
@ -3499,7 +2720,8 @@ Element::InsertAdjacentHTML(const nsAString& aPosition, const nsAString& aText,
eCompatibility_NavQuirks,
true);
// HTML5 parser has notified, but not fired mutation events.
FireMutationEventsForDirectParsing(doc, destination, oldChildCount);
nsContentUtils::FireMutationEventsForDirectParsing(doc, destination,
oldChildCount);
return;
}

View File

@ -21,6 +21,7 @@
#include "nsIAtom.h"
#include "nsINodeInfo.h"
#include "nsIDocumentInlines.h"
#include "nsIDocumentEncoder.h"
#include "nsIDOMNodeList.h"
#include "nsIDOMDocument.h"
#include "nsIContentIterator.h"
@ -73,6 +74,8 @@
#include "nsLayoutUtils.h"
#include "nsGkAtoms.h"
#include "nsContentUtils.h"
#include "nsTextFragment.h"
#include "nsContentCID.h"
#include "nsIDOMEventListener.h"
#include "nsIWebNavigation.h"
@ -121,6 +124,7 @@
#include "mozilla/CORSMode.h"
#include "mozilla/dom/ShadowRoot.h"
#include "mozilla/dom/HTMLTemplateElement.h"
#include "nsStyledElement.h"
@ -1902,6 +1906,771 @@ FragmentOrElement::IndexOf(const nsINode* aPossibleChild) const
return mAttrsAndChildren.IndexOfChild(aPossibleChild);
}
// Try to keep the size of StringBuilder close to a jemalloc bucket size.
#define STRING_BUFFER_UNITS 1020
namespace {
// We put StringBuilder in the anonymous namespace to prevent anything outside
// this file from accidentally being linked against it.
class StringBuilder
{
private:
class Unit
{
public:
Unit() : mAtom(nullptr), mType(eUnknown), mLength(0)
{
MOZ_COUNT_CTOR(StringBuilder::Unit);
}
~Unit()
{
if (mType == eString || mType == eStringWithEncode) {
delete mString;
}
MOZ_COUNT_DTOR(StringBuilder::Unit);
}
enum Type
{
eUnknown,
eAtom,
eString,
eStringWithEncode,
eLiteral,
eTextFragment,
eTextFragmentWithEncode,
};
union
{
nsIAtom* mAtom;
const char* mLiteral;
nsAutoString* mString;
const nsTextFragment* mTextFragment;
};
Type mType;
uint32_t mLength;
};
public:
StringBuilder() : mLast(MOZ_THIS_IN_INITIALIZER_LIST()), mLength(0)
{
MOZ_COUNT_CTOR(StringBuilder);
}
~StringBuilder()
{
MOZ_COUNT_DTOR(StringBuilder);
}
void Append(nsIAtom* aAtom)
{
Unit* u = AddUnit();
u->mAtom = aAtom;
u->mType = Unit::eAtom;
uint32_t len = aAtom->GetLength();
u->mLength = len;
mLength += len;
}
template<int N>
void Append(const char (&aLiteral)[N])
{
Unit* u = AddUnit();
u->mLiteral = aLiteral;
u->mType = Unit::eLiteral;
uint32_t len = N - 1;
u->mLength = len;
mLength += len;
}
template<int N>
void Append(char (&aLiteral)[N])
{
Unit* u = AddUnit();
u->mLiteral = aLiteral;
u->mType = Unit::eLiteral;
uint32_t len = N - 1;
u->mLength = len;
mLength += len;
}
void Append(const nsAString& aString)
{
Unit* u = AddUnit();
u->mString = new nsAutoString(aString);
u->mType = Unit::eString;
uint32_t len = aString.Length();
u->mLength = len;
mLength += len;
}
void Append(nsAutoString* aString)
{
Unit* u = AddUnit();
u->mString = aString;
u->mType = Unit::eString;
uint32_t len = aString->Length();
u->mLength = len;
mLength += len;
}
void AppendWithAttrEncode(nsAutoString* aString, uint32_t aLen)
{
Unit* u = AddUnit();
u->mString = aString;
u->mType = Unit::eStringWithEncode;
u->mLength = aLen;
mLength += aLen;
}
void Append(const nsTextFragment* aTextFragment)
{
Unit* u = AddUnit();
u->mTextFragment = aTextFragment;
u->mType = Unit::eTextFragment;
uint32_t len = aTextFragment->GetLength();
u->mLength = len;
mLength += len;
}
void AppendWithEncode(const nsTextFragment* aTextFragment, uint32_t aLen)
{
Unit* u = AddUnit();
u->mTextFragment = aTextFragment;
u->mType = Unit::eTextFragmentWithEncode;
u->mLength = aLen;
mLength += aLen;
}
bool ToString(nsAString& aOut)
{
if (!aOut.SetCapacity(mLength, fallible_t())) {
return false;
}
for (StringBuilder* current = this; current; current = current->mNext) {
uint32_t len = current->mUnits.Length();
for (uint32_t i = 0; i < len; ++i) {
Unit& u = current->mUnits[i];
switch (u.mType) {
case Unit::eAtom:
aOut.Append(nsDependentAtomString(u.mAtom));
break;
case Unit::eString:
aOut.Append(*(u.mString));
break;
case Unit::eStringWithEncode:
EncodeAttrString(*(u.mString), aOut);
break;
case Unit::eLiteral:
aOut.AppendASCII(u.mLiteral, u.mLength);
break;
case Unit::eTextFragment:
u.mTextFragment->AppendTo(aOut);
break;
case Unit::eTextFragmentWithEncode:
EncodeTextFragment(u.mTextFragment, aOut);
break;
default:
MOZ_CRASH("Unknown unit type?");
}
}
}
return true;
}
private:
Unit* AddUnit()
{
if (mLast->mUnits.Length() == STRING_BUFFER_UNITS) {
new StringBuilder(this);
}
return mLast->mUnits.AppendElement();
}
StringBuilder(StringBuilder* aFirst)
: mLast(nullptr), mLength(0)
{
MOZ_COUNT_CTOR(StringBuilder);
aFirst->mLast->mNext = this;
aFirst->mLast = this;
}
void EncodeAttrString(const nsAutoString& aValue, nsAString& aOut)
{
const PRUnichar* c = aValue.BeginReading();
const PRUnichar* end = aValue.EndReading();
while (c < end) {
switch (*c) {
case '"':
aOut.AppendLiteral("&quot;");
break;
case '&':
aOut.AppendLiteral("&amp;");
break;
case 0x00A0:
aOut.AppendLiteral("&nbsp;");
break;
default:
aOut.Append(*c);
break;
}
++c;
}
}
void EncodeTextFragment(const nsTextFragment* aValue, nsAString& aOut)
{
uint32_t len = aValue->GetLength();
if (aValue->Is2b()) {
const PRUnichar* data = aValue->Get2b();
for (uint32_t i = 0; i < len; ++i) {
const PRUnichar c = data[i];
switch (c) {
case '<':
aOut.AppendLiteral("&lt;");
break;
case '>':
aOut.AppendLiteral("&gt;");
break;
case '&':
aOut.AppendLiteral("&amp;");
break;
case 0x00A0:
aOut.AppendLiteral("&nbsp;");
break;
default:
aOut.Append(c);
break;
}
}
} else {
const char* data = aValue->Get1b();
for (uint32_t i = 0; i < len; ++i) {
const unsigned char c = data[i];
switch (c) {
case '<':
aOut.AppendLiteral("&lt;");
break;
case '>':
aOut.AppendLiteral("&gt;");
break;
case '&':
aOut.AppendLiteral("&amp;");
break;
case 0x00A0:
aOut.AppendLiteral("&nbsp;");
break;
default:
aOut.Append(c);
break;
}
}
}
}
nsAutoTArray<Unit, STRING_BUFFER_UNITS> mUnits;
nsAutoPtr<StringBuilder> mNext;
StringBuilder* mLast;
// mLength is used only in the first StringBuilder object in the linked list.
uint32_t mLength;
};
} // anonymous namespace
static void
AppendEncodedCharacters(const nsTextFragment* aText, StringBuilder& aBuilder)
{
uint32_t extraSpaceNeeded = 0;
uint32_t len = aText->GetLength();
if (aText->Is2b()) {
const PRUnichar* data = aText->Get2b();
for (uint32_t i = 0; i < len; ++i) {
const PRUnichar c = data[i];
switch (c) {
case '<':
extraSpaceNeeded += ArrayLength("&lt;") - 2;
break;
case '>':
extraSpaceNeeded += ArrayLength("&gt;") - 2;
break;
case '&':
extraSpaceNeeded += ArrayLength("&amp;") - 2;
break;
case 0x00A0:
extraSpaceNeeded += ArrayLength("&nbsp;") - 2;
break;
default:
break;
}
}
} else {
const char* data = aText->Get1b();
for (uint32_t i = 0; i < len; ++i) {
const unsigned char c = data[i];
switch (c) {
case '<':
extraSpaceNeeded += ArrayLength("&lt;") - 2;
break;
case '>':
extraSpaceNeeded += ArrayLength("&gt;") - 2;
break;
case '&':
extraSpaceNeeded += ArrayLength("&amp;") - 2;
break;
case 0x00A0:
extraSpaceNeeded += ArrayLength("&nbsp;") - 2;
break;
default:
break;
}
}
}
if (extraSpaceNeeded) {
aBuilder.AppendWithEncode(aText, len + extraSpaceNeeded);
} else {
aBuilder.Append(aText);
}
}
static void
AppendEncodedAttributeValue(nsAutoString* aValue, StringBuilder& aBuilder)
{
const PRUnichar* c = aValue->BeginReading();
const PRUnichar* end = aValue->EndReading();
uint32_t extraSpaceNeeded = 0;
while (c < end) {
switch (*c) {
case '"':
extraSpaceNeeded += ArrayLength("&quot;") - 2;
break;
case '&':
extraSpaceNeeded += ArrayLength("&amp;") - 2;
break;
case 0x00A0:
extraSpaceNeeded += ArrayLength("&nbsp;") - 2;
break;
default:
break;
}
++c;
}
if (extraSpaceNeeded) {
aBuilder.AppendWithAttrEncode(aValue, aValue->Length() + extraSpaceNeeded);
} else {
aBuilder.Append(aValue);
}
}
static void
StartElement(Element* aContent, StringBuilder& aBuilder)
{
nsIAtom* localName = aContent->Tag();
int32_t tagNS = aContent->GetNameSpaceID();
aBuilder.Append("<");
if (aContent->IsHTML() || aContent->IsSVG() || aContent->IsMathML()) {
aBuilder.Append(localName);
} else {
aBuilder.Append(aContent->NodeName());
}
int32_t count = aContent->GetAttrCount();
for (int32_t i = count; i > 0;) {
--i;
const nsAttrName* name = aContent->GetAttrNameAt(i);
int32_t attNs = name->NamespaceID();
nsIAtom* attName = name->LocalName();
// Filter out any attribute starting with [-|_]moz
nsDependentAtomString attrNameStr(attName);
if (StringBeginsWith(attrNameStr, NS_LITERAL_STRING("_moz")) ||
StringBeginsWith(attrNameStr, NS_LITERAL_STRING("-moz"))) {
continue;
}
nsAutoString* attValue = new nsAutoString();
aContent->GetAttr(attNs, attName, *attValue);
// Filter out special case of <br type="_moz*"> used by the editor.
// Bug 16988. Yuck.
if (localName == nsGkAtoms::br && tagNS == kNameSpaceID_XHTML &&
attName == nsGkAtoms::type && attNs == kNameSpaceID_None &&
StringBeginsWith(*attValue, NS_LITERAL_STRING("_moz"))) {
delete attValue;
continue;
}
if (MOZ_LIKELY(attNs == kNameSpaceID_None) ||
(attNs == kNameSpaceID_XMLNS &&
attName == nsGkAtoms::xmlns)) {
aBuilder.Append(" ");
} else if (attNs == kNameSpaceID_XML) {
aBuilder.Append(" xml:");
} else if (attNs == kNameSpaceID_XMLNS) {
aBuilder.Append(" xmlns:");
} else if (attNs == kNameSpaceID_XLink) {
aBuilder.Append(" xlink:");
} else {
nsIAtom* prefix = name->GetPrefix();
if (prefix) {
aBuilder.Append(" ");
aBuilder.Append(prefix);
aBuilder.Append(":");
}
}
aBuilder.Append(attName);
aBuilder.Append("=\"");
AppendEncodedAttributeValue(attValue, aBuilder);
aBuilder.Append("\"");
}
aBuilder.Append(">");
/*
// Per HTML spec we should append one \n if the first child of
// pre/textarea/listing is a textnode and starts with a \n.
// But because browsers haven't traditionally had that behavior,
// we're not changing our behavior either - yet.
if (aContent->IsHTML()) {
if (localName == nsGkAtoms::pre || localName == nsGkAtoms::textarea ||
localName == nsGkAtoms::listing) {
nsIContent* fc = aContent->GetFirstChild();
if (fc &&
(fc->NodeType() == nsIDOMNode::TEXT_NODE ||
fc->NodeType() == nsIDOMNode::CDATA_SECTION_NODE)) {
const nsTextFragment* text = fc->GetText();
if (text && text->GetLength() && text->CharAt(0) == PRUnichar('\n')) {
aBuilder.Append("\n");
}
}
}
}*/
}
static inline bool
ShouldEscape(nsIContent* aParent)
{
if (!aParent || !aParent->IsHTML()) {
return true;
}
static const nsIAtom* nonEscapingElements[] = {
nsGkAtoms::style, nsGkAtoms::script, nsGkAtoms::xmp,
nsGkAtoms::iframe, nsGkAtoms::noembed, nsGkAtoms::noframes,
nsGkAtoms::plaintext,
// Per the current spec noscript should be escaped in case
// scripts are disabled or if document doesn't have
// browsing context. However the latter seems to be a spec bug
// and Gecko hasn't traditionally done the former.
nsGkAtoms::noscript
};
static mozilla::BloomFilter<12, nsIAtom> sFilter;
static bool sInitialized = false;
if (!sInitialized) {
sInitialized = true;
for (uint32_t i = 0; i < ArrayLength(nonEscapingElements); ++i) {
sFilter.add(nonEscapingElements[i]);
}
}
nsIAtom* tag = aParent->Tag();
if (sFilter.mightContain(tag)) {
for (uint32_t i = 0; i < ArrayLength(nonEscapingElements); ++i) {
if (tag == nonEscapingElements[i]) {
return false;
}
}
}
return true;
}
static inline bool
IsVoidTag(Element* aElement)
{
if (!aElement->IsHTML()) {
return false;
}
static const nsIAtom* voidElements[] = {
nsGkAtoms::area, nsGkAtoms::base, nsGkAtoms::basefont,
nsGkAtoms::bgsound, nsGkAtoms::br, nsGkAtoms::col,
nsGkAtoms::command, nsGkAtoms::embed, nsGkAtoms::frame,
nsGkAtoms::hr, nsGkAtoms::img, nsGkAtoms::input,
nsGkAtoms::keygen, nsGkAtoms::link, nsGkAtoms::meta,
nsGkAtoms::param, nsGkAtoms::source, nsGkAtoms::track,
nsGkAtoms::wbr
};
static mozilla::BloomFilter<12, nsIAtom> sFilter;
static bool sInitialized = false;
if (!sInitialized) {
sInitialized = true;
for (uint32_t i = 0; i < ArrayLength(voidElements); ++i) {
sFilter.add(voidElements[i]);
}
}
nsIAtom* tag = aElement->Tag();
if (sFilter.mightContain(tag)) {
for (uint32_t i = 0; i < ArrayLength(voidElements); ++i) {
if (tag == voidElements[i]) {
return true;
}
}
}
return false;
}
static bool
Serialize(FragmentOrElement* aRoot, bool aDescendentsOnly, nsAString& aOut)
{
nsINode* current = aDescendentsOnly ?
nsNodeUtils::GetFirstChildOfTemplateOrNode(aRoot) : aRoot;
if (!current) {
return true;
}
StringBuilder builder;
nsIContent* next;
while (true) {
bool isVoid = false;
switch (current->NodeType()) {
case nsIDOMNode::ELEMENT_NODE: {
Element* elem = current->AsElement();
StartElement(elem, builder);
isVoid = IsVoidTag(elem);
if (!isVoid &&
(next = nsNodeUtils::GetFirstChildOfTemplateOrNode(current))) {
current = next;
continue;
}
break;
}
case nsIDOMNode::TEXT_NODE:
case nsIDOMNode::CDATA_SECTION_NODE: {
const nsTextFragment* text = static_cast<nsIContent*>(current)->GetText();
nsIContent* parent = current->GetParent();
if (ShouldEscape(parent)) {
AppendEncodedCharacters(text, builder);
} else {
builder.Append(text);
}
break;
}
case nsIDOMNode::COMMENT_NODE: {
builder.Append("<!--");
builder.Append(static_cast<nsIContent*>(current)->GetText());
builder.Append("-->");
break;
}
case nsIDOMNode::DOCUMENT_TYPE_NODE: {
builder.Append("<!DOCTYPE ");
builder.Append(current->NodeName());
builder.Append(">");
break;
}
case nsIDOMNode::PROCESSING_INSTRUCTION_NODE: {
builder.Append("<?");
builder.Append(current->NodeName());
builder.Append(" ");
builder.Append(static_cast<nsIContent*>(current)->GetText());
builder.Append(">");
break;
}
}
while (true) {
if (!isVoid && current->NodeType() == nsIDOMNode::ELEMENT_NODE) {
builder.Append("</");
nsIContent* elem = static_cast<nsIContent*>(current);
if (elem->IsHTML() || elem->IsSVG() || elem->IsMathML()) {
builder.Append(elem->Tag());
} else {
builder.Append(current->NodeName());
}
builder.Append(">");
}
isVoid = false;
if (current == aRoot) {
return builder.ToString(aOut);
}
if ((next = current->GetNextSibling())) {
current = next;
break;
}
current = current->GetParentNode();
// Template case, if we are in a template's content, then the parent
// should be the host template element.
if (current->NodeType() == nsIDOMNode::DOCUMENT_FRAGMENT_NODE) {
DocumentFragment* frag = static_cast<DocumentFragment*>(current);
nsIContent* fragHost = frag->GetHost();
if (fragHost && nsNodeUtils::IsTemplateElement(fragHost)) {
current = fragHost;
}
}
if (aDescendentsOnly && current == aRoot) {
return builder.ToString(aOut);
}
}
}
}
void
FragmentOrElement::GetMarkup(bool aIncludeSelf, nsAString& aMarkup)
{
aMarkup.Truncate();
nsIDocument* doc = OwnerDoc();
if (IsInHTMLDocument()) {
Serialize(this, !aIncludeSelf, aMarkup);
return;
}
nsAutoString contentType;
doc->GetContentType(contentType);
bool tryToCacheEncoder = !aIncludeSelf;
nsCOMPtr<nsIDocumentEncoder> docEncoder = doc->GetCachedEncoder();
if (!docEncoder) {
docEncoder =
do_CreateInstance(PromiseFlatCString(
nsDependentCString(NS_DOC_ENCODER_CONTRACTID_BASE) +
NS_ConvertUTF16toUTF8(contentType)
).get());
}
if (!docEncoder) {
// This could be some type for which we create a synthetic document. Try
// again as XML
contentType.AssignLiteral("application/xml");
docEncoder = do_CreateInstance(NS_DOC_ENCODER_CONTRACTID_BASE "application/xml");
// Don't try to cache the encoder since it would point to a different
// contentType once it has been reinitialized.
tryToCacheEncoder = false;
}
NS_ENSURE_TRUE_VOID(docEncoder);
uint32_t flags = nsIDocumentEncoder::OutputEncodeBasicEntities |
// Output DOM-standard newlines
nsIDocumentEncoder::OutputLFLineBreak |
// Don't do linebreaking that's not present in
// the source
nsIDocumentEncoder::OutputRaw |
// Only check for mozdirty when necessary (bug 599983)
nsIDocumentEncoder::OutputIgnoreMozDirty;
if (IsEditable()) {
nsCOMPtr<Element> elem = do_QueryInterface(this);
nsIEditor* editor = elem ? elem->GetEditorInternal() : nullptr;
if (editor && editor->OutputsMozDirty()) {
flags &= ~nsIDocumentEncoder::OutputIgnoreMozDirty;
}
}
DebugOnly<nsresult> rv = docEncoder->NativeInit(doc, contentType, flags);
MOZ_ASSERT(NS_SUCCEEDED(rv));
if (aIncludeSelf) {
docEncoder->SetNativeNode(this);
} else {
docEncoder->SetNativeContainerNode(this);
}
rv = docEncoder->EncodeToString(aMarkup);
MOZ_ASSERT(NS_SUCCEEDED(rv));
if (tryToCacheEncoder) {
doc->SetCachedEncoder(docEncoder.forget());
}
}
void
FragmentOrElement::SetInnerHTMLInternal(const nsAString& aInnerHTML, ErrorResult& aError)
{
FragmentOrElement* target = this;
// Handle template case.
if (nsNodeUtils::IsTemplateElement(target)) {
DocumentFragment* frag =
static_cast<HTMLTemplateElement*>(target)->Content();
MOZ_ASSERT(frag);
target = frag;
}
nsIDocument* doc = target->OwnerDoc();
// Batch possible DOMSubtreeModified events.
mozAutoSubtreeModified subtree(doc, nullptr);
target->FireNodeRemovedForChildren();
// Needed when innerHTML is used in combination with contenteditable
mozAutoDocUpdate updateBatch(doc, UPDATE_CONTENT_MODEL, true);
// Remove childnodes.
uint32_t childCount = target->GetChildCount();
nsAutoMutationBatch mb(target, true, false);
for (uint32_t i = 0; i < childCount; ++i) {
target->RemoveChildAt(0, true);
}
mb.RemovalDone();
nsAutoScriptLoaderDisabler sld(doc);
nsIAtom* contextLocalName = Tag();
int32_t contextNameSpaceID = GetNameSpaceID();
ShadowRoot* shadowRoot = ShadowRoot::FromNode(this);
if (shadowRoot) {
// Fix up the context to be the host of the ShadowRoot.
contextLocalName = shadowRoot->GetHost()->Tag();
contextNameSpaceID = shadowRoot->GetHost()->GetNameSpaceID();
}
if (doc->IsHTML()) {
int32_t oldChildCount = target->GetChildCount();
aError = nsContentUtils::ParseFragmentHTML(aInnerHTML,
target,
contextLocalName,
contextNameSpaceID,
doc->GetCompatibilityMode() ==
eCompatibility_NavQuirks,
true);
mb.NodesAdded();
// HTML5 parser has notified, but not fired mutation events.
nsContentUtils::FireMutationEventsForDirectParsing(doc, target,
oldChildCount);
} else {
nsCOMPtr<nsIDOMDocumentFragment> df;
aError = nsContentUtils::CreateContextualFragment(target, aInnerHTML,
true,
getter_AddRefs(df));
nsCOMPtr<nsINode> fragment = do_QueryInterface(df);
if (!aError.Failed()) {
// Suppress assertion about node removal mutation events that can't have
// listeners anyway, because no one has had the chance to register mutation
// listeners on the fragment that comes from the parser.
nsAutoScriptBlockerSuppressNodeRemoved scriptBlocker;
static_cast<nsINode*>(target)->AppendChild(*fragment, aError);
mb.NodesAdded();
}
}
}
nsINode::nsSlots*
FragmentOrElement::CreateSlots()
{

View File

@ -150,3 +150,15 @@ ShadowRoot::PrefEnabled()
return Preferences::GetBool("dom.webcomponents.enabled", false);
}
void
ShadowRoot::GetInnerHTML(nsAString& aInnerHTML)
{
GetMarkup(false, aInnerHTML);
}
void
ShadowRoot::SetInnerHTML(const nsAString& aInnerHTML, ErrorResult& aError)
{
SetInnerHTMLInternal(aInnerHTML, aError);
}

View File

@ -57,6 +57,8 @@ public:
const nsAString& aLocalName);
already_AddRefed<nsContentList>
GetElementsByClassName(const nsAString& aClasses);
void GetInnerHTML(nsAString& aInnerHTML);
void SetInnerHTML(const nsAString& aInnerHTML, ErrorResult& aError);
protected:
nsCOMPtr<nsIContent> mHost;
nsTHashtable<nsIdentifierMapEntry> mIdentifierMap;

View File

@ -6404,6 +6404,29 @@ nsContentUtils::HasPluginWithUncontrolledEventDispatch(nsIContent* aContent)
return result;
}
/* static */
void
nsContentUtils::FireMutationEventsForDirectParsing(nsIDocument* aDoc,
nsIContent* aDest,
int32_t aOldChildCount)
{
// Fire mutation events. Optimize for the case when there are no listeners
int32_t newChildCount = aDest->GetChildCount();
if (newChildCount && nsContentUtils::
HasMutationListeners(aDoc, NS_EVENT_BITS_MUTATION_NODEINSERTED)) {
nsAutoTArray<nsCOMPtr<nsIContent>, 50> childNodes;
NS_ASSERTION(newChildCount - aOldChildCount >= 0,
"What, some unexpected dom mutation has happened?");
childNodes.SetCapacity(newChildCount - aOldChildCount);
for (nsIContent* child = aDest->GetFirstChild();
child;
child = child->GetNextSibling()) {
childNodes.AppendElement(child);
}
FragmentOrElement::FireNodeInserted(aDoc, aDest, childNodes);
}
}
/* static */
nsIDocument*
nsContentUtils::GetFullscreenAncestor(nsIDocument* aDoc)

View File

@ -73,6 +73,11 @@ try {
ok(true, "Excpetion should be thrown when creating a cycle with host content.");
}
// Basic innerHTML tests.
shadow.innerHTML = '<span id="first"></span><div id="second"></div>';
is(shadow.childNodes.length, 2, "There should be two children in the ShadowRoot.");
is(shadow.getElementById("second").tagName, "DIV", "Elements created by innerHTML should be accessible by ShadowRoot API.");
</script>
</body>
</html>

View File

@ -17,5 +17,7 @@ interface ShadowRoot : DocumentFragment
HTMLCollection getElementsByTagName(DOMString localName);
HTMLCollection getElementsByTagNameNS(DOMString? namespace, DOMString localName);
HTMLCollection getElementsByClassName(DOMString classNames);
[SetterThrows,TreatNullAs=EmptyString]
attribute DOMString innerHTML;
};