summaryrefslogtreecommitdiffstats
path: root/dom/prototype
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /dom/prototype
parentInitial commit. (diff)
downloadfirefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz
firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/prototype')
-rw-r--r--dom/prototype/PrototypeDocumentContentSink.cpp1137
-rw-r--r--dom/prototype/PrototypeDocumentContentSink.h262
-rw-r--r--dom/prototype/moz.build25
-rw-r--r--dom/prototype/tests/chrome/chrome.ini7
-rw-r--r--dom/prototype/tests/chrome/form.xhtml8
-rw-r--r--dom/prototype/tests/chrome/no_whitespace.xhtml3
-rw-r--r--dom/prototype/tests/chrome/test_prototype_document.xhtml73
-rw-r--r--dom/prototype/tests/chrome/whitespace.xhtml8
8 files changed, 1523 insertions, 0 deletions
diff --git a/dom/prototype/PrototypeDocumentContentSink.cpp b/dom/prototype/PrototypeDocumentContentSink.cpp
new file mode 100644
index 0000000000..2c299f6a91
--- /dev/null
+++ b/dom/prototype/PrototypeDocumentContentSink.cpp
@@ -0,0 +1,1137 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#include "nsCOMPtr.h"
+#include "mozilla/dom/PrototypeDocumentContentSink.h"
+#include "nsIParser.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/URL.h"
+#include "nsIContent.h"
+#include "nsIURI.h"
+#include "nsNetUtil.h"
+#include "nsHTMLParts.h"
+#include "nsCRT.h"
+#include "mozilla/StyleSheetInlines.h"
+#include "mozilla/css/Loader.h"
+#include "nsGkAtoms.h"
+#include "nsContentUtils.h"
+#include "nsDocElementCreatedNotificationRunner.h"
+#include "nsIScriptContext.h"
+#include "nsNameSpaceManager.h"
+#include "nsIScriptError.h"
+#include "prtime.h"
+#include "mozilla/Logging.h"
+#include "nsRect.h"
+#include "nsIScriptElement.h"
+#include "nsReadableUtils.h"
+#include "nsUnicharUtils.h"
+#include "nsIChannel.h"
+#include "nsNodeInfoManager.h"
+#include "nsContentCreatorFunctions.h"
+#include "nsIContentPolicy.h"
+#include "nsContentPolicyUtils.h"
+#include "nsError.h"
+#include "nsIScriptGlobalObject.h"
+#include "mozAutoDocUpdate.h"
+#include "nsMimeTypes.h"
+#include "nsHtml5SVGLoadDispatcher.h"
+#include "nsTextNode.h"
+#include "mozilla/dom/AutoEntryScript.h"
+#include "mozilla/dom/CDATASection.h"
+#include "mozilla/dom/Comment.h"
+#include "mozilla/dom/DocumentType.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/HTMLTemplateElement.h"
+#include "mozilla/dom/ProcessingInstruction.h"
+#include "mozilla/dom/XMLStylesheetProcessingInstruction.h"
+#include "mozilla/dom/ScriptLoader.h"
+#include "mozilla/LoadInfo.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/ProfilerLabels.h"
+#include "mozilla/RefPtr.h"
+
+#include "nsXULPrototypeCache.h"
+#include "nsXULElement.h"
+#include "mozilla/CycleCollectedJSContext.h"
+#include "js/CompilationAndEvaluation.h"
+#include "js/experimental/JSStencil.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+LazyLogModule PrototypeDocumentContentSink::gLog("PrototypeDocument");
+
+nsresult NS_NewPrototypeDocumentContentSink(nsIContentSink** aResult,
+ Document* aDoc, nsIURI* aURI,
+ nsISupports* aContainer,
+ nsIChannel* aChannel) {
+ MOZ_ASSERT(nullptr != aResult, "null ptr");
+ if (nullptr == aResult) {
+ return NS_ERROR_NULL_POINTER;
+ }
+ RefPtr<PrototypeDocumentContentSink> it = new PrototypeDocumentContentSink();
+
+ nsresult rv = it->Init(aDoc, aURI, aContainer, aChannel);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ it.forget(aResult);
+ return NS_OK;
+}
+
+namespace mozilla::dom {
+
+PrototypeDocumentContentSink::PrototypeDocumentContentSink()
+ : mNextSrcLoadWaiter(nullptr),
+ mCurrentScriptProto(nullptr),
+ mOffThreadCompiling(false),
+ mOffThreadCompileStringBuf(nullptr),
+ mOffThreadCompileStringLength(0),
+ mStillWalking(false),
+ mPendingSheets(0) {}
+
+PrototypeDocumentContentSink::~PrototypeDocumentContentSink() {
+ NS_ASSERTION(
+ mNextSrcLoadWaiter == nullptr,
+ "unreferenced document still waiting for script source to load?");
+
+ if (mOffThreadCompileStringBuf) {
+ js_free(mOffThreadCompileStringBuf);
+ }
+}
+
+nsresult PrototypeDocumentContentSink::Init(Document* aDoc, nsIURI* aURI,
+ nsISupports* aContainer,
+ nsIChannel* aChannel) {
+ MOZ_ASSERT(aDoc, "null ptr");
+ MOZ_ASSERT(aURI, "null ptr");
+
+ mDocument = aDoc;
+
+ mDocument->SetDelayFrameLoaderInitialization(true);
+ mDocument->SetMayStartLayout(false);
+
+ // Get the URI. this should match the uri used for the OnNewURI call in
+ // nsDocShell::CreateContentViewer.
+ nsresult rv = NS_GetFinalChannelURI(aChannel, getter_AddRefs(mDocumentURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mScriptLoader = mDocument->ScriptLoader();
+
+ return NS_OK;
+}
+
+NS_IMPL_CYCLE_COLLECTION(PrototypeDocumentContentSink, mParser, mDocumentURI,
+ mDocument, mScriptLoader, mContextStack,
+ mCurrentPrototype)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PrototypeDocumentContentSink)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIContentSink)
+ NS_INTERFACE_MAP_ENTRY(nsIContentSink)
+ NS_INTERFACE_MAP_ENTRY(nsIStreamLoaderObserver)
+ NS_INTERFACE_MAP_ENTRY(nsICSSLoaderObserver)
+ NS_INTERFACE_MAP_ENTRY(nsIOffThreadScriptReceiver)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(PrototypeDocumentContentSink)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(PrototypeDocumentContentSink)
+
+//----------------------------------------------------------------------
+//
+// nsIContentSink interface
+//
+
+void PrototypeDocumentContentSink::SetDocumentCharset(
+ NotNull<const Encoding*> aEncoding) {
+ if (mDocument) {
+ mDocument->SetDocumentCharacterSet(aEncoding);
+ }
+}
+
+nsISupports* PrototypeDocumentContentSink::GetTarget() {
+ return ToSupports(mDocument);
+}
+
+bool PrototypeDocumentContentSink::IsScriptExecuting() {
+ return !!mScriptLoader->GetCurrentScript();
+}
+
+NS_IMETHODIMP
+PrototypeDocumentContentSink::SetParser(nsParserBase* aParser) {
+ MOZ_ASSERT(aParser, "Should have a parser here!");
+ mParser = aParser;
+ return NS_OK;
+}
+
+nsIParser* PrototypeDocumentContentSink::GetParser() {
+ return static_cast<nsIParser*>(mParser.get());
+}
+
+void PrototypeDocumentContentSink::ContinueInterruptedParsingIfEnabled() {
+ if (mParser && mParser->IsParserEnabled()) {
+ GetParser()->ContinueInterruptedParsing();
+ }
+}
+
+void PrototypeDocumentContentSink::ContinueInterruptedParsingAsync() {
+ nsCOMPtr<nsIRunnable> ev = NewRunnableMethod(
+ "PrototypeDocumentContentSink::ContinueInterruptedParsingIfEnabled", this,
+ &PrototypeDocumentContentSink::ContinueInterruptedParsingIfEnabled);
+
+ mDocument->Dispatch(mozilla::TaskCategory::Other, ev.forget());
+}
+
+//----------------------------------------------------------------------
+//
+// PrototypeDocumentContentSink::ContextStack
+//
+
+PrototypeDocumentContentSink::ContextStack::ContextStack()
+ : mTop(nullptr), mDepth(0) {}
+
+PrototypeDocumentContentSink::ContextStack::~ContextStack() { Clear(); }
+
+void PrototypeDocumentContentSink::ContextStack::Traverse(
+ nsCycleCollectionTraversalCallback& aCallback, const char* aName,
+ uint32_t aFlags) {
+ aFlags |= CycleCollectionEdgeNameArrayFlag;
+ Entry* current = mTop;
+ while (current) {
+ CycleCollectionNoteChild(aCallback, current->mElement, aName, aFlags);
+ current = current->mNext;
+ }
+}
+
+void PrototypeDocumentContentSink::ContextStack::Clear() {
+ while (mTop) {
+ Entry* doomed = mTop;
+ mTop = mTop->mNext;
+ NS_IF_RELEASE(doomed->mElement);
+ delete doomed;
+ }
+ mDepth = 0;
+}
+
+nsresult PrototypeDocumentContentSink::ContextStack::Push(
+ nsXULPrototypeElement* aPrototype, nsIContent* aElement) {
+ Entry* entry = new Entry;
+ entry->mPrototype = aPrototype;
+ entry->mElement = aElement;
+ NS_IF_ADDREF(entry->mElement);
+ entry->mIndex = 0;
+
+ entry->mNext = mTop;
+ mTop = entry;
+
+ ++mDepth;
+ return NS_OK;
+}
+
+nsresult PrototypeDocumentContentSink::ContextStack::Pop() {
+ if (mDepth == 0) return NS_ERROR_UNEXPECTED;
+
+ Entry* doomed = mTop;
+ mTop = mTop->mNext;
+ --mDepth;
+
+ NS_IF_RELEASE(doomed->mElement);
+ delete doomed;
+ return NS_OK;
+}
+
+nsresult PrototypeDocumentContentSink::ContextStack::Peek(
+ nsXULPrototypeElement** aPrototype, nsIContent** aElement,
+ int32_t* aIndex) {
+ if (mDepth == 0) return NS_ERROR_UNEXPECTED;
+
+ *aPrototype = mTop->mPrototype;
+ *aElement = mTop->mElement;
+ NS_IF_ADDREF(*aElement);
+ *aIndex = mTop->mIndex;
+
+ return NS_OK;
+}
+
+nsresult PrototypeDocumentContentSink::ContextStack::SetTopIndex(
+ int32_t aIndex) {
+ if (mDepth == 0) return NS_ERROR_UNEXPECTED;
+
+ mTop->mIndex = aIndex;
+ return NS_OK;
+}
+
+//----------------------------------------------------------------------
+//
+// Content model walking routines
+//
+
+nsresult PrototypeDocumentContentSink::OnPrototypeLoadDone(
+ nsXULPrototypeDocument* aPrototype) {
+ mCurrentPrototype = aPrototype;
+ mDocument->SetPrototypeDocument(aPrototype);
+
+ nsresult rv = PrepareToWalk();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = ResumeWalk();
+
+ return rv;
+}
+
+nsresult PrototypeDocumentContentSink::PrepareToWalk() {
+ MOZ_ASSERT(mCurrentPrototype);
+ nsresult rv;
+
+ mStillWalking = true;
+
+ // Notify document that the load is beginning
+ mDocument->BeginLoad();
+
+ // Get the prototype's root element and initialize the context
+ // stack for the prototype walk.
+ nsXULPrototypeElement* proto = mCurrentPrototype->GetRootElement();
+
+ if (!proto) {
+ if (MOZ_LOG_TEST(gLog, LogLevel::Error)) {
+ nsCOMPtr<nsIURI> url = mCurrentPrototype->GetURI();
+
+ nsAutoCString urlspec;
+ rv = url->GetSpec(urlspec);
+ if (NS_FAILED(rv)) return rv;
+
+ MOZ_LOG(gLog, LogLevel::Error,
+ ("prototype: error parsing '%s'", urlspec.get()));
+ }
+
+ return NS_OK;
+ }
+
+ nsINode* nodeToInsertBefore = mDocument->GetFirstChild();
+
+ const nsTArray<RefPtr<nsXULPrototypePI> >& processingInstructions =
+ mCurrentPrototype->GetProcessingInstructions();
+
+ uint32_t total = processingInstructions.Length();
+ for (uint32_t i = 0; i < total; ++i) {
+ rv = CreateAndInsertPI(processingInstructions[i], mDocument,
+ nodeToInsertBefore);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ // Do one-time initialization.
+ RefPtr<Element> root;
+
+ // Add the root element
+ rv = CreateElementFromPrototype(proto, getter_AddRefs(root), nullptr);
+ if (NS_FAILED(rv)) return rv;
+
+ ErrorResult error;
+ mDocument->AppendChildTo(root, false, error);
+ if (error.Failed()) {
+ return error.StealNSResult();
+ }
+
+ // TODO(emilio): Should this really notify? We don't notify of appends anyhow,
+ // and we just appended the root so no styles can possibly depend on it.
+ mDocument->UpdateDocumentStates(DocumentState::RTL_LOCALE, true);
+
+ nsContentUtils::AddScriptRunner(
+ new nsDocElementCreatedNotificationRunner(mDocument));
+
+ // There'd better not be anything on the context stack at this
+ // point! This is the basis case for our "induction" in
+ // ResumeWalk(), below, which'll assume that there's always a
+ // content element on the context stack if we're in the document.
+ NS_ASSERTION(mContextStack.Depth() == 0,
+ "something's on the context stack already");
+ if (mContextStack.Depth() != 0) return NS_ERROR_UNEXPECTED;
+
+ rv = mContextStack.Push(proto, root);
+ if (NS_FAILED(rv)) return rv;
+
+ return NS_OK;
+}
+
+nsresult PrototypeDocumentContentSink::CreateAndInsertPI(
+ const nsXULPrototypePI* aProtoPI, nsINode* aParent, nsINode* aBeforeThis) {
+ MOZ_ASSERT(aProtoPI, "null ptr");
+ MOZ_ASSERT(aParent, "null ptr");
+
+ RefPtr<ProcessingInstruction> node =
+ NS_NewXMLProcessingInstruction(aParent->OwnerDoc()->NodeInfoManager(),
+ aProtoPI->mTarget, aProtoPI->mData);
+
+ nsresult rv;
+ if (aProtoPI->mTarget.EqualsLiteral("xml-stylesheet")) {
+ MOZ_ASSERT(LinkStyle::FromNode(*node),
+ "XML Stylesheet node does not implement LinkStyle!");
+ auto* pi = static_cast<XMLStylesheetProcessingInstruction*>(node.get());
+ rv = InsertXMLStylesheetPI(aProtoPI, aParent, aBeforeThis, pi);
+ } else {
+ // No special processing, just add the PI to the document.
+ ErrorResult error;
+ aParent->InsertChildBefore(node->AsContent(),
+ aBeforeThis ? aBeforeThis->AsContent() : nullptr,
+ false, error);
+ rv = error.StealNSResult();
+ }
+
+ return rv;
+}
+
+nsresult PrototypeDocumentContentSink::InsertXMLStylesheetPI(
+ const nsXULPrototypePI* aProtoPI, nsINode* aParent, nsINode* aBeforeThis,
+ XMLStylesheetProcessingInstruction* aPINode) {
+ // We want to be notified when the style sheet finishes loading, so
+ // disable style sheet loading for now.
+ aPINode->SetEnableUpdates(false);
+ aPINode->OverrideBaseURI(mCurrentPrototype->GetURI());
+
+ ErrorResult rv;
+ aParent->InsertChildBefore(
+ aPINode, aBeforeThis ? aBeforeThis->AsContent() : nullptr, false, rv);
+ if (rv.Failed()) {
+ return rv.StealNSResult();
+ }
+
+ aPINode->SetEnableUpdates(true);
+
+ // load the stylesheet if necessary, passing ourselves as
+ // nsICSSObserver
+ auto result = aPINode->UpdateStyleSheet(this);
+ if (result.isErr()) {
+ // Ignore errors from UpdateStyleSheet; we don't want failure to
+ // do that to break the XUL document load. But do propagate out
+ // NS_ERROR_OUT_OF_MEMORY.
+ if (result.unwrapErr() == NS_ERROR_OUT_OF_MEMORY) {
+ return result.unwrapErr();
+ }
+ return NS_OK;
+ }
+
+ auto update = result.unwrap();
+ if (update.ShouldBlock()) {
+ ++mPendingSheets;
+ }
+
+ return NS_OK;
+}
+
+void PrototypeDocumentContentSink::CloseElement(Element* aElement,
+ bool aHadChildren) {
+ if (nsIContent::RequiresDoneAddingChildren(
+ aElement->NodeInfo()->NamespaceID(),
+ aElement->NodeInfo()->NameAtom())) {
+ aElement->DoneAddingChildren(false);
+ }
+
+ if (!aHadChildren) {
+ return;
+ }
+
+ // See bug 370111 and bug 1495946. We don't cache inline styles nor module
+ // scripts in the prototype cache, and we don't notify on node insertion, so
+ // we need to do this for the stylesheet / script to be properly processed.
+ // This kinda sucks, but notifying was a pretty sizeable perf regression so...
+ if (aElement->IsHTMLElement(nsGkAtoms::script) ||
+ aElement->IsSVGElement(nsGkAtoms::script)) {
+ nsCOMPtr<nsIScriptElement> sele = do_QueryInterface(aElement);
+ MOZ_ASSERT(sele, "Node didn't QI to script.");
+ if (sele->GetScriptIsModule()) {
+ DebugOnly<bool> block = sele->AttemptToExecute();
+ MOZ_ASSERT(!block, "<script type=module> shouldn't block the parser");
+ }
+ }
+
+ if (aElement->IsHTMLElement(nsGkAtoms::style) ||
+ aElement->IsSVGElement(nsGkAtoms::style)) {
+ auto* linkStyle = LinkStyle::FromNode(*aElement);
+ NS_ASSERTION(linkStyle,
+ "<html:style> doesn't implement "
+ "nsIStyleSheetLinkingElement?");
+ Unused << linkStyle->UpdateStyleSheet(nullptr);
+ }
+}
+
+nsresult PrototypeDocumentContentSink::ResumeWalk() {
+ nsresult rv = ResumeWalkInternal();
+ if (NS_FAILED(rv)) {
+ nsContentUtils::ReportToConsoleNonLocalized(
+ u"Failed to load document from prototype document."_ns,
+ nsIScriptError::errorFlag, "Prototype Document"_ns, mDocument,
+ mDocumentURI);
+ }
+ return rv;
+}
+
+nsresult PrototypeDocumentContentSink::ResumeWalkInternal() {
+ MOZ_ASSERT(mStillWalking);
+ // Walk the prototype and build the delegate content model. The
+ // walk is performed in a top-down, left-to-right fashion. That
+ // is, a parent is built before any of its children; a node is
+ // only built after all of its siblings to the left are fully
+ // constructed.
+ //
+ // It is interruptable so that transcluded documents (e.g.,
+ // <html:script src="..." />) can be properly re-loaded if the
+ // cached copy of the document becomes stale.
+ nsresult rv;
+ nsCOMPtr<nsIURI> docURI =
+ mCurrentPrototype ? mCurrentPrototype->GetURI() : nullptr;
+
+ while (1) {
+ // Begin (or resume) walking the current prototype.
+
+ while (mContextStack.Depth() > 0) {
+ // Look at the top of the stack to determine what we're
+ // currently working on.
+ // This will always be a node already constructed and
+ // inserted to the actual document.
+ nsXULPrototypeElement* proto;
+ nsCOMPtr<nsIContent> element;
+ nsCOMPtr<nsIContent> nodeToPushTo;
+ int32_t indx; // all children of proto before indx (not
+ // inclusive) have already been constructed
+ rv = mContextStack.Peek(&proto, getter_AddRefs(element), &indx);
+ if (NS_FAILED(rv)) return rv;
+
+ if (indx >= (int32_t)proto->mChildren.Length()) {
+ if (element) {
+ // We've processed all of the prototype's children.
+ CloseElement(element->AsElement(), /* aHadChildren = */ true);
+ }
+ // Now pop the context stack back up to the parent
+ // element and continue the prototype walk.
+ mContextStack.Pop();
+ continue;
+ }
+
+ nodeToPushTo = element;
+ // For template elements append the content to the template's document
+ // fragment.
+ if (auto* templateElement = HTMLTemplateElement::FromNode(element)) {
+ nodeToPushTo = templateElement->Content();
+ }
+
+ // Grab the next child, and advance the current context stack
+ // to the next sibling to our right.
+ nsXULPrototypeNode* childproto = proto->mChildren[indx];
+ mContextStack.SetTopIndex(++indx);
+
+ switch (childproto->mType) {
+ case nsXULPrototypeNode::eType_Element: {
+ // An 'element', which may contain more content.
+ auto* protoele = static_cast<nsXULPrototypeElement*>(childproto);
+
+ RefPtr<Element> child;
+
+ rv = CreateElementFromPrototype(protoele, getter_AddRefs(child),
+ nodeToPushTo);
+ if (NS_FAILED(rv)) return rv;
+
+ // ...and append it to the content model.
+ ErrorResult error;
+ nodeToPushTo->AppendChildTo(child, false, error);
+ if (error.Failed()) {
+ return error.StealNSResult();
+ }
+
+ if (nsIContent::RequiresDoneCreatingElement(
+ protoele->mNodeInfo->NamespaceID(),
+ protoele->mNodeInfo->NameAtom())) {
+ child->DoneCreatingElement();
+ }
+
+ // If it has children, push the element onto the context
+ // stack and begin to process them.
+ if (protoele->mChildren.Length() > 0) {
+ rv = mContextStack.Push(protoele, child);
+ if (NS_FAILED(rv)) return rv;
+ } else {
+ // If there are no children, close the element immediately.
+ CloseElement(child, /* aHadChildren = */ false);
+ }
+ } break;
+
+ case nsXULPrototypeNode::eType_Script: {
+ // A script reference. Execute the script immediately;
+ // this may have side effects in the content model.
+ auto* scriptproto = static_cast<nsXULPrototypeScript*>(childproto);
+ if (scriptproto->mSrcURI) {
+ // A transcluded script reference; this may
+ // "block" our prototype walk if the script isn't
+ // cached, or the cached copy of the script is
+ // stale and must be reloaded.
+ bool blocked;
+ rv = LoadScript(scriptproto, &blocked);
+ // If the script cannot be loaded, just keep going!
+
+ if (NS_SUCCEEDED(rv) && blocked) return NS_OK;
+ } else if (scriptproto->HasStencil()) {
+ // An inline script
+ rv = ExecuteScript(scriptproto);
+ if (NS_FAILED(rv)) return rv;
+ }
+ } break;
+
+ case nsXULPrototypeNode::eType_Text: {
+ nsNodeInfoManager* nim = nodeToPushTo->NodeInfo()->NodeInfoManager();
+ // A simple text node.
+ RefPtr<nsTextNode> text = new (nim) nsTextNode(nim);
+
+ auto* textproto = static_cast<nsXULPrototypeText*>(childproto);
+ text->SetText(textproto->mValue, false);
+
+ ErrorResult error;
+ nodeToPushTo->AppendChildTo(text, false, error);
+ if (error.Failed()) {
+ return error.StealNSResult();
+ }
+ } break;
+
+ case nsXULPrototypeNode::eType_PI: {
+ auto* piProto = static_cast<nsXULPrototypePI*>(childproto);
+
+ // <?xml-stylesheet?> doesn't have an effect
+ // outside the prolog, like it used to. Issue a warning.
+
+ if (piProto->mTarget.EqualsLiteral("xml-stylesheet")) {
+ AutoTArray<nsString, 1> params = {piProto->mTarget};
+
+ nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
+ "XUL Document"_ns, nullptr,
+ nsContentUtils::eXUL_PROPERTIES,
+ "PINotInProlog", params, docURI);
+ }
+
+ nsIContent* parent = element.get();
+ if (parent) {
+ // an inline script could have removed the root element
+ rv = CreateAndInsertPI(piProto, parent, nullptr);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ } break;
+
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unexpected nsXULPrototypeNode::Type");
+ }
+ }
+
+ // Once we get here, the context stack will have been
+ // depleted. That means that the entire prototype has been
+ // walked and content has been constructed.
+ break;
+ }
+
+ mStillWalking = false;
+ return MaybeDoneWalking();
+}
+
+void PrototypeDocumentContentSink::InitialTranslationCompleted() {
+ MaybeDoneWalking();
+}
+
+nsresult PrototypeDocumentContentSink::MaybeDoneWalking() {
+ if (mPendingSheets > 0 || mStillWalking) {
+ return NS_OK;
+ }
+
+ if (mDocument->HasPendingInitialTranslation()) {
+ mDocument->OnParsingCompleted();
+ return NS_OK;
+ }
+
+ return DoneWalking();
+}
+
+nsresult PrototypeDocumentContentSink::DoneWalking() {
+ MOZ_ASSERT(mPendingSheets == 0, "there are sheets to be loaded");
+ MOZ_ASSERT(!mStillWalking, "walk not done");
+ MOZ_ASSERT(!mDocument->HasPendingInitialTranslation(), "translation pending");
+
+ if (mDocument) {
+ MOZ_ASSERT(mDocument->GetReadyStateEnum() == Document::READYSTATE_LOADING,
+ "Bad readyState");
+ mDocument->SetReadyStateInternal(Document::READYSTATE_INTERACTIVE);
+ mDocument->NotifyPossibleTitleChange(false);
+
+ nsContentUtils::DispatchEventOnlyToChrome(mDocument, ToSupports(mDocument),
+ u"MozBeforeInitialXULLayout"_ns,
+ CanBubble::eYes, Cancelable::eNo);
+ }
+
+ if (mScriptLoader) {
+ mScriptLoader->ParsingComplete(false);
+ mScriptLoader->DeferCheckpointReached();
+ }
+
+ StartLayout();
+
+ if (IsChromeURI(mDocumentURI) &&
+ nsXULPrototypeCache::GetInstance()->IsEnabled()) {
+ bool isCachedOnDisk;
+ nsXULPrototypeCache::GetInstance()->HasPrototype(mDocumentURI,
+ &isCachedOnDisk);
+ if (!isCachedOnDisk) {
+ nsXULPrototypeCache::GetInstance()->WritePrototype(mCurrentPrototype);
+ }
+ }
+
+ mDocument->SetDelayFrameLoaderInitialization(false);
+ RefPtr<Document> doc = mDocument;
+ doc->MaybeInitializeFinalizeFrameLoaders();
+
+ // If the document we are loading has a reference or it is a
+ // frameset document, disable the scroll bars on the views.
+
+ doc->SetScrollToRef(mDocument->GetDocumentURI());
+
+ doc->EndLoad();
+
+ return NS_OK;
+}
+
+void PrototypeDocumentContentSink::StartLayout() {
+ AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING(
+ "PrototypeDocumentContentSink::StartLayout", LAYOUT,
+ mDocumentURI->GetSpecOrDefault());
+ mDocument->SetMayStartLayout(true);
+ RefPtr<PresShell> presShell = mDocument->GetPresShell();
+ if (presShell && !presShell->DidInitialize()) {
+ nsresult rv = presShell->Initialize();
+ if (NS_FAILED(rv)) {
+ return;
+ }
+ }
+}
+
+NS_IMETHODIMP
+PrototypeDocumentContentSink::StyleSheetLoaded(StyleSheet* aSheet,
+ bool aWasDeferred,
+ nsresult aStatus) {
+ if (!aWasDeferred) {
+ // Don't care about when alternate sheets finish loading
+ MOZ_ASSERT(mPendingSheets > 0, "Unexpected StyleSheetLoaded notification");
+
+ --mPendingSheets;
+
+ return MaybeDoneWalking();
+ }
+
+ return NS_OK;
+}
+
+nsresult PrototypeDocumentContentSink::LoadScript(
+ nsXULPrototypeScript* aScriptProto, bool* aBlock) {
+ // Load a transcluded script
+ nsresult rv;
+
+ bool isChromeDoc = IsChromeURI(mDocumentURI);
+
+ if (isChromeDoc && aScriptProto->HasStencil()) {
+ rv = ExecuteScript(aScriptProto);
+
+ // Ignore return value from execution, and don't block
+ *aBlock = false;
+ return NS_OK;
+ }
+
+ // Try the XUL script cache, in case two XUL documents source the same
+ // .js file (e.g., strres.js from navigator.xul and utilityOverlay.xul).
+ // XXXbe the cache relies on aScriptProto's GC root!
+ bool useXULCache = nsXULPrototypeCache::GetInstance()->IsEnabled();
+
+ if (isChromeDoc && useXULCache) {
+ RefPtr<JS::Stencil> newStencil =
+ nsXULPrototypeCache::GetInstance()->GetStencil(aScriptProto->mSrcURI);
+ if (newStencil) {
+ // The script language for a proto must remain constant - we
+ // can't just change it for this unexpected language.
+ aScriptProto->Set(newStencil);
+ }
+
+ if (aScriptProto->HasStencil()) {
+ rv = ExecuteScript(aScriptProto);
+
+ // Ignore return value from execution, and don't block
+ *aBlock = false;
+ return NS_OK;
+ }
+ }
+
+ // Release stencil from FastLoad since we decided against using them
+ aScriptProto->Set(nullptr);
+
+ // Set the current script prototype so that OnStreamComplete can report
+ // the right file if there are errors in the script.
+ NS_ASSERTION(!mCurrentScriptProto,
+ "still loading a script when starting another load?");
+ mCurrentScriptProto = aScriptProto;
+
+ if (isChromeDoc && aScriptProto->mSrcLoading) {
+ // Another document load has started, which is still in progress.
+ // Remember to ResumeWalk this document when the load completes.
+ mNextSrcLoadWaiter = aScriptProto->mSrcLoadWaiters;
+ aScriptProto->mSrcLoadWaiters = this;
+ NS_ADDREF_THIS();
+ } else {
+ nsCOMPtr<nsILoadGroup> group =
+ mDocument
+ ->GetDocumentLoadGroup(); // found in
+ // mozilla::dom::Document::SetScriptGlobalObject
+
+ // Note: the loader will keep itself alive while it's loading.
+ nsCOMPtr<nsIStreamLoader> loader;
+ rv = NS_NewStreamLoader(
+ getter_AddRefs(loader), aScriptProto->mSrcURI,
+ this, // aObserver
+ mDocument, // aRequestingContext
+ nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_INHERITS_SEC_CONTEXT,
+ nsIContentPolicy::TYPE_INTERNAL_SCRIPT, group);
+
+ if (NS_FAILED(rv)) {
+ mCurrentScriptProto = nullptr;
+ return rv;
+ }
+
+ aScriptProto->mSrcLoading = true;
+ }
+
+ // Block until OnStreamComplete resumes us.
+ *aBlock = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+PrototypeDocumentContentSink::OnStreamComplete(nsIStreamLoader* aLoader,
+ nsISupports* context,
+ nsresult aStatus,
+ uint32_t stringLen,
+ const uint8_t* string) {
+ nsCOMPtr<nsIRequest> request;
+ aLoader->GetRequest(getter_AddRefs(request));
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(request);
+
+#ifdef DEBUG
+ // print a load error on bad status
+ if (NS_FAILED(aStatus)) {
+ if (channel) {
+ nsCOMPtr<nsIURI> uri;
+ channel->GetURI(getter_AddRefs(uri));
+ if (uri) {
+ printf("Failed to load %s\n", uri->GetSpecOrDefault().get());
+ }
+ }
+ }
+#endif
+
+ // This is the completion routine that will be called when a
+ // transcluded script completes. Compile and execute the script
+ // if the load was successful, then continue building content
+ // from the prototype.
+ nsresult rv = aStatus;
+
+ NS_ASSERTION(mCurrentScriptProto && mCurrentScriptProto->mSrcLoading,
+ "script source not loading on unichar stream complete?");
+ if (!mCurrentScriptProto) {
+ // XXX Wallpaper for bug 270042
+ return NS_OK;
+ }
+
+ if (NS_SUCCEEDED(aStatus)) {
+ // If the including document is a FastLoad document, and we're
+ // compiling an out-of-line script (one with src=...), then we must
+ // be writing a new FastLoad file. If we were reading this script
+ // from the FastLoad file, XULContentSinkImpl::OpenScript (over in
+ // nsXULContentSink.cpp) would have already deserialized a non-null
+ // script->mStencil, causing control flow at the top of LoadScript
+ // not to reach here.
+ nsCOMPtr<nsIURI> uri = mCurrentScriptProto->mSrcURI;
+
+ // XXX should also check nsIHttpChannel::requestSucceeded
+
+ MOZ_ASSERT(!mOffThreadCompiling && (mOffThreadCompileStringLength == 0 &&
+ !mOffThreadCompileStringBuf),
+ "PrototypeDocument can't load multiple scripts at once");
+
+ rv = ScriptLoader::ConvertToUTF8(channel, string, stringLen, u""_ns,
+ mDocument, mOffThreadCompileStringBuf,
+ mOffThreadCompileStringLength);
+ if (NS_SUCCEEDED(rv)) {
+ // Pass ownership of the buffer, carefully emptying the existing
+ // fields in the process. Note that the |Compile| function called
+ // below always takes ownership of the buffer.
+ Utf8Unit* units = nullptr;
+ size_t unitsLength = 0;
+
+ std::swap(units, mOffThreadCompileStringBuf);
+ std::swap(unitsLength, mOffThreadCompileStringLength);
+
+ rv = mCurrentScriptProto->Compile(units, unitsLength,
+ JS::SourceOwnership::TakeOwnership, uri,
+ 1, mDocument, this);
+ if (NS_SUCCEEDED(rv) && !mCurrentScriptProto->HasStencil()) {
+ mOffThreadCompiling = true;
+ mDocument->BlockOnload();
+ return NS_OK;
+ }
+ }
+ }
+
+ return OnScriptCompileComplete(mCurrentScriptProto->GetStencil(), rv);
+}
+
+NS_IMETHODIMP
+PrototypeDocumentContentSink::OnScriptCompileComplete(JS::Stencil* aStencil,
+ nsresult aStatus) {
+ // The mCurrentScriptProto may have been cleared out by another
+ // PrototypeDocumentContentSink.
+ if (!mCurrentScriptProto) {
+ return NS_OK;
+ }
+
+ // When compiling off thread the script will not have been attached to the
+ // script proto yet.
+ if (aStencil && !mCurrentScriptProto->HasStencil()) {
+ mCurrentScriptProto->Set(aStencil);
+ }
+
+ // Allow load events to be fired once off thread compilation finishes.
+ if (mOffThreadCompiling) {
+ mOffThreadCompiling = false;
+ mDocument->UnblockOnload(false);
+ }
+
+ // After compilation finishes the script's characters are no longer needed.
+ if (mOffThreadCompileStringBuf) {
+ js_free(mOffThreadCompileStringBuf);
+ mOffThreadCompileStringBuf = nullptr;
+ mOffThreadCompileStringLength = 0;
+ }
+
+ // Clear mCurrentScriptProto now, but save it first for use below in
+ // the execute code, and in the while loop that resumes walks of other
+ // documents that raced to load this script.
+ nsXULPrototypeScript* scriptProto = mCurrentScriptProto;
+ mCurrentScriptProto = nullptr;
+
+ // Clear the prototype's loading flag before executing the script or
+ // resuming document walks, in case any of those control flows starts a
+ // new script load.
+ scriptProto->mSrcLoading = false;
+
+ nsresult rv = aStatus;
+ if (NS_SUCCEEDED(rv)) {
+ rv = ExecuteScript(scriptProto);
+
+ // If the XUL cache is enabled, save the script object there in
+ // case different XUL documents source the same script.
+ //
+ // But don't save the script in the cache unless the master XUL
+ // document URL is a chrome: URL. It is valid for a URL such as
+ // about:config to translate into a master document URL, whose
+ // prototype document nodes -- including prototype scripts that
+ // hold GC roots protecting their mJSObject pointers -- are not
+ // cached in the XUL prototype cache. See StartDocumentLoad,
+ // the fillXULCache logic.
+ //
+ // A document such as about:config is free to load a script via
+ // a URL such as chrome://global/content/config.js, and we must
+ // not cache that script object without a prototype cache entry
+ // containing a companion nsXULPrototypeScript node that owns a
+ // GC root protecting the script object. Otherwise, the script
+ // cache entry will dangle once the uncached prototype document
+ // is released when its owning document is unloaded.
+ //
+ // (See http://bugzilla.mozilla.org/show_bug.cgi?id=98207 for
+ // the true crime story.)
+ bool useXULCache = nsXULPrototypeCache::GetInstance()->IsEnabled();
+
+ if (useXULCache && IsChromeURI(mDocumentURI) && scriptProto->HasStencil()) {
+ nsXULPrototypeCache::GetInstance()->PutStencil(scriptProto->mSrcURI,
+ scriptProto->GetStencil());
+ }
+ // ignore any evaluation errors
+ }
+
+ rv = ResumeWalk();
+
+ // Load a pointer to the prototype-script's list of documents who
+ // raced to load the same script
+ PrototypeDocumentContentSink** docp = &scriptProto->mSrcLoadWaiters;
+
+ // Resume walking other documents that waited for this one's load, first
+ // executing the script we just compiled, in each doc's script context
+ PrototypeDocumentContentSink* doc;
+ while ((doc = *docp) != nullptr) {
+ NS_ASSERTION(doc->mCurrentScriptProto == scriptProto,
+ "waiting for wrong script to load?");
+ doc->mCurrentScriptProto = nullptr;
+
+ // Unlink doc from scriptProto's list before executing and resuming
+ *docp = doc->mNextSrcLoadWaiter;
+ doc->mNextSrcLoadWaiter = nullptr;
+
+ if (aStatus == NS_BINDING_ABORTED && !scriptProto->HasStencil()) {
+ // If the previous doc load was aborted, we want to try loading
+ // again for the next doc. Otherwise, one abort would lead to all
+ // subsequent waiting docs to abort as well.
+ bool block = false;
+ doc->LoadScript(scriptProto, &block);
+ NS_RELEASE(doc);
+ return rv;
+ }
+
+ // Execute only if we loaded and compiled successfully, then resume
+ if (NS_SUCCEEDED(aStatus) && scriptProto->HasStencil()) {
+ doc->ExecuteScript(scriptProto);
+ }
+ doc->ResumeWalk();
+ NS_RELEASE(doc);
+ }
+
+ return rv;
+}
+
+nsresult PrototypeDocumentContentSink::ExecuteScript(
+ nsXULPrototypeScript* aScript) {
+ MOZ_ASSERT(aScript != nullptr, "null ptr");
+ NS_ENSURE_TRUE(aScript, NS_ERROR_NULL_POINTER);
+
+ nsIScriptGlobalObject* scriptGlobalObject;
+ bool aHasHadScriptHandlingObject;
+ scriptGlobalObject =
+ mDocument->GetScriptHandlingObject(aHasHadScriptHandlingObject);
+
+ NS_ENSURE_TRUE(scriptGlobalObject, NS_ERROR_NOT_INITIALIZED);
+
+ nsresult rv;
+ rv = scriptGlobalObject->EnsureScriptEnvironment();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Execute the precompiled script with the given version
+ nsAutoMicroTask mt;
+
+ // We're about to run script via JS_ExecuteScript, so we need an
+ // AutoEntryScript. This is Gecko specific and not in any spec.
+ AutoEntryScript aes(scriptGlobalObject, "precompiled XUL <script> element");
+ JSContext* cx = aes.cx();
+
+ JS::Rooted<JSScript*> scriptObject(cx);
+ rv = aScript->InstantiateScript(cx, &scriptObject);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ JS::Rooted<JSObject*> global(cx, JS::CurrentGlobalOrNull(cx));
+ NS_ENSURE_TRUE(xpc::Scriptability::Get(global).Allowed(), NS_OK);
+
+ // On failure, ~AutoScriptEntry will handle exceptions, so
+ // there is no need to manually check the return value.
+ JS::Rooted<JS::Value> rval(cx);
+ Unused << JS_ExecuteScript(cx, scriptObject, &rval);
+
+ return NS_OK;
+}
+
+nsresult PrototypeDocumentContentSink::CreateElementFromPrototype(
+ nsXULPrototypeElement* aPrototype, Element** aResult, nsIContent* aParent) {
+ // Create a content model element from a prototype element.
+ MOZ_ASSERT(aPrototype, "null ptr");
+ if (!aPrototype) return NS_ERROR_NULL_POINTER;
+
+ *aResult = nullptr;
+ nsresult rv = NS_OK;
+
+ if (MOZ_LOG_TEST(gLog, LogLevel::Debug)) {
+ MOZ_LOG(
+ gLog, LogLevel::Debug,
+ ("prototype: creating <%s> from prototype",
+ NS_ConvertUTF16toUTF8(aPrototype->mNodeInfo->QualifiedName()).get()));
+ }
+
+ RefPtr<Element> result;
+
+ Document* doc = aParent ? aParent->OwnerDoc() : mDocument.get();
+ if (aPrototype->mNodeInfo->NamespaceEquals(kNameSpaceID_XUL)) {
+ const bool isRoot = !aParent;
+ // If it's a XUL element, it'll be lightweight until somebody
+ // monkeys with it.
+ rv = nsXULElement::CreateFromPrototype(aPrototype, doc, true, isRoot,
+ getter_AddRefs(result));
+ if (NS_FAILED(rv)) return rv;
+ } else {
+ // If it's not a XUL element, it's gonna be heavyweight no matter
+ // what. So we need to copy everything out of the prototype
+ // into the element. Get a nodeinfo from our nodeinfo manager
+ // for this node.
+ RefPtr<NodeInfo> newNodeInfo = doc->NodeInfoManager()->GetNodeInfo(
+ aPrototype->mNodeInfo->NameAtom(),
+ aPrototype->mNodeInfo->GetPrefixAtom(),
+ aPrototype->mNodeInfo->NamespaceID(), nsINode::ELEMENT_NODE);
+ if (!newNodeInfo) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ const bool isScript =
+ newNodeInfo->Equals(nsGkAtoms::script, kNameSpaceID_XHTML) ||
+ newNodeInfo->Equals(nsGkAtoms::script, kNameSpaceID_SVG);
+ if (aPrototype->mIsAtom &&
+ newNodeInfo->NamespaceID() == kNameSpaceID_XHTML) {
+ rv = NS_NewHTMLElement(getter_AddRefs(result), newNodeInfo.forget(),
+ NOT_FROM_PARSER, aPrototype->mIsAtom);
+ } else {
+ rv = NS_NewElement(getter_AddRefs(result), newNodeInfo.forget(),
+ NOT_FROM_PARSER);
+ }
+ if (NS_FAILED(rv)) return rv;
+
+ rv = AddAttributes(aPrototype, result);
+ if (NS_FAILED(rv)) return rv;
+
+ if (isScript) {
+ nsCOMPtr<nsIScriptElement> sele = do_QueryInterface(result);
+ MOZ_ASSERT(sele, "Node didn't QI to script.");
+
+ sele->FreezeExecutionAttrs(doc);
+ // Script loading is handled by the this content sink, so prevent the
+ // script from loading when it is bound to the document.
+ //
+ // NOTE(emilio): This is only done for non-module scripts, because we
+ // don't support caching modules properly yet, see the comment in
+ // XULContentSinkImpl::OpenScript. For non-inline scripts, this is enough,
+ // since we can start the load when the node is inserted. Non-inline
+ // scripts need another special-case in CloseElement.
+ if (!sele->GetScriptIsModule()) {
+ sele->PreventExecution();
+ }
+ }
+ }
+
+ // FIXME(bug 1627474): Is this right if this is inside an <html:template>?
+ if (result->HasAttr(kNameSpaceID_None, nsGkAtoms::datal10nid)) {
+ mDocument->mL10nProtoElements.InsertOrUpdate(result, RefPtr{aPrototype});
+ result->SetElementCreatedFromPrototypeAndHasUnmodifiedL10n();
+ }
+ result.forget(aResult);
+ return NS_OK;
+}
+
+nsresult PrototypeDocumentContentSink::AddAttributes(
+ nsXULPrototypeElement* aPrototype, Element* aElement) {
+ nsresult rv;
+
+ for (size_t i = 0; i < aPrototype->mAttributes.Length(); ++i) {
+ nsXULPrototypeAttribute* protoattr = &(aPrototype->mAttributes[i]);
+ nsAutoString valueStr;
+ protoattr->mValue.ToString(valueStr);
+
+ rv = aElement->SetAttr(protoattr->mName.NamespaceID(),
+ protoattr->mName.LocalName(),
+ protoattr->mName.GetPrefix(), valueStr, false);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ return NS_OK;
+}
+
+} // namespace mozilla::dom
diff --git a/dom/prototype/PrototypeDocumentContentSink.h b/dom/prototype/PrototypeDocumentContentSink.h
new file mode 100644
index 0000000000..c4735ed52c
--- /dev/null
+++ b/dom/prototype/PrototypeDocumentContentSink.h
@@ -0,0 +1,262 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_PrototypeDocumentContentSink_h__
+#define mozilla_dom_PrototypeDocumentContentSink_h__
+
+#include "mozilla/Attributes.h"
+#include "nsIContentSink.h"
+#include "nsTArray.h"
+#include "nsCOMPtr.h"
+#include "nsCRT.h"
+#include "nsCycleCollectionNoteChild.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsIDTD.h"
+#include "mozilla/dom/FromParser.h"
+#include "nsXULPrototypeDocument.h"
+#include "nsIStreamLoader.h"
+#include "nsIScriptContext.h"
+#include "nsICSSLoaderObserver.h"
+#include "mozilla/Logging.h"
+#include "js/experimental/JSStencil.h"
+#include "mozilla/RefPtr.h"
+
+class nsIURI;
+class nsIChannel;
+class nsIContent;
+class nsIParser;
+class nsTextNode;
+class nsINode;
+class nsXULPrototypeElement;
+class nsXULPrototypePI;
+class nsXULPrototypeScript;
+
+namespace mozilla::dom {
+class Element;
+class ScriptLoader;
+class Document;
+class XMLStylesheetProcessingInstruction;
+} // namespace mozilla::dom
+
+nsresult NS_NewPrototypeDocumentContentSink(nsIContentSink** aResult,
+ mozilla::dom::Document* aDoc,
+ nsIURI* aURI,
+ nsISupports* aContainer,
+ nsIChannel* aChannel);
+
+namespace mozilla::dom {
+
+class PrototypeDocumentContentSink final : public nsIStreamLoaderObserver,
+ public nsIContentSink,
+ public nsICSSLoaderObserver,
+ public nsIOffThreadScriptReceiver {
+ public:
+ PrototypeDocumentContentSink();
+
+ nsresult Init(Document* aDoc, nsIURI* aURL, nsISupports* aContainer,
+ nsIChannel* aChannel);
+
+ // nsISupports
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_NSISTREAMLOADEROBSERVER
+
+ NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(PrototypeDocumentContentSink,
+ nsIContentSink)
+
+ // nsIContentSink
+ NS_IMETHOD WillParse(void) override { return NS_OK; };
+ NS_IMETHOD WillInterrupt(void) override { return NS_OK; };
+ void WillResume() override{};
+ NS_IMETHOD SetParser(nsParserBase* aParser) override;
+ virtual void InitialTranslationCompleted() override;
+ virtual void FlushPendingNotifications(FlushType aType) override{};
+ virtual void SetDocumentCharset(NotNull<const Encoding*> aEncoding) override;
+ virtual nsISupports* GetTarget() override;
+ virtual bool IsScriptExecuting() override;
+ virtual void ContinueInterruptedParsingAsync() override;
+
+ // nsICSSLoaderObserver
+ NS_IMETHOD StyleSheetLoaded(StyleSheet* aSheet, bool aWasDeferred,
+ nsresult aStatus) override;
+
+ // nsIOffThreadScriptReceiver
+ NS_IMETHOD OnScriptCompileComplete(JS::Stencil* aStencil,
+ nsresult aStatus) override;
+
+ nsresult OnPrototypeLoadDone(nsXULPrototypeDocument* aPrototype);
+
+ protected:
+ virtual ~PrototypeDocumentContentSink();
+
+ static LazyLogModule gLog;
+
+ nsIParser* GetParser();
+
+ void ContinueInterruptedParsingIfEnabled();
+ void StartLayout();
+
+ virtual nsresult AddAttributes(nsXULPrototypeElement* aPrototype,
+ Element* aElement);
+
+ RefPtr<nsParserBase> mParser;
+ nsCOMPtr<nsIURI> mDocumentURI;
+ RefPtr<Document> mDocument;
+ RefPtr<ScriptLoader> mScriptLoader;
+
+ PrototypeDocumentContentSink* mNextSrcLoadWaiter; // [OWNER] but not COMPtr
+
+ /**
+ * The prototype-script of the current transcluded script that is being
+ * loaded. For document.write('<script src="nestedwrite.js"><\/script>')
+ * to work, these need to be in a stack element type, and we need to hold
+ * the top of stack here.
+ */
+ nsXULPrototypeScript* mCurrentScriptProto;
+
+ /**
+ * Whether the current transcluded script is being compiled off thread.
+ * The load event is blocked while this is in progress.
+ */
+ bool mOffThreadCompiling;
+
+ /**
+ * If the current transcluded script is being compiled off thread, the
+ * source for that script.
+ */
+ Utf8Unit* mOffThreadCompileStringBuf;
+ size_t mOffThreadCompileStringLength;
+
+ /**
+ * Wether the prototype document is still be traversed to create the DOM.
+ * Layout will not be started until false.
+ */
+ bool mStillWalking;
+
+ /**
+ * Number of style sheets still loading. Layout will not start until zero.
+ */
+ uint32_t mPendingSheets;
+
+ /**
+ * Context stack, which maintains the state of the Builder and allows
+ * it to be interrupted.
+ */
+ class ContextStack {
+ protected:
+ struct Entry {
+ nsXULPrototypeElement* mPrototype;
+ nsIContent* mElement;
+ int32_t mIndex;
+ Entry* mNext;
+ };
+
+ Entry* mTop;
+ int32_t mDepth;
+
+ public:
+ ContextStack();
+ ~ContextStack();
+
+ int32_t Depth() { return mDepth; }
+
+ nsresult Push(nsXULPrototypeElement* aPrototype, nsIContent* aElement);
+ nsresult Pop();
+ nsresult Peek(nsXULPrototypeElement** aPrototype, nsIContent** aElement,
+ int32_t* aIndex);
+
+ nsresult SetTopIndex(int32_t aIndex);
+
+ void Traverse(nsCycleCollectionTraversalCallback& aCallback,
+ const char* aName, uint32_t aFlags = 0);
+ void Clear();
+
+ // Cycle collector helpers for ContextStack.
+ friend void ImplCycleCollectionUnlink(
+ PrototypeDocumentContentSink::ContextStack& aField) {
+ aField.Clear();
+ }
+
+ friend void ImplCycleCollectionTraverse(
+ nsCycleCollectionTraversalCallback& aCallback,
+ PrototypeDocumentContentSink::ContextStack& aField, const char* aName,
+ uint32_t aFlags = 0) {
+ aField.Traverse(aCallback, aName, aFlags);
+ }
+ };
+
+ friend class ContextStack;
+ ContextStack mContextStack;
+
+ /**
+ * The current prototype that we are walking to construct the
+ * content model.
+ */
+ RefPtr<nsXULPrototypeDocument> mCurrentPrototype;
+ nsresult CreateAndInsertPI(const nsXULPrototypePI* aProtoPI);
+ nsresult ExecuteScript(nsXULPrototypeScript* aScript);
+ nsresult LoadScript(nsXULPrototypeScript* aScriptProto, bool* aBlock);
+
+ /**
+ * A wrapper around ResumeWalkInternal to report walking errors.
+ */
+ nsresult ResumeWalk();
+
+ /**
+ * Resume (or initiate) an interrupted (or newly prepared)
+ * prototype walk.
+ */
+ nsresult ResumeWalkInternal();
+
+ /**
+ * Called at the end of ResumeWalk(), from StyleSheetLoaded(),
+ * and from DocumentL10n.
+ * If walking, stylesheets and l10n are not blocking, it
+ * will trigger `DoneWalking()`.
+ */
+ nsresult MaybeDoneWalking();
+
+ /**
+ * Called from `MaybeDoneWalking()`.
+ * Expects that both the prototype document walk is complete and
+ * all referenced stylesheets finished loading.
+ */
+ nsresult DoneWalking();
+
+ /**
+ * Create a delegate content model element from a prototype.
+ * Note that the resulting content node is not bound to any tree
+ */
+ nsresult CreateElementFromPrototype(nsXULPrototypeElement* aPrototype,
+ Element** aResult, nsIContent* aParent);
+ /**
+ * Prepare to walk the current prototype.
+ */
+ nsresult PrepareToWalk();
+ /**
+ * Creates a processing instruction based on aProtoPI and inserts
+ * it to the DOM.
+ */
+ nsresult CreateAndInsertPI(const nsXULPrototypePI* aProtoPI, nsINode* aParent,
+ nsINode* aBeforeThis);
+
+ /**
+ * Inserts the passed <?xml-stylesheet ?> PI at the specified
+ * index. Loads and applies the associated stylesheet
+ * asynchronously.
+ * The prototype document walk can happen before the stylesheets
+ * are loaded, but the final steps in the load process (see
+ * DoneWalking()) are not run before all the stylesheets are done
+ * loading.
+ */
+ nsresult InsertXMLStylesheetPI(const nsXULPrototypePI* aProtoPI,
+ nsINode* aParent, nsINode* aBeforeThis,
+ XMLStylesheetProcessingInstruction* aPINode);
+ void CloseElement(Element* aElement, bool aHadChildren);
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_PrototypeDocumentContentSink_h__
diff --git a/dom/prototype/moz.build b/dom/prototype/moz.build
new file mode 100644
index 0000000000..f73b3824ac
--- /dev/null
+++ b/dom/prototype/moz.build
@@ -0,0 +1,25 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+with Files("**"):
+ BUG_COMPONENT = ("Core", "XUL")
+
+EXPORTS.mozilla.dom += [
+ "PrototypeDocumentContentSink.h",
+]
+
+SOURCES += [
+ "PrototypeDocumentContentSink.cpp",
+]
+
+LOCAL_INCLUDES += [
+ "/dom/base",
+ "/dom/xul",
+]
+
+MOCHITEST_CHROME_MANIFESTS += ["tests/chrome/chrome.ini"]
+
+FINAL_LIBRARY = "xul"
diff --git a/dom/prototype/tests/chrome/chrome.ini b/dom/prototype/tests/chrome/chrome.ini
new file mode 100644
index 0000000000..8de4e4bfb5
--- /dev/null
+++ b/dom/prototype/tests/chrome/chrome.ini
@@ -0,0 +1,7 @@
+[DEFAULT]
+support-files =
+ whitespace.xhtml
+ no_whitespace.xhtml
+ form.xhtml
+
+[test_prototype_document.xhtml]
diff --git a/dom/prototype/tests/chrome/form.xhtml b/dom/prototype/tests/chrome/form.xhtml
new file mode 100644
index 0000000000..0659215e6e
--- /dev/null
+++ b/dom/prototype/tests/chrome/form.xhtml
@@ -0,0 +1,8 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <body>
+ <input type="text" id="input" value=""/>
+ </body>
+</html>
diff --git a/dom/prototype/tests/chrome/no_whitespace.xhtml b/dom/prototype/tests/chrome/no_whitespace.xhtml
new file mode 100644
index 0000000000..78a8c34cff
--- /dev/null
+++ b/dom/prototype/tests/chrome/no_whitespace.xhtml
@@ -0,0 +1,3 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/strict.dtd">
+<html xmlns="http://www.w3.org/TR/xhtml1/strict"><body>Hello<p>there!</p></body></html>
diff --git a/dom/prototype/tests/chrome/test_prototype_document.xhtml b/dom/prototype/tests/chrome/test_prototype_document.xhtml
new file mode 100644
index 0000000000..47dff2d565
--- /dev/null
+++ b/dom/prototype/tests/chrome/test_prototype_document.xhtml
@@ -0,0 +1,73 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
+<window title="Test prototype document"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script><![CDATA[
+ SimpleTest.waitForExplicitFinish();
+ ]]></script>
+ <browser type="chrome" id="browser" flex="1"/>
+ <body xmlns="http://www.w3.org/1999/xhtml">
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+
+
+<script><![CDATA[
+
+async function load(frame, url) {
+ return new Promise((resolve) => {
+ frame.addEventListener("load", () => {
+ resolve();
+ }, {once: true});
+
+ if (frame.src === url) {
+ frame.reload();
+ } else {
+ frame.src = url;
+ }
+ });
+}
+
+// Load a file with and without the prototype document cache enabled.
+async function compare(filename) {
+ let browser = document.getElementById("browser");
+ // Load the page with no prototype document cache (the regular loading flow of
+ // an XHTML page).
+ await SpecialPowers.pushPrefEnv({ set: [["dom.prototype_document_cache.enabled", false]] });
+ await load(browser, filename);
+ ok(!browser.contentDocument.loadedFromPrototype, `${filename} should not use prototype.`);
+ const contentWithoutPrototype = browser.contentDocument.documentElement.outerHTML;
+
+ // Load the page with the prototype document cache enabled. The prototype should
+ // be built from the source file.
+ await SpecialPowers.pushPrefEnv({ set: [["dom.prototype_document_cache.enabled", true]] });
+ await load(browser, filename);
+ ok(browser.contentDocument.loadedFromPrototype, `${filename} should load from prototype.`);
+ const contentWithPrototype = browser.contentDocument.documentElement.outerHTML;
+ is(contentWithPrototype, contentWithoutPrototype, `${filename} document contents should be the same.`);
+}
+
+add_task(async function test_prototype_document() {
+ await compare("no_whitespace.xhtml");
+ await compare("whitespace.xhtml");
+ // TODO: Test whitespace within XUL elements, since it is handled differently
+ // with and without the prototype sink (bug 1544567).
+});
+
+add_task(async function test_prototype_document_form() {
+ let browser = document.getElementById("browser");
+ await load(browser, "form.xhtml");
+ ok(browser.contentDocument.loadedFromPrototype, `form.xhtml should load from prototype.`);
+ browser.contentDocument.getElementById("input").value = "test";
+ await load(browser, "form.xhtml");
+ is(browser.contentDocument.getElementById("input").value, "test", "input value should persist after reload");
+});
+]]></script>
+</body>
+</window>
diff --git a/dom/prototype/tests/chrome/whitespace.xhtml b/dom/prototype/tests/chrome/whitespace.xhtml
new file mode 100644
index 0000000000..0f121973c7
--- /dev/null
+++ b/dom/prototype/tests/chrome/whitespace.xhtml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/strict.dtd">
+<html xmlns="http://www.w3.org/TR/xhtml1/strict">
+ <body>
+ Hello
+ <p>there!</p>
+ </body>
+</html>