tenfourfox/dom/xslt/xslt/txMozillaXMLOutput.cpp

1042 lines
31 KiB
C++

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* 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 "nsIDocument.h"
#include "nsIDocShell.h"
#include "nsScriptLoader.h"
#include "nsIDOMDocument.h"
#include "nsIDOMDocumentType.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 "nsIConsoleService.h"
#include "nsIDOMDocumentFragment.h"
#include "nsNameSpaceManager.h"
#include "txStringUtils.h"
#include "txURIUtils.h"
#include "nsIHTMLDocument.h"
#include "nsIStyleSheetLinkingElement.h"
#include "nsIDocumentTransformer.h"
#include "mozilla/CSSStyleSheet.h"
#include "mozilla/css/Loader.h"
#include "mozilla/dom/Element.h"
#include "mozilla/dom/EncodingUtils.h"
#include "nsContentUtils.h"
#include "txXMLUtils.h"
#include "nsContentSink.h"
#include "nsINode.h"
#include "nsContentCreatorFunctions.h"
#include "nsError.h"
#include "nsIFrame.h"
#include <algorithm>
#include "nsTextNode.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(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();
if (mNotifier) {
mNotifier->Init(aObserver);
}
}
mOutputFormat.merge(*aFormat);
mOutputFormat.setFromDefaults();
}
txMozillaXMLOutput::txMozillaXMLOutput(txOutputFormat* aFormat,
nsIDOMDocumentFragment* 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 = do_QueryInterface(aFragment);
mDocument = mCurrentNode->OwnerDoc();
mNodeInfoManager = mDocument->NodeInfoManager();
}
txMozillaXMLOutput::~txMozillaXMLOutput()
{
MOZ_COUNT_DTOR(txMozillaXMLOutput);
}
nsresult
txMozillaXMLOutput::attribute(nsIAtom* aPrefix,
nsIAtom* aLocalName,
nsIAtom* aLowercaseLocalName,
const int32_t aNsID,
const nsString& aValue)
{
nsCOMPtr<nsIAtom> 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(nsIAtom* aPrefix,
const nsSubstring& aLocalName,
const int32_t aNsID,
const nsString& aValue)
{
nsCOMPtr<nsIAtom> lname;
if (mOpenedElementIsHTML && aNsID == kNameSpaceID_None) {
nsAutoString lnameStr;
nsContentUtils::ASCIIToLower(aLocalName, lnameStr);
lname = do_GetAtom(lnameStr);
}
else {
lname = do_GetAtom(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(nsIAtom* aPrefix,
nsIAtom* 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 nsSubstring& 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 Comment(mNodeInfoManager);
rv = comment->SetText(aData, false);
NS_ENSURE_SUCCESS(rv, rv);
return mCurrentNode->AppendChildTo(comment, true);
}
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 nsIDocument::EndLoad
MOZ_ASSERT(mDocument->GetReadyStateEnum() ==
nsIDocument::READYSTATE_LOADING, "Bad readyState");
mDocument->SetReadyStateInternal(nsIDocument::READYSTATE_INTERACTIVE);
nsScriptLoader* loader = mDocument->ScriptLoader();
if (loader) {
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()) {
rv = endHTMLElement(element);
NS_ENSURE_SUCCESS(rv, rv);
}
// Handle elements that are different when parser-created
if (element->IsAnyOfHTMLElements(nsGkAtoms::title,
nsGkAtoms::object,
nsGkAtoms::applet,
nsGkAtoms::select,
nsGkAtoms::textarea) ||
element->IsSVGElement(nsGkAtoms::title)) {
element->DoneAddingChildren(true);
} else if (element->IsSVGElement(nsGkAtoms::script) ||
element->IsHTMLElement(nsGkAtoms::script)) {
nsCOMPtr<nsIScriptElement> sele = do_QueryInterface(element);
MOZ_ASSERT(sele, "script elements need to implement nsIScriptElement");
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) {
rv = mNotifier->AddScriptElement(sele);
NS_ENSURE_SUCCESS(rv, rv);
}
} else if (element->IsAnyOfHTMLElements(nsGkAtoms::input,
nsGkAtoms::button,
nsGkAtoms::menuitem,
nsGkAtoms::audio,
nsGkAtoms::video)) {
element->DoneCreatingElement();
}
}
if (mCreatingNewDocument) {
// Handle all sorts of stylesheets
nsCOMPtr<nsIStyleSheetLinkingElement> ssle =
do_QueryInterface(mCurrentNode);
if (ssle) {
ssle->SetEnableUpdates(true);
bool willNotify;
bool isAlternate;
nsresult rv = ssle->UpdateStyleSheet(mNotifier, &willNotify,
&isAlternate);
if (mNotifier && NS_SUCCEEDED(rv) && willNotify && !isAlternate) {
mNotifier->AddPendingStylesheet();
}
}
}
// Add the element to the tree if it wasn't added before and take one step
// up the tree
uint32_t last = mCurrentNodeStack.Count() - 1;
NS_ASSERTION(last != (uint32_t)-1, "empty stack");
nsCOMPtr<nsINode> parent = mCurrentNodeStack.SafeObjectAt(last);
mCurrentNodeStack.RemoveObjectAt(last);
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);
}
mNonAddedNode = nullptr;
}
mCurrentNode = parent;
mTableState =
static_cast<TableState>(NS_PTR_TO_INT32(mTableStateStack.pop()));
return NS_OK;
}
void txMozillaXMLOutput::getOutputDocument(nsIDOMDocument** aDocument)
{
CallQueryInterface(mDocument, aDocument);
}
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);
nsCOMPtr<nsIStyleSheetLinkingElement> ssle;
if (mCreatingNewDocument) {
ssle = do_QueryInterface(pi);
if (ssle) {
ssle->InitStyleLinkElement(false);
ssle->SetEnableUpdates(false);
}
}
rv = mCurrentNode->AppendChildTo(pi, true);
NS_ENSURE_SUCCESS(rv, rv);
if (ssle) {
ssle->SetEnableUpdates(true);
bool willNotify;
bool isAlternate;
rv = ssle->UpdateStyleSheet(mNotifier, &willNotify, &isAlternate);
if (mNotifier && NS_SUCCEEDED(rv) && willNotify && !isAlternate) {
mNotifier->AddPendingStylesheet();
}
}
return NS_OK;
}
nsresult
txMozillaXMLOutput::startDocument()
{
if (mNotifier) {
mNotifier->OnTransformStart();
}
if (mCreatingNewDocument) {
nsScriptLoader* loader = mDocument->ScriptLoader();
if (loader) {
loader->BeginDeferringScripts();
}
}
return NS_OK;
}
nsresult
txMozillaXMLOutput::startElement(nsIAtom* aPrefix, nsIAtom* aLocalName,
nsIAtom* aLowercaseLocalName,
const int32_t aNsID)
{
NS_PRECONDITION(aNsID != kNameSpaceID_None || !aPrefix,
"Can't have prefix without namespace");
if (mOutputFormat.mMethod == eHTMLOutput && aNsID == kNameSpaceID_None) {
nsCOMPtr<nsIAtom> 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(nsIAtom* aPrefix,
const nsSubstring& aLocalName,
const int32_t aNsID)
{
int32_t nsId = aNsID;
nsCOMPtr<nsIAtom> lname;
if (mOutputFormat.mMethod == eHTMLOutput && aNsID == kNameSpaceID_None) {
nsId = kNameSpaceID_XHTML;
nsAutoString lnameStr;
nsContentUtils::ASCIIToLower(aLocalName, lnameStr);
lname = do_GetAtom(lnameStr);
}
else {
lname = do_GetAtom(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(nsIAtom* aPrefix,
nsIAtom* 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;
rv = mTableStateStack.push(NS_INT32_TO_PTR(mTableState));
NS_ENSURE_SUCCESS(rv, rv);
if (!mCurrentNodeStack.AppendObject(mCurrentNode)) {
return NS_ERROR_OUT_OF_MEMORY;
}
mTableState = NORMAL;
mOpenedElementIsHTML = false;
// Create the element
RefPtr<NodeInfo> ni =
mNodeInfoManager->GetNodeInfo(aLocalName, aPrefix, aNsID,
nsIDOMNode::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
nsCOMPtr<nsIStyleSheetLinkingElement> ssle =
do_QueryInterface(mOpenedElement);
if (ssle) {
ssle->InitStyleLinkElement(false);
ssle->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);
}
rv = mCurrentNode->AppendChildTo(mOpenedElement, true);
NS_ENSURE_SUCCESS(rv, rv);
if (currentIsDoc) {
mRootContentCreated = true;
nsContentSink::NotifyDocElementCreated(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 nsTextNode(mNodeInfoManager);
rv = text->SetText(mText, false);
NS_ENSURE_SUCCESS(rv, rv);
rv = mCurrentNode->AppendChildTo(text, true);
NS_ENSURE_SUCCESS(rv, rv);
mText.Truncate();
}
return NS_OK;
}
nsresult
txMozillaXMLOutput::createTxWrapper()
{
NS_ASSERTION(mDocument == mCurrentNode,
"creating wrapper when document isn't parent");
int32_t namespaceID;
nsresult rv = nsContentUtils::NameSpaceManager()->
RegisterNameSpace(NS_LITERAL_STRING(kTXNameSpaceURI), namespaceID);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<Element> wrapper =
mDocument->CreateElem(nsDependentAtomString(nsGkAtoms::result),
nsGkAtoms::transformiix, namespaceID);
uint32_t i, j, childCount = mDocument->GetChildCount();
#ifdef DEBUG
// Keep track of the location of the current documentElement, if there is
// one, so we can verify later
uint32_t rootLocation = 0;
#endif
for (i = 0, j = 0; i < childCount; ++i) {
nsCOMPtr<nsIContent> childContent = mDocument->GetChildAt(j);
#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);
#endif
++j;
}
else {
mDocument->RemoveChildAt(j, true);
rv = wrapper->AppendChildTo(childContent, true);
NS_ENSURE_SUCCESS(rv, rv);
break;
}
}
if (!mCurrentNodeStack.AppendObject(wrapper)) {
return NS_ERROR_OUT_OF_MEMORY;
}
mCurrentNode = wrapper;
mRootContentCreated = true;
NS_ASSERTION(rootLocation == mDocument->GetChildCount(),
"Incorrect root location");
return mDocument->AppendChildTo(wrapper, true);
}
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) {
uint32_t last = mCurrentNodeStack.Count() - 1;
NS_ASSERTION(last != (uint32_t)-1, "empty stack");
mCurrentNode = mCurrentNodeStack.SafeObjectAt(last);
mCurrentNodeStack.RemoveObjectAt(last);
mTableStateStack.pop();
}
if (aElement->IsHTMLElement(nsGkAtoms::table) && aIsHTML) {
mTableState = TABLE;
}
else if (aElement->IsHTMLElement(nsGkAtoms::tr) && aIsHTML &&
NS_PTR_TO_INT32(mTableStateStack.peek()) == TABLE) {
nsCOMPtr<nsIContent> tbody;
rv = createHTMLElement(nsGkAtoms::tbody, getter_AddRefs(tbody));
NS_ENSURE_SUCCESS(rv, rv);
rv = mCurrentNode->AppendChildTo(tbody, true);
NS_ENSURE_SUCCESS(rv, rv);
rv = mTableStateStack.push(NS_INT32_TO_PTR(ADDED_TBODY));
NS_ENSURE_SUCCESS(rv, rv);
if (!mCurrentNodeStack.AppendObject(tbody)) {
return NS_ERROR_OUT_OF_MEMORY;
}
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">
nsCOMPtr<nsIContent> meta;
rv = createHTMLElement(nsGkAtoms::meta, getter_AddRefs(meta));
NS_ENSURE_SUCCESS(rv, rv);
rv = meta->SetAttr(kNameSpaceID_None, nsGkAtoms::httpEquiv,
NS_LITERAL_STRING("Content-Type"), false);
NS_ENSURE_SUCCESS(rv, rv);
nsAutoString metacontent;
metacontent.Append(mOutputFormat.mMediaType);
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->IsInDoc(), "should not be in doc");
rv = aElement->AppendChildTo(meta, false);
NS_ENSURE_SUCCESS(rv, rv);
}
return NS_OK;
}
nsresult
txMozillaXMLOutput::endHTMLElement(nsIContent* aElement)
{
if (mTableState == ADDED_TBODY) {
NS_ASSERTION(aElement->IsHTMLElement(nsGkAtoms::tbody),
"Element flagged as added tbody isn't a tbody");
uint32_t last = mCurrentNodeStack.Count() - 1;
NS_ASSERTION(last != (uint32_t)-1, "empty stack");
mCurrentNode = mCurrentNodeStack.SafeObjectAt(last);
mCurrentNodeStack.RemoveObjectAt(last);
mTableState = static_cast<TableState>
(NS_PTR_TO_INT32(mTableStateStack.pop()));
return NS_OK;
}
return NS_OK;
}
void txMozillaXMLOutput::processHTTPEquiv(nsIAtom* aHeader, const nsString& aValue)
{
MOZ_CRASH("Don't call processHTTPEquiv, see bug 1746720");
}
nsresult
txMozillaXMLOutput::createResultDocument(const nsSubstring& aName, int32_t aNsID,
nsIDOMDocument* 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 nsIDocument::BeginLoad
MOZ_ASSERT(mDocument->GetReadyStateEnum() ==
nsIDocument::READYSTATE_UNINITIALIZED, "Bad readyState");
mDocument->SetReadyStateInternal(nsIDocument::READYSTATE_LOADING);
nsCOMPtr<nsIDocument> source = do_QueryInterface(aSourceDocument);
NS_ENSURE_STATE(source);
bool hasHadScriptObject = false;
nsIScriptGlobalObject* sgo =
source->GetScriptHandlingObject(hasHadScriptObject);
NS_ENSURE_STATE(sgo || !hasHadScriptObject);
mDocument->SetScriptHandlingObject(sgo);
mCurrentNode = mDocument;
mNodeInfoManager = mDocument->NodeInfoManager();
// Reset and set up the document
URIUtils::ResetWithSource(mDocument, aSourceDocument);
// Set the charset
if (!mOutputFormat.mEncoding.IsEmpty()) {
nsAutoCString canonicalCharset;
if (EncodingUtils::FindEncodingForLabel(mOutputFormat.mEncoding,
canonicalCharset)) {
mDocument->SetDocumentCharacterSetSource(kCharsetFromOtherComponent);
mDocument->SetDocumentCharacterSet(canonicalCharset);
}
}
// Set the mime-type
if (!mOutputFormat.mMediaType.IsEmpty()) {
mDocument->SetContentType(mOutputFormat.mMediaType);
}
else if (mOutputFormat.mMethod == eHTMLOutput) {
mDocument->SetContentType(NS_LITERAL_STRING("text/html"));
}
else {
mDocument->SetContentType(NS_LITERAL_STRING("application/xml"));
}
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.
nsScriptLoader *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);
}
// Do this after calling OnDocumentCreated to ensure that the
// PresShell/PresContext has been hooked up and get notified.
nsCOMPtr<nsIHTMLDocument> htmlDoc = do_QueryInterface(mDocument);
if (htmlDoc) {
htmlDoc->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);
}
nsCOMPtr<nsIDOMDocumentType> documentType;
nsresult rv = nsContentUtils::CheckQName(qName);
if (NS_SUCCEEDED(rv)) {
nsCOMPtr<nsIAtom> doctypeName = do_GetAtom(qName);
if (!doctypeName) {
return NS_ERROR_OUT_OF_MEMORY;
}
// Indicate that there is no internal subset (not just an empty one)
rv = NS_NewDOMDocumentType(getter_AddRefs(documentType),
mNodeInfoManager,
doctypeName,
mOutputFormat.mPublicId,
mOutputFormat.mSystemId,
NullString());
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIContent> docType = do_QueryInterface(documentType);
rv = mDocument->AppendChildTo(docType, true);
NS_ENSURE_SUCCESS(rv, rv);
}
}
return NS_OK;
}
nsresult
txMozillaXMLOutput::createHTMLElement(nsIAtom* aName,
nsIContent** aResult)
{
NS_ASSERTION(mOutputFormat.mMethod == eHTMLOutput,
"need to adjust createHTMLElement");
*aResult = nullptr;
RefPtr<NodeInfo> ni;
ni = mNodeInfoManager->GetNodeInfo(aName, nullptr,
kNameSpaceID_XHTML,
nsIDOMNode::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()
: mPendingStylesheetCount(0),
mInTransform(false)
{
}
txTransformNotifier::~txTransformNotifier()
{
}
NS_IMPL_ISUPPORTS(txTransformNotifier,
nsIScriptLoaderObserver,
nsICSSLoaderObserver)
NS_IMETHODIMP
txTransformNotifier::ScriptAvailable(nsresult aResult,
nsIScriptElement *aElement,
bool aIsInline,
nsIURI *aURI,
int32_t aLineNo)
{
if (NS_FAILED(aResult) &&
mScriptElements.RemoveObject(aElement)) {
SignalTransformEnd();
}
return NS_OK;
}
NS_IMETHODIMP
txTransformNotifier::ScriptEvaluated(nsresult aResult,
nsIScriptElement *aElement,
bool aIsInline)
{
if (mScriptElements.RemoveObject(aElement)) {
SignalTransformEnd();
}
return NS_OK;
}
NS_IMETHODIMP
txTransformNotifier::StyleSheetLoaded(CSSStyleSheet* aSheet,
bool aWasAlternate,
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 (!aWasAlternate) {
--mPendingStylesheetCount;
SignalTransformEnd();
}
return NS_OK;
}
void
txTransformNotifier::Init(nsITransformObserver* aObserver)
{
mObserver = aObserver;
}
nsresult
txTransformNotifier::AddScriptElement(nsIScriptElement* aElement)
{
return mScriptElements.AppendObject(aElement) ? NS_OK :
NS_ERROR_OUT_OF_MEMORY;
}
void
txTransformNotifier::AddPendingStylesheet()
{
++mPendingStylesheetCount;
}
void
txTransformNotifier::OnTransformEnd(nsresult aResult)
{
mInTransform = false;
SignalTransformEnd(aResult);
}
void
txTransformNotifier::OnTransformStart()
{
mInTransform = true;
}
nsresult
txTransformNotifier::SetOutputDocument(nsIDocument* aDocument)
{
mDocument = aDocument;
// Notify the contentsink that the document is created
return mObserver->OnDocumentCreated(mDocument);
}
void
txTransformNotifier::SignalTransformEnd(nsresult aResult)
{
if (mInTransform ||
(NS_SUCCEEDED(aResult) &&
(mScriptElements.Count() > 0 || 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()->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(aResult, mDocument);
}
}