/* -*- 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 #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 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 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 = 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 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 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(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 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 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 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 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 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 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 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 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 // RefPtr 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(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 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 = 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 ni; ni = mNodeInfoManager->GetNodeInfo(aName, nullptr, kNameSpaceID_XHTML, nsINode::ELEMENT_NODE); nsCOMPtr 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 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); } }