From b9bbd3c2ccf25d2ed7ae6b8a7447113e983180c0 Mon Sep 17 00:00:00 2001 From: Adam Vandolder Date: Thu, 7 Dec 2023 21:27:10 +0000 Subject: [PATCH] Bug 1712140 - Part 3: Add support for parsing and building Declarative ShadowDOMs. r=dom-core,webidl,saschanaz,hsivonen Differential Revision: https://phabricator.services.mozilla.com/D193675 --- dom/base/Document.cpp | 11 ++++ dom/base/Document.h | 5 ++ dom/base/ShadowRoot.h | 3 ++ dom/base/nsContentUtils.cpp | 49 ++++++++++++++++- dom/base/nsContentUtils.h | 10 ++++ dom/base/nsINode.cpp | 33 ++++++++++++ dom/html/HTMLTemplateElement.cpp | 45 +++++++++++++++- dom/html/HTMLTemplateElement.h | 30 +++++++++++ dom/webidl/HTMLTemplateElement.webidl | 6 ++- layout/build/nsContentDLF.cpp | 1 + parser/html/javasrc/TreeBuilder.java | 44 +++++++++++++++- parser/html/nsHtml5Parser.cpp | 2 + parser/html/nsHtml5StreamParser.cpp | 2 + parser/html/nsHtml5StringParser.cpp | 23 ++++---- parser/html/nsHtml5StringParser.h | 8 ++- parser/html/nsHtml5TreeBuilder.cpp | 38 +++++++++++++- parser/html/nsHtml5TreeBuilder.h | 6 +++ parser/html/nsHtml5TreeBuilderCppSupplement.h | 52 +++++++++++++++++++ parser/html/nsHtml5TreeBuilderHSupplement.h | 7 +++ parser/html/nsHtml5TreeOperation.cpp | 38 ++++++++++++++ parser/html/nsHtml5TreeOperation.h | 37 ++++++++++++- 21 files changed, 427 insertions(+), 23 deletions(-) diff --git a/dom/base/Document.cpp b/dom/base/Document.cpp index ce6596b1b5d9..7e055d0d1872 100644 --- a/dom/base/Document.cpp +++ b/dom/base/Document.cpp @@ -1394,6 +1394,7 @@ Document::Document(const char* aContentType) mHasUserInteractionTimerScheduled(false), mShouldResistFingerprinting(false), mCloningForSVGUse(false), + mAllowDeclarativeShadowRoots(false), mXMLDeclarationBits(0), mOnloadBlockCount(0), mWriteLevel(0), @@ -18964,4 +18965,14 @@ void Document::UpdateHiddenByContentVisibilityForAnimations() { timeline->UpdateHiddenByContentVisibility(); } } + +void Document::SetAllowDeclarativeShadowRoots( + bool aAllowDeclarativeShadowRoots) { + mAllowDeclarativeShadowRoots = aAllowDeclarativeShadowRoots; +} + +bool Document::AllowsDeclarativeShadowRoots() const { + return mAllowDeclarativeShadowRoots; +} + } // namespace mozilla::dom diff --git a/dom/base/Document.h b/dom/base/Document.h index e253cf41b0f3..86502bace160 100644 --- a/dom/base/Document.h +++ b/dom/base/Document.h @@ -3866,6 +3866,9 @@ class Document : public nsINode, */ bool AllowsL10n() const; + void SetAllowDeclarativeShadowRoots(bool aAllowDeclarativeShadowRoots); + bool AllowsDeclarativeShadowRoots() const; + protected: RefPtr mDocumentL10n; @@ -4830,6 +4833,8 @@ class Document : public nsINode, // Whether we're cloning the contents of an SVG use element. bool mCloningForSVGUse : 1; + bool mAllowDeclarativeShadowRoots : 1; + // The fingerprinting protections overrides for this document. The value will // override the default enabled fingerprinting protections for this document. // This will only get populated if these is one that comes from the local diff --git a/dom/base/ShadowRoot.h b/dom/base/ShadowRoot.h index ab6041c318a2..9fe459e72be8 100644 --- a/dom/base/ShadowRoot.h +++ b/dom/base/ShadowRoot.h @@ -239,6 +239,9 @@ class ShadowRoot final : public DocumentFragment, public DocumentOrShadowRoot { void SetIsDeclarative(Declarative aIsDeclarative) { mIsDeclarative = aIsDeclarative; } + void SetIsDeclarative(bool aIsDeclarative) { + mIsDeclarative = aIsDeclarative ? Declarative::Yes : Declarative::No; + } bool IsClonable() const { return mIsClonable == Clonable::Yes; } diff --git a/dom/base/nsContentUtils.cpp b/dom/base/nsContentUtils.cpp index 427e387ff3aa..d86c656d59f3 100644 --- a/dom/base/nsContentUtils.cpp +++ b/dom/base/nsContentUtils.cpp @@ -5446,6 +5446,34 @@ bool AllowsUnsanitizedContentForAboutNewTab(nsIPrincipal* aPrincipal) { return aboutModuleFlags & nsIAboutModule::ALLOW_UNSANITIZED_CONTENT; } +/* static */ +void nsContentUtils::SetHTMLUnsafe(FragmentOrElement* aTarget, + Element* aContext, + const nsAString& aSource) { + MOZ_ASSERT(!sFragmentParsingActive, "Re-entrant fragment parsing attempted."); + mozilla::AutoRestore guard(sFragmentParsingActive); + sFragmentParsingActive = true; + if (!sHTMLFragmentParser) { + NS_ADDREF(sHTMLFragmentParser = new nsHtml5StringParser()); + // Now sHTMLFragmentParser owns the object + } + + nsAtom* contextLocalName = aContext->NodeInfo()->NameAtom(); + int32_t contextNameSpaceID = aContext->GetNameSpaceID(); + + RefPtr doc = aTarget->OwnerDoc(); + RefPtr fragment = doc->CreateDocumentFragment(); + nsresult rv = sHTMLFragmentParser->ParseFragment( + aSource, fragment, contextLocalName, contextNameSpaceID, + fragment->OwnerDoc()->GetCompatibilityMode() == eCompatibility_NavQuirks, + true, true); + if (NS_FAILED(rv)) { + NS_WARNING("Failed to parse fragment for SetHTMLUnsafe"); + } + + aTarget->ReplaceChildren(fragment, IgnoreErrors()); +} + /* static */ nsresult nsContentUtils::ParseFragmentHTML( const nsAString& aSourceBuffer, nsIContent* aTargetNode, @@ -5501,7 +5529,7 @@ nsresult nsContentUtils::ParseFragmentHTML( nsresult rv = sHTMLFragmentParser->ParseFragment( aSourceBuffer, target, aContextLocalName, aContextNamespace, aQuirks, - aPreventScriptExecution); + aPreventScriptExecution, false); NS_ENSURE_SUCCESS(rv, rv); if (fragment) { @@ -11339,6 +11367,25 @@ template bool nsContentUtils::AddElementToListByTreeOrder( nsTArray>& aList, HTMLInputElement* aChild, nsIContent* aAncestor); +nsIContent* nsContentUtils::AttachDeclarativeShadowRoot(nsIContent* aHost, + ShadowRootMode aMode, + bool aDelegatesFocus) { + RefPtr host = mozilla::dom::Element::FromNodeOrNull(aHost); + if (!host) { + return nullptr; + } + + ShadowRootInit init; + init.mMode = aMode; + init.mDelegatesFocus = aDelegatesFocus; + init.mSlotAssignment = SlotAssignmentMode::Named; + init.mClonable = true; + + RefPtr shadowRoot = host->AttachShadow(init, IgnoreErrors(), + Element::ShadowRootDeclarative::Yes); + return shadowRoot; +} + namespace mozilla { std::ostream& operator<<(std::ostream& aOut, const PreventDefaultResult aPreventDefaultResult) { diff --git a/dom/base/nsContentUtils.h b/dom/base/nsContentUtils.h index 3aca84f01071..43572f09baec 100644 --- a/dom/base/nsContentUtils.h +++ b/dom/base/nsContentUtils.h @@ -176,6 +176,7 @@ class DOMArena; class Element; class Event; class EventTarget; +class FragmentOrElement; class HTMLElement; class HTMLInputElement; class IPCTransferable; @@ -187,6 +188,7 @@ class MessageBroadcaster; class NodeInfo; class OwningFileOrUSVStringOrFormData; class Selection; +enum class ShadowRootMode : uint8_t; struct StructuredSerializeOptions; class WorkerPrivate; enum class ElementCallbackType; @@ -1802,6 +1804,9 @@ class nsContentUtils { bool aPreventScriptExecution, mozilla::ErrorResult& aRv); + MOZ_CAN_RUN_SCRIPT + static void SetHTMLUnsafe(mozilla::dom::FragmentOrElement* aTarget, + Element* aContext, const nsAString& aSource); /** * Invoke the fragment parsing algorithm (innerHTML) using the HTML parser. * @@ -3457,6 +3462,11 @@ class nsContentUtils { nsIContent* aContent2, const nsIContent* aCommonAncestor); + MOZ_CAN_RUN_SCRIPT_BOUNDARY + static nsIContent* AttachDeclarativeShadowRoot( + nsIContent* aHost, mozilla::dom::ShadowRootMode aMode, + bool aDelegatesFocus); + private: static bool InitializeEventTable(); diff --git a/dom/base/nsINode.cpp b/dom/base/nsINode.cpp index 85047c38732e..7dcdd35846c4 100644 --- a/dom/base/nsINode.cpp +++ b/dom/base/nsINode.cpp @@ -37,6 +37,7 @@ #include "mozilla/dom/DebuggerNotificationBinding.h" #include "mozilla/dom/DocumentType.h" #include "mozilla/dom/Element.h" +#include "mozilla/dom/ElementBinding.h" #include "mozilla/dom/Event.h" #include "mozilla/dom/Exceptions.h" #include "mozilla/dom/Link.h" @@ -3620,6 +3621,38 @@ already_AddRefed nsINode::CloneAndAdopt( } } + if (aClone && aNode->IsElement() && + !nodeInfo->GetDocument()->IsStaticDocument()) { + // Clone the Shadow DOM + ShadowRoot* originalShadowRoot = aNode->AsElement()->GetShadowRoot(); + if (originalShadowRoot && originalShadowRoot->IsClonable()) { + ShadowRootInit init; + init.mMode = originalShadowRoot->Mode(); + init.mDelegatesFocus = originalShadowRoot->DelegatesFocus(); + init.mSlotAssignment = originalShadowRoot->SlotAssignment(); + init.mClonable = true; + + RefPtr newShadowRoot = + clone->AsElement()->AttachShadow(init, aError); + if (NS_WARN_IF(aError.Failed())) { + return nullptr; + } + newShadowRoot->SetIsDeclarative(originalShadowRoot->IsDeclarative()); + + if (aDeep) { + for (nsIContent* origChild = originalShadowRoot->GetFirstChild(); + origChild; origChild = origChild->GetNextSibling()) { + nsCOMPtr child = + CloneAndAdopt(origChild, aClone, aDeep, nodeInfoManager, + aReparentScope, newShadowRoot, aError); + if (NS_WARN_IF(aError.Failed())) { + return nullptr; + } + } + } + } + } + // Cloning template element. if (aDeep && aClone && aNode->IsTemplateElement()) { DocumentFragment* origContent = diff --git a/dom/html/HTMLTemplateElement.cpp b/dom/html/HTMLTemplateElement.cpp index 1ffe93b7f53b..419322b2d317 100644 --- a/dom/html/HTMLTemplateElement.cpp +++ b/dom/html/HTMLTemplateElement.cpp @@ -8,6 +8,9 @@ #include "mozilla/dom/HTMLTemplateElementBinding.h" #include "mozilla/dom/Document.h" +#include "mozilla/dom/NameSpaceConstants.h" +#include "mozilla/dom/ShadowRootBinding.h" +#include "nsGenericHTMLElement.h" #include "nsGkAtoms.h" #include "nsStyleConsts.h" #include "nsAtom.h" @@ -16,6 +19,13 @@ NS_IMPL_NS_NEW_HTML_ELEMENT(Template) namespace mozilla::dom { +static constexpr nsAttrValue::EnumTable kShadowRootModeTable[] = { + {"open", ShadowRootMode::Open}, + {"closed", ShadowRootMode::Closed}, + {nullptr, {}}}; + +const nsAttrValue::EnumTable* kShadowRootModeDefault = &kShadowRootModeTable[2]; + HTMLTemplateElement::HTMLTemplateElement( already_AddRefed&& aNodeInfo) : nsGenericHTMLElement(std::move(aNodeInfo)) { @@ -31,7 +41,7 @@ HTMLTemplateElement::HTMLTemplateElement( } HTMLTemplateElement::~HTMLTemplateElement() { - if (mContent) { + if (mContent && mContent->GetHost() == this) { mContent->SetHost(nullptr); } } @@ -44,7 +54,9 @@ NS_IMPL_CYCLE_COLLECTION_CLASS(HTMLTemplateElement) NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(HTMLTemplateElement, nsGenericHTMLElement) if (tmp->mContent) { - tmp->mContent->SetHost(nullptr); + if (tmp->mContent->GetHost() == tmp) { + tmp->mContent->SetHost(nullptr); + } tmp->mContent = nullptr; } NS_IMPL_CYCLE_COLLECTION_UNLINK_END @@ -61,4 +73,33 @@ JSObject* HTMLTemplateElement::WrapNode(JSContext* aCx, return HTMLTemplateElement_Binding::Wrap(aCx, this, aGivenProto); } +void HTMLTemplateElement::AfterSetAttr(int32_t aNamespaceID, nsAtom* aName, + const nsAttrValue* aValue, + const nsAttrValue* aOldValue, + nsIPrincipal* aMaybeScriptedPrincipal, + bool aNotify) { + if (aNamespaceID == kNameSpaceID_None && aName == nsGkAtoms::shadowrootmode && + aValue && aValue->Type() == nsAttrValue::ValueType::eEnum && + !mShadowRootMode.isSome()) { + mShadowRootMode.emplace( + static_cast(aValue->GetEnumValue())); + } + + nsGenericHTMLElement::AfterSetAttr(aNamespaceID, aName, aValue, aOldValue, + aMaybeScriptedPrincipal, aNotify); +} + +bool HTMLTemplateElement::ParseAttribute(int32_t aNamespaceID, + nsAtom* aAttribute, + const nsAString& aValue, + nsIPrincipal* aMaybeScriptedPrincipal, + nsAttrValue& aResult) { + if (aNamespaceID == kNameSpaceID_None && + aAttribute == nsGkAtoms::shadowrootmode) { + return aResult.ParseEnumValue(aValue, kShadowRootModeTable, false, nullptr); + } + return nsGenericHTMLElement::ParseAttribute(aNamespaceID, aAttribute, aValue, + aMaybeScriptedPrincipal, aResult); +} + } // namespace mozilla::dom diff --git a/dom/html/HTMLTemplateElement.h b/dom/html/HTMLTemplateElement.h index 085b39b55052..7e049e149211 100644 --- a/dom/html/HTMLTemplateElement.h +++ b/dom/html/HTMLTemplateElement.h @@ -8,8 +8,11 @@ #define mozilla_dom_HTMLTemplateElement_h #include "mozilla/Attributes.h" +#include "mozilla/ErrorResult.h" #include "nsGenericHTMLElement.h" #include "mozilla/dom/DocumentFragment.h" +#include "mozilla/dom/ShadowRootBinding.h" +#include "nsGkAtoms.h" namespace mozilla::dom { @@ -26,9 +29,35 @@ class HTMLTemplateElement final : public nsGenericHTMLElement { NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(HTMLTemplateElement, nsGenericHTMLElement) + void AfterSetAttr(int32_t aNamespaceID, nsAtom* aName, + const nsAttrValue* aValue, const nsAttrValue* aOldValue, + nsIPrincipal* aMaybeScriptedPrincipal, + bool aNotify) override; + + bool ParseAttribute(int32_t aNamespaceID, nsAtom* aAttribute, + const nsAString& aValue, + nsIPrincipal* aMaybeScriptedPrincipal, + nsAttrValue& aResult) override; + virtual nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; DocumentFragment* Content() { return mContent; } + void SetContent(DocumentFragment* aContent) { mContent = aContent; } + + void GetShadowRootMode(nsAString& aResult) const { + GetEnumAttr(nsGkAtoms::shadowrootmode, nullptr, aResult); + } + void SetShadowRootMode(const nsAString& aValue) { + SetHTMLAttr(nsGkAtoms::shadowrootmode, aValue); + } + + bool ShadowRootDelegatesFocus() { + return GetBoolAttr(nsGkAtoms::shadowrootdelegatesfocus); + } + void SetShadowRootDelegatesFocus(bool aValue) { + SetHTMLBoolAttr(nsGkAtoms::shadowrootdelegatesfocus, aValue, + IgnoredErrorResult()); + } protected: virtual ~HTMLTemplateElement(); @@ -37,6 +66,7 @@ class HTMLTemplateElement final : public nsGenericHTMLElement { JS::Handle aGivenProto) override; RefPtr mContent; + Maybe mShadowRootMode; }; } // namespace mozilla::dom diff --git a/dom/webidl/HTMLTemplateElement.webidl b/dom/webidl/HTMLTemplateElement.webidl index f65d00b0c2a1..ff17b16b91a8 100644 --- a/dom/webidl/HTMLTemplateElement.webidl +++ b/dom/webidl/HTMLTemplateElement.webidl @@ -3,7 +3,7 @@ * You can obtain one at http://mozilla.org/MPL/2.0/. * * The origin of this IDL file is - * https://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/templates/index.html + * https://html.spec.whatwg.org/multipage/scripting.html#the-template-element * * Copyright © 2012 W3C® (MIT, ERCIM, Keio), All Rights Reserved. W3C * liability, trademark and document use rules apply. @@ -13,5 +13,7 @@ interface HTMLTemplateElement : HTMLElement { [HTMLConstructor] constructor(); - readonly attribute DocumentFragment content; + readonly attribute DocumentFragment content; + [CEReactions] attribute DOMString shadowRootMode; + [CEReactions] attribute boolean shadowRootDelegatesFocus; }; diff --git a/layout/build/nsContentDLF.cpp b/layout/build/nsContentDLF.cpp index 60375d8e5657..7c4528e49fb7 100644 --- a/layout/build/nsContentDLF.cpp +++ b/layout/build/nsContentDLF.cpp @@ -314,6 +314,7 @@ nsresult nsContentDLF::CreateDocument( nsCOMPtr viewer = NS_NewDocumentViewer(); doc->SetContainer(static_cast(aContainer)); + doc->SetAllowDeclarativeShadowRoots(true); // Initialize the document to begin loading the data. An // nsIStreamListener connected to the parser is returned in diff --git a/parser/html/javasrc/TreeBuilder.java b/parser/html/javasrc/TreeBuilder.java index db5fe9474390..1fa67647e193 100644 --- a/parser/html/javasrc/TreeBuilder.java +++ b/parser/html/javasrc/TreeBuilder.java @@ -434,6 +434,8 @@ public abstract class TreeBuilder implements TokenHandler, private boolean forceNoQuirks = false; + private boolean allowDeclarativeShadowRoots = false; + // [NOCPP[ private boolean reportingDoctype = true; @@ -2958,6 +2960,20 @@ public abstract class TreeBuilder implements TokenHandler, || (("http://www.w3.org/1998/Math/MathML" == ns) && (stackNode.getGroup() == MI_MO_MN_MS_MTEXT)); } + private T getDeclarativeShadowRoot(T currentNode, T templateNode, HtmlAttributes attributes) { + if (!isAllowDeclarativeShadowRoots()) { + return null; + } + + String shadowRootMode = attributes.getValue(AttributeName.SHADOWROOTMODE); + if (shadowRootMode == null) { + return null; + } + + boolean shadowRootDelegatesFocus = attributes.contains(AttributeName.SHADOWROOTDELEGATESFOCUS); + return getShadowRootFromHost(currentNode, templateNode, shadowRootMode, shadowRootDelegatesFocus); + } + /** * *

@@ -5302,9 +5318,17 @@ public abstract class TreeBuilder implements TokenHandler, T elt = createElement("http://www.w3.org/1999/xhtml", elementName.getName(), attributes, currentNode // CPPONLY: , htmlCreator(elementName.getHtmlCreator()) ); - appendElement(elt, currentNode); if (ElementName.TEMPLATE == elementName) { - elt = getDocumentFragmentForTemplate(elt); + T root = getDeclarativeShadowRoot(currentNode, elt, attributes); + if (root != null) { + setDocumentFragmentForTemplate(elt, root); + elt = root; + } else { + appendElement(elt, currentNode); + elt = getDocumentFragmentForTemplate(elt); + } + } else { + appendElement(elt, currentNode); } StackNode node = createStackNode(elementName, elt // [NOCPP[ @@ -5391,6 +5415,13 @@ public abstract class TreeBuilder implements TokenHandler, return template; } + void setDocumentFragmentForTemplate(T template, T fragment) { + } + + T getShadowRootFromHost(T host, T template, String shadowRootMode, boolean shadowRootDelegatesFocus) { + return null; + } + T getFormPointerForContext(T context) { return null; } @@ -5509,6 +5540,7 @@ public abstract class TreeBuilder implements TokenHandler, } else { T currentNode = nodeFromStackWithBlinkCompat(currentPtr); elt = createElement("http://www.w3.org/1999/xhtml", name, + attributes, formOwner, currentNode // CPPONLY: , htmlCreator(elementName.getHtmlCreator()) ); @@ -5893,6 +5925,14 @@ public abstract class TreeBuilder implements TokenHandler, this.setForceNoQuirks(isSrcdocDocument); } + public boolean isAllowDeclarativeShadowRoots() { + return allowDeclarativeShadowRoots; + } + + public void setAllowDeclarativeShadowRoots(boolean allow) { + allowDeclarativeShadowRoots = allow; + } + // [NOCPP[ public void setNamePolicy(XmlViolationPolicy namePolicy) { diff --git a/parser/html/nsHtml5Parser.cpp b/parser/html/nsHtml5Parser.cpp index d234ece64e69..04097e56d931 100644 --- a/parser/html/nsHtml5Parser.cpp +++ b/parser/html/nsHtml5Parser.cpp @@ -678,6 +678,8 @@ void nsHtml5Parser::StartTokenizer(bool aScriptingEnabled) { mTreeBuilder->SetPreventScriptExecution(!aScriptingEnabled); mTreeBuilder->setScriptingEnabled(aScriptingEnabled); + mTreeBuilder->setAllowDeclarativeShadowRoots( + mExecutor->GetDocument()->AllowsDeclarativeShadowRoots()); mTokenizer->start(); } diff --git a/parser/html/nsHtml5StreamParser.cpp b/parser/html/nsHtml5StreamParser.cpp index d0334fba4cef..a6d9a0c73915 100644 --- a/parser/html/nsHtml5StreamParser.cpp +++ b/parser/html/nsHtml5StreamParser.cpp @@ -1125,6 +1125,8 @@ nsresult nsHtml5StreamParser::OnStartRequest(nsIRequest* aRequest) { mTreeBuilder->setScriptingEnabled(scriptingEnabled); mTreeBuilder->SetPreventScriptExecution( !((mMode == NORMAL) && scriptingEnabled)); + mTreeBuilder->setAllowDeclarativeShadowRoots( + mExecutor->GetDocument()->AllowsDeclarativeShadowRoots()); mTokenizer->start(); mExecutor->Start(); mExecutor->StartReadingFromStage(); diff --git a/parser/html/nsHtml5StringParser.cpp b/parser/html/nsHtml5StringParser.cpp index 98f643abb44a..5fe2b2e323cb 100644 --- a/parser/html/nsHtml5StringParser.cpp +++ b/parser/html/nsHtml5StringParser.cpp @@ -24,12 +24,10 @@ nsHtml5StringParser::nsHtml5StringParser() nsHtml5StringParser::~nsHtml5StringParser() {} -nsresult nsHtml5StringParser::ParseFragment(const nsAString& aSourceBuffer, - nsIContent* aTargetNode, - nsAtom* aContextLocalName, - int32_t aContextNamespace, - bool aQuirks, - bool aPreventScriptExecution) { +nsresult nsHtml5StringParser::ParseFragment( + const nsAString& aSourceBuffer, nsIContent* aTargetNode, + nsAtom* aContextLocalName, int32_t aContextNamespace, bool aQuirks, + bool aPreventScriptExecution, bool aAllowDeclarativeShadowRoots) { NS_ENSURE_TRUE(aSourceBuffer.Length() <= INT32_MAX, NS_ERROR_OUT_OF_MEMORY); Document* doc = aTargetNode->OwnerDoc(); @@ -52,7 +50,7 @@ nsresult nsHtml5StringParser::ParseFragment(const nsAString& aSourceBuffer, mTreeBuilder->SetPreventScriptExecution(aPreventScriptExecution); - return Tokenize(aSourceBuffer, doc, true); + return Tokenize(aSourceBuffer, doc, true, aAllowDeclarativeShadowRoots); } nsresult nsHtml5StringParser::ParseDocument( @@ -67,12 +65,14 @@ nsresult nsHtml5StringParser::ParseDocument( mTreeBuilder->SetPreventScriptExecution(true); return Tokenize(aSourceBuffer, aTargetDoc, - aScriptingEnabledForNoscriptParsing); + aScriptingEnabledForNoscriptParsing, + aTargetDoc->AllowsDeclarativeShadowRoots()); } -nsresult nsHtml5StringParser::Tokenize( - const nsAString& aSourceBuffer, Document* aDocument, - bool aScriptingEnabledForNoscriptParsing) { +nsresult nsHtml5StringParser::Tokenize(const nsAString& aSourceBuffer, + Document* aDocument, + bool aScriptingEnabledForNoscriptParsing, + bool aDeclarativeShadowRootsAllowed) { nsIURI* uri = aDocument->GetDocumentURI(); mBuilder->Init(aDocument, uri, nullptr, nullptr); @@ -85,6 +85,7 @@ nsresult nsHtml5StringParser::Tokenize( mTreeBuilder->setScriptingEnabled(aScriptingEnabledForNoscriptParsing); mTreeBuilder->setIsSrcdocDocument(aDocument->IsSrcdocDocument()); + mTreeBuilder->setAllowDeclarativeShadowRoots(aDeclarativeShadowRootsAllowed); mBuilder->Start(); mTokenizer->start(); if (!aSourceBuffer.IsEmpty()) { diff --git a/parser/html/nsHtml5StringParser.h b/parser/html/nsHtml5StringParser.h index 098a913748a1..1d4140484137 100644 --- a/parser/html/nsHtml5StringParser.h +++ b/parser/html/nsHtml5StringParser.h @@ -41,11 +41,14 @@ class nsHtml5StringParser : public nsParserBase { * @param aPreventScriptExecution true to prevent scripts from executing; * don't set to false when parsing into a target node that has been bound * to tree. + * @param aAllowDeclarativeShadowRoots allow the creation of declarative + * shadow roots. */ nsresult ParseFragment(const nsAString& aSourceBuffer, nsIContent* aTargetNode, nsAtom* aContextLocalName, int32_t aContextNamespace, bool aQuirks, - bool aPreventScriptExecution); + bool aPreventScriptExecution, + bool aAllowDeclarativeShadowRoots); /** * Parse an entire HTML document from a source string. @@ -61,7 +64,8 @@ class nsHtml5StringParser : public nsParserBase { nsresult Tokenize(const nsAString& aSourceBuffer, mozilla::dom::Document* aDocument, - bool aScriptingEnabledForNoscriptParsing); + bool aScriptingEnabledForNoscriptParsing, + bool aDeclarativeShadowRootsAllowed); /** * The tree operation executor diff --git a/parser/html/nsHtml5TreeBuilder.cpp b/parser/html/nsHtml5TreeBuilder.cpp index 6551612fd93d..af299105011a 100644 --- a/parser/html/nsHtml5TreeBuilder.cpp +++ b/parser/html/nsHtml5TreeBuilder.cpp @@ -2093,6 +2093,23 @@ bool nsHtml5TreeBuilder::isSpecialParentInForeign(nsHtml5StackNode* stackNode) { (stackNode->getGroup() == MI_MO_MN_MS_MTEXT)); } +nsIContentHandle* nsHtml5TreeBuilder::getDeclarativeShadowRoot( + nsIContentHandle* currentNode, nsIContentHandle* templateNode, + nsHtml5HtmlAttributes* attributes) { + if (!isAllowDeclarativeShadowRoots()) { + return nullptr; + } + nsHtml5String shadowRootMode = + attributes->getValue(nsHtml5AttributeName::ATTR_SHADOWROOTMODE); + if (!shadowRootMode) { + return nullptr; + } + bool shadowRootDelegatesFocus = + attributes->contains(nsHtml5AttributeName::ATTR_SHADOWROOTDELEGATESFOCUS); + return getShadowRootFromHost(currentNode, templateNode, shadowRootMode, + shadowRootDelegatesFocus); +} + nsHtml5String nsHtml5TreeBuilder::extractCharsetFromContent( nsHtml5String attributeValue, nsHtml5TreeBuilder* tb) { int32_t charsetState = CHARSET_INITIAL; @@ -4218,9 +4235,18 @@ void nsHtml5TreeBuilder::appendToCurrentNodeAndPushElement( nsIContentHandle* elt = createElement(kNameSpaceID_XHTML, elementName->getName(), attributes, currentNode, htmlCreator(elementName->getHtmlCreator())); - appendElement(elt, currentNode); if (nsHtml5ElementName::ELT_TEMPLATE == elementName) { - elt = getDocumentFragmentForTemplate(elt); + nsIContentHandle* root = + getDeclarativeShadowRoot(currentNode, elt, attributes); + if (root) { + setDocumentFragmentForTemplate(elt, root); + elt = root; + } else { + appendElement(elt, currentNode); + elt = getDocumentFragmentForTemplate(elt); + } + } else { + appendElement(elt, currentNode); } nsHtml5StackNode* node = createStackNode(elementName, elt); push(node); @@ -4482,6 +4508,14 @@ void nsHtml5TreeBuilder::setIsSrcdocDocument(bool isSrcdocDocument) { this->setForceNoQuirks(isSrcdocDocument); } +bool nsHtml5TreeBuilder::isAllowDeclarativeShadowRoots() { + return allowDeclarativeShadowRoots; +} + +void nsHtml5TreeBuilder::setAllowDeclarativeShadowRoots(bool allow) { + allowDeclarativeShadowRoots = allow; +} + void nsHtml5TreeBuilder::flushCharacters() { if (charBufferLen > 0) { if ((mode == IN_TABLE || mode == IN_TABLE_BODY || mode == IN_ROW) && diff --git a/parser/html/nsHtml5TreeBuilder.h b/parser/html/nsHtml5TreeBuilder.h index fab83f309581..d6c4828e7cf9 100644 --- a/parser/html/nsHtml5TreeBuilder.h +++ b/parser/html/nsHtml5TreeBuilder.h @@ -314,6 +314,7 @@ class nsHtml5TreeBuilder : public nsAHtml5TreeBuilderState { private: bool quirks; bool forceNoQuirks; + bool allowDeclarativeShadowRoots; inline nsHtml5ContentCreatorFunction htmlCreator( mozilla::dom::HTMLContentCreatorFunction htmlCreator) { nsHtml5ContentCreatorFunction creator; @@ -353,6 +354,9 @@ class nsHtml5TreeBuilder : public nsAHtml5TreeBuilderState { bool isTemplateContents(); bool isTemplateModeStackEmpty(); bool isSpecialParentInForeign(nsHtml5StackNode* stackNode); + nsIContentHandle* getDeclarativeShadowRoot(nsIContentHandle* currentNode, + nsIContentHandle* templateNode, + nsHtml5HtmlAttributes* attributes); public: static nsHtml5String extractCharsetFromContent(nsHtml5String attributeValue, @@ -556,6 +560,8 @@ class nsHtml5TreeBuilder : public nsAHtml5TreeBuilderState { void setScriptingEnabled(bool scriptingEnabled); void setForceNoQuirks(bool forceNoQuirks); void setIsSrcdocDocument(bool isSrcdocDocument); + bool isAllowDeclarativeShadowRoots(); + void setAllowDeclarativeShadowRoots(bool allow); void flushCharacters(); private: diff --git a/parser/html/nsHtml5TreeBuilderCppSupplement.h b/parser/html/nsHtml5TreeBuilderCppSupplement.h index d84adcd8a0d7..fe97c78eddd4 100644 --- a/parser/html/nsHtml5TreeBuilderCppSupplement.h +++ b/parser/html/nsHtml5TreeBuilderCppSupplement.h @@ -7,9 +7,12 @@ #include "ErrorList.h" #include "nsError.h" #include "nsHtml5AttributeName.h" +#include "nsHtml5HtmlAttributes.h" #include "nsHtml5String.h" #include "nsNetUtil.h" #include "mozilla/dom/FetchPriority.h" +#include "mozilla/dom/ShadowRoot.h" +#include "mozilla/dom/ShadowRootBinding.h" #include "mozilla/CheckedInt.h" #include "mozilla/Likely.h" #include "mozilla/StaticPrefs_dom.h" @@ -38,6 +41,7 @@ nsHtml5TreeBuilder::nsHtml5TreeBuilder(nsHtml5OplessBuilder* aBuilder) charBufferLen(0), quirks(false), forceNoQuirks(false), + allowDeclarativeShadowRoots(false), mBuilder(aBuilder), mViewSource(nullptr), mOpSink(nullptr), @@ -80,6 +84,7 @@ nsHtml5TreeBuilder::nsHtml5TreeBuilder(nsAHtml5TreeOpSink* aOpSink, charBufferLen(0), quirks(false), forceNoQuirks(false), + allowDeclarativeShadowRoots(false), mBuilder(nullptr), mViewSource(nullptr), mOpSink(aOpSink), @@ -1636,6 +1641,53 @@ nsIContentHandle* nsHtml5TreeBuilder::getDocumentFragmentForTemplate( return fragHandle; } +void nsHtml5TreeBuilder::setDocumentFragmentForTemplate( + nsIContentHandle* aTemplate, nsIContentHandle* aFragment) { + if (mBuilder) { + nsHtml5TreeOperation::SetDocumentFragmentForTemplate( + static_cast(aTemplate), + static_cast(aFragment)); + return; + } + + nsHtml5TreeOperation* treeOp = mOpQueue.AppendElement(mozilla::fallible); + if (MOZ_UNLIKELY(!treeOp)) { + MarkAsBrokenAndRequestSuspensionWithoutBuilder(NS_ERROR_OUT_OF_MEMORY); + return; + } + opSetDocumentFragmentForTemplate operation(aTemplate, aFragment); + treeOp->Init(mozilla::AsVariant(operation)); +} + +nsIContentHandle* nsHtml5TreeBuilder::getShadowRootFromHost( + nsIContentHandle* aHost, nsIContentHandle* aTemplateNode, + nsHtml5String aShadowRootMode, bool aShadowRootDelegatesFocus) { + mozilla::dom::ShadowRootMode mode; + if (aShadowRootMode.LowerCaseEqualsASCII("open")) { + mode = mozilla::dom::ShadowRootMode::Open; + } else if (aShadowRootMode.LowerCaseEqualsASCII("closed")) { + mode = mozilla::dom::ShadowRootMode::Closed; + } else { + return nullptr; + } + + if (mBuilder) { + return nsContentUtils::AttachDeclarativeShadowRoot( + static_cast(aHost), mode, aShadowRootDelegatesFocus); + } + + nsHtml5TreeOperation* treeOp = mOpQueue.AppendElement(mozilla::fallible); + if (MOZ_UNLIKELY(!treeOp)) { + MarkAsBrokenAndRequestSuspensionWithoutBuilder(NS_ERROR_OUT_OF_MEMORY); + return nullptr; + } + nsIContentHandle* fragHandle = AllocateContentHandle(); + opGetShadowRootFromHost operation(aHost, fragHandle, aTemplateNode, mode, + aShadowRootDelegatesFocus); + treeOp->Init(mozilla::AsVariant(operation)); + return fragHandle; +} + nsIContentHandle* nsHtml5TreeBuilder::getFormPointerForContext( nsIContentHandle* aContext) { MOZ_ASSERT(mBuilder, "Must have builder."); diff --git a/parser/html/nsHtml5TreeBuilderHSupplement.h b/parser/html/nsHtml5TreeBuilderHSupplement.h index 3fc1609e5abc..813654cb24bb 100644 --- a/parser/html/nsHtml5TreeBuilderHSupplement.h +++ b/parser/html/nsHtml5TreeBuilderHSupplement.h @@ -58,6 +58,13 @@ bool mActive; void documentMode(nsHtml5DocumentMode m); nsIContentHandle* getDocumentFragmentForTemplate(nsIContentHandle* aTemplate); +void setDocumentFragmentForTemplate(nsIContentHandle* aTemplate, + nsIContentHandle* aFragment); + +nsIContentHandle* getShadowRootFromHost(nsIContentHandle* aHost, + nsIContentHandle* aTemplateNode, + nsHtml5String aShadowRootMode, + bool aShadowRootDelegatesFocus); nsIContentHandle* getFormPointerForContext(nsIContentHandle* aContext); diff --git a/parser/html/nsHtml5TreeOperation.cpp b/parser/html/nsHtml5TreeOperation.cpp index fb28166e4684..b80b67e33d23 100644 --- a/parser/html/nsHtml5TreeOperation.cpp +++ b/parser/html/nsHtml5TreeOperation.cpp @@ -11,6 +11,7 @@ #include "mozilla/dom/Comment.h" #include "mozilla/dom/CustomElementRegistry.h" #include "mozilla/dom/DocGroup.h" +#include "mozilla/dom/DocumentFragment.h" #include "mozilla/dom/DocumentType.h" #include "mozilla/dom/Element.h" #include "mozilla/dom/LinkStyle.h" @@ -18,12 +19,14 @@ #include "mozilla/dom/HTMLImageElement.h" #include "mozilla/dom/HTMLTemplateElement.h" #include "mozilla/dom/MutationObservers.h" +#include "mozilla/dom/ShadowRoot.h" #include "mozilla/dom/Text.h" #include "nsAttrName.h" #include "nsContentCreatorFunctions.h" #include "nsContentUtils.h" #include "nsDocElementCreatedNotificationRunner.h" #include "nsEscape.h" +#include "nsGenericHTMLElement.h" #include "nsHtml5AutoPauseUpdate.h" #include "nsHtml5DocumentMode.h" #include "nsHtml5HtmlAttributes.h" @@ -135,6 +138,10 @@ nsHtml5TreeOperation::~nsHtml5TreeOperation() { void operator()(const opGetDocumentFragmentForTemplate& aOperation) {} + void operator()(const opSetDocumentFragmentForTemplate& aOperation) {} + + void operator()(const opGetShadowRootFromHost& aOperation) {} + void operator()(const opGetFosterParent& aOperation) {} void operator()(const opMarkAsBroken& aOperation) {} @@ -694,6 +701,12 @@ nsIContent* nsHtml5TreeOperation::GetDocumentFragmentForTemplate( return tempElem->Content(); } +void nsHtml5TreeOperation::SetDocumentFragmentForTemplate( + nsIContent* aNode, nsIContent* aDocumentFragment) { + auto* tempElem = static_cast(aNode); + tempElem->SetContent(static_cast(aDocumentFragment)); +} + nsIContent* nsHtml5TreeOperation::GetFosterParent(nsIContent* aTable, nsIContent* aStackParent) { nsIContent* tableParent = aTable->GetParent(); @@ -894,6 +907,31 @@ nsresult nsHtml5TreeOperation::Perform(nsHtml5TreeOpExecutor* aBuilder, return NS_OK; } + nsresult operator()(const opSetDocumentFragmentForTemplate& aOperation) { + SetDocumentFragmentForTemplate(*aOperation.mTemplate, + *aOperation.mFragment); + return NS_OK; + } + + nsresult operator()(const opGetShadowRootFromHost& aOperation) { + nsIContent* root = nsContentUtils::AttachDeclarativeShadowRoot( + *aOperation.mHost, aOperation.mShadowRootMode, + aOperation.mShadowRootDelegatesFocus); + if (root) { + *aOperation.mFragHandle = root; + return NS_OK; + } + + // We failed to attach a new shadow root, so instead attach a template + // element and return its content. + nsHtml5TreeOperation::Append(*aOperation.mTemplateNode, *aOperation.mHost, + mBuilder); + *aOperation.mFragHandle = + static_cast(*aOperation.mTemplateNode) + ->Content(); + return NS_OK; + } + nsresult operator()(const opGetFosterParent& aOperation) { nsIContent* table = *(aOperation.mTable); nsIContent* stackParent = *(aOperation.mStackParent); diff --git a/parser/html/nsHtml5TreeOperation.h b/parser/html/nsHtml5TreeOperation.h index 6fa2165f0800..fa6cb0245200 100644 --- a/parser/html/nsHtml5TreeOperation.h +++ b/parser/html/nsHtml5TreeOperation.h @@ -8,6 +8,7 @@ #include "nsHtml5DocumentMode.h" #include "nsHtml5HtmlAttributes.h" #include "mozilla/dom/FromParser.h" +#include "mozilla/dom/ShadowRootBinding.h" #include "mozilla/NotNull.h" #include "mozilla/Variant.h" #include "nsCharsetSource.h" @@ -264,6 +265,37 @@ struct opGetDocumentFragmentForTemplate { } }; +struct opSetDocumentFragmentForTemplate { + nsIContent** mTemplate; + nsIContent** mFragment; + + explicit opSetDocumentFragmentForTemplate(nsIContentHandle* aTemplate, + nsIContentHandle* aFragment) { + mTemplate = static_cast(aTemplate); + mFragment = static_cast(aFragment); + } +}; + +struct opGetShadowRootFromHost { + nsIContent** mHost; + nsIContent** mFragHandle; + nsIContent** mTemplateNode; + mozilla::dom::ShadowRootMode mShadowRootMode; + bool mShadowRootDelegatesFocus; + + explicit opGetShadowRootFromHost(nsIContentHandle* aHost, + nsIContentHandle* aFragHandle, + nsIContentHandle* aTemplateNode, + mozilla::dom::ShadowRootMode aShadowRootMode, + bool aShadowRootDelegatesFocus) { + mHost = static_cast(aHost); + mFragHandle = static_cast(aFragHandle); + mTemplateNode = static_cast(aTemplateNode); + mShadowRootMode = aShadowRootMode; + mShadowRootDelegatesFocus = aShadowRootDelegatesFocus; + } +}; + struct opGetFosterParent { nsIContent** mTable; nsIContent** mStackParent; @@ -492,7 +524,8 @@ typedef mozilla::Variant< opCreateHTMLElement, opCreateSVGElement, opCreateMathMLElement, opSetFormElement, opAppendText, opFosterParentText, opAppendComment, opAppendCommentToDocument, opAppendDoctypeToDocument, - opGetDocumentFragmentForTemplate, opGetFosterParent, + opGetDocumentFragmentForTemplate, opSetDocumentFragmentForTemplate, + opGetShadowRootFromHost, opGetFosterParent, // Gecko-specific on-pop ops opMarkAsBroken, opRunScriptThatMayDocumentWriteOrBlock, opRunScriptThatCannotDocumentWriteOrBlock, opPreventScriptExecution, @@ -587,6 +620,8 @@ class nsHtml5TreeOperation final { nsHtml5DocumentBuilder* aBuilder); static nsIContent* GetDocumentFragmentForTemplate(nsIContent* aNode); + static void SetDocumentFragmentForTemplate(nsIContent* aNode, + nsIContent* aDocumentFragment); static nsIContent* GetFosterParent(nsIContent* aTable, nsIContent* aStackParent);