/* -*- 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 mSourceDocument; nsCOMPtr mObserver; bool mDocumentIsData; }; class txToFragmentHandlerFactory : public txAOutputHandlerFactory { public: explicit txToFragmentHandlerFactory(DocumentFragment* aFragment) : mFragment(aFragment) {} TX_DECL_TXAOUTPUTHANDLERFACTORY private: RefPtr 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 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 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 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 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 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&& 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&& aValue) { mValue = nullptr; mUnionValue = std::move(aValue); } static UniquePtr 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 mUnionValue; RefPtr mValue; }; inline void ImplCycleCollectionTraverse( nsCycleCollectionTraversalCallback& aCallback, txVariable& aVariable, const char* aName, uint32_t aFlags) { ImplCycleCollectionTraverse(aCallback, *aVariable.mUnionValue, aName, aFlags); } inline void ImplCycleCollectionUnlink( txOwningExpandedNameMap& aMap) { aMap.clear(); } inline void ImplCycleCollectionTraverse( nsCycleCollectionTraversalCallback& aCallback, txOwningExpandedNameMap& aMap, const char* aName, uint32_t aFlags = 0) { aFlags |= CycleCollectionEdgeNameArrayFlag; txOwningExpandedNameMap::iterator iter(aMap); while (iter.next()) { ImplCycleCollectionTraverse( aCallback, *static_cast(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 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 value; uint16_t resultType; if (!aSelect.IsVoid()) { // Set up context UniquePtr 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; 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 name = NS_Atomize(aName); int32_t nsId = kNameSpaceID_Unknown; rv = nsNameSpaceManager::GetInstance()->RegisterNameSpace(aNamespace, nsId); NS_ENSURE_SUCCESS(rv, rv); RefPtr xpathResult = MakeRefPtr(aContext); ErrorResult error; xpathResult->SetExprResult(value, resultType, aContext, error); if (error.Failed()) { return error.StealNSResult(); } UniquePtr varValue = MakeUnique(); varValue->SetAsXPathResult() = xpathResult.forget(); txExpandedName varName(nsId, name); txVariable* var = static_cast(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 mProcessor; explicit nsTransformBlockerEvent(txMozillaXSLTProcessor* processor) : mozilla::Runnable("nsTransformBlockerEvent"), mProcessor(processor) {} ~nsTransformBlockerEvent() { nsCOMPtr 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 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 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 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 sourceNode( txXPathNativeNode::createXPathNode(mSource)); if (!sourceNode) { return NS_ERROR_OUT_OF_MEMORY; } txExecutionState es(mStylesheet, IsLoadDisabled()); Document* sourceDoc = mSource->OwnerDoc(); nsCOMPtr loadGroup = sourceDoc->GetDocumentLoadGroup(); if (!loadGroup) { nsCOMPtr 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 = 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(es.mOutputHandler); nsCOMPtr 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 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 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 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>& 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 result; aError = xpathResult.GetExprResult(getter_AddRefs(result)); if (aError.Failed()) { return; } if (result->getResultType() == txAExprResult::NODESET) { txNodeSet* nodeSet = static_cast(static_cast(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 localName = NS_Atomize(aLocalName); txExpandedName varName(nsId, localName); UniquePtr value = txVariable::convertToOwning(aValue, aError); if (aError.Failed()) { return; } txVariable* var = static_cast(mVariables.get(varName)); if (var) { var->setValue(std::move(value)); return; } UniquePtr newVar = MakeUnique(std::move(value)); mVariables.add(varName, newVar.release()); } void txMozillaXSLTProcessor::GetParameter( const nsAString& aNamespaceURI, const nsAString& aLocalName, Nullable& 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 localName = NS_Atomize(aLocalName); txExpandedName varName(nsId, localName); txVariable* var = static_cast(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 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 sbs = mozilla::components::StringBundle::Service(); if (sbs) { nsString errorText; sbs->FormatStatusMessage(aResult, u"", errorText); nsAutoString errorMessage; nsCOMPtr bundle; sbs->CreateBundle(XSLT_MSGS_URL, getter_AddRefs(bundle)); if (bundle) { AutoTArray 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; { 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 = document->CreateElementNS(ns, u"parsererror"_ns, options, rv); if (rv.Failed()) { return; } document->AppendChild(*element, rv); if (rv.Failed()) { return; } RefPtr text = document->CreateTextNode(mErrorText); element->AppendChild(*text, rv); if (rv.Failed()) { return; } if (!mSourceText.IsEmpty()) { ElementCreationOptionsOrString options; options.SetAsString(); nsCOMPtr 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 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 aGivenProto) { return XSLTProcessor_Binding::Wrap(aCx, this, aGivenProto); } DocGroup* txMozillaXSLTProcessor::GetDocGroup() const { return mStylesheetDocument ? mStylesheetDocument->GetDocGroup() : nullptr; } /* static */ already_AddRefed txMozillaXSLTProcessor::Constructor( const GlobalObject& aGlobal) { RefPtr 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::convertToOwning( const XSLTParameterValue& aValue, ErrorResult& aError) { UniquePtr value = MakeUnique(); 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 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 xpathNode(txXPathNativeNode::createXPathNode(&node)); if (!xpathNode) { return NS_ERROR_FAILURE; } NS_ADDREF(*aValue = new txNodeSet(*xpathNode, nullptr)); return NS_OK; } if (aUnionValue.IsNodeSequence()) { RefPtr nodeSet(new txNodeSet(nullptr)); const Sequence>& values = aUnionValue.GetAsNodeSequence(); for (const auto& node : values) { UniquePtr 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); }