diff options
Diffstat (limited to 'dom/xslt/xslt/txMozillaXSLTProcessor.cpp')
-rw-r--r-- | dom/xslt/xslt/txMozillaXSLTProcessor.cpp | 1227 |
1 files changed, 1227 insertions, 0 deletions
diff --git a/dom/xslt/xslt/txMozillaXSLTProcessor.cpp b/dom/xslt/xslt/txMozillaXSLTProcessor.cpp new file mode 100644 index 0000000000..f3d4272e6f --- /dev/null +++ b/dom/xslt/xslt/txMozillaXSLTProcessor.cpp @@ -0,0 +1,1227 @@ +/* -*- 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 "txMozillaXSLTProcessor.h" +#include "nsContentCID.h" +#include "nsError.h" +#include "mozilla/dom/Element.h" +#include "mozilla/dom/Document.h" +#include "nsIStringBundle.h" +#include "nsIURI.h" +#include "XPathResult.h" +#include "txExecutionState.h" +#include "txMozillaTextOutput.h" +#include "txMozillaXMLOutput.h" +#include "txURIUtils.h" +#include "txXMLUtils.h" +#include "txUnknownHandler.h" +#include "txXSLTMsgsURL.h" +#include "txXSLTProcessor.h" +#include "nsIPrincipal.h" +#include "nsThreadUtils.h" +#include "jsapi.h" +#include "txExprParser.h" +#include "nsJSUtils.h" +#include "nsIXPConnect.h" +#include "nsNameSpaceManager.h" +#include "nsVariant.h" +#include "nsTextNode.h" +#include "mozilla/Components.h" +#include "mozilla/dom/DocumentFragment.h" +#include "mozilla/dom/XSLTProcessorBinding.h" + +using namespace mozilla; +using namespace mozilla::dom; + +/** + * Output Handler Factories + */ +class txToDocHandlerFactory : public txAOutputHandlerFactory { + public: + txToDocHandlerFactory(txExecutionState* aEs, Document* aSourceDocument, + nsITransformObserver* aObserver, bool aDocumentIsData) + : mEs(aEs), + mSourceDocument(aSourceDocument), + mObserver(aObserver), + mDocumentIsData(aDocumentIsData) {} + + TX_DECL_TXAOUTPUTHANDLERFACTORY + + private: + txExecutionState* mEs; + nsCOMPtr<Document> mSourceDocument; + nsCOMPtr<nsITransformObserver> mObserver; + bool mDocumentIsData; +}; + +class txToFragmentHandlerFactory : public txAOutputHandlerFactory { + public: + explicit txToFragmentHandlerFactory(DocumentFragment* aFragment) + : mFragment(aFragment) {} + + TX_DECL_TXAOUTPUTHANDLERFACTORY + + private: + RefPtr<DocumentFragment> mFragment; +}; + +nsresult txToDocHandlerFactory::createHandlerWith( + txOutputFormat* aFormat, txAXMLEventHandler** aHandler) { + *aHandler = nullptr; + switch (aFormat->mMethod) { + case eMethodNotSet: + case eXMLOutput: { + *aHandler = new txUnknownHandler(mEs); + return NS_OK; + } + + case eHTMLOutput: { + UniquePtr<txMozillaXMLOutput> handler( + new txMozillaXMLOutput(mSourceDocument, aFormat, mObserver)); + + nsresult rv = handler->createResultDocument( + u""_ns, kNameSpaceID_None, mSourceDocument, mDocumentIsData); + if (NS_SUCCEEDED(rv)) { + *aHandler = handler.release(); + } + + return rv; + } + + case eTextOutput: { + UniquePtr<txMozillaTextOutput> handler( + new txMozillaTextOutput(mSourceDocument, mObserver)); + + nsresult rv = handler->createResultDocument(mDocumentIsData); + if (NS_SUCCEEDED(rv)) { + *aHandler = handler.release(); + } + + return rv; + } + } + + MOZ_CRASH("Unknown output method"); + + return NS_ERROR_FAILURE; +} + +nsresult txToDocHandlerFactory::createHandlerWith( + txOutputFormat* aFormat, const nsAString& aName, int32_t aNsID, + txAXMLEventHandler** aHandler) { + *aHandler = nullptr; + switch (aFormat->mMethod) { + case eMethodNotSet: { + NS_ERROR("How can method not be known when root element is?"); + return NS_ERROR_UNEXPECTED; + } + + case eXMLOutput: + case eHTMLOutput: { + UniquePtr<txMozillaXMLOutput> handler( + new txMozillaXMLOutput(mSourceDocument, aFormat, mObserver)); + + nsresult rv = handler->createResultDocument(aName, aNsID, mSourceDocument, + mDocumentIsData); + if (NS_SUCCEEDED(rv)) { + *aHandler = handler.release(); + } + + return rv; + } + + case eTextOutput: { + UniquePtr<txMozillaTextOutput> handler( + new txMozillaTextOutput(mSourceDocument, mObserver)); + + nsresult rv = handler->createResultDocument(mDocumentIsData); + if (NS_SUCCEEDED(rv)) { + *aHandler = handler.release(); + } + + return rv; + } + } + + MOZ_CRASH("Unknown output method"); + + return NS_ERROR_FAILURE; +} + +nsresult txToFragmentHandlerFactory::createHandlerWith( + txOutputFormat* aFormat, txAXMLEventHandler** aHandler) { + *aHandler = nullptr; + switch (aFormat->mMethod) { + case eMethodNotSet: { + txOutputFormat format; + format.merge(*aFormat); + nsCOMPtr<Document> doc = mFragment->OwnerDoc(); + + if (doc->IsHTMLDocument()) { + format.mMethod = eHTMLOutput; + } else { + format.mMethod = eXMLOutput; + } + + *aHandler = new txMozillaXMLOutput(&format, mFragment, false); + break; + } + + case eXMLOutput: + case eHTMLOutput: { + *aHandler = new txMozillaXMLOutput(aFormat, mFragment, false); + break; + } + + case eTextOutput: { + *aHandler = new txMozillaTextOutput(mFragment); + break; + } + } + return NS_OK; +} + +nsresult txToFragmentHandlerFactory::createHandlerWith( + txOutputFormat* aFormat, const nsAString& aName, int32_t aNsID, + txAXMLEventHandler** aHandler) { + *aHandler = nullptr; + NS_ASSERTION(aFormat->mMethod != eMethodNotSet, + "How can method not be known when root element is?"); + NS_ENSURE_TRUE(aFormat->mMethod != eMethodNotSet, NS_ERROR_UNEXPECTED); + return createHandlerWith(aFormat, aHandler); +} + +class txVariable : public txIGlobalParameter { + using XSLTParameterValue = txMozillaXSLTProcessor::XSLTParameterValue; + using OwningXSLTParameterValue = + txMozillaXSLTProcessor::OwningXSLTParameterValue; + + public: + explicit txVariable(UniquePtr<OwningXSLTParameterValue>&& aValue) + : mUnionValue(std::move(aValue)) {} + nsresult getValue(txAExprResult** aValue) override { + if (!mValue) { + nsresult rv = convert(*mUnionValue, getter_AddRefs(mValue)); + NS_ENSURE_SUCCESS(rv, rv); + } + + NS_ADDREF(*aValue = mValue); + + return NS_OK; + } + OwningXSLTParameterValue getUnionValue() { + return OwningXSLTParameterValue(*mUnionValue); + } + void setValue(UniquePtr<OwningXSLTParameterValue>&& aValue) { + mValue = nullptr; + mUnionValue = std::move(aValue); + } + + static UniquePtr<OwningXSLTParameterValue> convertToOwning( + const XSLTParameterValue& aValue, ErrorResult& aError); + + friend void ImplCycleCollectionTraverse( + nsCycleCollectionTraversalCallback& aCallback, txVariable& aVariable, + const char* aName, uint32_t aFlags); + + private: + static nsresult convert(const OwningXSLTParameterValue& aUnionValue, + txAExprResult** aValue); + + UniquePtr<OwningXSLTParameterValue> mUnionValue; + RefPtr<txAExprResult> mValue; +}; + +inline void ImplCycleCollectionTraverse( + nsCycleCollectionTraversalCallback& aCallback, txVariable& aVariable, + const char* aName, uint32_t aFlags) { + ImplCycleCollectionTraverse(aCallback, *aVariable.mUnionValue, aName, aFlags); +} + +inline void ImplCycleCollectionUnlink( + txOwningExpandedNameMap<txIGlobalParameter>& aMap) { + aMap.clear(); +} + +inline void ImplCycleCollectionTraverse( + nsCycleCollectionTraversalCallback& aCallback, + txOwningExpandedNameMap<txIGlobalParameter>& aMap, const char* aName, + uint32_t aFlags = 0) { + aFlags |= CycleCollectionEdgeNameArrayFlag; + txOwningExpandedNameMap<txIGlobalParameter>::iterator iter(aMap); + while (iter.next()) { + ImplCycleCollectionTraverse( + aCallback, *static_cast<txVariable*>(iter.value()), aName, aFlags); + } +} + +/** + * txMozillaXSLTProcessor + */ + +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(txMozillaXSLTProcessor, mOwner, + mEmbeddedStylesheetRoot, mSource, + mVariables) + +NS_IMPL_CYCLE_COLLECTING_ADDREF(txMozillaXSLTProcessor) +NS_IMPL_CYCLE_COLLECTING_RELEASE(txMozillaXSLTProcessor) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(txMozillaXSLTProcessor) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY + NS_INTERFACE_MAP_ENTRY(nsIDocumentTransformer) + NS_INTERFACE_MAP_ENTRY(nsIMutationObserver) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDocumentTransformer) +NS_INTERFACE_MAP_END + +txMozillaXSLTProcessor::txMozillaXSLTProcessor() + : mOwner(nullptr), + mStylesheetDocument(nullptr), + mTransformResult(NS_OK), + mCompileResult(NS_OK), + mFlags(0) {} + +txMozillaXSLTProcessor::txMozillaXSLTProcessor(nsISupports* aOwner) + : mOwner(aOwner), + mStylesheetDocument(nullptr), + mTransformResult(NS_OK), + mCompileResult(NS_OK), + mFlags(0) {} + +txMozillaXSLTProcessor::~txMozillaXSLTProcessor() { + if (mStylesheetDocument) { + mStylesheetDocument->RemoveMutationObserver(this); + } +} + +NS_IMETHODIMP +txMozillaXSLTProcessor::SetTransformObserver(nsITransformObserver* aObserver) { + mObserver = aObserver; + return NS_OK; +} + +NS_IMETHODIMP +txMozillaXSLTProcessor::SetSourceContentModel(nsINode* aSource) { + mSource = aSource; + + if (NS_FAILED(mTransformResult)) { + notifyError(); + return NS_OK; + } + + if (mStylesheet) { + return DoTransform(); + } + + return NS_OK; +} + +NS_IMETHODIMP +txMozillaXSLTProcessor::AddXSLTParamNamespace(const nsString& aPrefix, + const nsString& aNamespace) { + RefPtr<nsAtom> pre = NS_Atomize(aPrefix); + return mParamNamespaceMap.mapNamespace(pre, aNamespace); +} + +class txXSLTParamContext : public txIParseContext, public txIEvalContext { + public: + txXSLTParamContext(txNamespaceMap* aResolver, const txXPathNode& aContext, + txResultRecycler* aRecycler) + : mResolver(aResolver), mContext(aContext), mRecycler(aRecycler) {} + + // txIParseContext + nsresult resolveNamespacePrefix(nsAtom* aPrefix, int32_t& aID) override { + aID = mResolver->lookupNamespace(aPrefix); + return aID == kNameSpaceID_Unknown ? NS_ERROR_DOM_NAMESPACE_ERR : NS_OK; + } + nsresult resolveFunctionCall(nsAtom* aName, int32_t aID, + FunctionCall** aFunction) override { + return NS_ERROR_XPATH_UNKNOWN_FUNCTION; + } + bool caseInsensitiveNameTests() override { return false; } + void SetErrorOffset(uint32_t aOffset) override {} + + // txIEvalContext + nsresult getVariable(int32_t aNamespace, nsAtom* aLName, + txAExprResult*& aResult) override { + aResult = nullptr; + return NS_ERROR_INVALID_ARG; + } + nsresult isStripSpaceAllowed(const txXPathNode& aNode, + bool& aAllowed) override { + aAllowed = false; + + return NS_OK; + } + void* getPrivateContext() override { return nullptr; } + txResultRecycler* recycler() override { return mRecycler; } + void receiveError(const nsAString& aMsg, nsresult aRes) override {} + const txXPathNode& getContextNode() override { return mContext; } + uint32_t size() override { return 1; } + uint32_t position() override { return 1; } + + private: + txNamespaceMap* mResolver; + const txXPathNode& mContext; + txResultRecycler* mRecycler; +}; + +NS_IMETHODIMP +txMozillaXSLTProcessor::AddXSLTParam(const nsString& aName, + const nsString& aNamespace, + const nsString& aSelect, + const nsString& aValue, + nsINode* aContext) { + nsresult rv = NS_OK; + + if (aSelect.IsVoid() == aValue.IsVoid()) { + // Ignore if neither or both are specified + return NS_ERROR_FAILURE; + } + + RefPtr<txAExprResult> value; + uint16_t resultType; + if (!aSelect.IsVoid()) { + // Set up context + UniquePtr<txXPathNode> contextNode( + txXPathNativeNode::createXPathNode(aContext)); + NS_ENSURE_TRUE(contextNode, NS_ERROR_OUT_OF_MEMORY); + + if (!mRecycler) { + mRecycler = new txResultRecycler; + } + + txXSLTParamContext paramContext(&mParamNamespaceMap, *contextNode, + mRecycler); + + // Parse + UniquePtr<Expr> expr; + rv = txExprParser::createExpr(aSelect, ¶mContext, + getter_Transfers(expr)); + NS_ENSURE_SUCCESS(rv, rv); + + // Evaluate + rv = expr->evaluate(¶mContext, getter_AddRefs(value)); + NS_ENSURE_SUCCESS(rv, rv); + + switch (value->getResultType()) { + case txAExprResult::NUMBER: + resultType = XPathResult::NUMBER_TYPE; + break; + case txAExprResult::STRING: + resultType = XPathResult::STRING_TYPE; + break; + case txAExprResult::BOOLEAN: + resultType = XPathResult::BOOLEAN_TYPE; + break; + case txAExprResult::NODESET: + resultType = XPathResult::UNORDERED_NODE_ITERATOR_TYPE; + break; + default: + MOZ_ASSERT_UNREACHABLE( + "We shouldn't have a txAExprResult::RESULT_TREE_FRAGMENT here."); + return NS_ERROR_FAILURE; + } + } else { + value = new StringResult(aValue, nullptr); + resultType = XPathResult::STRING_TYPE; + } + + RefPtr<nsAtom> name = NS_Atomize(aName); + int32_t nsId = kNameSpaceID_Unknown; + rv = nsNameSpaceManager::GetInstance()->RegisterNameSpace(aNamespace, nsId); + NS_ENSURE_SUCCESS(rv, rv); + + RefPtr<XPathResult> xpathResult = MakeRefPtr<XPathResult>(aContext); + + ErrorResult error; + xpathResult->SetExprResult(value, resultType, aContext, error); + if (error.Failed()) { + return error.StealNSResult(); + } + + UniquePtr<OwningXSLTParameterValue> varValue = + MakeUnique<OwningXSLTParameterValue>(); + varValue->SetAsXPathResult() = xpathResult.forget(); + + txExpandedName varName(nsId, name); + txVariable* var = static_cast<txVariable*>(mVariables.get(varName)); + if (var) { + var->setValue(std::move(varValue)); + + return NS_OK; + } + + var = new txVariable(std::move(varValue)); + + return mVariables.add(varName, var); +} + +class nsTransformBlockerEvent : public mozilla::Runnable { + public: + RefPtr<txMozillaXSLTProcessor> mProcessor; + + explicit nsTransformBlockerEvent(txMozillaXSLTProcessor* processor) + : mozilla::Runnable("nsTransformBlockerEvent"), mProcessor(processor) {} + + ~nsTransformBlockerEvent() { + nsCOMPtr<Document> document = + mProcessor->GetSourceContentModel()->OwnerDoc(); + document->UnblockOnload(true); + } + + NS_IMETHOD Run() override { + mProcessor->TransformToDoc(nullptr, false); + return NS_OK; + } +}; + +nsresult txMozillaXSLTProcessor::DoTransform() { + NS_ENSURE_TRUE(mSource, NS_ERROR_UNEXPECTED); + NS_ENSURE_TRUE(mStylesheet, NS_ERROR_UNEXPECTED); + NS_ASSERTION(mObserver, "no observer"); + NS_ASSERTION(NS_IsMainThread(), "should only be on main thread"); + + nsCOMPtr<nsIRunnable> event = new nsTransformBlockerEvent(this); + mSource->OwnerDoc()->BlockOnload(); + nsresult rv = NS_DispatchToCurrentThread(event); + if (NS_FAILED(rv)) { + // XXX Maybe we should just display the source document in this case? + // Also, set up context information, see bug 204655. + reportError(rv, nullptr, nullptr); + } + + return rv; +} + +void txMozillaXSLTProcessor::ImportStylesheet(nsINode& aStyle, + mozilla::ErrorResult& aRv) { + // We don't support importing multiple stylesheets yet. + if (NS_WARN_IF(mStylesheetDocument || mStylesheet)) { + aRv.Throw(NS_ERROR_NOT_IMPLEMENTED); + return; + } + + if (!nsContentUtils::SubjectPrincipalOrSystemIfNativeCaller()->Subsumes( + aStyle.NodePrincipal())) { + aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); + return; + } + + if (NS_WARN_IF(!aStyle.IsElement() && !aStyle.IsDocument())) { + aRv.Throw(NS_ERROR_INVALID_ARG); + return; + } + + nsresult rv = + TX_CompileStylesheet(&aStyle, this, getter_AddRefs(mStylesheet)); + // XXX set up exception context, bug 204658 + if (NS_WARN_IF(NS_FAILED(rv))) { + aRv.Throw(rv); + return; + } + + mStylesheetDocument = aStyle.OwnerDoc(); + if (aStyle.IsElement()) { + mEmbeddedStylesheetRoot = aStyle.AsElement(); + } + + mStylesheetDocument->AddMutationObserver(this); +} + +already_AddRefed<Document> txMozillaXSLTProcessor::TransformToDocument( + nsINode& aSource, ErrorResult& aRv) { + if (NS_WARN_IF(NS_FAILED(mCompileResult))) { + aRv.Throw(mCompileResult); + return nullptr; + } + + if (!nsContentUtils::CanCallerAccess(&aSource)) { + aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); + return nullptr; + } + + nsresult rv = ensureStylesheet(); + if (NS_WARN_IF(NS_FAILED(rv))) { + aRv.Throw(rv); + return nullptr; + } + + mSource = &aSource; + + nsCOMPtr<Document> doc; + rv = TransformToDoc(getter_AddRefs(doc), true); + if (NS_FAILED(rv)) { + aRv.Throw(rv); + return nullptr; + } + return doc.forget(); +} + +class XSLTProcessRequest final : public nsIRequest { + public: + explicit XSLTProcessRequest(txExecutionState* aState) : mState(aState) {} + + NS_DECL_ISUPPORTS + NS_DECL_NSIREQUEST + + void Done() { mState = nullptr; } + + private: + ~XSLTProcessRequest() {} + txExecutionState* mState; +}; +NS_IMPL_ISUPPORTS(XSLTProcessRequest, nsIRequest) + +NS_IMETHODIMP +XSLTProcessRequest::GetName(nsACString& aResult) { + aResult.AssignLiteral("about:xslt-load-blocker"); + return NS_OK; +} + +NS_IMETHODIMP +XSLTProcessRequest::IsPending(bool* _retval) { + *_retval = true; + return NS_OK; +} + +NS_IMETHODIMP +XSLTProcessRequest::GetStatus(nsresult* status) { + *status = NS_OK; + return NS_OK; +} + +NS_IMETHODIMP XSLTProcessRequest::SetCanceledReason(const nsACString& aReason) { + return SetCanceledReasonImpl(aReason); +} + +NS_IMETHODIMP XSLTProcessRequest::GetCanceledReason(nsACString& aReason) { + return GetCanceledReasonImpl(aReason); +} + +NS_IMETHODIMP XSLTProcessRequest::CancelWithReason(nsresult aStatus, + const nsACString& aReason) { + return CancelWithReasonImpl(aStatus, aReason); +} + +NS_IMETHODIMP +XSLTProcessRequest::Cancel(nsresult status) { + mState->stopProcessing(); + return NS_OK; +} + +NS_IMETHODIMP +XSLTProcessRequest::Suspend(void) { return NS_OK; } + +NS_IMETHODIMP +XSLTProcessRequest::Resume(void) { return NS_OK; } + +NS_IMETHODIMP +XSLTProcessRequest::GetLoadGroup(nsILoadGroup** aLoadGroup) { + *aLoadGroup = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +XSLTProcessRequest::SetLoadGroup(nsILoadGroup* aLoadGroup) { return NS_OK; } + +NS_IMETHODIMP +XSLTProcessRequest::GetLoadFlags(nsLoadFlags* aLoadFlags) { + *aLoadFlags = nsIRequest::LOAD_NORMAL; + return NS_OK; +} + +NS_IMETHODIMP +XSLTProcessRequest::SetLoadFlags(nsLoadFlags aLoadFlags) { return NS_OK; } + +NS_IMETHODIMP +XSLTProcessRequest::GetTRRMode(nsIRequest::TRRMode* aTRRMode) { + return GetTRRModeImpl(aTRRMode); +} + +NS_IMETHODIMP +XSLTProcessRequest::SetTRRMode(nsIRequest::TRRMode aTRRMode) { + return SetTRRModeImpl(aTRRMode); +} + +nsresult txMozillaXSLTProcessor::TransformToDoc(Document** aResult, + bool aCreateDataDocument) { + UniquePtr<txXPathNode> sourceNode( + txXPathNativeNode::createXPathNode(mSource)); + if (!sourceNode) { + return NS_ERROR_OUT_OF_MEMORY; + } + + txExecutionState es(mStylesheet, IsLoadDisabled()); + + Document* sourceDoc = mSource->OwnerDoc(); + nsCOMPtr<nsILoadGroup> loadGroup = sourceDoc->GetDocumentLoadGroup(); + if (!loadGroup) { + nsCOMPtr<nsPIDOMWindowInner> win = do_QueryInterface(mOwner); + if (win && win->IsCurrentInnerWindow()) { + Document* doc = win->GetDoc(); + if (doc) { + loadGroup = doc->GetDocumentLoadGroup(); + } + } + + if (!loadGroup) { + return NS_ERROR_FAILURE; + } + } + + RefPtr<XSLTProcessRequest> xsltProcessRequest = new XSLTProcessRequest(&es); + loadGroup->AddRequest(xsltProcessRequest, nullptr); + + // XXX Need to add error observers + + // If aResult is non-null, we're a data document + txToDocHandlerFactory handlerFactory(&es, sourceDoc, mObserver, + aCreateDataDocument); + es.mOutputHandlerFactory = &handlerFactory; + + nsresult rv = es.init(*sourceNode, &mVariables); + + // Process root of XML source document + if (NS_SUCCEEDED(rv)) { + rv = txXSLTProcessor::execute(es); + } + + xsltProcessRequest->Done(); + loadGroup->RemoveRequest(xsltProcessRequest, nullptr, NS_OK); + + nsresult endRv = es.end(rv); + if (NS_SUCCEEDED(rv)) { + rv = endRv; + } + + if (NS_SUCCEEDED(rv)) { + if (aResult) { + txAOutputXMLEventHandler* handler = + static_cast<txAOutputXMLEventHandler*>(es.mOutputHandler); + nsCOMPtr<Document> doc; + handler->getOutputDocument(getter_AddRefs(doc)); + MOZ_ASSERT(doc->GetReadyStateEnum() == Document::READYSTATE_INTERACTIVE, + "Bad readyState"); + doc->SetReadyStateInternal(Document::READYSTATE_COMPLETE); + doc.forget(aResult); + } + } else if (mObserver) { + // XXX set up context information, bug 204655 + reportError(rv, nullptr, nullptr); + } + + return rv; +} + +already_AddRefed<DocumentFragment> txMozillaXSLTProcessor::TransformToFragment( + nsINode& aSource, Document& aOutput, ErrorResult& aRv) { + if (NS_WARN_IF(NS_FAILED(mCompileResult))) { + aRv.Throw(mCompileResult); + return nullptr; + } + + nsIPrincipal* subject = + nsContentUtils::SubjectPrincipalOrSystemIfNativeCaller(); + if (!subject->Subsumes(aSource.NodePrincipal()) || + !subject->Subsumes(aOutput.NodePrincipal())) { + aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); + return nullptr; + } + + nsresult rv = ensureStylesheet(); + if (NS_WARN_IF(NS_FAILED(rv))) { + aRv.Throw(rv); + return nullptr; + } + + UniquePtr<txXPathNode> sourceNode( + txXPathNativeNode::createXPathNode(&aSource)); + if (!sourceNode) { + aRv.Throw(NS_ERROR_OUT_OF_MEMORY); + return nullptr; + } + + txExecutionState es(mStylesheet, IsLoadDisabled()); + + // XXX Need to add error observers + + RefPtr<DocumentFragment> frag = aOutput.CreateDocumentFragment(); + txToFragmentHandlerFactory handlerFactory(frag); + es.mOutputHandlerFactory = &handlerFactory; + + rv = es.init(*sourceNode, &mVariables); + + // Process root of XML source document + if (NS_SUCCEEDED(rv)) { + rv = txXSLTProcessor::execute(es); + } + // XXX setup exception context, bug 204658 + nsresult endRv = es.end(rv); + if (NS_SUCCEEDED(rv)) { + rv = endRv; + } + + if (NS_FAILED(rv)) { + aRv.Throw(rv); + return nullptr; + } + + return frag.forget(); +} + +void txMozillaXSLTProcessor::SetParameter(const nsAString& aNamespaceURI, + const nsAString& aLocalName, + const XSLTParameterValue& aValue, + ErrorResult& aError) { + if (aValue.IsNode()) { + if (!nsContentUtils::CanCallerAccess(&aValue.GetAsNode())) { + aError.ThrowSecurityError("Caller is not allowed to access node."); + return; + } + } else if (aValue.IsNodeSequence()) { + const Sequence<OwningNonNull<nsINode>>& values = aValue.GetAsNodeSequence(); + for (const auto& node : values) { + if (!nsContentUtils::CanCallerAccess(node.get())) { + aError.ThrowSecurityError( + "Caller is not allowed to access node in sequence."); + return; + } + } + } else if (aValue.IsXPathResult()) { + XPathResult& xpathResult = aValue.GetAsXPathResult(); + RefPtr<txAExprResult> result; + aError = xpathResult.GetExprResult(getter_AddRefs(result)); + if (aError.Failed()) { + return; + } + + if (result->getResultType() == txAExprResult::NODESET) { + txNodeSet* nodeSet = + static_cast<txNodeSet*>(static_cast<txAExprResult*>(result)); + + int32_t i, count = nodeSet->size(); + for (i = 0; i < count; ++i) { + nsINode* node = txXPathNativeNode::getNode(nodeSet->get(i)); + if (!nsContentUtils::CanCallerAccess(node)) { + aError.ThrowSecurityError( + "Caller is not allowed to access node in node-set."); + return; + } + } + } + } + + int32_t nsId = kNameSpaceID_Unknown; + aError = + nsNameSpaceManager::GetInstance()->RegisterNameSpace(aNamespaceURI, nsId); + if (aError.Failed()) { + return; + } + + RefPtr<nsAtom> localName = NS_Atomize(aLocalName); + txExpandedName varName(nsId, localName); + + UniquePtr<OwningXSLTParameterValue> value = + txVariable::convertToOwning(aValue, aError); + if (aError.Failed()) { + return; + } + + txVariable* var = static_cast<txVariable*>(mVariables.get(varName)); + if (var) { + var->setValue(std::move(value)); + return; + } + + UniquePtr<txVariable> newVar = MakeUnique<txVariable>(std::move(value)); + mVariables.add(varName, newVar.release()); +} + +void txMozillaXSLTProcessor::GetParameter( + const nsAString& aNamespaceURI, const nsAString& aLocalName, + Nullable<OwningXSLTParameterValue>& aValue, ErrorResult& aRv) { + int32_t nsId = kNameSpaceID_Unknown; + nsresult rv = + nsNameSpaceManager::GetInstance()->RegisterNameSpace(aNamespaceURI, nsId); + if (NS_WARN_IF(NS_FAILED(rv))) { + aRv.Throw(rv); + return; + } + RefPtr<nsAtom> localName = NS_Atomize(aLocalName); + txExpandedName varName(nsId, localName); + + txVariable* var = static_cast<txVariable*>(mVariables.get(varName)); + if (!var) { + return; + } + + aValue.SetValue(var->getUnionValue()); +} + +void txMozillaXSLTProcessor::RemoveParameter(const nsAString& aNamespaceURI, + const nsAString& aLocalName, + ErrorResult& aRv) { + int32_t nsId = kNameSpaceID_Unknown; + nsresult rv = + nsNameSpaceManager::GetInstance()->RegisterNameSpace(aNamespaceURI, nsId); + if (NS_WARN_IF(NS_FAILED(rv))) { + aRv.Throw(rv); + return; + } + RefPtr<nsAtom> localName = NS_Atomize(aLocalName); + txExpandedName varName(nsId, localName); + + mVariables.remove(varName); +} + +void txMozillaXSLTProcessor::ClearParameters() { mVariables.clear(); } + +void txMozillaXSLTProcessor::Reset() { + if (mStylesheetDocument) { + mStylesheetDocument->RemoveMutationObserver(this); + } + mStylesheet = nullptr; + mStylesheetDocument = nullptr; + mEmbeddedStylesheetRoot = nullptr; + mCompileResult = NS_OK; + mVariables.clear(); +} + +void txMozillaXSLTProcessor::SetFlags(uint32_t aFlags, SystemCallerGuarantee) { + mFlags = aFlags; +} + +uint32_t txMozillaXSLTProcessor::Flags(SystemCallerGuarantee) { return mFlags; } + +NS_IMETHODIMP +txMozillaXSLTProcessor::LoadStyleSheet(nsIURI* aUri, + Document* aLoaderDocument) { + mozilla::dom::ReferrerPolicy refpol = mozilla::dom::ReferrerPolicy::_empty; + if (mStylesheetDocument) { + refpol = mStylesheetDocument->GetReferrerPolicy(); + } + + nsresult rv = TX_LoadSheet(aUri, this, aLoaderDocument, refpol); + if (NS_FAILED(rv) && mObserver) { + // This is most likely a network or security error, just + // use the uri as context. + nsAutoCString spec; + aUri->GetSpec(spec); + CopyUTF8toUTF16(spec, mSourceText); + nsresult status = NS_ERROR_GET_MODULE(rv) == NS_ERROR_MODULE_XSLT + ? rv + : NS_ERROR_XSLT_NETWORK_ERROR; + reportError(status, nullptr, nullptr); + } + return rv; +} + +nsresult txMozillaXSLTProcessor::setStylesheet(txStylesheet* aStylesheet) { + mStylesheet = aStylesheet; + if (mSource) { + return DoTransform(); + } + return NS_OK; +} + +void txMozillaXSLTProcessor::reportError(nsresult aResult, + const char16_t* aErrorText, + const char16_t* aSourceText) { + if (!mObserver) { + return; + } + + mTransformResult = aResult; + + if (aErrorText) { + mErrorText.Assign(aErrorText); + } else { + nsCOMPtr<nsIStringBundleService> sbs = + mozilla::components::StringBundle::Service(); + if (sbs) { + nsString errorText; + sbs->FormatStatusMessage(aResult, u"", errorText); + + nsAutoString errorMessage; + nsCOMPtr<nsIStringBundle> bundle; + sbs->CreateBundle(XSLT_MSGS_URL, getter_AddRefs(bundle)); + + if (bundle) { + AutoTArray<nsString, 1> error = {errorText}; + if (mStylesheet) { + bundle->FormatStringFromName("TransformError", error, errorMessage); + } else { + bundle->FormatStringFromName("LoadingError", error, errorMessage); + } + } + mErrorText.Assign(errorMessage); + } + } + + if (aSourceText) { + mSourceText.Assign(aSourceText); + } + + if (mSource) { + notifyError(); + } +} + +void txMozillaXSLTProcessor::notifyError() { + nsCOMPtr<Document> document; + { + nsresult rv = NS_NewXMLDocument(getter_AddRefs(document)); + NS_ENSURE_SUCCESS_VOID(rv); + } + + URIUtils::ResetWithSource(document, mSource); + + MOZ_ASSERT( + document->GetReadyStateEnum() == Document::READYSTATE_UNINITIALIZED, + "Bad readyState."); + document->SetReadyStateInternal(Document::READYSTATE_LOADING); + + constexpr auto ns = + u"http://www.mozilla.org/newlayout/xml/parsererror.xml"_ns; + + IgnoredErrorResult rv; + ElementCreationOptionsOrString options; + options.SetAsString(); + + nsCOMPtr<Element> element = + document->CreateElementNS(ns, u"parsererror"_ns, options, rv); + if (rv.Failed()) { + return; + } + + document->AppendChild(*element, rv); + if (rv.Failed()) { + return; + } + + RefPtr<nsTextNode> text = document->CreateTextNode(mErrorText); + + element->AppendChild(*text, rv); + if (rv.Failed()) { + return; + } + + if (!mSourceText.IsEmpty()) { + ElementCreationOptionsOrString options; + options.SetAsString(); + + nsCOMPtr<Element> sourceElement = + document->CreateElementNS(ns, u"sourcetext"_ns, options, rv); + if (rv.Failed()) { + return; + } + + element->AppendChild(*sourceElement, rv); + if (rv.Failed()) { + return; + } + + text = document->CreateTextNode(mSourceText); + + sourceElement->AppendChild(*text, rv); + if (rv.Failed()) { + return; + } + } + + MOZ_ASSERT(document->GetReadyStateEnum() == Document::READYSTATE_LOADING, + "Bad readyState."); + document->SetReadyStateInternal(Document::READYSTATE_INTERACTIVE); + mObserver->OnTransformDone(mSource->OwnerDoc(), mTransformResult, document); +} + +nsresult txMozillaXSLTProcessor::ensureStylesheet() { + if (mStylesheet) { + return NS_OK; + } + + NS_ENSURE_TRUE(mStylesheetDocument, NS_ERROR_NOT_INITIALIZED); + + nsINode* style = mEmbeddedStylesheetRoot; + if (!style) { + style = mStylesheetDocument; + } + + return TX_CompileStylesheet(style, this, getter_AddRefs(mStylesheet)); +} + +void txMozillaXSLTProcessor::NodeWillBeDestroyed(nsINode* aNode) { + nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this); + if (NS_FAILED(mCompileResult)) { + return; + } + + mCompileResult = ensureStylesheet(); + mStylesheetDocument = nullptr; + mEmbeddedStylesheetRoot = nullptr; +} + +void txMozillaXSLTProcessor::CharacterDataChanged( + nsIContent* aContent, const CharacterDataChangeInfo&) { + mStylesheet = nullptr; +} + +void txMozillaXSLTProcessor::AttributeChanged(Element* aElement, + int32_t aNameSpaceID, + nsAtom* aAttribute, + int32_t aModType, + const nsAttrValue* aOldValue) { + mStylesheet = nullptr; +} + +void txMozillaXSLTProcessor::ContentAppended(nsIContent* aFirstNewContent) { + mStylesheet = nullptr; +} + +void txMozillaXSLTProcessor::ContentInserted(nsIContent* aChild) { + mStylesheet = nullptr; +} + +void txMozillaXSLTProcessor::ContentRemoved(nsIContent* aChild, + nsIContent* aPreviousSibling) { + mStylesheet = nullptr; +} + +/* virtual */ +JSObject* txMozillaXSLTProcessor::WrapObject( + JSContext* aCx, JS::Handle<JSObject*> aGivenProto) { + return XSLTProcessor_Binding::Wrap(aCx, this, aGivenProto); +} + +DocGroup* txMozillaXSLTProcessor::GetDocGroup() const { + return mStylesheetDocument ? mStylesheetDocument->GetDocGroup() : nullptr; +} + +/* static */ +already_AddRefed<txMozillaXSLTProcessor> txMozillaXSLTProcessor::Constructor( + const GlobalObject& aGlobal) { + RefPtr<txMozillaXSLTProcessor> processor = + new txMozillaXSLTProcessor(aGlobal.GetAsSupports()); + return processor.forget(); +} + +/* static*/ +nsresult txMozillaXSLTProcessor::Startup() { + if (!txXSLTProcessor::init()) { + return NS_ERROR_OUT_OF_MEMORY; + } + + return NS_OK; +} + +/* static*/ +void txMozillaXSLTProcessor::Shutdown() { txXSLTProcessor::shutdown(); } + +/* static */ +UniquePtr<txVariable::OwningXSLTParameterValue> txVariable::convertToOwning( + const XSLTParameterValue& aValue, ErrorResult& aError) { + UniquePtr<OwningXSLTParameterValue> value = + MakeUnique<OwningXSLTParameterValue>(); + if (aValue.IsUnrestrictedDouble()) { + value->SetAsUnrestrictedDouble() = aValue.GetAsUnrestrictedDouble(); + } else if (aValue.IsBoolean()) { + value->SetAsBoolean() = aValue.GetAsBoolean(); + } else if (aValue.IsString()) { + value->SetAsString() = aValue.GetAsString(); + } else if (aValue.IsNode()) { + value->SetAsNode() = aValue.GetAsNode(); + } else if (aValue.IsNodeSequence()) { + value->SetAsNodeSequence() = aValue.GetAsNodeSequence(); + } else if (aValue.IsXPathResult()) { + // Clone the XPathResult so that mutations don't affect this variable. + RefPtr<XPathResult> clone = aValue.GetAsXPathResult().Clone(aError); + if (aError.Failed()) { + return nullptr; + } + value->SetAsXPathResult() = *clone; + } else { + MOZ_ASSERT(false, "Unknown type?"); + } + return value; +} + +/* static */ +nsresult txVariable::convert(const OwningXSLTParameterValue& aUnionValue, + txAExprResult** aValue) { + if (aUnionValue.IsUnrestrictedDouble()) { + NS_ADDREF(*aValue = new NumberResult(aUnionValue.GetAsUnrestrictedDouble(), + nullptr)); + return NS_OK; + } + + if (aUnionValue.IsBoolean()) { + NS_ADDREF(*aValue = new BooleanResult(aUnionValue.GetAsBoolean())); + return NS_OK; + } + + if (aUnionValue.IsString()) { + NS_ADDREF(*aValue = new StringResult(aUnionValue.GetAsString(), nullptr)); + return NS_OK; + } + + if (aUnionValue.IsNode()) { + nsINode& node = aUnionValue.GetAsNode(); + UniquePtr<txXPathNode> xpathNode(txXPathNativeNode::createXPathNode(&node)); + if (!xpathNode) { + return NS_ERROR_FAILURE; + } + + NS_ADDREF(*aValue = new txNodeSet(*xpathNode, nullptr)); + return NS_OK; + } + + if (aUnionValue.IsNodeSequence()) { + RefPtr<txNodeSet> nodeSet(new txNodeSet(nullptr)); + const Sequence<OwningNonNull<nsINode>>& values = + aUnionValue.GetAsNodeSequence(); + for (const auto& node : values) { + UniquePtr<txXPathNode> xpathNode( + txXPathNativeNode::createXPathNode(node.get())); + if (!xpathNode) { + return NS_ERROR_FAILURE; + } + + nodeSet->append(*xpathNode); + } + nodeSet.forget(aValue); + return NS_OK; + } + + MOZ_ASSERT(aUnionValue.IsXPathResult()); + + XPathResult& xpathResult = aUnionValue.GetAsXPathResult(); + if (xpathResult.ResultType() == XPathResult::NUMBER_TYPE) { + IgnoredErrorResult rv; + NS_ADDREF(*aValue = + new NumberResult(xpathResult.GetNumberValue(rv), nullptr)); + MOZ_ASSERT(!rv.Failed()); + return NS_OK; + } + + if (xpathResult.ResultType() == XPathResult::BOOLEAN_TYPE) { + IgnoredErrorResult rv; + NS_ADDREF(*aValue = new BooleanResult(xpathResult.GetBooleanValue(rv))); + MOZ_ASSERT(!rv.Failed()); + return NS_OK; + } + + if (xpathResult.ResultType() == XPathResult::STRING_TYPE) { + IgnoredErrorResult rv; + nsString value; + xpathResult.GetStringValue(value, rv); + NS_ADDREF(*aValue = new StringResult(value, nullptr)); + MOZ_ASSERT(!rv.Failed()); + return NS_OK; + } + + // If the XPathResult holds a nodeset, then it will keep the nodes alive and + // we'll hold the XPathResult alive. + return xpathResult.GetExprResult(aValue); +} |