diff options
Diffstat (limited to 'dom/xslt/xslt/txMozillaXMLOutput.cpp')
-rw-r--r-- | dom/xslt/xslt/txMozillaXMLOutput.cpp | 948 |
1 files changed, 948 insertions, 0 deletions
diff --git a/dom/xslt/xslt/txMozillaXMLOutput.cpp b/dom/xslt/xslt/txMozillaXMLOutput.cpp new file mode 100644 index 0000000000..81f260420b --- /dev/null +++ b/dom/xslt/xslt/txMozillaXMLOutput.cpp @@ -0,0 +1,948 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "txMozillaXMLOutput.h" + +#include "mozilla/dom/Document.h" +#include "nsIDocShell.h" +#include "nsIScriptElement.h" +#include "nsCharsetSource.h" +#include "nsIRefreshURI.h" +#include "nsPIDOMWindow.h" +#include "nsIContent.h" +#include "nsContentCID.h" +#include "nsUnicharUtils.h" +#include "nsGkAtoms.h" +#include "txLog.h" +#include "nsNameSpaceManager.h" +#include "txStringUtils.h" +#include "txURIUtils.h" +#include "nsIDocumentTransformer.h" +#include "mozilla/StyleSheetInlines.h" +#include "mozilla/css/Loader.h" +#include "mozilla/dom/DocumentType.h" +#include "mozilla/dom/DocumentFragment.h" +#include "mozilla/dom/Element.h" +#include "mozilla/dom/ScriptLoader.h" +#include "mozilla/Encoding.h" +#include "nsContentUtils.h" +#include "nsDocElementCreatedNotificationRunner.h" +#include "txXMLUtils.h" +#include "nsContentSink.h" +#include "nsINode.h" +#include "nsContentCreatorFunctions.h" +#include "nsError.h" +#include "nsStringFlags.h" +#include "nsStyleUtil.h" +#include "nsIFrame.h" +#include <algorithm> +#include "nsTextNode.h" +#include "nsDocShell.h" +#include "mozilla/dom/Comment.h" +#include "mozilla/dom/ProcessingInstruction.h" + +using namespace mozilla; +using namespace mozilla::dom; + +#define TX_ENSURE_CURRENTNODE \ + NS_ASSERTION(mCurrentNode, "mCurrentNode is nullptr"); \ + if (!mCurrentNode) return NS_ERROR_UNEXPECTED + +txMozillaXMLOutput::txMozillaXMLOutput(Document* aSourceDocument, + txOutputFormat* aFormat, + nsITransformObserver* aObserver) + : mTreeDepth(0), + mBadChildLevel(0), + mTableState(NORMAL), + mCreatingNewDocument(true), + mOpenedElementIsHTML(false), + mRootContentCreated(false), + mNoFixup(false) { + MOZ_COUNT_CTOR(txMozillaXMLOutput); + if (aObserver) { + mNotifier = new txTransformNotifier(aSourceDocument); + if (mNotifier) { + mNotifier->Init(aObserver); + } + } + + mOutputFormat.merge(*aFormat); + mOutputFormat.setFromDefaults(); +} + +txMozillaXMLOutput::txMozillaXMLOutput(txOutputFormat* aFormat, + DocumentFragment* aFragment, + bool aNoFixup) + : mTreeDepth(0), + mBadChildLevel(0), + mTableState(NORMAL), + mCreatingNewDocument(false), + mOpenedElementIsHTML(false), + mRootContentCreated(false), + mNoFixup(aNoFixup) { + MOZ_COUNT_CTOR(txMozillaXMLOutput); + mOutputFormat.merge(*aFormat); + mOutputFormat.setFromDefaults(); + + mCurrentNode = aFragment; + mDocument = mCurrentNode->OwnerDoc(); + mNodeInfoManager = mDocument->NodeInfoManager(); +} + +txMozillaXMLOutput::~txMozillaXMLOutput() { + MOZ_COUNT_DTOR(txMozillaXMLOutput); +} + +nsresult txMozillaXMLOutput::attribute(nsAtom* aPrefix, nsAtom* aLocalName, + nsAtom* aLowercaseLocalName, + const int32_t aNsID, + const nsString& aValue) { + RefPtr<nsAtom> owner; + if (mOpenedElementIsHTML && aNsID == kNameSpaceID_None) { + if (aLowercaseLocalName) { + aLocalName = aLowercaseLocalName; + } else { + owner = TX_ToLowerCaseAtom(aLocalName); + NS_ENSURE_TRUE(owner, NS_ERROR_OUT_OF_MEMORY); + + aLocalName = owner; + } + } + + return attributeInternal(aPrefix, aLocalName, aNsID, aValue); +} + +nsresult txMozillaXMLOutput::attribute(nsAtom* aPrefix, + const nsAString& aLocalName, + const int32_t aNsID, + const nsString& aValue) { + RefPtr<nsAtom> lname; + + if (mOpenedElementIsHTML && aNsID == kNameSpaceID_None) { + nsAutoString lnameStr; + nsContentUtils::ASCIIToLower(aLocalName, lnameStr); + lname = NS_Atomize(lnameStr); + } else { + lname = NS_Atomize(aLocalName); + } + + NS_ENSURE_TRUE(lname, NS_ERROR_OUT_OF_MEMORY); + + // Check that it's a valid name + if (!nsContentUtils::IsValidNodeName(lname, aPrefix, aNsID)) { + // Try without prefix + aPrefix = nullptr; + if (!nsContentUtils::IsValidNodeName(lname, aPrefix, aNsID)) { + // Don't return error here since the callers don't deal + return NS_OK; + } + } + + return attributeInternal(aPrefix, lname, aNsID, aValue); +} + +nsresult txMozillaXMLOutput::attributeInternal(nsAtom* aPrefix, + nsAtom* aLocalName, + int32_t aNsID, + const nsString& aValue) { + if (!mOpenedElement) { + // XXX Signal this? (can't add attributes after element closed) + return NS_OK; + } + + NS_ASSERTION(!mBadChildLevel, "mBadChildLevel set when element is opened"); + + return mOpenedElement->SetAttr(aNsID, aLocalName, aPrefix, aValue, false); +} + +nsresult txMozillaXMLOutput::characters(const nsAString& aData, bool aDOE) { + nsresult rv = closePrevious(false); + NS_ENSURE_SUCCESS(rv, rv); + + if (!mBadChildLevel) { + mText.Append(aData); + } + + return NS_OK; +} + +nsresult txMozillaXMLOutput::comment(const nsString& aData) { + nsresult rv = closePrevious(true); + NS_ENSURE_SUCCESS(rv, rv); + + if (mBadChildLevel) { + return NS_OK; + } + + TX_ENSURE_CURRENTNODE; + + RefPtr<Comment> comment = new (mNodeInfoManager) Comment(mNodeInfoManager); + + rv = comment->SetText(aData, false); + NS_ENSURE_SUCCESS(rv, rv); + + ErrorResult error; + mCurrentNode->AppendChildTo(comment, true, error); + return error.StealNSResult(); +} + +nsresult txMozillaXMLOutput::endDocument(nsresult aResult) { + TX_ENSURE_CURRENTNODE; + + if (NS_FAILED(aResult)) { + if (mNotifier) { + mNotifier->OnTransformEnd(aResult); + } + + return NS_OK; + } + + nsresult rv = closePrevious(true); + if (NS_FAILED(rv)) { + if (mNotifier) { + mNotifier->OnTransformEnd(rv); + } + + return rv; + } + + if (mCreatingNewDocument) { + // This should really be handled by Document::EndLoad + MOZ_ASSERT(mDocument->GetReadyStateEnum() == Document::READYSTATE_LOADING, + "Bad readyState"); + mDocument->SetReadyStateInternal(Document::READYSTATE_INTERACTIVE); + if (ScriptLoader* loader = mDocument->ScriptLoader()) { + loader->ParsingComplete(false); + } + } + + if (mNotifier) { + mNotifier->OnTransformEnd(); + } + + return NS_OK; +} + +nsresult txMozillaXMLOutput::endElement() { + TX_ENSURE_CURRENTNODE; + + if (mBadChildLevel) { + --mBadChildLevel; + MOZ_LOG(txLog::xslt, LogLevel::Debug, + ("endElement, mBadChildLevel = %d\n", mBadChildLevel)); + return NS_OK; + } + + --mTreeDepth; + + nsresult rv = closePrevious(true); + NS_ENSURE_SUCCESS(rv, rv); + + NS_ASSERTION(mCurrentNode->IsElement(), "borked mCurrentNode"); + NS_ENSURE_TRUE(mCurrentNode->IsElement(), NS_ERROR_UNEXPECTED); + + Element* element = mCurrentNode->AsElement(); + + // Handle html-elements + if (!mNoFixup) { + if (element->IsHTMLElement()) { + endHTMLElement(element); + } + + // Handle elements that are different when parser-created + if (nsIContent::RequiresDoneCreatingElement( + element->NodeInfo()->NamespaceID(), + element->NodeInfo()->NameAtom())) { + element->DoneCreatingElement(); + } else if (element->IsSVGElement(nsGkAtoms::script) || + element->IsHTMLElement(nsGkAtoms::script)) { + nsCOMPtr<nsIScriptElement> sele = do_QueryInterface(element); + if (sele) { + bool block = sele->AttemptToExecute(); + // If the act of insertion evaluated the script, we're fine. + // Else, add this script element to the array of loading scripts. + if (block) { + mNotifier->AddScriptElement(sele); + } + } else { + MOZ_ASSERT(nsNameSpaceManager::GetInstance()->mSVGDisabled, + "Script elements need to implement nsIScriptElement and SVG " + "wasn't disabled."); + } + } else if (nsIContent::RequiresDoneAddingChildren( + element->NodeInfo()->NamespaceID(), + element->NodeInfo()->NameAtom())) { + element->DoneAddingChildren(true); + } + } + + if (mCreatingNewDocument) { + // Handle all sorts of stylesheets + if (auto* linkStyle = LinkStyle::FromNode(*mCurrentNode)) { + linkStyle->SetEnableUpdates(true); + auto updateOrError = linkStyle->UpdateStyleSheet(mNotifier); + if (mNotifier && updateOrError.isOk() && + updateOrError.unwrap().ShouldBlock()) { + mNotifier->AddPendingStylesheet(); + } + } + } + + // Add the element to the tree if it wasn't added before and take one step + // up the tree + MOZ_ASSERT(!mCurrentNodeStack.IsEmpty(), "empty stack"); + nsCOMPtr<nsINode> parent; + if (!mCurrentNodeStack.IsEmpty()) { + parent = mCurrentNodeStack.PopLastElement(); + } + + if (mCurrentNode == mNonAddedNode) { + if (parent == mDocument) { + NS_ASSERTION(!mRootContentCreated, + "Parent to add to shouldn't be a document if we " + "have a root content"); + mRootContentCreated = true; + } + + // Check to make sure that script hasn't inserted the node somewhere + // else in the tree + if (!mCurrentNode->GetParentNode()) { + parent->AppendChildTo(mNonAddedNode, true, IgnoreErrors()); + } + mNonAddedNode = nullptr; + } + + mCurrentNode = parent; + + mTableState = + static_cast<TableState>(NS_PTR_TO_INT32(mTableStateStack.pop())); + + return NS_OK; +} + +void txMozillaXMLOutput::getOutputDocument(Document** aDocument) { + NS_IF_ADDREF(*aDocument = mDocument); +} + +nsresult txMozillaXMLOutput::processingInstruction(const nsString& aTarget, + const nsString& aData) { + nsresult rv = closePrevious(true); + NS_ENSURE_SUCCESS(rv, rv); + + if (mOutputFormat.mMethod == eHTMLOutput) return NS_OK; + + TX_ENSURE_CURRENTNODE; + + rv = nsContentUtils::CheckQName(aTarget, false); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIContent> pi = + NS_NewXMLProcessingInstruction(mNodeInfoManager, aTarget, aData); + + LinkStyle* linkStyle = nullptr; + if (mCreatingNewDocument) { + linkStyle = LinkStyle::FromNode(*pi); + if (linkStyle) { + linkStyle->SetEnableUpdates(false); + } + } + + ErrorResult error; + mCurrentNode->AppendChildTo(pi, true, error); + if (error.Failed()) { + return error.StealNSResult(); + } + + if (linkStyle) { + linkStyle->SetEnableUpdates(true); + auto updateOrError = linkStyle->UpdateStyleSheet(mNotifier); + if (mNotifier && updateOrError.isOk() && + updateOrError.unwrap().ShouldBlock()) { + mNotifier->AddPendingStylesheet(); + } + } + + return NS_OK; +} + +nsresult txMozillaXMLOutput::startDocument() { + if (mNotifier) { + mNotifier->OnTransformStart(); + } + + if (mCreatingNewDocument) { + ScriptLoader* loader = mDocument->ScriptLoader(); + if (loader) { + loader->BeginDeferringScripts(); + } + } + + return NS_OK; +} + +nsresult txMozillaXMLOutput::startElement(nsAtom* aPrefix, nsAtom* aLocalName, + nsAtom* aLowercaseLocalName, + const int32_t aNsID) { + MOZ_ASSERT(aNsID != kNameSpaceID_None || !aPrefix, + "Can't have prefix without namespace"); + + if (mOutputFormat.mMethod == eHTMLOutput && aNsID == kNameSpaceID_None) { + RefPtr<nsAtom> owner; + if (!aLowercaseLocalName) { + owner = TX_ToLowerCaseAtom(aLocalName); + NS_ENSURE_TRUE(owner, NS_ERROR_OUT_OF_MEMORY); + + aLowercaseLocalName = owner; + } + return startElementInternal(nullptr, aLowercaseLocalName, + kNameSpaceID_XHTML); + } + + return startElementInternal(aPrefix, aLocalName, aNsID); +} + +nsresult txMozillaXMLOutput::startElement(nsAtom* aPrefix, + const nsAString& aLocalName, + const int32_t aNsID) { + int32_t nsId = aNsID; + RefPtr<nsAtom> lname; + + if (mOutputFormat.mMethod == eHTMLOutput && aNsID == kNameSpaceID_None) { + nsId = kNameSpaceID_XHTML; + + nsAutoString lnameStr; + nsContentUtils::ASCIIToLower(aLocalName, lnameStr); + lname = NS_Atomize(lnameStr); + } else { + lname = NS_Atomize(aLocalName); + } + + // No biggie if we lose the prefix due to OOM + NS_ENSURE_TRUE(lname, NS_ERROR_OUT_OF_MEMORY); + + // Check that it's a valid name + if (!nsContentUtils::IsValidNodeName(lname, aPrefix, nsId)) { + // Try without prefix + aPrefix = nullptr; + if (!nsContentUtils::IsValidNodeName(lname, aPrefix, nsId)) { + return NS_ERROR_XSLT_BAD_NODE_NAME; + } + } + + return startElementInternal(aPrefix, lname, nsId); +} + +nsresult txMozillaXMLOutput::startElementInternal(nsAtom* aPrefix, + nsAtom* aLocalName, + int32_t aNsID) { + TX_ENSURE_CURRENTNODE; + + if (mBadChildLevel) { + ++mBadChildLevel; + MOZ_LOG(txLog::xslt, LogLevel::Debug, + ("startElement, mBadChildLevel = %d\n", mBadChildLevel)); + return NS_OK; + } + + nsresult rv = closePrevious(true); + NS_ENSURE_SUCCESS(rv, rv); + + // Push and init state + if (mTreeDepth == MAX_REFLOW_DEPTH) { + // eCloseElement couldn't add the parent so we fail as well or we've + // reached the limit of the depth of the tree that we allow. + ++mBadChildLevel; + MOZ_LOG(txLog::xslt, LogLevel::Debug, + ("startElement, mBadChildLevel = %d\n", mBadChildLevel)); + return NS_OK; + } + + ++mTreeDepth; + + mTableStateStack.push(NS_INT32_TO_PTR(mTableState)); + + mCurrentNodeStack.AppendElement(mCurrentNode); + + mTableState = NORMAL; + mOpenedElementIsHTML = false; + + // Create the element + RefPtr<NodeInfo> ni = mNodeInfoManager->GetNodeInfo( + aLocalName, aPrefix, aNsID, nsINode::ELEMENT_NODE); + + NS_NewElement(getter_AddRefs(mOpenedElement), ni.forget(), + mCreatingNewDocument ? FROM_PARSER_XSLT : FROM_PARSER_FRAGMENT); + + // Set up the element and adjust state + if (!mNoFixup) { + if (aNsID == kNameSpaceID_XHTML) { + mOpenedElementIsHTML = (mOutputFormat.mMethod == eHTMLOutput); + rv = startHTMLElement(mOpenedElement, mOpenedElementIsHTML); + NS_ENSURE_SUCCESS(rv, rv); + } + } + + if (mCreatingNewDocument) { + // Handle all sorts of stylesheets + if (auto* linkStyle = LinkStyle::FromNodeOrNull(mOpenedElement)) { + linkStyle->SetEnableUpdates(false); + } + } + + return NS_OK; +} + +nsresult txMozillaXMLOutput::closePrevious(bool aFlushText) { + TX_ENSURE_CURRENTNODE; + + nsresult rv; + if (mOpenedElement) { + bool currentIsDoc = mCurrentNode == mDocument; + if (currentIsDoc && mRootContentCreated) { + // We already have a document element, but the XSLT spec allows this. + // As a workaround, create a wrapper object and use that as the + // document element. + + rv = createTxWrapper(); + NS_ENSURE_SUCCESS(rv, rv); + } + + ErrorResult error; + mCurrentNode->AppendChildTo(mOpenedElement, true, error); + if (error.Failed()) { + return error.StealNSResult(); + } + + if (currentIsDoc) { + mRootContentCreated = true; + nsContentUtils::AddScriptRunner( + new nsDocElementCreatedNotificationRunner(mDocument)); + } + + mCurrentNode = mOpenedElement; + mOpenedElement = nullptr; + } else if (aFlushText && !mText.IsEmpty()) { + // Text can't appear in the root of a document + if (mDocument == mCurrentNode) { + if (XMLUtils::isWhitespace(mText)) { + mText.Truncate(); + + return NS_OK; + } + + rv = createTxWrapper(); + NS_ENSURE_SUCCESS(rv, rv); + } + RefPtr<nsTextNode> text = + new (mNodeInfoManager) nsTextNode(mNodeInfoManager); + + rv = text->SetText(mText, false); + NS_ENSURE_SUCCESS(rv, rv); + + ErrorResult error; + mCurrentNode->AppendChildTo(text, true, error); + if (error.Failed()) { + return error.StealNSResult(); + } + + mText.Truncate(); + } + + return NS_OK; +} + +nsresult txMozillaXMLOutput::createTxWrapper() { + NS_ASSERTION(mDocument == mCurrentNode, + "creating wrapper when document isn't parent"); + + int32_t namespaceID; + nsresult rv = nsNameSpaceManager::GetInstance()->RegisterNameSpace( + nsLiteralString(kTXNameSpaceURI), namespaceID); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<Element> wrapper = + mDocument->CreateElem(nsDependentAtomString(nsGkAtoms::result), + nsGkAtoms::transformiix, namespaceID); + +#ifdef DEBUG + // Keep track of the location of the current documentElement, if there is + // one, so we can verify later + uint32_t j = 0, rootLocation = 0; +#endif + for (nsCOMPtr<nsIContent> childContent = mDocument->GetFirstChild(); + childContent; childContent = childContent->GetNextSibling()) { +#ifdef DEBUG + if (childContent->IsElement()) { + rootLocation = j; + } +#endif + + if (childContent->NodeInfo()->NameAtom() == + nsGkAtoms::documentTypeNodeName) { +#ifdef DEBUG + // The new documentElement should go after the document type. + // This is needed for cases when there is no existing + // documentElement in the document. + rootLocation = std::max(rootLocation, j + 1); + ++j; +#endif + } else { + mDocument->RemoveChildNode(childContent, true); + + ErrorResult error; + wrapper->AppendChildTo(childContent, true, error); + if (error.Failed()) { + return error.StealNSResult(); + } + break; + } + } + + mCurrentNodeStack.AppendElement(wrapper); + mCurrentNode = wrapper; + mRootContentCreated = true; + NS_ASSERTION(rootLocation == mDocument->GetChildCount(), + "Incorrect root location"); + ErrorResult error; + mDocument->AppendChildTo(wrapper, true, error); + return error.StealNSResult(); +} + +nsresult txMozillaXMLOutput::startHTMLElement(nsIContent* aElement, + bool aIsHTML) { + nsresult rv = NS_OK; + + if ((!aElement->IsHTMLElement(nsGkAtoms::tr) || !aIsHTML) && + NS_PTR_TO_INT32(mTableStateStack.peek()) == ADDED_TBODY) { + MOZ_ASSERT(!mCurrentNodeStack.IsEmpty(), "empty stack"); + if (mCurrentNodeStack.IsEmpty()) { + mCurrentNode = nullptr; + } else { + mCurrentNode = mCurrentNodeStack.PopLastElement(); + } + mTableStateStack.pop(); + } + + if (aElement->IsHTMLElement(nsGkAtoms::table) && aIsHTML) { + mTableState = TABLE; + } else if (aElement->IsHTMLElement(nsGkAtoms::tr) && aIsHTML && + NS_PTR_TO_INT32(mTableStateStack.peek()) == TABLE) { + RefPtr<Element> tbody; + rv = createHTMLElement(nsGkAtoms::tbody, getter_AddRefs(tbody)); + NS_ENSURE_SUCCESS(rv, rv); + + ErrorResult error; + mCurrentNode->AppendChildTo(tbody, true, error); + if (error.Failed()) { + return error.StealNSResult(); + } + + mTableStateStack.push(NS_INT32_TO_PTR(ADDED_TBODY)); + + mCurrentNodeStack.AppendElement(tbody); + mCurrentNode = tbody; + } else if (aElement->IsHTMLElement(nsGkAtoms::head) && + mOutputFormat.mMethod == eHTMLOutput) { + // Insert META tag, according to spec, 16.2, like + // <META http-equiv="Content-Type" content="text/html; charset=EUC-JP"> + RefPtr<Element> meta; + rv = createHTMLElement(nsGkAtoms::meta, getter_AddRefs(meta)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = meta->SetAttr(kNameSpaceID_None, nsGkAtoms::httpEquiv, + u"Content-Type"_ns, false); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoString metacontent; + CopyUTF8toUTF16(mOutputFormat.mMediaType, metacontent); + metacontent.AppendLiteral("; charset="); + metacontent.Append(mOutputFormat.mEncoding); + rv = meta->SetAttr(kNameSpaceID_None, nsGkAtoms::content, metacontent, + false); + NS_ENSURE_SUCCESS(rv, rv); + + // No need to notify since aElement hasn't been inserted yet + NS_ASSERTION(!aElement->IsInUncomposedDoc(), "should not be in doc"); + ErrorResult error; + aElement->AppendChildTo(meta, false, error); + if (error.Failed()) { + return error.StealNSResult(); + } + } + + return NS_OK; +} + +void txMozillaXMLOutput::endHTMLElement(nsIContent* aElement) { + if (mTableState == ADDED_TBODY) { + NS_ASSERTION(aElement->IsHTMLElement(nsGkAtoms::tbody), + "Element flagged as added tbody isn't a tbody"); + MOZ_ASSERT(!mCurrentNodeStack.IsEmpty(), "empty stack"); + if (mCurrentNodeStack.IsEmpty()) { + mCurrentNode = nullptr; + } else { + mCurrentNode = mCurrentNodeStack.PopLastElement(); + } + mTableState = + static_cast<TableState>(NS_PTR_TO_INT32(mTableStateStack.pop())); + } +} + +nsresult txMozillaXMLOutput::createResultDocument(const nsAString& aName, + int32_t aNsID, + Document* aSourceDocument, + bool aLoadedAsData) { + nsresult rv; + + // Create the document + if (mOutputFormat.mMethod == eHTMLOutput) { + rv = NS_NewHTMLDocument(getter_AddRefs(mDocument), aLoadedAsData); + NS_ENSURE_SUCCESS(rv, rv); + } else { + // We should check the root name/namespace here and create the + // appropriate document + rv = NS_NewXMLDocument(getter_AddRefs(mDocument), aLoadedAsData); + NS_ENSURE_SUCCESS(rv, rv); + } + // This should really be handled by Document::BeginLoad + MOZ_ASSERT( + mDocument->GetReadyStateEnum() == Document::READYSTATE_UNINITIALIZED, + "Bad readyState"); + mDocument->SetReadyStateInternal(Document::READYSTATE_LOADING); + mDocument->SetMayStartLayout(false); + bool hasHadScriptObject = false; + nsIScriptGlobalObject* sgo = + aSourceDocument->GetScriptHandlingObject(hasHadScriptObject); + NS_ENSURE_STATE(sgo || !hasHadScriptObject); + + mCurrentNode = mDocument; + mNodeInfoManager = mDocument->NodeInfoManager(); + + // Reset and set up the document + URIUtils::ResetWithSource(mDocument, aSourceDocument); + + // Make sure we set the script handling object after resetting with the + // source, so that we have the right principal. + mDocument->SetScriptHandlingObject(sgo); + + mDocument->SetStateObjectFrom(aSourceDocument); + + // Set the charset + if (!mOutputFormat.mEncoding.IsEmpty()) { + const Encoding* encoding = Encoding::ForLabel(mOutputFormat.mEncoding); + if (encoding) { + mDocument->SetDocumentCharacterSetSource(kCharsetFromOtherComponent); + mDocument->SetDocumentCharacterSet(WrapNotNull(encoding)); + } + } + + // Set the mime-type + if (!mOutputFormat.mMediaType.IsEmpty()) { + mDocument->SetContentType(mOutputFormat.mMediaType); + } else if (mOutputFormat.mMethod == eHTMLOutput) { + mDocument->SetContentType("text/html"_ns); + } else { + mDocument->SetContentType("application/xml"_ns); + } + + if (mOutputFormat.mMethod == eXMLOutput && + mOutputFormat.mOmitXMLDeclaration != eTrue) { + int32_t standalone; + if (mOutputFormat.mStandalone == eNotSet) { + standalone = -1; + } else if (mOutputFormat.mStandalone == eFalse) { + standalone = 0; + } else { + standalone = 1; + } + + // Could use mOutputFormat.mVersion.get() when we support + // versions > 1.0. + static const char16_t kOneDotZero[] = {'1', '.', '0', '\0'}; + mDocument->SetXMLDeclaration(kOneDotZero, mOutputFormat.mEncoding.get(), + standalone); + } + + // Set up script loader of the result document. + ScriptLoader* loader = mDocument->ScriptLoader(); + if (mNotifier) { + loader->AddObserver(mNotifier); + } else { + // Don't load scripts, we can't notify the caller when they're loaded. + loader->SetEnabled(false); + } + + if (mNotifier) { + rv = mNotifier->SetOutputDocument(mDocument); + NS_ENSURE_SUCCESS(rv, rv); + + rv = mDocument->InitFeaturePolicy(mDocument->GetChannel()); + NS_ENSURE_SUCCESS(rv, rv); + } + + // Do this after calling OnDocumentCreated to ensure that the + // PresShell/PresContext has been hooked up and get notified. + if (mDocument) { + mDocument->SetCompatibilityMode(eCompatibility_FullStandards); + } + + // Add a doc-type if requested + if (!mOutputFormat.mSystemId.IsEmpty()) { + nsAutoString qName; + if (mOutputFormat.mMethod == eHTMLOutput) { + qName.AssignLiteral("html"); + } else { + qName.Assign(aName); + } + + nsresult rv = nsContentUtils::CheckQName(qName); + if (NS_SUCCEEDED(rv)) { + RefPtr<nsAtom> doctypeName = NS_Atomize(qName); + if (!doctypeName) { + return NS_ERROR_OUT_OF_MEMORY; + } + + // Indicate that there is no internal subset (not just an empty one) + RefPtr<DocumentType> documentType = NS_NewDOMDocumentType( + mNodeInfoManager, doctypeName, mOutputFormat.mPublicId, + mOutputFormat.mSystemId, VoidString()); + + ErrorResult error; + mDocument->AppendChildTo(documentType, true, error); + if (error.Failed()) { + return error.StealNSResult(); + } + } + } + + return NS_OK; +} + +nsresult txMozillaXMLOutput::createHTMLElement(nsAtom* aName, + Element** aResult) { + NS_ASSERTION(mOutputFormat.mMethod == eHTMLOutput, + "need to adjust createHTMLElement"); + + *aResult = nullptr; + + RefPtr<NodeInfo> ni; + ni = mNodeInfoManager->GetNodeInfo(aName, nullptr, kNameSpaceID_XHTML, + nsINode::ELEMENT_NODE); + + nsCOMPtr<Element> el; + nsresult rv = NS_NewHTMLElement( + getter_AddRefs(el), ni.forget(), + mCreatingNewDocument ? FROM_PARSER_XSLT : FROM_PARSER_FRAGMENT); + el.forget(aResult); + return rv; +} + +txTransformNotifier::txTransformNotifier(Document* aSourceDocument) + : mSourceDocument(aSourceDocument), + mPendingStylesheetCount(0), + mInTransform(false) {} + +txTransformNotifier::~txTransformNotifier() = default; + +NS_IMPL_ISUPPORTS(txTransformNotifier, nsIScriptLoaderObserver, + nsICSSLoaderObserver) + +NS_IMETHODIMP +txTransformNotifier::ScriptAvailable(nsresult aResult, + nsIScriptElement* aElement, + bool aIsInlineClassicScript, nsIURI* aURI, + uint32_t aLineNo) { + if (NS_FAILED(aResult) && mScriptElements.RemoveElement(aElement)) { + SignalTransformEnd(); + } + + return NS_OK; +} + +NS_IMETHODIMP +txTransformNotifier::ScriptEvaluated(nsresult aResult, + nsIScriptElement* aElement, + bool aIsInline) { + if (mScriptElements.RemoveElement(aElement)) { + SignalTransformEnd(); + } + + return NS_OK; +} + +NS_IMETHODIMP +txTransformNotifier::StyleSheetLoaded(StyleSheet* aSheet, bool aWasDeferred, + nsresult aStatus) { + if (mPendingStylesheetCount == 0) { + // We weren't waiting on this stylesheet anyway. This can happen if + // SignalTransformEnd got called with an error aResult. See + // http://bugzilla.mozilla.org/show_bug.cgi?id=215465. + return NS_OK; + } + + // We're never waiting for alternate stylesheets + if (!aWasDeferred) { + --mPendingStylesheetCount; + SignalTransformEnd(); + } + + return NS_OK; +} + +void txTransformNotifier::Init(nsITransformObserver* aObserver) { + mObserver = aObserver; +} + +void txTransformNotifier::AddScriptElement(nsIScriptElement* aElement) { + mScriptElements.AppendElement(aElement); +} + +void txTransformNotifier::AddPendingStylesheet() { ++mPendingStylesheetCount; } + +void txTransformNotifier::OnTransformEnd(nsresult aResult) { + mInTransform = false; + SignalTransformEnd(aResult); +} + +void txTransformNotifier::OnTransformStart() { mInTransform = true; } + +nsresult txTransformNotifier::SetOutputDocument(Document* aDocument) { + mDocument = aDocument; + + // Notify the contentsink that the document is created + return mObserver->OnDocumentCreated(mSourceDocument, mDocument); +} + +void txTransformNotifier::SignalTransformEnd(nsresult aResult) { + if (mInTransform || + (NS_SUCCEEDED(aResult) && + (!mScriptElements.IsEmpty() || mPendingStylesheetCount > 0))) { + return; + } + + // mPendingStylesheetCount is nonzero at this point only if aResult is an + // error. Set it to 0 so we won't reenter this code when we stop the + // CSSLoader. + mPendingStylesheetCount = 0; + mScriptElements.Clear(); + + // Make sure that we don't get deleted while this function is executed and + // we remove ourselfs from the scriptloader + nsCOMPtr<nsIScriptLoaderObserver> kungFuDeathGrip(this); + + if (mDocument) { + mDocument->ScriptLoader()->DeferCheckpointReached(); + mDocument->ScriptLoader()->RemoveObserver(this); + // XXX Maybe we want to cancel script loads if NS_FAILED(rv)? + + if (NS_FAILED(aResult)) { + mDocument->CSSLoader()->Stop(); + } + } + + if (NS_SUCCEEDED(aResult)) { + mObserver->OnTransformDone(mSourceDocument, aResult, mDocument); + } +} |