Source code

Revision control

Copy as Markdown

Other Tools

/* -*- 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 "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, &paramContext,
getter_Transfers(expr));
NS_ENSURE_SUCCESS(rv, rv);
// Evaluate
rv = expr->evaluate(&paramContext, 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), nullptr, nullptr);
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 =
IgnoredErrorResult rv;
ElementCreationOptionsOrString options;
Unused << 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;
Unused << 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);
}