summaryrefslogtreecommitdiffstats
path: root/dom/xslt
diff options
context:
space:
mode:
Diffstat (limited to 'dom/xslt')
-rw-r--r--dom/xslt/base/moz.build23
-rw-r--r--dom/xslt/base/txCore.h42
-rw-r--r--dom/xslt/base/txDouble.cpp194
-rw-r--r--dom/xslt/base/txErrorObserver.h36
-rw-r--r--dom/xslt/base/txExpandedName.cpp37
-rw-r--r--dom/xslt/base/txExpandedName.h50
-rw-r--r--dom/xslt/base/txExpandedNameMap.cpp94
-rw-r--r--dom/xslt/base/txExpandedNameMap.h153
-rw-r--r--dom/xslt/base/txList.cpp249
-rw-r--r--dom/xslt/base/txList.h152
-rw-r--r--dom/xslt/base/txLog.h23
-rw-r--r--dom/xslt/base/txNamespaceMap.cpp81
-rw-r--r--dom/xslt/base/txNamespaceMap.h38
-rw-r--r--dom/xslt/base/txOwningArray.h29
-rw-r--r--dom/xslt/base/txStack.h99
-rw-r--r--dom/xslt/base/txStringUtils.h28
-rw-r--r--dom/xslt/base/txURIUtils.cpp87
-rw-r--r--dom/xslt/base/txURIUtils.h40
-rw-r--r--dom/xslt/crashtests/1089049.html3
-rw-r--r--dom/xslt/crashtests/111994.xml5
-rw-r--r--dom/xslt/crashtests/111994.xsl13
-rw-r--r--dom/xslt/crashtests/1205163.xml6
-rw-r--r--dom/xslt/crashtests/1205163.xsl11
-rw-r--r--dom/xslt/crashtests/1243337.xml3
-rw-r--r--dom/xslt/crashtests/1243337.xsl6
-rw-r--r--dom/xslt/crashtests/1330492.html23
-rw-r--r--dom/xslt/crashtests/1336828.html22
-rw-r--r--dom/xslt/crashtests/1336830.html27
-rw-r--r--dom/xslt/crashtests/1336832.html28
-rw-r--r--dom/xslt/crashtests/1338277.html21
-rw-r--r--dom/xslt/crashtests/1361892.html23
-rw-r--r--dom/xslt/crashtests/1430818.sjs49
-rw-r--r--dom/xslt/crashtests/1430818.xml4
-rw-r--r--dom/xslt/crashtests/1527277.html14
-rw-r--r--dom/xslt/crashtests/1589930.xml10
-rw-r--r--dom/xslt/crashtests/182460-select.xml3
-rw-r--r--dom/xslt/crashtests/182460-selects.xsl5
-rw-r--r--dom/xslt/crashtests/182460-table.xhtml9
-rw-r--r--dom/xslt/crashtests/226425.xml4
-rw-r--r--dom/xslt/crashtests/226425.xsl10
-rw-r--r--dom/xslt/crashtests/406106-1.html18
-rw-r--r--dom/xslt/crashtests/483444.xml19
-rw-r--r--dom/xslt/crashtests/485217.xml8
-rw-r--r--dom/xslt/crashtests/485217.xsl18
-rw-r--r--dom/xslt/crashtests/485286.xml12
-rw-r--r--dom/xslt/crashtests/527558_1.xml17
-rw-r--r--dom/xslt/crashtests/528300.xml22
-rw-r--r--dom/xslt/crashtests/528488.xml19
-rw-r--r--dom/xslt/crashtests/528963.xml22
-rw-r--r--dom/xslt/crashtests/545927.html28
-rw-r--r--dom/xslt/crashtests/601543.html4
-rw-r--r--dom/xslt/crashtests/602115.html22
-rw-r--r--dom/xslt/crashtests/603844.html32
-rw-r--r--dom/xslt/crashtests/667315.xml11
-rw-r--r--dom/xslt/crashtests/91332.xml10
-rw-r--r--dom/xslt/crashtests/91332.xsl21
-rw-r--r--dom/xslt/crashtests/949990.html15
-rw-r--r--dom/xslt/crashtests/crashtests.list30
-rw-r--r--dom/xslt/moz.build23
-rw-r--r--dom/xslt/nsIDocumentTransformer.h74
-rw-r--r--dom/xslt/tests/browser/browser.toml8
-rw-r--r--dom/xslt/tests/browser/browser_bug1309630.js74
-rw-r--r--dom/xslt/tests/browser/bug1309630.sjs54
-rw-r--r--dom/xslt/tests/browser/file_bug1309630.html19
-rw-r--r--dom/xslt/tests/mochitest/bug1729517_2.sjs3
-rw-r--r--dom/xslt/tests/mochitest/file_bug1135764.xml3
-rw-r--r--dom/xslt/tests/mochitest/file_bug1135764.xsl19
-rw-r--r--dom/xslt/tests/mochitest/file_bug1729517.js4
-rw-r--r--dom/xslt/tests/mochitest/file_bug1729517.xml31
-rw-r--r--dom/xslt/tests/mochitest/file_bug1729517_2.xml38
-rw-r--r--dom/xslt/tests/mochitest/file_bug1729517_2.xml^headers^2
-rw-r--r--dom/xslt/tests/mochitest/file_metaRefresh.xml16
-rw-r--r--dom/xslt/tests/mochitest/mochitest.toml57
-rw-r--r--dom/xslt/tests/mochitest/test_bug1072116.html37
-rw-r--r--dom/xslt/tests/mochitest/test_bug1135764.html58
-rw-r--r--dom/xslt/tests/mochitest/test_bug1436040.html54
-rw-r--r--dom/xslt/tests/mochitest/test_bug1527308.html38
-rw-r--r--dom/xslt/tests/mochitest/test_bug1729517.html48
-rw-r--r--dom/xslt/tests/mochitest/test_bug319374.html101
-rw-r--r--dom/xslt/tests/mochitest/test_bug427060.html49
-rw-r--r--dom/xslt/tests/mochitest/test_bug440974.html46
-rw-r--r--dom/xslt/tests/mochitest/test_bug453441.html57
-rw-r--r--dom/xslt/tests/mochitest/test_bug468208.html35
-rw-r--r--dom/xslt/tests/mochitest/test_bug511487.html59
-rw-r--r--dom/xslt/tests/mochitest/test_bug551412.html48
-rw-r--r--dom/xslt/tests/mochitest/test_bug551654.html49
-rw-r--r--dom/xslt/tests/mochitest/test_bug566629.html70
-rw-r--r--dom/xslt/tests/mochitest/test_bug566629.xhtml73
-rw-r--r--dom/xslt/tests/mochitest/test_bug603159.html54
-rw-r--r--dom/xslt/tests/mochitest/test_bug616774.html28
-rw-r--r--dom/xslt/tests/mochitest/test_bug667315.html46
-rw-r--r--dom/xslt/tests/mochitest/test_exslt.html249
-rw-r--r--dom/xslt/tests/mochitest/test_metaRefresh.html48
-rw-r--r--dom/xslt/tests/mochitest/test_parameter.html157
-rw-r--r--dom/xslt/tests/mochitest/test_sorting_invalid_lang.html82
-rw-r--r--dom/xslt/xml/moz.build19
-rw-r--r--dom/xslt/xml/txXMLParser.cpp59
-rw-r--r--dom/xslt/xml/txXMLParser.h27
-rw-r--r--dom/xslt/xml/txXMLUtils.cpp164
-rw-r--r--dom/xslt/xml/txXMLUtils.h75
-rw-r--r--dom/xslt/xpath/XPathEvaluator.cpp187
-rw-r--r--dom/xslt/xpath/XPathEvaluator.h65
-rw-r--r--dom/xslt/xpath/XPathExpression.cpp210
-rw-r--r--dom/xslt/xpath/XPathExpression.h68
-rw-r--r--dom/xslt/xpath/XPathResult.cpp264
-rw-r--r--dom/xslt/xpath/XPathResult.h156
-rw-r--r--dom/xslt/xpath/moz.build58
-rw-r--r--dom/xslt/xpath/txBooleanExpr.cpp78
-rw-r--r--dom/xslt/xpath/txBooleanResult.cpp48
-rw-r--r--dom/xslt/xpath/txCoreFunctionCall.cpp665
-rw-r--r--dom/xslt/xpath/txErrorExpr.cpp35
-rw-r--r--dom/xslt/xpath/txExpr.cpp26
-rw-r--r--dom/xslt/xpath/txExpr.h847
-rw-r--r--dom/xslt/xpath/txExprLexer.cpp333
-rw-r--r--dom/xslt/xpath/txExprLexer.h200
-rw-r--r--dom/xslt/xpath/txExprParser.cpp855
-rw-r--r--dom/xslt/xpath/txExprParser.h95
-rw-r--r--dom/xslt/xpath/txExprResult.h118
-rw-r--r--dom/xslt/xpath/txFilterExpr.cpp88
-rw-r--r--dom/xslt/xpath/txForwardContext.cpp50
-rw-r--r--dom/xslt/xpath/txForwardContext.h28
-rw-r--r--dom/xslt/xpath/txFunctionCall.cpp110
-rw-r--r--dom/xslt/xpath/txIXPathContext.h132
-rw-r--r--dom/xslt/xpath/txLiteralExpr.cpp74
-rw-r--r--dom/xslt/xpath/txLocationStep.cpp308
-rw-r--r--dom/xslt/xpath/txMozillaXPathTreeWalker.cpp674
-rw-r--r--dom/xslt/xpath/txNameTest.cpp92
-rw-r--r--dom/xslt/xpath/txNamedAttributeStep.cpp53
-rw-r--r--dom/xslt/xpath/txNodeSet.cpp567
-rw-r--r--dom/xslt/xpath/txNodeSet.h199
-rw-r--r--dom/xslt/xpath/txNodeSetContext.cpp51
-rw-r--r--dom/xslt/xpath/txNodeSetContext.h36
-rw-r--r--dom/xslt/xpath/txNodeTypeTest.cpp91
-rw-r--r--dom/xslt/xpath/txNumberExpr.cpp108
-rw-r--r--dom/xslt/xpath/txNumberResult.cpp48
-rw-r--r--dom/xslt/xpath/txPathExpr.cpp229
-rw-r--r--dom/xslt/xpath/txPredicateList.cpp78
-rw-r--r--dom/xslt/xpath/txPredicatedNodeTest.cpp50
-rw-r--r--dom/xslt/xpath/txRelationalExpr.cpp184
-rw-r--r--dom/xslt/xpath/txResultRecycler.cpp171
-rw-r--r--dom/xslt/xpath/txResultRecycler.h76
-rw-r--r--dom/xslt/xpath/txRootExpr.cpp35
-rw-r--r--dom/xslt/xpath/txSingleNodeContext.h64
-rw-r--r--dom/xslt/xpath/txStringResult.cpp43
-rw-r--r--dom/xslt/xpath/txUnaryExpr.cpp49
-rw-r--r--dom/xslt/xpath/txUnionExpr.cpp85
-rw-r--r--dom/xslt/xpath/txUnionNodeTest.cpp59
-rw-r--r--dom/xslt/xpath/txVariableRefExpr.cpp62
-rw-r--r--dom/xslt/xpath/txXPathNode.h91
-rw-r--r--dom/xslt/xpath/txXPathOptimizer.cpp246
-rw-r--r--dom/xslt/xpath/txXPathOptimizer.h30
-rw-r--r--dom/xslt/xpath/txXPathTreeWalker.h201
-rw-r--r--dom/xslt/xslt/moz.build66
-rw-r--r--dom/xslt/xslt/txBufferingHandler.cpp385
-rw-r--r--dom/xslt/xslt/txBufferingHandler.h45
-rw-r--r--dom/xslt/xslt/txCurrentFunctionCall.cpp50
-rw-r--r--dom/xslt/xslt/txDocumentFunctionCall.cpp150
-rw-r--r--dom/xslt/xslt/txEXSLTFunctions.cpp818
-rw-r--r--dom/xslt/xslt/txEXSLTRegExFunctions.sys.mjs32
-rw-r--r--dom/xslt/xslt/txExecutionState.cpp460
-rw-r--r--dom/xslt/xslt/txExecutionState.h166
-rw-r--r--dom/xslt/xslt/txFormatNumberFunctionCall.cpp406
-rw-r--r--dom/xslt/xslt/txGenerateIdFunctionCall.cpp99
-rw-r--r--dom/xslt/xslt/txIEXSLTFunctions.idl20
-rw-r--r--dom/xslt/xslt/txInstructions.cpp764
-rw-r--r--dom/xslt/xslt/txInstructions.h362
-rw-r--r--dom/xslt/xslt/txKey.h186
-rw-r--r--dom/xslt/xslt/txKeyFunctionCall.cpp345
-rw-r--r--dom/xslt/xslt/txMozillaStylesheetCompiler.cpp622
-rw-r--r--dom/xslt/xslt/txMozillaTextOutput.cpp251
-rw-r--r--dom/xslt/xslt/txMozillaTextOutput.h47
-rw-r--r--dom/xslt/xslt/txMozillaXMLOutput.cpp926
-rw-r--r--dom/xslt/xslt/txMozillaXMLOutput.h130
-rw-r--r--dom/xslt/xslt/txMozillaXSLTProcessor.cpp1227
-rw-r--r--dom/xslt/xslt/txMozillaXSLTProcessor.h170
-rw-r--r--dom/xslt/xslt/txNodeSorter.cpp236
-rw-r--r--dom/xslt/xslt/txNodeSorter.h55
-rw-r--r--dom/xslt/xslt/txOutputFormat.cpp101
-rw-r--r--dom/xslt/xslt/txOutputFormat.h64
-rw-r--r--dom/xslt/xslt/txPatternOptimizer.cpp65
-rw-r--r--dom/xslt/xslt/txPatternOptimizer.h30
-rw-r--r--dom/xslt/xslt/txPatternParser.cpp260
-rw-r--r--dom/xslt/xslt/txPatternParser.h35
-rw-r--r--dom/xslt/xslt/txRtfHandler.cpp54
-rw-r--r--dom/xslt/xslt/txRtfHandler.h42
-rw-r--r--dom/xslt/xslt/txStylesheet.cpp550
-rw-r--r--dom/xslt/xslt/txStylesheet.h185
-rw-r--r--dom/xslt/xslt/txStylesheetCompileHandlers.cpp2352
-rw-r--r--dom/xslt/xslt/txStylesheetCompileHandlers.h54
-rw-r--r--dom/xslt/xslt/txStylesheetCompiler.cpp839
-rw-r--r--dom/xslt/xslt/txStylesheetCompiler.h234
-rw-r--r--dom/xslt/xslt/txTextHandler.cpp60
-rw-r--r--dom/xslt/xslt/txTextHandler.h25
-rw-r--r--dom/xslt/xslt/txToplevelItems.cpp45
-rw-r--r--dom/xslt/xslt/txToplevelItems.h118
-rw-r--r--dom/xslt/xslt/txUnknownHandler.cpp177
-rw-r--r--dom/xslt/xslt/txUnknownHandler.h37
-rw-r--r--dom/xslt/xslt/txVariableMap.h88
-rw-r--r--dom/xslt/xslt/txXMLEventHandler.h184
-rw-r--r--dom/xslt/xslt/txXPathResultComparator.cpp120
-rw-r--r--dom/xslt/xslt/txXPathResultComparator.h86
-rw-r--r--dom/xslt/xslt/txXSLTEnvironmentFunctionCall.cpp124
-rw-r--r--dom/xslt/xslt/txXSLTFunctions.h149
-rw-r--r--dom/xslt/xslt/txXSLTMsgsURL.h11
-rw-r--r--dom/xslt/xslt/txXSLTNumber.cpp741
-rw-r--r--dom/xslt/xslt/txXSLTNumber.h67
-rw-r--r--dom/xslt/xslt/txXSLTNumberCounters.cpp199
-rw-r--r--dom/xslt/xslt/txXSLTPatterns.cpp530
-rw-r--r--dom/xslt/xslt/txXSLTPatterns.h200
-rw-r--r--dom/xslt/xslt/txXSLTProcessor.cpp50
-rw-r--r--dom/xslt/xslt/txXSLTProcessor.h26
211 files changed, 29027 insertions, 0 deletions
diff --git a/dom/xslt/base/moz.build b/dom/xslt/base/moz.build
new file mode 100644
index 0000000000..1b22391983
--- /dev/null
+++ b/dom/xslt/base/moz.build
@@ -0,0 +1,23 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+UNIFIED_SOURCES += [
+ "txDouble.cpp",
+ "txExpandedName.cpp",
+ "txExpandedNameMap.cpp",
+ "txList.cpp",
+ "txNamespaceMap.cpp",
+ "txURIUtils.cpp",
+]
+
+LOCAL_INCLUDES += [
+ "..",
+ "../xml",
+ "../xpath",
+ "../xslt",
+]
+
+FINAL_LIBRARY = "xul"
diff --git a/dom/xslt/base/txCore.h b/dom/xslt/base/txCore.h
new file mode 100644
index 0000000000..6e462a5a31
--- /dev/null
+++ b/dom/xslt/base/txCore.h
@@ -0,0 +1,42 @@
+/* -*- 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/. */
+
+#ifndef __txCore_h__
+#define __txCore_h__
+
+#include "nscore.h"
+#include "nsDebug.h"
+#include "nsISupportsImpl.h"
+#include "nsStringFwd.h"
+
+class txObject {
+ public:
+ MOZ_COUNTED_DEFAULT_CTOR(txObject)
+
+ /**
+ * Deletes this txObject
+ */
+ MOZ_COUNTED_DTOR_VIRTUAL(txObject)
+};
+
+/**
+ * Utility class for doubles
+ */
+class txDouble {
+ public:
+ /**
+ * Converts the value of the given double to a string, and appends
+ * the result to the destination string.
+ */
+ static void toString(double aValue, nsAString& aDest);
+
+ /**
+ * Converts the given String to a double, if the string value does not
+ * represent a double, NaN will be returned.
+ */
+ static double toDouble(const nsAString& aStr);
+};
+
+#endif
diff --git a/dom/xslt/base/txDouble.cpp b/dom/xslt/base/txDouble.cpp
new file mode 100644
index 0000000000..ad7fa690be
--- /dev/null
+++ b/dom/xslt/base/txDouble.cpp
@@ -0,0 +1,194 @@
+/* -*- 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 "mozilla/FloatingPoint.h"
+
+#include "nsString.h"
+#include "txCore.h"
+#include "txXMLUtils.h"
+#include <math.h>
+#include <stdlib.h>
+#include <algorithm>
+#ifdef WIN32
+# include <float.h>
+#endif
+#include "prdtoa.h"
+
+/*
+ * Utility class for doubles
+ */
+
+/*
+ * Converts the given String to a double, if the String value does not
+ * represent a double, NaN will be returned
+ */
+class txStringToDouble {
+ public:
+ txStringToDouble() : mState(eWhitestart), mSign(ePositive) {}
+
+ void Parse(const nsAString& aSource) {
+ if (mState == eIllegal) {
+ return;
+ }
+ uint32_t i = 0;
+ char16_t c;
+ auto len = aSource.Length();
+ for (; i < len; ++i) {
+ c = aSource[i];
+ switch (mState) {
+ case eWhitestart:
+ if (c == '-') {
+ mState = eDecimal;
+ mSign = eNegative;
+ } else if (c >= '0' && c <= '9') {
+ mState = eDecimal;
+ mBuffer.Append((char)c);
+ } else if (c == '.') {
+ mState = eMantissa;
+ mBuffer.Append((char)c);
+ } else if (!XMLUtils::isWhitespace(c)) {
+ mState = eIllegal;
+ return;
+ }
+ break;
+ case eDecimal:
+ if (c >= '0' && c <= '9') {
+ mBuffer.Append((char)c);
+ } else if (c == '.') {
+ mState = eMantissa;
+ mBuffer.Append((char)c);
+ } else if (XMLUtils::isWhitespace(c)) {
+ mState = eWhiteend;
+ } else {
+ mState = eIllegal;
+ return;
+ }
+ break;
+ case eMantissa:
+ if (c >= '0' && c <= '9') {
+ mBuffer.Append((char)c);
+ } else if (XMLUtils::isWhitespace(c)) {
+ mState = eWhiteend;
+ } else {
+ mState = eIllegal;
+ return;
+ }
+ break;
+ case eWhiteend:
+ if (!XMLUtils::isWhitespace(c)) {
+ mState = eIllegal;
+ return;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ double getDouble() {
+ if (mState == eIllegal || mBuffer.IsEmpty() ||
+ (mBuffer.Length() == 1 && mBuffer[0] == '.')) {
+ return mozilla::UnspecifiedNaN<double>();
+ }
+ return static_cast<double>(mSign) * PR_strtod(mBuffer.get(), nullptr);
+ }
+
+ private:
+ nsAutoCString mBuffer;
+ enum { eWhitestart, eDecimal, eMantissa, eWhiteend, eIllegal } mState;
+ enum { eNegative = -1, ePositive = 1 } mSign;
+};
+
+double txDouble::toDouble(const nsAString& aSrc) {
+ txStringToDouble sink;
+ sink.Parse(aSrc);
+ return sink.getDouble();
+}
+
+/*
+ * Converts the value of the given double to a String, and places
+ * The result into the destination String.
+ * @return the given dest string
+ */
+void txDouble::toString(double aValue, nsAString& aDest) {
+ // check for special cases
+
+ if (std::isnan(aValue)) {
+ aDest.AppendLiteral("NaN");
+ return;
+ }
+ if (std::isinf(aValue)) {
+ if (aValue < 0) aDest.Append(char16_t('-'));
+ aDest.AppendLiteral("Infinity");
+ return;
+ }
+
+ // Mantissa length is 17, so this is plenty
+ const int buflen = 20;
+ char buf[buflen];
+
+ int intDigits, sign;
+ char* endp;
+ PR_dtoa(aValue, 0, 0, &intDigits, &sign, &endp, buf, buflen - 1);
+
+ // compute length
+ int32_t length = endp - buf;
+ if (length > intDigits) {
+ // decimal point needed
+ ++length;
+ if (intDigits < 1) {
+ // leading zeros, -intDigits + 1
+ length += 1 - intDigits;
+ }
+ } else {
+ // trailing zeros, total length given by intDigits
+ length = intDigits;
+ }
+ if (aValue < 0) ++length;
+ // grow the string
+ uint32_t oldlength = aDest.Length();
+ if (!aDest.SetLength(oldlength + length, mozilla::fallible))
+ return; // out of memory
+ auto dest = aDest.BeginWriting();
+ std::advance(dest, oldlength);
+ if (aValue < 0) {
+ *dest = '-';
+ ++dest;
+ }
+ int i;
+ // leading zeros
+ if (intDigits < 1) {
+ *dest = '0';
+ ++dest;
+ *dest = '.';
+ ++dest;
+ for (i = 0; i > intDigits; --i) {
+ *dest = '0';
+ ++dest;
+ }
+ }
+ // mantissa
+ int firstlen = std::min<size_t>(intDigits, endp - buf);
+ for (i = 0; i < firstlen; i++) {
+ *dest = buf[i];
+ ++dest;
+ }
+ if (i < endp - buf) {
+ if (i > 0) {
+ *dest = '.';
+ ++dest;
+ }
+ for (; i < endp - buf; i++) {
+ *dest = buf[i];
+ ++dest;
+ }
+ }
+ // trailing zeros
+ for (; i < intDigits; i++) {
+ *dest = '0';
+ ++dest;
+ }
+}
diff --git a/dom/xslt/base/txErrorObserver.h b/dom/xslt/base/txErrorObserver.h
new file mode 100644
index 0000000000..30b541f2a9
--- /dev/null
+++ b/dom/xslt/base/txErrorObserver.h
@@ -0,0 +1,36 @@
+/* -*- 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/. */
+
+#ifndef MITRE_ERROROBSERVER_H
+#define MITRE_ERROROBSERVER_H
+
+#include "txCore.h"
+
+/**
+ * A simple interface for observing errors
+ **/
+class ErrorObserver {
+ public:
+ /**
+ * Default Destructor for ErrorObserver
+ **/
+ virtual ~ErrorObserver(){};
+
+ /**
+ * Notifies this Error observer of a new error aRes
+ **/
+ virtual void receiveError(const nsAString& errorMessage, nsresult aRes) = 0;
+
+ /**
+ * Notifies this Error observer of a new error, with default
+ * error code NS_ERROR_FAILURE
+ **/
+ void receiveError(const nsAString& errorMessage) {
+ receiveError(errorMessage, NS_ERROR_FAILURE);
+ }
+
+}; //-- ErrorObserver
+
+#endif
diff --git a/dom/xslt/base/txExpandedName.cpp b/dom/xslt/base/txExpandedName.cpp
new file mode 100644
index 0000000000..af11476056
--- /dev/null
+++ b/dom/xslt/base/txExpandedName.cpp
@@ -0,0 +1,37 @@
+/* -*- 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 "txExpandedName.h"
+#include "nsString.h"
+#include "nsReadableUtils.h"
+#include "txStringUtils.h"
+#include "txNamespaceMap.h"
+#include "txXMLUtils.h"
+
+nsresult txExpandedName::init(const nsAString& aQName,
+ txNamespaceMap* aResolver, bool aUseDefault) {
+ const nsString& qName = PromiseFlatString(aQName);
+ const char16_t* colon;
+ bool valid = XMLUtils::isValidQName(qName, &colon);
+ if (!valid) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (colon) {
+ RefPtr<nsAtom> prefix = NS_Atomize(Substring(qName.get(), colon));
+ int32_t namespaceID = aResolver->lookupNamespace(prefix);
+ if (namespaceID == kNameSpaceID_Unknown) return NS_ERROR_FAILURE;
+ mNamespaceID = namespaceID;
+
+ const char16_t* end;
+ qName.EndReading(end);
+ mLocalName = NS_Atomize(Substring(colon + 1, end));
+ } else {
+ mNamespaceID =
+ aUseDefault ? aResolver->lookupNamespace(nullptr) : kNameSpaceID_None;
+ mLocalName = NS_Atomize(aQName);
+ }
+ return NS_OK;
+}
diff --git a/dom/xslt/base/txExpandedName.h b/dom/xslt/base/txExpandedName.h
new file mode 100644
index 0000000000..76d82a8eee
--- /dev/null
+++ b/dom/xslt/base/txExpandedName.h
@@ -0,0 +1,50 @@
+/* -*- 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/. */
+
+#ifndef TRANSFRMX_EXPANDEDNAME_H
+#define TRANSFRMX_EXPANDEDNAME_H
+
+#include "nsCOMPtr.h"
+#include "nsAtom.h"
+#include "mozilla/dom/NameSpaceConstants.h"
+
+class txNamespaceMap;
+
+class txExpandedName {
+ public:
+ txExpandedName() : mNamespaceID(kNameSpaceID_None) {}
+
+ txExpandedName(int32_t aNsID, nsAtom* aLocalName)
+ : mNamespaceID(aNsID), mLocalName(aLocalName) {}
+
+ txExpandedName(const txExpandedName& aOther) = default;
+
+ nsresult init(const nsAString& aQName, txNamespaceMap* aResolver,
+ bool aUseDefault);
+
+ void reset() {
+ mNamespaceID = kNameSpaceID_None;
+ mLocalName = nullptr;
+ }
+
+ bool isNull() { return mNamespaceID == kNameSpaceID_None && !mLocalName; }
+
+ txExpandedName& operator=(const txExpandedName& rhs) = default;
+
+ bool operator==(const txExpandedName& rhs) const {
+ return ((mLocalName == rhs.mLocalName) &&
+ (mNamespaceID == rhs.mNamespaceID));
+ }
+
+ bool operator!=(const txExpandedName& rhs) const {
+ return ((mLocalName != rhs.mLocalName) ||
+ (mNamespaceID != rhs.mNamespaceID));
+ }
+
+ int32_t mNamespaceID;
+ RefPtr<nsAtom> mLocalName;
+};
+
+#endif
diff --git a/dom/xslt/base/txExpandedNameMap.cpp b/dom/xslt/base/txExpandedNameMap.cpp
new file mode 100644
index 0000000000..996799abf0
--- /dev/null
+++ b/dom/xslt/base/txExpandedNameMap.cpp
@@ -0,0 +1,94 @@
+/* -*- 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 "txExpandedNameMap.h"
+#include "txCore.h"
+
+class txMapItemComparator {
+ public:
+ bool Equals(const txExpandedNameMap_base::MapItem& aItem,
+ const txExpandedName& aKey) const {
+ return aItem.mNamespaceID == aKey.mNamespaceID &&
+ aItem.mLocalName == aKey.mLocalName;
+ }
+};
+
+/**
+ * Adds an item, if an item with this key already exists an error is
+ * returned
+ * @param aKey key for item to add
+ * @param aValue value of item to add
+ * @return errorcode
+ */
+nsresult txExpandedNameMap_base::addItem(const txExpandedName& aKey,
+ void* aValue) {
+ size_t pos = mItems.IndexOf(aKey, 0, txMapItemComparator());
+ if (pos != mItems.NoIndex) {
+ return NS_ERROR_XSLT_ALREADY_SET;
+ }
+
+ MapItem* item = mItems.AppendElement();
+ item->mNamespaceID = aKey.mNamespaceID;
+ item->mLocalName = aKey.mLocalName;
+ item->mValue = aValue;
+
+ return NS_OK;
+}
+
+/**
+ * Sets an item, if an item with this key already exists it is overwritten
+ * with the new value
+ * @param aKey key for item to set
+ * @param aValue value of item to set
+ * @return errorcode
+ */
+nsresult txExpandedNameMap_base::setItem(const txExpandedName& aKey,
+ void* aValue, void** aOldValue) {
+ *aOldValue = nullptr;
+ size_t pos = mItems.IndexOf(aKey, 0, txMapItemComparator());
+ if (pos != mItems.NoIndex) {
+ *aOldValue = mItems[pos].mValue;
+ mItems[pos].mValue = aValue;
+ return NS_OK;
+ }
+
+ MapItem* item = mItems.AppendElement();
+ item->mNamespaceID = aKey.mNamespaceID;
+ item->mLocalName = aKey.mLocalName;
+ item->mValue = aValue;
+
+ return NS_OK;
+}
+
+/**
+ * Gets an item
+ * @param aKey key for item to get
+ * @return item with specified key, or null if no such item exists
+ */
+void* txExpandedNameMap_base::getItem(const txExpandedName& aKey) const {
+ size_t pos = mItems.IndexOf(aKey, 0, txMapItemComparator());
+ if (pos != mItems.NoIndex) {
+ return mItems[pos].mValue;
+ }
+
+ return nullptr;
+}
+
+/**
+ * Removes an item, deleting it if the map owns the values
+ * @param aKey key for item to remove
+ * @return item with specified key, or null if it has been deleted
+ * or no such item exists
+ */
+void* txExpandedNameMap_base::removeItem(const txExpandedName& aKey) {
+ void* value = nullptr;
+ size_t pos = mItems.IndexOf(aKey, 0, txMapItemComparator());
+ if (pos != mItems.NoIndex) {
+ value = mItems[pos].mValue;
+ mItems.RemoveElementAt(pos);
+ }
+
+ return value;
+}
diff --git a/dom/xslt/base/txExpandedNameMap.h b/dom/xslt/base/txExpandedNameMap.h
new file mode 100644
index 0000000000..6e308a65f8
--- /dev/null
+++ b/dom/xslt/base/txExpandedNameMap.h
@@ -0,0 +1,153 @@
+/* -*- 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/. */
+
+#ifndef TRANSFRMX_EXPANDEDNAMEMAP_H
+#define TRANSFRMX_EXPANDEDNAMEMAP_H
+
+#include "mozilla/UniquePtr.h"
+#include "mozilla/UniquePtrExtensions.h"
+#include "nsError.h"
+#include "txExpandedName.h"
+#include "nsTArray.h"
+
+class txExpandedNameMap_base {
+ protected:
+ /**
+ * Adds an item, if an item with this key already exists an error is
+ * returned
+ * @param aKey key for item to add
+ * @param aValue value of item to add
+ * @return errorcode
+ */
+ nsresult addItem(const txExpandedName& aKey, void* aValue);
+
+ /**
+ * Sets an item, if an item with this key already exists it is overwritten
+ * with the new value
+ * @param aKey key for item to set
+ * @param aValue value of item to set
+ * @return errorcode
+ */
+ nsresult setItem(const txExpandedName& aKey, void* aValue, void** aOldValue);
+
+ /**
+ * Gets an item
+ * @param aKey key for item to get
+ * @return item with specified key, or null if no such item exists
+ */
+ void* getItem(const txExpandedName& aKey) const;
+
+ /**
+ * Removes an item, deleting it if the map owns the values
+ * @param aKey key for item to remove
+ * @return item with specified key, or null if it has been deleted
+ * or no such item exists
+ */
+ void* removeItem(const txExpandedName& aKey);
+
+ /**
+ * Clears the items
+ */
+ void clearItems() { mItems.Clear(); }
+
+ class iterator_base {
+ public:
+ explicit iterator_base(txExpandedNameMap_base& aMap)
+ : mMap(aMap), mCurrentPos(uint32_t(-1)) {}
+
+ bool next() { return ++mCurrentPos < mMap.mItems.Length(); }
+
+ const txExpandedName key() {
+ NS_ASSERTION(mCurrentPos < mMap.mItems.Length(),
+ "invalid position in txExpandedNameMap::iterator");
+ return txExpandedName(mMap.mItems[mCurrentPos].mNamespaceID,
+ mMap.mItems[mCurrentPos].mLocalName);
+ }
+
+ protected:
+ void* itemValue() {
+ NS_ASSERTION(mCurrentPos < mMap.mItems.Length(),
+ "invalid position in txExpandedNameMap::iterator");
+ return mMap.mItems[mCurrentPos].mValue;
+ }
+
+ private:
+ txExpandedNameMap_base& mMap;
+ uint32_t mCurrentPos;
+ };
+
+ friend class iterator_base;
+
+ friend class txMapItemComparator;
+ struct MapItem {
+ int32_t mNamespaceID;
+ RefPtr<nsAtom> mLocalName;
+ void* mValue;
+ };
+
+ nsTArray<MapItem> mItems;
+};
+
+template <class E>
+class txExpandedNameMap : public txExpandedNameMap_base {
+ public:
+ nsresult add(const txExpandedName& aKey, E* aValue) {
+ return addItem(aKey, (void*)aValue);
+ }
+
+ nsresult set(const txExpandedName& aKey, E* aValue) {
+ void* oldValue;
+ return setItem(aKey, (void*)aValue, &oldValue);
+ }
+
+ E* get(const txExpandedName& aKey) const { return (E*)getItem(aKey); }
+
+ E* remove(const txExpandedName& aKey) { return (E*)removeItem(aKey); }
+
+ void clear() { clearItems(); }
+
+ class iterator : public iterator_base {
+ public:
+ explicit iterator(txExpandedNameMap& aMap) : iterator_base(aMap) {}
+
+ E* value() { return (E*)itemValue(); }
+ };
+};
+
+template <class E>
+class txOwningExpandedNameMap : public txExpandedNameMap_base {
+ public:
+ ~txOwningExpandedNameMap() { clear(); }
+
+ nsresult add(const txExpandedName& aKey, E* aValue) {
+ return addItem(aKey, (void*)aValue);
+ }
+
+ nsresult set(const txExpandedName& aKey, E* aValue) {
+ mozilla::UniquePtr<E> oldValue;
+ return setItem(aKey, (void*)aValue, getter_Transfers(oldValue));
+ }
+
+ E* get(const txExpandedName& aKey) const { return (E*)getItem(aKey); }
+
+ void remove(const txExpandedName& aKey) { delete (E*)removeItem(aKey); }
+
+ void clear() {
+ uint32_t i, len = mItems.Length();
+ for (i = 0; i < len; ++i) {
+ delete (E*)mItems[i].mValue;
+ }
+ clearItems();
+ }
+
+ class iterator : public iterator_base {
+ public:
+ explicit iterator(txOwningExpandedNameMap& aMap) : iterator_base(aMap) {}
+
+ E* value() { return (E*)itemValue(); }
+ };
+};
+
+#endif // TRANSFRMX_EXPANDEDNAMEMAP_H
diff --git a/dom/xslt/base/txList.cpp b/dom/xslt/base/txList.cpp
new file mode 100644
index 0000000000..a361490784
--- /dev/null
+++ b/dom/xslt/base/txList.cpp
@@ -0,0 +1,249 @@
+/* -*- 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 "txList.h"
+
+//----------------------------/
+//- Implementation of txList -/
+//----------------------------/
+
+/**
+ * Default constructor for a txList;
+ **/
+
+txList::txList() {
+ firstItem = 0;
+ lastItem = 0;
+ itemCount = 0;
+} //-- txList;
+
+/**
+ * txList destructor, cleans up ListItems, but will not delete the Object
+ * references
+ */
+txList::~txList() { clear(); } //-- ~txList
+
+void txList::add(void* objPtr) { insertBefore(objPtr, nullptr); } //-- add
+
+/**
+ * Returns the number of items in this txList
+ **/
+int32_t List::getLength() { return itemCount; } //-- getLength
+
+/**
+ * Inserts the given Object pointer as the item just after refItem.
+ * If refItem is a null pointer the Object will be inserted at the
+ * beginning of the txList (ie, insert after nothing).
+ * This method assumes refItem is a member of this list, and since this
+ * is a private method, I feel that's a valid assumption
+ **/
+void txList::insertAfter(void* objPtr, ListItem* refItem) {
+ insertBefore(objPtr, refItem ? refItem->nextItem : firstItem);
+} //-- insertAfter
+
+/**
+ * Inserts the given Object pointer as the item just before refItem.
+ * If refItem is a null pointer the Object will be inserted at the
+ * end of the txList (ie, insert before nothing).
+ * This method assumes refItem is a member of this list, and since this
+ * is a private method, I feel that's a valid assumption
+ **/
+void txList::insertBefore(void* objPtr, ListItem* refItem) {
+ ListItem* item = new ListItem;
+ item->objPtr = objPtr;
+ item->nextItem = 0;
+ item->prevItem = 0;
+
+ //-- if refItem == null insert at end
+ if (!refItem) {
+ //-- add to back of list
+ if (lastItem) {
+ lastItem->nextItem = item;
+ item->prevItem = lastItem;
+ }
+ lastItem = item;
+ if (!firstItem) firstItem = item;
+ } else {
+ //-- insert before given item
+ item->nextItem = refItem;
+ item->prevItem = refItem->prevItem;
+ refItem->prevItem = item;
+
+ if (item->prevItem)
+ item->prevItem->nextItem = item;
+ else
+ firstItem = item;
+ }
+
+ // increase the item count
+ ++itemCount;
+} //-- insertBefore
+
+txList::ListItem* txList::remove(ListItem* item) {
+ if (!item) return item;
+
+ //-- adjust the previous item's next pointer
+ if (item->prevItem) {
+ item->prevItem->nextItem = item->nextItem;
+ }
+ //-- adjust the next item's previous pointer
+ if (item->nextItem) {
+ item->nextItem->prevItem = item->prevItem;
+ }
+
+ //-- adjust first and last items
+ if (item == firstItem) firstItem = item->nextItem;
+ if (item == lastItem) lastItem = item->prevItem;
+
+ //-- decrease Item count
+ --itemCount;
+ return item;
+} //-- remove
+
+void txList::clear() {
+ ListItem* item = firstItem;
+ while (item) {
+ ListItem* tItem = item;
+ item = item->nextItem;
+ delete tItem;
+ }
+ firstItem = 0;
+ lastItem = 0;
+ itemCount = 0;
+}
+
+//------------------------------------/
+//- Implementation of txListIterator -/
+//------------------------------------/
+
+/**
+ * Creates a new txListIterator for the given txList
+ * @param list, the txList to create an Iterator for
+ **/
+txListIterator::txListIterator(txList* list) {
+ this->list = list;
+ currentItem = 0;
+ atEndOfList = false;
+} //-- txListIterator
+
+/**
+ * Adds the Object pointer to the txList pointed to by this txListIterator.
+ * The Object pointer is inserted as the next item in the txList
+ * based on the current position within the txList
+ * @param objPtr the Object pointer to add to the list
+ **/
+void txListIterator::addAfter(void* objPtr) {
+ if (currentItem || !atEndOfList) {
+ list->insertAfter(objPtr, currentItem);
+ } else {
+ list->insertBefore(objPtr, nullptr);
+ }
+} //-- addAfter
+
+/**
+ * Adds the Object pointer to the txList pointed to by this txListIterator.
+ * The Object pointer is inserted as the previous item in the txList
+ * based on the current position within the txList
+ * @param objPtr the Object pointer to add to the list
+ **/
+void txListIterator::addBefore(void* objPtr) {
+ if (currentItem || atEndOfList) {
+ list->insertBefore(objPtr, currentItem);
+ } else {
+ list->insertAfter(objPtr, nullptr);
+ }
+} //-- addBefore
+
+/**
+ * Returns true if a successful call to the next() method can be made
+ * @return true if a successful call to the next() method can be made,
+ * otherwise false
+ **/
+bool txListIterator::hasNext() {
+ bool hasNext = false;
+ if (currentItem)
+ hasNext = (currentItem->nextItem != 0);
+ else if (!atEndOfList)
+ hasNext = (list->firstItem != 0);
+
+ return hasNext;
+} //-- hasNext
+
+/**
+ * Returns the next Object pointer in the list
+ **/
+void* txListIterator::next() {
+ void* obj = 0;
+ if (currentItem)
+ currentItem = currentItem->nextItem;
+ else if (!atEndOfList)
+ currentItem = list->firstItem;
+
+ if (currentItem)
+ obj = currentItem->objPtr;
+ else
+ atEndOfList = true;
+
+ return obj;
+} //-- next
+
+/**
+ * Returns the previous Object in the list
+ **/
+void* txListIterator::previous() {
+ void* obj = 0;
+
+ if (currentItem)
+ currentItem = currentItem->prevItem;
+ else if (atEndOfList)
+ currentItem = list->lastItem;
+
+ if (currentItem) obj = currentItem->objPtr;
+
+ atEndOfList = false;
+
+ return obj;
+} //-- previous
+
+/**
+ * Returns the current Object
+ **/
+void* txListIterator::current() {
+ if (currentItem) return currentItem->objPtr;
+
+ return 0;
+} //-- current
+
+/**
+ * Removes the Object last returned by the next() or previous() methods;
+ * @return the removed Object pointer
+ **/
+void* txListIterator::remove() {
+ void* obj = 0;
+ if (currentItem) {
+ obj = currentItem->objPtr;
+ txList::ListItem* item = currentItem;
+ previous(); //-- make previous item the current item
+ list->remove(item);
+ delete item;
+ }
+ return obj;
+} //-- remove
+
+/**
+ * Resets the current location within the txList to the beginning of the txList
+ **/
+void txListIterator::reset() {
+ atEndOfList = false;
+ currentItem = 0;
+} //-- reset
+
+/**
+ * Move the iterator to right after the last element
+ **/
+void txListIterator::resetToEnd() {
+ atEndOfList = true;
+ currentItem = 0;
+} //-- moveToEnd
diff --git a/dom/xslt/base/txList.h b/dom/xslt/base/txList.h
new file mode 100644
index 0000000000..eeafd12dda
--- /dev/null
+++ b/dom/xslt/base/txList.h
@@ -0,0 +1,152 @@
+/* -*- 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/. */
+
+#ifndef TRANSFRMX_LIST_H
+#define TRANSFRMX_LIST_H
+
+#include "txCore.h"
+
+class txListIterator;
+
+/**
+ * Represents an ordered list of Object pointers. Modeled after a Java 2 List.
+ **/
+class txList : public txObject {
+ friend class txListIterator;
+
+ public:
+ /**
+ * Creates an empty txList
+ **/
+ txList();
+
+ /**
+ * txList destructor, object references will not be deleted.
+ **/
+ ~txList();
+
+ /**
+ * Returns the number of items in this txList
+ **/
+ int32_t getLength();
+
+ /**
+ * Returns true if there are no items in this txList
+ */
+ inline bool isEmpty() { return itemCount == 0; }
+
+ /**
+ * Adds the given Object to the list
+ **/
+ void add(void* objPtr);
+
+ /*
+ * Removes all the objects from the list
+ */
+ void clear();
+
+ protected:
+ struct ListItem {
+ ListItem* nextItem;
+ ListItem* prevItem;
+ void* objPtr;
+ };
+
+ /**
+ * Removes the given ListItem pointer from the list
+ **/
+ ListItem* remove(ListItem* sItem);
+
+ private:
+ txList(const txList& aOther); // not implemented
+
+ ListItem* firstItem;
+ ListItem* lastItem;
+ int32_t itemCount;
+
+ void insertAfter(void* objPtr, ListItem* sItem);
+ void insertBefore(void* objPtr, ListItem* sItem);
+};
+
+/**
+ * An Iterator for the txList Class
+ **/
+class txListIterator {
+ public:
+ /**
+ * Creates a new txListIterator for the given txList
+ * @param list, the txList to create an Iterator for
+ **/
+ explicit txListIterator(txList* list);
+
+ /**
+ * Adds the Object pointer to the txList pointed to by this txListIterator.
+ * The Object pointer is inserted as the next item in the txList
+ * based on the current position within the txList
+ * @param objPtr the Object pointer to add to the list
+ **/
+ void addAfter(void* objPtr);
+
+ /**
+ * Adds the Object pointer to the txList pointed to by this txListIterator.
+ * The Object pointer is inserted as the previous item in the txList
+ * based on the current position within the txList
+ * @param objPtr the Object pointer to add to the list
+ **/
+ void addBefore(void* objPtr);
+
+ /**
+ * Returns true if a successful call to the next() method can be made
+ * @return true if a successful call to the next() method can be made,
+ * otherwise false
+ **/
+ bool hasNext();
+
+ /**
+ * Returns the next Object pointer from the list
+ **/
+ void* next();
+
+ /**
+ * Returns the previous Object pointer from the list
+ **/
+ void* previous();
+
+ /**
+ * Returns the current Object
+ **/
+ void* current();
+
+ /**
+ * Removes the Object last returned by the next() or previous() methods;
+ * @return the removed Object pointer
+ **/
+ void* remove();
+
+ /**
+ * Resets the current location within the txList to the beginning of the
+ * txList
+ **/
+ void reset();
+
+ /**
+ * Resets the current location within the txList to the end of the txList
+ **/
+ void resetToEnd();
+
+ private:
+ //-- points to the current list item
+ txList::ListItem* currentItem;
+
+ //-- points to the list to iterator over
+ txList* list;
+
+ //-- we've moved off the end of the list
+ bool atEndOfList;
+};
+
+using List = txList;
+
+#endif
diff --git a/dom/xslt/base/txLog.h b/dom/xslt/base/txLog.h
new file mode 100644
index 0000000000..c5b03ca83e
--- /dev/null
+++ b/dom/xslt/base/txLog.h
@@ -0,0 +1,23 @@
+/* -*- 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/. */
+
+#ifndef txLog_h__
+#define txLog_h__
+
+#include "mozilla/Logging.h"
+
+class txLog {
+ public:
+ static mozilla::LazyLogModule xpath;
+ static mozilla::LazyLogModule xslt;
+};
+
+#define TX_LG_IMPL \
+ mozilla::LazyLogModule txLog::xpath("xpath"); \
+ mozilla::LazyLogModule txLog::xslt("xslt");
+
+#define TX_LG_CREATE
+
+#endif
diff --git a/dom/xslt/base/txNamespaceMap.cpp b/dom/xslt/base/txNamespaceMap.cpp
new file mode 100644
index 0000000000..284e08a705
--- /dev/null
+++ b/dom/xslt/base/txNamespaceMap.cpp
@@ -0,0 +1,81 @@
+/* -*- 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 "txNamespaceMap.h"
+#include "nsGkAtoms.h"
+#include "txXPathNode.h"
+
+txNamespaceMap::txNamespaceMap() = default;
+
+txNamespaceMap::txNamespaceMap(const txNamespaceMap& aOther)
+ : mPrefixes(aOther.mPrefixes.Clone()),
+ mNamespaces(aOther.mNamespaces.Clone()) {}
+
+nsresult txNamespaceMap::mapNamespace(nsAtom* aPrefix,
+ const nsAString& aNamespaceURI) {
+ nsAtom* prefix = aPrefix == nsGkAtoms::_empty ? nullptr : aPrefix;
+
+ int32_t nsId;
+ if (prefix && aNamespaceURI.IsEmpty()) {
+ // Remove the mapping
+ int32_t index = mPrefixes.IndexOf(prefix);
+ if (index >= 0) {
+ mPrefixes.RemoveElementAt(index);
+ mNamespaces.RemoveElementAt(index);
+ }
+
+ return NS_OK;
+ }
+
+ if (aNamespaceURI.IsEmpty()) {
+ // Set default to empty namespace
+ nsId = kNameSpaceID_None;
+ } else {
+ nsId = txNamespaceManager::getNamespaceID(aNamespaceURI);
+ NS_ENSURE_FALSE(nsId == kNameSpaceID_Unknown, NS_ERROR_FAILURE);
+ }
+
+ // Check if the mapping already exists
+ int32_t index = mPrefixes.IndexOf(prefix);
+ if (index >= 0) {
+ mNamespaces.ElementAt(index) = nsId;
+
+ return NS_OK;
+ }
+
+ // New mapping
+ mPrefixes.AppendElement(prefix);
+ mNamespaces.AppendElement(nsId);
+
+ return NS_OK;
+}
+
+int32_t txNamespaceMap::lookupNamespace(nsAtom* aPrefix) {
+ if (aPrefix == nsGkAtoms::xml) {
+ return kNameSpaceID_XML;
+ }
+
+ nsAtom* prefix = aPrefix == nsGkAtoms::_empty ? 0 : aPrefix;
+
+ int32_t index = mPrefixes.IndexOf(prefix);
+ if (index >= 0) {
+ return mNamespaces.SafeElementAt(index, kNameSpaceID_Unknown);
+ }
+
+ if (!prefix) {
+ return kNameSpaceID_None;
+ }
+
+ return kNameSpaceID_Unknown;
+}
+
+int32_t txNamespaceMap::lookupNamespaceWithDefault(const nsAString& aPrefix) {
+ RefPtr<nsAtom> prefix = NS_Atomize(aPrefix);
+ if (prefix != nsGkAtoms::_poundDefault) {
+ return lookupNamespace(prefix);
+ }
+
+ return lookupNamespace(nullptr);
+}
diff --git a/dom/xslt/base/txNamespaceMap.h b/dom/xslt/base/txNamespaceMap.h
new file mode 100644
index 0000000000..e2be7ebf15
--- /dev/null
+++ b/dom/xslt/base/txNamespaceMap.h
@@ -0,0 +1,38 @@
+/* -*- 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/. */
+
+#ifndef TRANSFRMX_TXNAMESPACEMAP_H
+#define TRANSFRMX_TXNAMESPACEMAP_H
+
+#include "nsAtom.h"
+#include "nsCOMPtr.h"
+#include "nsTArray.h"
+
+class txNamespaceMap {
+ public:
+ txNamespaceMap();
+ txNamespaceMap(const txNamespaceMap& aOther);
+
+ nsrefcnt AddRef() { return ++mRefCnt; }
+ nsrefcnt Release() {
+ if (--mRefCnt == 0) {
+ mRefCnt = 1; // stabilize
+ delete this;
+ return 0;
+ }
+ return mRefCnt;
+ }
+
+ nsresult mapNamespace(nsAtom* aPrefix, const nsAString& aNamespaceURI);
+ int32_t lookupNamespace(nsAtom* aPrefix);
+ int32_t lookupNamespaceWithDefault(const nsAString& aPrefix);
+
+ private:
+ nsAutoRefCnt mRefCnt;
+ nsTArray<RefPtr<nsAtom>> mPrefixes;
+ nsTArray<int32_t> mNamespaces;
+};
+
+#endif // TRANSFRMX_TXNAMESPACEMAP_H
diff --git a/dom/xslt/base/txOwningArray.h b/dom/xslt/base/txOwningArray.h
new file mode 100644
index 0000000000..f25015a597
--- /dev/null
+++ b/dom/xslt/base/txOwningArray.h
@@ -0,0 +1,29 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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/. */
+
+#ifndef txOwningArray_h__
+#define txOwningArray_h__
+
+// Class acting like a nsTArray except that it deletes its objects
+// on destruction. It does not however delete its objects on operations
+// like RemoveElementsAt or on |array[i] = bar|.
+
+template <class E>
+class txOwningArray : public nsTArray<E*> {
+ public:
+ typedef nsTArray<E*> base_type;
+ typedef typename base_type::value_type value_type;
+
+ ~txOwningArray() {
+ value_type* iter = base_type::Elements();
+ value_type* end = iter + base_type::Length();
+ for (; iter < end; ++iter) {
+ delete *iter;
+ }
+ }
+};
+
+#endif // txOwningArray_h__
diff --git a/dom/xslt/base/txStack.h b/dom/xslt/base/txStack.h
new file mode 100644
index 0000000000..9a9269ed69
--- /dev/null
+++ b/dom/xslt/base/txStack.h
@@ -0,0 +1,99 @@
+/* -*- 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/. */
+
+#ifndef txStack_h___
+#define txStack_h___
+
+#include "nsTArray.h"
+
+class txStack : private nsTArray<void*> {
+ public:
+ /**
+ * Returns the specified object from the top of this stack,
+ * without removing it from the stack.
+ *
+ * @return a pointer to the object that is the top of this stack.
+ */
+ inline void* peek() {
+ NS_ASSERTION(!isEmpty(), "peeking at empty stack");
+ return !isEmpty() ? ElementAt(Length() - 1) : nullptr;
+ }
+
+ /**
+ * Adds the specified object to the top of this stack.
+ *
+ * @param obj a pointer to the object that is to be added to the
+ * top of this stack.
+ */
+ inline void push(void* aObject) { AppendElement(aObject); }
+
+ /**
+ * Removes and returns the specified object from the top of this
+ * stack.
+ *
+ * @return a pointer to the object that was the top of this stack.
+ */
+ inline void* pop() {
+ void* object = nullptr;
+ NS_ASSERTION(!isEmpty(), "popping from empty stack");
+ if (!isEmpty()) {
+ object = PopLastElement();
+ }
+ return object;
+ }
+
+ /**
+ * Returns true if there are no objects in the stack.
+ *
+ * @return true if there are no objects in the stack.
+ */
+ inline bool isEmpty() { return IsEmpty(); }
+
+ /**
+ * Returns the number of elements in the Stack.
+ *
+ * @return the number of elements in the Stack.
+ */
+ inline int32_t size() { return Length(); }
+
+ private:
+ friend class txStackIterator;
+};
+
+class txStackIterator {
+ public:
+ /**
+ * Creates an iterator for the given stack.
+ *
+ * @param aStack the stack to create an iterator for.
+ */
+ inline explicit txStackIterator(txStack* aStack)
+ : mStack(aStack), mPosition(0) {}
+
+ /**
+ * Returns true if there is more objects on the stack.
+ *
+ * @return .
+ */
+ inline bool hasNext() { return (mPosition < mStack->Length()); }
+
+ /**
+ * Returns the next object pointer from the stack.
+ *
+ * @return .
+ */
+ inline void* next() {
+ if (mPosition == mStack->Length()) {
+ return nullptr;
+ }
+ return mStack->ElementAt(mPosition++);
+ }
+
+ private:
+ txStack* mStack;
+ uint32_t mPosition;
+};
+
+#endif /* txStack_h___ */
diff --git a/dom/xslt/base/txStringUtils.h b/dom/xslt/base/txStringUtils.h
new file mode 100644
index 0000000000..2064c01720
--- /dev/null
+++ b/dom/xslt/base/txStringUtils.h
@@ -0,0 +1,28 @@
+/* -*- Mode: C++; tab-width: 2; 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/. */
+
+#ifndef txStringUtils_h__
+#define txStringUtils_h__
+
+#include "nsAString.h"
+#include "nsAtom.h"
+#include "nsUnicharUtils.h"
+#include "nsContentUtils.h" // For ASCIIToLower().
+
+/**
+ * Check equality between a string and an atom containing ASCII.
+ */
+inline bool TX_StringEqualsAtom(const nsAString& aString, nsAtom* aAtom) {
+ return aAtom->Equals(aString);
+}
+
+inline already_AddRefed<nsAtom> TX_ToLowerCaseAtom(nsAtom* aAtom) {
+ nsAutoString str;
+ aAtom->ToString(str);
+ nsContentUtils::ASCIIToLower(str);
+ return NS_Atomize(str);
+}
+
+#endif // txStringUtils_h__
diff --git a/dom/xslt/base/txURIUtils.cpp b/dom/xslt/base/txURIUtils.cpp
new file mode 100644
index 0000000000..bbb06221cf
--- /dev/null
+++ b/dom/xslt/base/txURIUtils.cpp
@@ -0,0 +1,87 @@
+/* -*- 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 "txURIUtils.h"
+#include "nsNetUtil.h"
+#include "mozilla/dom/Document.h"
+#include "nsIPrincipal.h"
+#include "mozilla/LoadInfo.h"
+#include "mozilla/dom/nsCSPContext.h"
+
+using mozilla::dom::Document;
+using mozilla::net::LoadInfo;
+
+/**
+ * URIUtils
+ * A set of utilities for handling URIs
+ **/
+
+/**
+ * Resolves the given href argument, using the given documentBase
+ * if necessary.
+ * The new resolved href will be appended to the given dest String
+ **/
+void URIUtils::resolveHref(const nsAString& href, const nsAString& base,
+ nsAString& dest) {
+ if (base.IsEmpty()) {
+ dest.Append(href);
+ return;
+ }
+ if (href.IsEmpty()) {
+ dest.Append(base);
+ return;
+ }
+ nsCOMPtr<nsIURI> pURL;
+ nsAutoString resultHref;
+ nsresult result = NS_NewURI(getter_AddRefs(pURL), base);
+ if (NS_SUCCEEDED(result)) {
+ NS_MakeAbsoluteURI(resultHref, href, pURL);
+ dest.Append(resultHref);
+ }
+} //-- resolveHref
+
+// static
+void URIUtils::ResetWithSource(Document* aNewDoc, nsINode* aSourceNode) {
+ nsCOMPtr<Document> sourceDoc = aSourceNode->OwnerDoc();
+ nsIPrincipal* sourcePrincipal = sourceDoc->NodePrincipal();
+ nsIPrincipal* sourcePartitionedPrincipal = sourceDoc->PartitionedPrincipal();
+
+ // Copy the channel and loadgroup from the source document.
+ nsCOMPtr<nsILoadGroup> loadGroup = sourceDoc->GetDocumentLoadGroup();
+ nsCOMPtr<nsIChannel> channel = sourceDoc->GetChannel();
+ if (!channel) {
+ // Need to synthesize one
+ nsresult rv = NS_NewChannel(
+ getter_AddRefs(channel), sourceDoc->GetDocumentURI(), sourceDoc,
+ nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL, nsIContentPolicy::TYPE_OTHER,
+ nullptr, // aPerformanceStorage
+ loadGroup,
+ nullptr, // aCallbacks
+ nsIChannel::LOAD_BYPASS_SERVICE_WORKER);
+
+ if (NS_FAILED(rv)) {
+ return;
+ }
+ }
+
+ aNewDoc->Reset(channel, loadGroup);
+ aNewDoc->SetPrincipals(sourcePrincipal, sourcePartitionedPrincipal);
+ aNewDoc->SetBaseURI(sourceDoc->GetDocBaseURI());
+ aNewDoc->SetSandboxFlags(sourceDoc->GetSandboxFlags());
+ aNewDoc->SetReferrerInfo(sourceDoc->GetReferrerInfo());
+ aNewDoc->SetEmbedderPolicy(sourceDoc->GetEmbedderPolicy());
+
+ // Inherit the csp if there is one
+ nsCOMPtr<nsIContentSecurityPolicy> csp = sourceDoc->GetCsp();
+ if (csp) {
+ RefPtr<nsCSPContext> cspToInherit = new nsCSPContext();
+ cspToInherit->InitFromOther(static_cast<nsCSPContext*>(csp.get()));
+ aNewDoc->SetCsp(cspToInherit);
+ }
+ // Copy charset
+ aNewDoc->SetDocumentCharacterSetSource(
+ sourceDoc->GetDocumentCharacterSetSource());
+ aNewDoc->SetDocumentCharacterSet(sourceDoc->GetDocumentCharacterSet());
+}
diff --git a/dom/xslt/base/txURIUtils.h b/dom/xslt/base/txURIUtils.h
new file mode 100644
index 0000000000..96082b99f7
--- /dev/null
+++ b/dom/xslt/base/txURIUtils.h
@@ -0,0 +1,40 @@
+/* -*- 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/. */
+
+#ifndef TRANSFRMX_URIUTILS_H
+#define TRANSFRMX_URIUTILS_H
+
+#include "txCore.h"
+
+class nsINode;
+
+namespace mozilla::dom {
+class Document;
+} // namespace mozilla::dom
+
+/**
+ * A utility class for URI handling
+ * Not yet finished, only handles file URI at this point
+ **/
+
+class URIUtils {
+ public:
+ /**
+ * Reset the given document with the document of the source node
+ */
+ static void ResetWithSource(mozilla::dom::Document* aNewDoc,
+ nsINode* aSourceNode);
+
+ /**
+ * Resolves the given href argument, using the given documentBase
+ * if necessary.
+ * The new resolved href will be appended to the given dest String
+ **/
+ static void resolveHref(const nsAString& href, const nsAString& base,
+ nsAString& dest);
+}; //-- URIUtils
+
+/* */
+#endif
diff --git a/dom/xslt/crashtests/1089049.html b/dom/xslt/crashtests/1089049.html
new file mode 100644
index 0000000000..84ef6494c3
--- /dev/null
+++ b/dom/xslt/crashtests/1089049.html
@@ -0,0 +1,3 @@
+<script>
+var xpathResult = document.evaluate('', null, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
+</script>
diff --git a/dom/xslt/crashtests/111994.xml b/dom/xslt/crashtests/111994.xml
new file mode 100644
index 0000000000..ce7ffad2ac
--- /dev/null
+++ b/dom/xslt/crashtests/111994.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" standalone="yes" ?>
+<?xml-stylesheet type="text/xsl" href="111994.xsl" ?>
+<root>
+ <item id="1001" name="name" />
+</root>
diff --git a/dom/xslt/crashtests/111994.xsl b/dom/xslt/crashtests/111994.xsl
new file mode 100644
index 0000000000..4cf68179fa
--- /dev/null
+++ b/dom/xslt/crashtests/111994.xsl
@@ -0,0 +1,13 @@
+<?xml version="1.0" ?>
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
+ <xsl:template match="root">
+ <html><body><xsl:apply-templates select="item" /></body></html>
+ </xsl:template>
+ <xsl:template match="item">
+ <img>
+ <xsl:attribute name="src">
+ <xsl:value-of select="@name" />
+ </xsl:attribute>
+ </img>
+ </xsl:template>
+</xsl:stylesheet>
diff --git a/dom/xslt/crashtests/1205163.xml b/dom/xslt/crashtests/1205163.xml
new file mode 100644
index 0000000000..2d450a1d50
--- /dev/null
+++ b/dom/xslt/crashtests/1205163.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/xsl" href="1205163.xsl"?>
+<result>
+ <Title>Example</Title>
+ <Error>Error</Error>
+</result>
diff --git a/dom/xslt/crashtests/1205163.xsl b/dom/xslt/crashtests/1205163.xsl
new file mode 100644
index 0000000000..f0bbb5cb95
--- /dev/null
+++ b/dom/xslt/crashtests/1205163.xsl
@@ -0,0 +1,11 @@
+<?xml version="1.0"?>
+<xsl:stylesheet version="1.0"
+ xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+ <xsl:output method="text"/>
+ <xsl:template match="node()|@*">
+ <xsl:copy>
+ <xsl:apply-templates select="node()|@*"/>
+ </xsl:copy>
+ </xsl:template>
+ <xsl:template match="Error"/>
+</xsl:stylesheet>
diff --git a/dom/xslt/crashtests/1243337.xml b/dom/xslt/crashtests/1243337.xml
new file mode 100644
index 0000000000..40f5e3a35d
--- /dev/null
+++ b/dom/xslt/crashtests/1243337.xml
@@ -0,0 +1,3 @@
+<?xml version="1.0" standalone="yes" ?>
+<?xml-stylesheet type="text/xsl" href="1243337.xsl" ?>
+<root/>
diff --git a/dom/xslt/crashtests/1243337.xsl b/dom/xslt/crashtests/1243337.xsl
new file mode 100644
index 0000000000..0e659bf900
--- /dev/null
+++ b/dom/xslt/crashtests/1243337.xsl
@@ -0,0 +1,6 @@
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+ <xsl:template match="/">
+ <xsl:value-of select="generate-id(5)"/>
+ <html><body/></html>
+ </xsl:template>
+</xsl:stylesheet>
diff --git a/dom/xslt/crashtests/1330492.html b/dom/xslt/crashtests/1330492.html
new file mode 100644
index 0000000000..a19c646a17
--- /dev/null
+++ b/dom/xslt/crashtests/1330492.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<html>
+<meta charset="UTF-8">
+<script id=o_xml type="text/plain"><?xml version="1.0" encoding="UTF-8"?>
+<ffsy></ffsy>
+</script>
+<script id=o_xslt type="text/plain"><?xml version="1.0" encoding="UTF-8"?>
+<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
+ <xsl:output method="text" />
+</xsl:transform>
+</script>
+<script>
+addEventListener("DOMContentLoaded", function(){
+ let doc = new DOMParser();
+ let xml = doc.parseFromString(o_xml.textContent, "text/xml");
+ let xsltPrs = new XSLTProcessor();
+ let xsl = doc.parseFromString(o_xslt.textContent, "text/xml");
+ xsltPrs.importStylesheet(xsl);
+ xsltPrs.transformToFragment(xml, document);
+ xsltPrs.transformToDocument(xml);
+});
+</script>
+</html>
diff --git a/dom/xslt/crashtests/1336828.html b/dom/xslt/crashtests/1336828.html
new file mode 100644
index 0000000000..3f25e23e03
--- /dev/null
+++ b/dom/xslt/crashtests/1336828.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<script id=o_xml type="text/plain"><?xml version="1.0" encoding="UTF-8"?>
+<tag_name/>
+</script>
+<script id=o_xslt type="text/plain"><?xml version="1.0" encoding="UTF-8"?>
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="42">
+ <xsl:variable name="var_name"><xsl:template/></xsl:variable>
+ <xsl:template match="tag_name[$var_name]"/>
+</xsl:stylesheet>
+</script>
+<script>
+window.onload = function(){
+ let doc = new DOMParser(), proc = new XSLTProcessor();
+ proc.importStylesheet(doc.parseFromString(document.getElementById('o_xslt').textContent, "text/xml"));
+ proc.transformToDocument(doc.parseFromString(document.getElementById('o_xml').textContent, "text/xml"));
+};
+</script>
+</head>
+</html>
diff --git a/dom/xslt/crashtests/1336830.html b/dom/xslt/crashtests/1336830.html
new file mode 100644
index 0000000000..907f589563
--- /dev/null
+++ b/dom/xslt/crashtests/1336830.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<script id=o_xml type="text/plain"><?xml version="1.0" encoding="UTF-8"?>
+<tag_name/>
+</script>
+<script id=o_xslt type="text/plain"><?xml version="1.0" encoding="UTF-8"?>
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="42">
+ <xsl:template match="*">
+ <xsl:apply-imports/>
+ <xsl:apply-templates select=".">
+ <xsl:with-param name="whatever_1">whatever_2</xsl:with-param>
+ </xsl:apply-templates>
+ </xsl:template>
+</xsl:stylesheet>
+</script>
+<script>
+window.onload = function(){
+ setTimeout(function(){ window.close(); }, 400);
+ let doc = new DOMParser(), proc = new XSLTProcessor();
+ proc.importStylesheet(doc.parseFromString(document.getElementById('o_xslt').textContent, "text/xml"));
+ proc.transformToFragment(doc.parseFromString(document.getElementById('o_xml').textContent, "text/xml"), document);
+};
+</script>
+</head>
+</html>
diff --git a/dom/xslt/crashtests/1336832.html b/dom/xslt/crashtests/1336832.html
new file mode 100644
index 0000000000..0fb9033d1a
--- /dev/null
+++ b/dom/xslt/crashtests/1336832.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<script id=o_xml type="text/plain"><?xml version="1.0" encoding="UTF-8"?>
+<tag_name/>
+</script>
+<script id=o_xslt type="text/plain"><?xml version="1.0" encoding="UTF-8"?>
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
+ <xsl:variable name="var_1">
+ <xsl:for-each select="/">
+ <xsl:value-of select="count()"/>
+ </xsl:for-each>
+ </xsl:variable>
+ <xsl:template match="/">
+ <xsl:value-of select="//*[1 = $var_1]"/>
+ </xsl:template>
+</xsl:stylesheet>
+</script>
+<script>
+window.onload = function(){
+ let doc = new DOMParser(), proc = new XSLTProcessor();
+ proc.importStylesheet(doc.parseFromString(document.getElementById('o_xslt').textContent, "text/xml"));
+ proc.transformToDocument(doc.parseFromString(document.getElementById('o_xml').textContent, "text/xml"));
+};
+</script>
+</head>
+</html>
diff --git a/dom/xslt/crashtests/1338277.html b/dom/xslt/crashtests/1338277.html
new file mode 100644
index 0000000000..d928819c75
--- /dev/null
+++ b/dom/xslt/crashtests/1338277.html
@@ -0,0 +1,21 @@
+<script id=o_xml type="text/plain"><?xml version="1.0" encoding="UTF-8"?>
+<xb></xb>
+</script>
+<script id=o_xslt type="text/plain"><?xml version="1.0" encoding="UTF-8"?>
+<xsl:transform version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+ <xsl:template match="xb[$v1]"></xsl:template>
+ <xsl:variable name="v1">
+ <xsl:attribute name="a1" namespace="z">
+ <xsl:variable name="v2" select="$v2"></xsl:variable>
+ </xsl:attribute>
+ </xsl:variable>
+</xsl:transform>
+</script>
+<script>
+addEventListener("DOMContentLoaded", function(){
+ let doc = new DOMParser();
+ let xsltPrs = new XSLTProcessor();
+ xsltPrs.importStylesheet(doc.parseFromString(o_xslt.textContent, "text/xml"));
+ xsltPrs.transformToDocument(doc.parseFromString(o_xml.textContent, "text/xml"));
+});
+</script>
diff --git a/dom/xslt/crashtests/1361892.html b/dom/xslt/crashtests/1361892.html
new file mode 100644
index 0000000000..a7befd2c42
--- /dev/null
+++ b/dom/xslt/crashtests/1361892.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="UTF-8">
+ <script id=o_xml type="text/plain"><?xml version="1.0" encoding="UTF-8"?>
+<tag_name/>
+ </script>
+ <script id=o_xslt type="text/plain"><?xml version="1.0" encoding="UTF-8"?>
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="42">
+ <xsl:template match="*">
+ <xsl:value-of xmlns:regexp="http://exslt.org/regular-expressions" select="regexp:match('foo','bar','-2')"/>
+ </xsl:template>
+</xsl:stylesheet>
+ </script>
+ <script>
+window.onload = function(){
+ let doc = new DOMParser(), proc = new XSLTProcessor();
+ proc.importStylesheet(doc.parseFromString(document.getElementById('o_xslt').textContent, "text/xml"));
+ proc.transformToDocument(doc.parseFromString(document.getElementById('o_xml').textContent, "text/xml"));
+};
+ </script>
+ </head>
+</html>
diff --git a/dom/xslt/crashtests/1430818.sjs b/dom/xslt/crashtests/1430818.sjs
new file mode 100644
index 0000000000..a1c9f1e662
--- /dev/null
+++ b/dom/xslt/crashtests/1430818.sjs
@@ -0,0 +1,49 @@
+function getFileStream(filename)
+{
+ let self = Components.classes["@mozilla.org/file/local;1"]
+ .createInstance(Components.interfaces.nsIFile);
+ self.initWithPath(getState("__LOCATION__"));
+ let file = self.parent;
+ file.append(filename);
+ let stream = Components.classes['@mozilla.org/network/file-input-stream;1']
+ .createInstance(Components.interfaces.nsIFileInputStream);
+ stream.init(file, -1, -1, false);
+ return stream;
+}
+
+function handleRequest(request, response)
+{
+ response.processAsync();
+ response.setStatusLine(null, 200, "OK");
+ response.setHeader("Content-Type", "text/xml", false);
+
+ switch (request.queryString) {
+ case "stylesheet":
+ {
+ let timer = Components.classes["@mozilla.org/timer;1"]
+ .createInstance(Components.interfaces.nsITimer);
+ timer.initWithCallback(() => {
+ setState("xslt", "loaded");
+ response.finish();
+ timer.cancel();
+ }, 1000 /* milliseconds */, Components.interfaces.nsITimer.TYPE_REPEATING_SLACK);
+ break;
+ }
+ default:
+ {
+ let stream = getFileStream("1430818.xml");
+ response.bodyOutputStream.writeFrom(stream,
+ stream.available());
+ stream.close();
+ let timer = Components.classes["@mozilla.org/timer;1"]
+ .createInstance(Components.interfaces.nsITimer);
+ timer.initWithCallback(() => {
+ if (getState("xslt") == "loaded") {
+ response.finish();
+ timer.cancel();
+ }
+ }, 100 /* milliseconds */, Components.interfaces.nsITimer.TYPE_REPEATING_SLACK);
+ break;
+ }
+ }
+}
diff --git a/dom/xslt/crashtests/1430818.xml b/dom/xslt/crashtests/1430818.xml
new file mode 100644
index 0000000000..95731bcb0b
--- /dev/null
+++ b/dom/xslt/crashtests/1430818.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/xsl" href="?stylesheet"?>
+<root>
+</root>
diff --git a/dom/xslt/crashtests/1527277.html b/dom/xslt/crashtests/1527277.html
new file mode 100644
index 0000000000..ef437803c9
--- /dev/null
+++ b/dom/xslt/crashtests/1527277.html
@@ -0,0 +1,14 @@
+<html>
+<script>
+var xml = "<e/>";
+var xsl = "<xsl:stylesheet version=\"1.0\" xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\"><xsl:decimal-format grouping-separator=\".\" /><xsl:template match=\"/\"><xsl:value-of select=\"format-number(0.1, '.#')\"/></xsl:template></xsl:stylesheet>";
+
+var parser = new DOMParser();
+var xmlDoc = parser.parseFromString(xml, "text/xml");
+var xslDoc = parser.parseFromString(xsl, "text/xml");
+
+xsltProcessor = new XSLTProcessor();
+xsltProcessor.importStylesheet(xslDoc);
+var result = xsltProcessor.transformToDocument(xmlDoc);
+</script>
+</html>
diff --git a/dom/xslt/crashtests/1589930.xml b/dom/xslt/crashtests/1589930.xml
new file mode 100644
index 0000000000..74d4cc8463
--- /dev/null
+++ b/dom/xslt/crashtests/1589930.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/xsl" href="#stylesheet"?>
+<!DOCTYPE root [
+ <!ATTLIST xsl:stylesheet id ID #IMPLIED>
+]>
+<root>
+<xsl:stylesheet id="stylesheet" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
+ <xsl:template match="/"><xsl:value-of select="format-number(99.98, '0.0')"/></xsl:template>
+</xsl:stylesheet>
+</root>
diff --git a/dom/xslt/crashtests/182460-select.xml b/dom/xslt/crashtests/182460-select.xml
new file mode 100644
index 0000000000..bb9f79b040
--- /dev/null
+++ b/dom/xslt/crashtests/182460-select.xml
@@ -0,0 +1,3 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<?xml-stylesheet type="text/xsl" href="182460-selects.xsl"?>
+<fud/> \ No newline at end of file
diff --git a/dom/xslt/crashtests/182460-selects.xsl b/dom/xslt/crashtests/182460-selects.xsl
new file mode 100644
index 0000000000..f083ea31fe
--- /dev/null
+++ b/dom/xslt/crashtests/182460-selects.xsl
@@ -0,0 +1,5 @@
+<html>
+<body>
+hi
+</body>
+</html>
diff --git a/dom/xslt/crashtests/182460-table.xhtml b/dom/xslt/crashtests/182460-table.xhtml
new file mode 100644
index 0000000000..1bf2a23cb4
--- /dev/null
+++ b/dom/xslt/crashtests/182460-table.xhtml
@@ -0,0 +1,9 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<title>XSL Crash</title>
+</head>
+<body>
+<iframe src="182460-select.xml"></iframe>
+<script type="text/javascript" src="data:text/javascript,/*no code*/"/>
+</body>
+</html>
diff --git a/dom/xslt/crashtests/226425.xml b/dom/xslt/crashtests/226425.xml
new file mode 100644
index 0000000000..c6662cfe2c
--- /dev/null
+++ b/dom/xslt/crashtests/226425.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="iso-8859-1"?>
+<?xml-stylesheet type="text/xsl" href="226425.xsl" ?>
+
+<text></text>
diff --git a/dom/xslt/crashtests/226425.xsl b/dom/xslt/crashtests/226425.xsl
new file mode 100644
index 0000000000..ddb61ae680
--- /dev/null
+++ b/dom/xslt/crashtests/226425.xsl
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="iso-8859-1"?>
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+<xsl:output method="html" indent="yes"/>
+
+<xsl:template match="/">
+<html><body>
+<xsl:apply-templates select="document('dontmatter')"/>
+</body></html>
+</xsl:template>
+</xsl:stylesheet>
diff --git a/dom/xslt/crashtests/406106-1.html b/dom/xslt/crashtests/406106-1.html
new file mode 100644
index 0000000000..86ab510ff8
--- /dev/null
+++ b/dom/xslt/crashtests/406106-1.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script type="text/javascript">
+
+function boom()
+{
+ var p = new XSLTProcessor();
+ p.setParameter("a", "b", {e: p});
+}
+
+boom();
+
+</script>
+</head>
+<body>
+</body>
+</html>
diff --git a/dom/xslt/crashtests/483444.xml b/dom/xslt/crashtests/483444.xml
new file mode 100644
index 0000000000..18fffceba5
--- /dev/null
+++ b/dom/xslt/crashtests/483444.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/xsl" href="#stylesheet"?>
+<!DOCTYPE root [
+ <!ATTLIST xsl:stylesheet id ID #IMPLIED>
+]>
+<root>
+<xsl:stylesheet id="stylesheet" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
+ <xsl:attribute-set name="a">
+ <xsl:attribute name="b">c</xsl:attribute>
+ </xsl:attribute-set>
+ <xsl:template match="/">
+ <b>Please output something!</b>
+ </xsl:template>
+ <xsl:attribute-set name="a">
+ <xsl:attribute name="b">c</xsl:attribute>
+ </xsl:attribute-set>
+</xsl:stylesheet>
+<doc id="foo">this</doc>
+</root>
diff --git a/dom/xslt/crashtests/485217.xml b/dom/xslt/crashtests/485217.xml
new file mode 100644
index 0000000000..93a20e4f77
--- /dev/null
+++ b/dom/xslt/crashtests/485217.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<?xml-stylesheet type="text/xsl" href="485217.xsl"?>
+
+
+<root xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <item1 id="AAAAAAA" />
+ <item2 id="AAAAAAAAA" label="AAAAAAAAAAAA"/>
+</root>
diff --git a/dom/xslt/crashtests/485217.xsl b/dom/xslt/crashtests/485217.xsl
new file mode 100644
index 0000000000..2935c5a41b
--- /dev/null
+++ b/dom/xslt/crashtests/485217.xsl
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+
+ <xsl:key name="label" match="item2" use="undeclaredfunc()"/>
+
+ <xsl:template match="root">
+ <xsl:for-each select="//item1">
+ <xsl:call-template name="item1" />
+ </xsl:for-each>
+ </xsl:template>
+
+ <xsl:template name="item1">
+ <xsl:for-each select="key('label', 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA')">
+ </xsl:for-each>
+ </xsl:template>
+
+</xsl:stylesheet>
diff --git a/dom/xslt/crashtests/485286.xml b/dom/xslt/crashtests/485286.xml
new file mode 100644
index 0000000000..c87aa940ef
--- /dev/null
+++ b/dom/xslt/crashtests/485286.xml
@@ -0,0 +1,12 @@
+<?xml-stylesheet type="application/xml" href="485286.xml"?>
+<transform xmlns="http://www.w3.org/1999/XSL/Transform" version="1.0">
+ <variable name="v">
+ <for-each select="/">
+ <value-of select="count(1)"/>
+ </for-each>
+ </variable>
+ <key name="k" match="/" use="$v"/>
+ <template match="/">
+ <value-of select="key('k', /..)"/>
+ </template>
+</transform>
diff --git a/dom/xslt/crashtests/527558_1.xml b/dom/xslt/crashtests/527558_1.xml
new file mode 100644
index 0000000000..ebb6c3d533
--- /dev/null
+++ b/dom/xslt/crashtests/527558_1.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/xml" href="#bug"?>
+<!DOCTYPE doc [
+<!ATTLIST xsl:transform
+ id ID #REQUIRED>
+]>
+<doc>
+<xsl:transform id="bug"
+ version="2.0"
+ xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+ <xsl:key name="k0" match="e1" use="key('k0', 'foobar')" />
+ <xsl:template id="t1" name="t1" match="key('k0', '1/2/2003')" />
+</xsl:transform>
+
+<e1 a1="foobar" a2="foobar"/>
+
+</doc>
diff --git a/dom/xslt/crashtests/528300.xml b/dom/xslt/crashtests/528300.xml
new file mode 100644
index 0000000000..8902bb373e
--- /dev/null
+++ b/dom/xslt/crashtests/528300.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/xml" href="#bug"?>
+<!DOCTYPE doc [
+<!ATTLIST xsl:transform
+ id ID #REQUIRED>
+]>
+<doc>
+<xsl:transform
+ xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ version="2.0"
+ id="bug">
+ <xsl:variable name="v0">
+ <xsl:for-each select="$v0" />
+ </xsl:variable>
+ <xsl:template name="t2" match="/">
+ <xsl:copy-of select="number($v0)" />
+ </xsl:template>
+</xsl:transform>
+
+<e1 />
+
+</doc>
diff --git a/dom/xslt/crashtests/528488.xml b/dom/xslt/crashtests/528488.xml
new file mode 100644
index 0000000000..904b345612
--- /dev/null
+++ b/dom/xslt/crashtests/528488.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/xml" href="#bug"?>
+<!DOCTYPE doc [
+<!ATTLIST xsl:transform
+ id ID #REQUIRED>
+]>
+<doc>
+ <xsl:transform
+ xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ xmlns:exslstrings="http://exslt.org/strings"
+ version="2.0"
+ id="bug">
+ <xsl:variable name="v0" select="$v0" />
+ <xsl:template name="t2" match="/">
+ <xsl:param name="p0" select="exslstrings:tokenize('1234','foobar')" />
+ <xsl:copy-of select="round($v0)" />
+ </xsl:template>
+ </xsl:transform>
+</doc>
diff --git a/dom/xslt/crashtests/528963.xml b/dom/xslt/crashtests/528963.xml
new file mode 100644
index 0000000000..d855e75634
--- /dev/null
+++ b/dom/xslt/crashtests/528963.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/xml" href="#bug"?>
+<!DOCTYPE doc [
+<!ATTLIST xsl:transform
+ id ID #REQUIRED>
+]>
+<doc>
+ <xsl:transform
+ xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ version="2.0"
+ id="bug">
+ <xsl:key name="k0" match="e2" use="name('foo')" />
+
+ <xsl:template name="t1" match="/">
+ <xsl:element name="e2" namespace="{//doc}" />
+ </xsl:template>
+ <xsl:template name="t2" match="key('k0', 'bar')" />
+ </xsl:transform>
+
+ <e2/>
+
+</doc>
diff --git a/dom/xslt/crashtests/545927.html b/dom/xslt/crashtests/545927.html
new file mode 100644
index 0000000000..0a9010e21e
--- /dev/null
+++ b/dom/xslt/crashtests/545927.html
@@ -0,0 +1,28 @@
+<html>
+<head>
+<script>
+function main()
+{
+ xml=document.implementation.createDocument('', '', null);
+ xml.appendChild(doc=xml.createElement('root'));
+
+ var p = new DOMParser();
+ text = '<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">';
+ text += '<xsl:template match="/">';
+ text += '<body>';
+ text += '<xsl:number value="2147483648" format="i"/>';
+ text += '</body>';
+ text += '</xsl:template>';
+ text += '</xsl:stylesheet>';
+ xsl=p.parseFromString(text, 'text/xml');
+
+ xsltProcessor=new XSLTProcessor();
+ xsltProcessor.importStylesheet(xsl);
+ d = xsltProcessor.transformToFragment(xml, document);
+}
+</script>
+</head>
+<body onload="main()">
+</body>
+</html>
+
diff --git a/dom/xslt/crashtests/601543.html b/dom/xslt/crashtests/601543.html
new file mode 100644
index 0000000000..af025b242a
--- /dev/null
+++ b/dom/xslt/crashtests/601543.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<script>
+(new XSLTProcessor).setParameter('', '', [{}, null]);
+</script>
diff --git a/dom/xslt/crashtests/602115.html b/dom/xslt/crashtests/602115.html
new file mode 100644
index 0000000000..ad42d76a05
--- /dev/null
+++ b/dom/xslt/crashtests/602115.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<script>
+
+try {
+ var docType = document.implementation.createDocumentType(undefined, '', '');
+ var doc = document.implementation.createDocument('', '', null);
+ var xp = new XSLTProcessor;
+ xp.importStylesheet(doc);
+ xp.transformToDocument(docType);
+}
+catch (ex) {}
+
+try {
+ docType = document.implementation.createDocumentType(undefined, '', '');
+ doc = document.implementation.createDocument('', '', null);
+ xp = new XSLTProcessor;
+ xp.importStylesheet(doc);
+ xp.transformToFragment(docType, document);
+}
+catch (ex) {}
+
+</script>
diff --git a/dom/xslt/crashtests/603844.html b/dom/xslt/crashtests/603844.html
new file mode 100644
index 0000000000..f576effdb6
--- /dev/null
+++ b/dom/xslt/crashtests/603844.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<head>
+<script>
+
+function boom()
+{
+ var frame = document.createElementNS("http://www.w3.org/1999/xhtml", "iframe");
+ frame.onload = y;
+ frame.src = "data:text/plain,0";
+ document.body.appendChild(frame);
+ frameDoc = frame.contentDocument;
+
+ function y()
+ {
+ frameDoc.removeChild(frameDoc.documentElement);
+
+ var xp = new XSLTProcessor;
+ xp.importStylesheet(frameDoc);
+ try {
+ xp.transformToDocument(frameDoc.createTextNode('x'));
+ } catch(e) { }
+
+ document.documentElement.removeAttribute("class");
+ }
+}
+
+</script>
+</head>
+
+<body onload="boom();"></body>
+</html>
diff --git a/dom/xslt/crashtests/667315.xml b/dom/xslt/crashtests/667315.xml
new file mode 100644
index 0000000000..7a560ba3bd
--- /dev/null
+++ b/dom/xslt/crashtests/667315.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/xsl" href="#stylesheet"?>
+<!DOCTYPE root [
+ <!ATTLIST xsl:stylesheet id ID #IMPLIED>
+]>
+<root>
+<xsl:stylesheet id="stylesheet" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
+ <xsl:variable name="var"><p>a</p></xsl:variable>
+ <xsl:template match="/"><xsl:copy-of select="$var" /></xsl:template>
+</xsl:stylesheet>
+</root>
diff --git a/dom/xslt/crashtests/91332.xml b/dom/xslt/crashtests/91332.xml
new file mode 100644
index 0000000000..c5a463c8a9
--- /dev/null
+++ b/dom/xslt/crashtests/91332.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" standalone="yes" ?>
+<?xml-stylesheet type="text/xsl" href="91332.xsl" ?>
+<root>
+ <category name="Rectangles">
+ <list item="square" />
+ </category>
+ <quad id="square">
+ <desc>A square is ...</desc>
+ </quad>
+</root>
diff --git a/dom/xslt/crashtests/91332.xsl b/dom/xslt/crashtests/91332.xsl
new file mode 100644
index 0000000000..7d01df22b6
--- /dev/null
+++ b/dom/xslt/crashtests/91332.xsl
@@ -0,0 +1,21 @@
+<?xml version="1.0" ?>
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
+
+ <xsl:key name="polyList" match="quad" use="@id" />
+
+ <xsl:template match="root">
+ <html><body><xsl:apply-templates select="category" /></body></html>
+ </xsl:template>
+
+ <xsl:template match="category">
+ <table><xsl:apply-templates select="list" /></table>
+ </xsl:template>
+
+ <xsl:template match="list">
+ <tr><td><xsl:apply-templates select="key('polyList',@item)" /></td></tr>
+ </xsl:template>
+
+ <xsl:template match="quad">
+ <b>Please output something!</b>
+ </xsl:template>
+</xsl:stylesheet>
diff --git a/dom/xslt/crashtests/949990.html b/dom/xslt/crashtests/949990.html
new file mode 100644
index 0000000000..23868e4db3
--- /dev/null
+++ b/dom/xslt/crashtests/949990.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<script>
+
+function boom() {
+ document.evaluate("a", document.documentElement, null, 4096, null);
+}
+
+</script>
+</head>
+
+<body onload="boom()"></body>
+</html>
diff --git a/dom/xslt/crashtests/crashtests.list b/dom/xslt/crashtests/crashtests.list
new file mode 100644
index 0000000000..711f357862
--- /dev/null
+++ b/dom/xslt/crashtests/crashtests.list
@@ -0,0 +1,30 @@
+load 91332.xml
+load 111994.xml
+load 182460-table.xhtml
+load 226425.xml
+load 406106-1.html
+load 483444.xml
+load 485217.xml
+load 485286.xml
+load 527558_1.xml
+load 528300.xml
+load 528488.xml
+load 528963.xml
+load 545927.html
+load 601543.html
+load 602115.html
+load 603844.html
+load 667315.xml
+load 949990.html
+load 1089049.html
+load 1205163.xml
+load 1243337.xml
+load 1330492.html
+load 1336828.html
+load 1336830.html
+load 1336832.html
+load 1338277.html
+load 1361892.html
+load 1527277.html
+load 1589930.xml
+HTTP load 1430818.sjs
diff --git a/dom/xslt/moz.build b/dom/xslt/moz.build
new file mode 100644
index 0000000000..1e94d70347
--- /dev/null
+++ b/dom/xslt/moz.build
@@ -0,0 +1,23 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+with Files("**"):
+ BUG_COMPONENT = ("Core", "XSLT")
+
+EXPORTS += [
+ "nsIDocumentTransformer.h",
+]
+
+DIRS += [
+ "base",
+ "xml",
+ "xpath",
+ "xslt",
+]
+
+if CONFIG["ENABLE_TESTS"]:
+ MOCHITEST_MANIFESTS += ["tests/mochitest/mochitest.toml"]
+ BROWSER_CHROME_MANIFESTS += ["tests/browser/browser.toml"]
diff --git a/dom/xslt/nsIDocumentTransformer.h b/dom/xslt/nsIDocumentTransformer.h
new file mode 100644
index 0000000000..3df790dae0
--- /dev/null
+++ b/dom/xslt/nsIDocumentTransformer.h
@@ -0,0 +1,74 @@
+/* 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/. */
+
+#ifndef nsIDocumentTransformer_h__
+#define nsIDocumentTransformer_h__
+
+#include "nsISupports.h"
+#include "nsStringFwd.h"
+
+template <class>
+class nsCOMPtr;
+class nsIContent;
+class nsINode;
+class nsIURI;
+template <class>
+class nsTArray;
+
+namespace mozilla {
+namespace dom {
+class Document;
+}
+} // namespace mozilla
+
+#define NS_ITRANSFORMOBSERVER_IID \
+ { \
+ 0x04b2d17c, 0xe98d, 0x45f5, { \
+ 0x9a, 0x67, 0xb7, 0x01, 0x19, 0x59, 0x7d, 0xe7 \
+ } \
+ }
+
+class nsITransformObserver : public nsISupports {
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_ITRANSFORMOBSERVER_IID)
+
+ virtual nsresult OnDocumentCreated(
+ mozilla::dom::Document* aSourceDocument,
+ mozilla::dom::Document* aResultDocument) = 0;
+
+ virtual nsresult OnTransformDone(mozilla::dom::Document* aSourceDocument,
+ nsresult aResult,
+ mozilla::dom::Document* aResultDocument) = 0;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsITransformObserver, NS_ITRANSFORMOBSERVER_IID)
+
+#define NS_IDOCUMENTTRANSFORMER_IID \
+ { \
+ 0xf45e1ff8, 0x50f3, 0x4496, { \
+ 0xb3, 0xa2, 0x0e, 0x03, 0xe8, 0x4a, 0x57, 0x11 \
+ } \
+ }
+
+class nsIDocumentTransformer : public nsISupports {
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_IDOCUMENTTRANSFORMER_IID)
+
+ NS_IMETHOD SetTransformObserver(nsITransformObserver* aObserver) = 0;
+ NS_IMETHOD LoadStyleSheet(nsIURI* aUri,
+ mozilla::dom::Document* aLoaderDocument) = 0;
+ NS_IMETHOD SetSourceContentModel(nsINode* aSource) = 0;
+ NS_IMETHOD CancelLoads() = 0;
+
+ NS_IMETHOD AddXSLTParamNamespace(const nsString& aPrefix,
+ const nsString& aNamespace) = 0;
+ NS_IMETHOD AddXSLTParam(const nsString& aName, const nsString& aNamespace,
+ const nsString& aValue, const nsString& aSelect,
+ nsINode* aContextNode) = 0;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsIDocumentTransformer,
+ NS_IDOCUMENTTRANSFORMER_IID)
+
+#endif // nsIDocumentTransformer_h__
diff --git a/dom/xslt/tests/browser/browser.toml b/dom/xslt/tests/browser/browser.toml
new file mode 100644
index 0000000000..03962f02b5
--- /dev/null
+++ b/dom/xslt/tests/browser/browser.toml
@@ -0,0 +1,8 @@
+[DEFAULT]
+
+["browser_bug1309630.js"]
+skip-if = ["true"] # Bug 1776052
+support-files = [
+ "bug1309630.sjs",
+ "file_bug1309630.html",
+]
diff --git a/dom/xslt/tests/browser/browser_bug1309630.js b/dom/xslt/tests/browser/browser_bug1309630.js
new file mode 100644
index 0000000000..0056870fd9
--- /dev/null
+++ b/dom/xslt/tests/browser/browser_bug1309630.js
@@ -0,0 +1,74 @@
+"use strict";
+
+const BASE = "https://example.com/browser/dom/xslt/tests/browser";
+const SERVER_SCRIPT = `${BASE}/bug1309630.sjs`;
+
+function resetCounter() {
+ return fetch(`${SERVER_SCRIPT}?reset_counter`);
+}
+function recordCounter() {
+ return fetch(`${SERVER_SCRIPT}?record_counter`);
+}
+// Returns a promise that resolves to true if the counter in
+// bug1309630.sjs changed by more than 'value' since last calling
+// recordCounter(), or false if it doesn't and we time out.
+function waitForCounterChangeAbove(value) {
+ return TestUtils.waitForCondition(() =>
+ fetch(`${SERVER_SCRIPT}?get_counter_change`).then(response =>
+ response.ok
+ ? response.text().then(str => Number(str) > value)
+ : Promise.reject()
+ )
+ ).then(
+ () => true,
+ () => false
+ );
+}
+
+add_task(async function test_eternal_xslt() {
+ await resetCounter();
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: SERVER_SCRIPT, waitForLoad: false },
+ async function (browser) {
+ info("Waiting for XSLT to keep loading");
+
+ ok(
+ await waitForCounterChangeAbove(1),
+ "We should receive at least a request from the document function call."
+ );
+
+ info("Navigating to about:blank");
+ BrowserTestUtils.startLoadingURIString(browser, "about:blank");
+ await BrowserTestUtils.browserLoaded(browser);
+
+ info("Check to see if XSLT stops loading");
+ await recordCounter();
+ ok(
+ !(await waitForCounterChangeAbove(0)),
+ "We shouldn't receive more requests to the XSLT file within the timeout period."
+ );
+ }
+ );
+
+ await resetCounter();
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: `${BASE}/file_bug1309630.html` },
+ async function (browser) {
+ ok(
+ await waitForCounterChangeAbove(1),
+ "We should receive at least a request from the document function call."
+ );
+
+ info("Navigating to about:blank");
+ BrowserTestUtils.startLoadingURIString(browser, "about:blank");
+ await BrowserTestUtils.browserLoaded(browser);
+
+ info("Check to see if XSLT stops loading");
+ await recordCounter();
+ ok(
+ !(await waitForCounterChangeAbove(0)),
+ "We shouldn't receive more requests to the XSLT file within the timeout period."
+ );
+ }
+ );
+});
diff --git a/dom/xslt/tests/browser/bug1309630.sjs b/dom/xslt/tests/browser/bug1309630.sjs
new file mode 100644
index 0000000000..9a3f464139
--- /dev/null
+++ b/dom/xslt/tests/browser/bug1309630.sjs
@@ -0,0 +1,54 @@
+function handleRequest(request, response) {
+ const XSLT = `<?xml version="1.0"?>
+ <?xml-stylesheet type="text/xsl" href="#bug"?>
+ <xsl:stylesheet id="bug" version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+ <xsl:template name="f" match="/">
+ <xsl:param name="n" select="0" />
+ <xsl:apply-templates select="document(concat('${request.path}?', $n))/xsl:stylesheet/xsl:template[@name='f']">
+ <xsl:with-param name="n" select="$n + 1"/>
+ </xsl:apply-templates>
+ <xsl:call-template name="f">
+ <xsl:with-param name="n" select="$n + 1"/>
+ </xsl:call-template>
+ </xsl:template>
+ </xsl:stylesheet>`;
+
+ // reset_counter sets the counter to -1.
+ if (request.queryString === "reset_counter") {
+ setState("base", "-1");
+ response.write("");
+ return;
+ }
+
+ // record_counter makes us store the current value of the counter.
+ if (request.queryString === "record_counter") {
+ setState("base", getState("counter"));
+ response.write("");
+ return;
+ }
+
+ // get_counter_change returns the difference between the current
+ // value of the counter and the value it had when the script was
+ // loaded with the record_counter query string.
+ if (request.queryString === "get_counter_change") {
+ response.write(
+ String(Number(getState("counter")) - Number(getState("base")))
+ );
+ return;
+ }
+
+ // The XSLT code calls the document() function with a URL pointing to
+ // this script, with the query string set to a counter starting from 0
+ // and incremementing with every call of the document() function.
+ // The first load will happen either from the xml-stylesheet PI, or
+ // with fetch(), to parse a document to pass to
+ // XSLTProcessor.importStylesheet. In that case the query string will
+ // be empty, and we don't change the counter value, we only care about
+ // the loads through the document() function.
+ if (request.queryString) {
+ setState("counter", request.queryString);
+ }
+
+ response.setHeader("Content-Type", "text/xml; charset=utf-8", false);
+ response.write(XSLT);
+}
diff --git a/dom/xslt/tests/browser/file_bug1309630.html b/dom/xslt/tests/browser/file_bug1309630.html
new file mode 100644
index 0000000000..c22889358c
--- /dev/null
+++ b/dom/xslt/tests/browser/file_bug1309630.html
@@ -0,0 +1,19 @@
+<script>
+ let styleDoc = fetch("bug1309630.sjs")
+ .then(response => {
+ if (response.ok) {
+ return response.text();
+ }
+ return Promise.reject();
+ }).then(xslt => {
+ let styleDoc = new DOMParser().parseFromString(xslt, "text/xml");
+ let originalDoc = new DOMParser().parseFromString(
+ "<root/>",
+ "text/xml"
+ );
+
+ let processor = new XSLTProcessor();
+ processor.importStylesheet(styleDoc);
+ processor.transformToDocument(originalDoc);
+ });
+</script>>
diff --git a/dom/xslt/tests/mochitest/bug1729517_2.sjs b/dom/xslt/tests/mochitest/bug1729517_2.sjs
new file mode 100644
index 0000000000..2537a971b0
--- /dev/null
+++ b/dom/xslt/tests/mochitest/bug1729517_2.sjs
@@ -0,0 +1,3 @@
+function handleRequest(request, response) {
+ response.write(request.hasHeader("Referer") ? "FAIL" : "PASS");
+}
diff --git a/dom/xslt/tests/mochitest/file_bug1135764.xml b/dom/xslt/tests/mochitest/file_bug1135764.xml
new file mode 100644
index 0000000000..b9da87e5e5
--- /dev/null
+++ b/dom/xslt/tests/mochitest/file_bug1135764.xml
@@ -0,0 +1,3 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<?xml-stylesheet type="text/xsl" href="file_bug1135764.xsl"?>
+<root/>
diff --git a/dom/xslt/tests/mochitest/file_bug1135764.xsl b/dom/xslt/tests/mochitest/file_bug1135764.xsl
new file mode 100644
index 0000000000..e739086cbe
--- /dev/null
+++ b/dom/xslt/tests/mochitest/file_bug1135764.xsl
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+
+<xsl:output method="html"
+ indent="yes"
+ version="5.0"
+ doctype-system="about:legacy-compat"/>
+
+<xsl:template match="/">
+<html>
+<head>
+</head>
+ <body>
+ Some text
+ </body>
+</html>
+</xsl:template>
+
+</xsl:stylesheet>
diff --git a/dom/xslt/tests/mochitest/file_bug1729517.js b/dom/xslt/tests/mochitest/file_bug1729517.js
new file mode 100644
index 0000000000..b404dc6778
--- /dev/null
+++ b/dom/xslt/tests/mochitest/file_bug1729517.js
@@ -0,0 +1,4 @@
+/* global fail */
+fail(
+ "documents sandboxed without allow-scripts should NOT be able to run <script src=...>"
+);
diff --git a/dom/xslt/tests/mochitest/file_bug1729517.xml b/dom/xslt/tests/mochitest/file_bug1729517.xml
new file mode 100644
index 0000000000..48658468fc
--- /dev/null
+++ b/dom/xslt/tests/mochitest/file_bug1729517.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/xsl" href="#stylesheet"?>
+<!DOCTYPE root [
+ <!ATTLIST xsl:stylesheet id ID #IMPLIED>
+]>
+<root>
+ <xsl:stylesheet id="stylesheet" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
+ <xsl:template match="/">
+ <html>
+ <head>
+ <title>[]</title>
+ <script type="text/javascript">
+ let failed = [];
+ function fail(desc) {
+ failed.push(desc);
+ document.title = JSON.stringify(failed);
+ }
+
+ function doStuff() {
+ fail("documents sandboxed without allow-scripts should NOT be able to run inline scripts");
+ }
+ </script>
+ <script src="file_bug1729517.js" />
+ </head>
+ <body onload="fail('documents sandboxed without allow-scripts should NOT be able to run script from event handlers'); doStuff();">
+ <img src="about:blank" onerror="fail('documents sandboxed without allow-scripts should NOT be able to run script from event handlers');" />
+ </body>
+ </html>
+ </xsl:template>
+</xsl:stylesheet>
+</root>
diff --git a/dom/xslt/tests/mochitest/file_bug1729517_2.xml b/dom/xslt/tests/mochitest/file_bug1729517_2.xml
new file mode 100644
index 0000000000..5d706e7a05
--- /dev/null
+++ b/dom/xslt/tests/mochitest/file_bug1729517_2.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/xsl" href="#stylesheet"?>
+<!DOCTYPE root [
+ <!ATTLIST xsl:stylesheet id ID #IMPLIED>
+]>
+<root>
+ <xsl:stylesheet id="stylesheet" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
+ <xsl:template match="/">
+ <html>
+ <head>
+ <title>[]</title>
+ <script type="text/javascript">
+ async function checkCOEPAndReferrer() {
+ let results = new Map();
+
+ let win = window.open();
+ result = win.fetch("https://example.org/tests/dom/xslt/tests/mochitest/bug1729517_2.sjs", { mode: "no-cors" }).then(() => {
+ return "FAIL";
+ }, () => {
+ return "PASS";
+ });
+ results.set("Cross-Origin-Embedder-Policy", await result);
+ win.close();
+
+ result = fetch("bug1729517_2.sjs").then((response) => {
+ return response.text();
+ });
+ results.set("Referrer-Policy", await result || "FAIL");
+
+ window.opener.postMessage(results, "*");
+ }
+ </script>
+ </head>
+ <body onload="checkCOEPAndReferrer()" />
+ </html>
+ </xsl:template>
+</xsl:stylesheet>
+</root>
diff --git a/dom/xslt/tests/mochitest/file_bug1729517_2.xml^headers^ b/dom/xslt/tests/mochitest/file_bug1729517_2.xml^headers^
new file mode 100644
index 0000000000..6c83b8d686
--- /dev/null
+++ b/dom/xslt/tests/mochitest/file_bug1729517_2.xml^headers^
@@ -0,0 +1,2 @@
+Referrer-Policy: no-referrer
+Cross-Origin-Embedder-Policy: require-corp
diff --git a/dom/xslt/tests/mochitest/file_metaRefresh.xml b/dom/xslt/tests/mochitest/file_metaRefresh.xml
new file mode 100644
index 0000000000..b9e127b8a5
--- /dev/null
+++ b/dom/xslt/tests/mochitest/file_metaRefresh.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/xsl" href="#stylesheet"?>
+<!DOCTYPE root [
+ <!ATTLIST xsl:stylesheet id ID #IMPLIED>
+]>
+<root>
+ <xsl:stylesheet id="stylesheet" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
+ <xsl:template match="/">
+ <html>
+ <head>
+ <meta http-equiv="refresh" content="2"></meta>
+ </head>
+ </html>
+ </xsl:template>
+</xsl:stylesheet>
+</root>
diff --git a/dom/xslt/tests/mochitest/mochitest.toml b/dom/xslt/tests/mochitest/mochitest.toml
new file mode 100644
index 0000000000..70d313758c
--- /dev/null
+++ b/dom/xslt/tests/mochitest/mochitest.toml
@@ -0,0 +1,57 @@
+[DEFAULT]
+
+["test_bug319374.html"]
+
+["test_bug427060.html"]
+
+["test_bug440974.html"]
+
+["test_bug453441.html"]
+
+["test_bug468208.html"]
+
+["test_bug511487.html"]
+
+["test_bug551412.html"]
+
+["test_bug551654.html"]
+
+["test_bug566629.html"]
+
+["test_bug566629.xhtml"]
+
+["test_bug603159.html"]
+
+["test_bug616774.html"]
+
+["test_bug667315.html"]
+
+["test_bug1072116.html"]
+
+["test_bug1135764.html"]
+support-files = [
+ "file_bug1135764.xml",
+ "file_bug1135764.xsl",
+]
+
+["test_bug1436040.html"]
+
+["test_bug1527308.html"]
+
+["test_bug1729517.html"]
+support-files = [
+ "bug1729517_2.sjs",
+ "file_bug1729517.xml",
+ "file_bug1729517.js",
+ "file_bug1729517_2.xml",
+ "file_bug1729517_2.xml^headers^",
+]
+
+["test_exslt.html"]
+
+["test_metaRefresh.html"]
+support-files = ["file_metaRefresh.xml"]
+
+["test_parameter.html"]
+
+["test_sorting_invalid_lang.html"]
diff --git a/dom/xslt/tests/mochitest/test_bug1072116.html b/dom/xslt/tests/mochitest/test_bug1072116.html
new file mode 100644
index 0000000000..d69b5812b2
--- /dev/null
+++ b/dom/xslt/tests/mochitest/test_bug1072116.html
@@ -0,0 +1,37 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1072116
+-->
+<head>
+ <title>Test for Bug 1072116</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1072116">Mozilla Bug 1072116</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 1072116 **/
+var attr = document.createAttribute("c");
+
+var xpathExpr = document.createExpression('a', null);
+
+let isAlive = false;
+try {
+ xpathExpr.evaluate(attr, null, null, null, null);
+} catch(e) {
+ isAlive = true;
+}
+
+ok(isAlive, "Still alive \\o/");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/xslt/tests/mochitest/test_bug1135764.html b/dom/xslt/tests/mochitest/test_bug1135764.html
new file mode 100644
index 0000000000..a368b9b4ab
--- /dev/null
+++ b/dom/xslt/tests/mochitest/test_bug1135764.html
@@ -0,0 +1,58 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1135764
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1135764</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ /** Test for Bug 1135764 **/
+ SimpleTest.waitForExplicitFinish();
+ var counter = 0;
+ var startTimelineValue;
+
+ function waitATick() {
+ ++counter;
+ if (counter == 1) {
+ frames[0].requestAnimationFrame(waitATick);
+ return;
+ }
+ ok(frames[0].document.timeline.currentTime !== startTimelineValue,
+ "The timeline in an XSLT-transformed document should still advance");
+ SimpleTest.finish();
+ }
+ addLoadEvent(function() {
+ SpecialPowers.pushPrefEnv(
+ {
+ set: [
+ ["dom.animations-api.timelines.enabled", true],
+ ],
+ },
+ function() {
+ var ifr = document.querySelector("iframe");
+ ifr.onload = function() {
+ startTimelineValue = frames[0].document.timeline.currentTime;
+ frames[0].requestAnimationFrame(waitATick);
+ };
+ ifr.src = "file_bug1135764.xml";
+ }
+ );
+ });
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1135764">Mozilla Bug 1135764</a>
+<p id="display">
+ <iframe></iframe>
+</p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/xslt/tests/mochitest/test_bug1436040.html b/dom/xslt/tests/mochitest/test_bug1436040.html
new file mode 100644
index 0000000000..df17e75779
--- /dev/null
+++ b/dom/xslt/tests/mochitest/test_bug1436040.html
@@ -0,0 +1,54 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Test for xslt-param PIs</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+async_test(t => {
+ let iframe = document.createElement('iframe');
+ let src = `<?xml version="1.0"?>
+ <?xslt-param name="param" value="true"?>
+ <?xml-stylesheet type="text/xml" href="#bug"?>
+ <doc>
+ <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0" id="bug">
+ <xsl:output method="html"/>
+ <xsl:param name="param">false</xsl:param>
+ <xsl:template match="/">
+ <xsl:element name="script">parent.postMessage({test: 1, result: <xsl:value-of select="$param"/>}, "*");</xsl:element>
+ </xsl:template>
+ </xsl:stylesheet>
+ </doc>`;
+ iframe.src = "data:text/xml," + encodeURIComponent(src);
+ self.addEventListener("message", t.step_func_done(({data: {test, result}}) => {
+ if (test == 1) {
+ assert_true(result, "The stylesheet param's value should be set by the xslt-param PI.");
+ }
+ }));
+ document.body.appendChild(iframe);
+}, "Test for xslt-param PIs");
+async_test(t => {
+ let iframe = document.createElement('iframe');
+ let src = `<?xml version="1.0"?>
+ <?xslt-param-namespace prefix="foo" namespace="foonamespace"?>
+ <?xslt-param name="param" select="//foo:default"?>
+ <?xml-stylesheet type="text/xml" href="#bug"?>
+ <doc>
+ <default xmlns="foonamespace">true</default>
+ <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0" id="bug">
+ <xsl:output method="html"/>
+ <xsl:param name="param">false</xsl:param>
+ <xsl:template match="/">
+ <xsl:element name="script">parent.postMessage({test: 2, result: <xsl:value-of select="$param"/>}, "*");</xsl:element>
+ </xsl:template>
+ </xsl:stylesheet>
+ </doc>`;
+ iframe.src = "data:text/xml," + encodeURIComponent(src);
+ self.addEventListener("message", t.step_func_done(({data: {test, result}}) => {
+ if (test == 2) {
+ assert_true(result, "xslt-param-namespace should have set the right namespace");
+ }
+ }));
+ document.body.appendChild(iframe);
+}, "Test for xslt-param PIs");
+</script>
diff --git a/dom/xslt/tests/mochitest/test_bug1527308.html b/dom/xslt/tests/mochitest/test_bug1527308.html
new file mode 100644
index 0000000000..c37a0c1f00
--- /dev/null
+++ b/dom/xslt/tests/mochitest/test_bug1527308.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Test for serialized state in XSLT result document</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+async_test(t => {
+ let iframe = document.createElement('iframe');
+ let src = `<?xml version="1.0"?>
+ <?xml-stylesheet type="text/xml" href="#stylesheet"?>
+ <doc>
+ <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0" id="stylesheet">
+ <xsl:output method="html"/>
+ <xsl:template match="/">
+ <html>
+ <xsl:element name="script">self.addEventListener("message", () => { history.go(0); });</xsl:element>
+ <body onload="parent.postMessage(history.state, '*'); history.replaceState('data', 'title');"></body>
+ </html>
+ </xsl:template>
+ </xsl:stylesheet>
+ </doc>`;
+ iframe.src = "data:text/xml," + encodeURIComponent(src);
+ let reloaded = false;
+ self.addEventListener("message", t.step_func(({data: state}) => {
+ if (!reloaded) {
+ assert_equals(state, null, "At this point history.state should be set.");
+ iframe.contentWindow.postMessage("", "*");
+ reloaded = true;
+ return;
+ }
+
+ assert_equals(state, 'data', "Data set through history.replaceState in an XSLT result document should persist.");
+ t.done();
+ }));
+ document.body.appendChild(iframe);
+}, "Test for serialized state in XSLT result document");
+</script>
diff --git a/dom/xslt/tests/mochitest/test_bug1729517.html b/dom/xslt/tests/mochitest/test_bug1729517.html
new file mode 100644
index 0000000000..3dc9eca3c0
--- /dev/null
+++ b/dom/xslt/tests/mochitest/test_bug1729517.html
@@ -0,0 +1,48 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title><!-- TODO: insert title here --></title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+ <script>
+ SimpleTest.waitForExplicitFinish();
+ async function runTest() {
+ let frame = document.getElementById("frame");
+ let loaded = new Promise((resolve) => {
+ frame.addEventListener("load", () => {
+ let failed = JSON.parse(frame.contentDocument.title);
+ ok(failed instanceof Array, "Frame's title is expected to be a JSON representation of the array of failed conditions.");
+ is(failed.length, 0, "No scripts should run in sandboxed iframe document created by XSLT.");
+ for (let desc of failed) {
+ info(desc);
+ }
+ resolve();
+ }, { once: true });
+ });
+ frame.src = "file_bug1729517.xml";
+ await loaded;
+
+ let results = new Promise((resolve) => {
+ addEventListener("message", ({ data }) => {
+ resolve(data);
+ }, { once: true });
+ });
+
+ let win = window.open(`https://example.com/tests/dom/xslt/tests/mochitest/file_bug1729517_2.xml`);
+ for (const [header, result] of await results) {
+ is(result, "PASS", `${header} of the source document should apply to document created by XSLT.`);
+ }
+ win.close();
+
+ SimpleTest.finish();
+ }
+ </script>
+</head>
+<body onload="runTest();">
+<p id="display"></p>
+<iframe sandbox="allow-same-origin" id="frame"></iframe>
+<div id="content" style="display: none"></div>
+<pre id="test"></pre>
+</body>
+</html>
diff --git a/dom/xslt/tests/mochitest/test_bug319374.html b/dom/xslt/tests/mochitest/test_bug319374.html
new file mode 100644
index 0000000000..a833cf281f
--- /dev/null
+++ b/dom/xslt/tests/mochitest/test_bug319374.html
@@ -0,0 +1,101 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=319374
+-->
+<head>
+ <title>Test for Bug 319374</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=319374">Mozilla Bug 319374</a>
+<p id="display"></p>
+<div id="content"><custom-el></custom-el><custom-el></custom-el><custom-el></custom-el></div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+customElements.define("custom-el", class extends HTMLElement {
+ constructor() {
+ super();
+ this.attachShadow({ mode: "open" });
+ this.shadowRoot.innerHTML =
+ `<span attr="attribute"><span></span></span><span> anon text </span><br>`;
+ }
+});
+
+ function testChangesInShadowDOM() {
+ // Test 1: Make sure that modifying anonymous content doesn't
+ // cause non-anonymous XPath result to throw exceptions..
+ var counter = 0;
+ var error = null;
+ try {
+ var xp = new XPathEvaluator();
+ var result = xp.evaluate("*",
+ document.getElementById('content'),
+ null,
+ XPathResult.UNORDERED_NODE_ITERATOR_TYPE,
+ null);
+ var res = null;
+ while ((res = result.iterateNext())) {
+ ++counter;
+ let anon = res.shadowRoot.childNodes;
+ anon[0].firstChild.remove(); // Removing a child node
+ anon[0].removeAttribute("attr1"); // Removing an attribute
+ anon[1].firstChild.data = "anon text changed" // Modifying text data
+ }
+ } catch (e) {
+ error = e;
+ }
+ ok(!error, error);
+ ok(counter == 3, "XPathEvaluator should have found 3 elements.")
+
+ // Test 2: If the context node is in anonymous content, changing some
+ // other anonymous tree shouldn't cause XPath result to throw.
+ let shadowAttr1 = document.getElementById("content").firstChild.
+ shadowRoot.firstChild.getAttributeNode("attr");
+ let shadowAttr2 = document.getElementById("content").lastChild.
+ shadowRoot.firstChild.getAttributeNode("attr");
+ var resultAttr = null;
+ try {
+ var xp2 = xp.evaluate(".",
+ shadowAttr1,
+ null,
+ XPathResult.UNORDERED_NODE_ITERATOR_TYPE,
+ null);
+ // Attribute changing in a different anonymous tree.
+ shadowAttr2.value = "foo";
+ resultAttr = xp2.iterateNext();
+ is(resultAttr, shadowAttr1, "XPathEvaluator returned wrong attribute!");
+ } catch (e) {
+ ok(false, e);
+ }
+
+ // Test 3: If the anonymous tree in which context node is in is modified,
+ // XPath result should throw when iterateNext() is called.
+ resultAttr = null;
+ try {
+ var xp3 = xp.evaluate(".",
+ shadowAttr1,
+ null,
+ XPathResult.UNORDERED_NODE_ITERATOR_TYPE,
+ null);
+ // Attribute changing in the same anonymous tree.
+ shadowAttr1.ownerElement.setAttribute("foo", "bar");
+ resultAttr = xp3.iterateNext();
+ ok(resultAttr == shadowAttr1,
+ "XPathEvaluator should have thrown an exception!")
+ } catch (e) {
+ ok(true, e);
+ }
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addLoadEvent(testChangesInShadowDOM);
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/xslt/tests/mochitest/test_bug427060.html b/dom/xslt/tests/mochitest/test_bug427060.html
new file mode 100644
index 0000000000..0dac88b74b
--- /dev/null
+++ b/dom/xslt/tests/mochitest/test_bug427060.html
@@ -0,0 +1,49 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=427060
+-->
+<head>
+ <title>Test for Bug 427060</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=427060">Mozilla Bug 427060</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 427060 **/
+
+var xmldoc, xsltdoc;
+
+xmldoc = new DOMParser().parseFromString('<opml version="1.0"><body></body></opml>', "text/xml");
+xsltdoc = new DOMParser().parseFromString('<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">\n\
+ <xsl:template match="/opml">\n\
+ <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">\n\
+ <head>\n\
+ <base target="_blank"></base>\n\
+ </head>\n\
+ <body></body>\n\
+ </html>\n\
+ </xsl:template>\n\
+ </xsl:stylesheet>', "text/xml");
+
+var processor = new XSLTProcessor;
+processor.importStylesheet(xsltdoc);
+try
+{
+ var result = processor.transformToDocument(xmldoc);
+}
+catch (e)
+{
+}
+ok(result && result instanceof Document, "XSLT transform should have created a document");
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/xslt/tests/mochitest/test_bug440974.html b/dom/xslt/tests/mochitest/test_bug440974.html
new file mode 100644
index 0000000000..40745ae210
--- /dev/null
+++ b/dom/xslt/tests/mochitest/test_bug440974.html
@@ -0,0 +1,46 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=440974
+-->
+<head>
+ <title>Test for Bug 440974</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=440974">Mozilla Bug 440974</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 440974 **/
+
+function isTxResult(node)
+{
+ return node.namespaceURI == "http://www.mozilla.org/TransforMiix" &&
+ node.localName == "result";
+}
+
+var xmldoc, xsltdoc;
+
+xmldoc = new DOMParser().parseFromString('<items><item><id>1</id></item><item><id>2</id></item><item><id>3</id></item></items>', "text/xml");
+xsltdoc = new DOMParser().parseFromString('<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">\n\
+ <xsl:output method="xml" />\n\
+ <xsl:template match="item"><foo id="{id}"/></xsl:template>\n\
+ </xsl:stylesheet>', "text/xml");
+
+var processor = new XSLTProcessor;
+processor.importStylesheet(xsltdoc);
+var result = processor.transformToDocument(xmldoc);
+var resultElements = Array.prototype.filter.call(result.getElementsByTagName('*'), isTxResult);
+is(resultElements.length, 1, "there should be only one 'transformiix:result' element");
+is(resultElements[0], result.documentElement, "the 'transformiix:result' element should be the document element");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/xslt/tests/mochitest/test_bug453441.html b/dom/xslt/tests/mochitest/test_bug453441.html
new file mode 100644
index 0000000000..1d8b554c00
--- /dev/null
+++ b/dom/xslt/tests/mochitest/test_bug453441.html
@@ -0,0 +1,57 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=453441
+-->
+<head>
+ <title>Test for Bug 453441</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=453441">Mozilla Bug 453441</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 453441 **/
+
+function tryImportStylesheet(xml, valid)
+{
+ var processor = new XSLTProcessor;
+
+ var xsltdoc = new DOMParser().parseFromString(xml, "text/xml");
+ try
+ {
+ processor.importStylesheet(xsltdoc);
+ ok(valid, "should be able to parse this XSLT stylesheet");
+ }
+ catch (e)
+ {
+ ok(!valid, "should not be able to parse this XSLT stylesheet");
+ }
+}
+
+tryImportStylesheet(
+ '<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">\n\
+ <xsl:template match="/">\n\
+ <html xmlns="http://www.w3.org/1999/xhtml" xsl:version="1.0" />\n\
+ </xsl:template>\n\
+ </xsl:stylesheet>'
+, true);
+
+tryImportStylesheet(
+ '<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" />'
+, false);
+
+tryImportStylesheet(
+ '<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" />'
+, false);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/xslt/tests/mochitest/test_bug468208.html b/dom/xslt/tests/mochitest/test_bug468208.html
new file mode 100644
index 0000000000..d3c05c7374
--- /dev/null
+++ b/dom/xslt/tests/mochitest/test_bug468208.html
@@ -0,0 +1,35 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=468208
+-->
+<head>
+ <title>Test for Bug 468208</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=468208">Mozilla Bug 468208</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 468208 **/
+var xslt =
+ '<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">\n\
+ <xsl:strip-space elements="color"/>\n\
+ </xsl:stylesheet>'
+;
+var xsltdoc = new DOMParser().parseFromString(xslt, "text/xml");
+
+var processor = new XSLTProcessor;
+processor.importStylesheet(xsltdoc);
+ok(true, "XSLT shouldn't leak");
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/xslt/tests/mochitest/test_bug511487.html b/dom/xslt/tests/mochitest/test_bug511487.html
new file mode 100644
index 0000000000..324ba04679
--- /dev/null
+++ b/dom/xslt/tests/mochitest/test_bug511487.html
@@ -0,0 +1,59 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=511487
+-->
+<head>
+ <title>Test for Bug 511487</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=511487">Mozilla Bug 511487</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 511487 **/
+
+ var didTransform = false;
+ var processor = new XSLTProcessor();
+ var style =
+ '<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0" xmlns="http://www.w3.org/1999/xhtml">' +
+ '<xsl:output method="xml" version="1.0" encoding="UTF-8" doctype-system="http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd" doctype-public="-//W3C//DTD XHTML 1.0 Transitional//EN" />' +
+ '<xsl:template match="wml">' +
+ '<html xmlns="http://www.w3.org/1999/xhtml">' +
+ '<head>' +
+ '<title>XSLT test</title>' +
+ '</head>' +
+ '<body onload="window.alert(this)">' +
+ '</body>' +
+ '</html>' +
+ '</xsl:template>' +
+ '</xsl:stylesheet>';
+ var styleDoc = new DOMParser().parseFromString (style, "text/xml");
+
+ var data =
+ '<?xml version="1.0"?>' +
+ '<!DOCTYPE wml PUBLIC "-//WAPFORUM//DTD WML 1.1//EN" "http://www.wapforum.org/DTD/wml_1.1.xml">' +
+ '<wml><card><p>paragraph</p></card></wml>';
+ var originalDoc = new DOMParser().parseFromString(data, "text/xml");
+
+ processor.importStylesheet(styleDoc);
+ try {
+ var transformedDocument = processor.transformToDocument(originalDoc);
+ didTransform = true;
+ } catch (e) {
+ ok(false, e);
+ }
+
+ ok(didTransform, "transformToDocument didn't succeed!");
+
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/xslt/tests/mochitest/test_bug551412.html b/dom/xslt/tests/mochitest/test_bug551412.html
new file mode 100644
index 0000000000..399310a014
--- /dev/null
+++ b/dom/xslt/tests/mochitest/test_bug551412.html
@@ -0,0 +1,48 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=551412
+-->
+<head>
+ <title>Test for Bug 551412</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=551412">Mozilla Bug 551412</a>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 551412 **/
+
+ var processor = new XSLTProcessor();
+ var style =
+ '<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" ' +
+ 'xmlns:exsl="http://exslt.org/common" ' +
+ 'version="1.0">' +
+ '<xsl:output method="html"/>' +
+ '<xsl:variable name="rtf">1 <b>2</b> 3</xsl:variable>' +
+ '<xsl:template match="/">' +
+ '<xsl:copy-of select="exsl:node-set($rtf)"/>' +
+ '</xsl:template>' +
+ '</xsl:stylesheet>';
+ var styleDoc = new DOMParser().parseFromString (style, "text/xml");
+
+ var data =
+ '<root/>';
+ var originalDoc = new DOMParser().parseFromString(data, "text/xml");
+
+ processor.importStylesheet(styleDoc);
+
+ var fragment = processor.transformToFragment(originalDoc, document);
+ var content = document.getElementById("content");
+ content.appendChild(fragment);
+ is(content.innerHTML, "1 <b>2</b> 3",
+ "Result of transform should be '1 <b>2</b> 3'");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/xslt/tests/mochitest/test_bug551654.html b/dom/xslt/tests/mochitest/test_bug551654.html
new file mode 100644
index 0000000000..0bdd2dda60
--- /dev/null
+++ b/dom/xslt/tests/mochitest/test_bug551654.html
@@ -0,0 +1,49 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=551654
+-->
+<head>
+ <title>Test for Bug 551654</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=551654">Mozilla Bug 551654</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 551654 **/
+
+ var didTransform = false;
+ var processor = new XSLTProcessor();
+ var style =
+ '<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" ' +
+ 'xmlns:exsl="http://exslt.org/common" ' +
+ 'version="1.0">' +
+ '<xsl:output method="html"/>' +
+ '<xsl:template match="/">' +
+ '<xsl:copy-of select="exsl:node-set(42)"/>' +
+ '</xsl:template>' +
+ '</xsl:stylesheet>';
+ var styleDoc = new DOMParser().parseFromString (style, "text/xml");
+
+ var data =
+ '<root/>';
+ var originalDoc = new DOMParser().parseFromString(data, "text/xml");
+
+ processor.importStylesheet(styleDoc);
+ var fragment = processor.transformToFragment(originalDoc, document);
+ is(fragment.firstChild.nodeType, Node.TEXT_NODE,
+ "Result of transform should be a textnode");
+ is(fragment.firstChild.nodeValue, "42",
+ "Result of transform should be a textnode with value '42'");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/xslt/tests/mochitest/test_bug566629.html b/dom/xslt/tests/mochitest/test_bug566629.html
new file mode 100644
index 0000000000..5670207145
--- /dev/null
+++ b/dom/xslt/tests/mochitest/test_bug566629.html
@@ -0,0 +1,70 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=566629
+-->
+<head>
+ <title>Test for Bug 566629</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=566629">Mozilla Bug 566629</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 566629 **/
+
+var xsltdoc = new DOMParser().parseFromString(
+ '<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"\
+ xmlns:xhtml="http://www.w3.org/1999/xhtml">\
+ <xsl:template match="/">\
+ <xsl:value-of select="count(//body)"/>\
+ <xsl:text>,</xsl:text>\
+ <xsl:value-of select="count(//xhtml:body)"/>\
+ <xsl:text>,</xsl:text>\
+ <xsl:value-of select="count(//xsl:body)"/>\
+ <xsl:text>,</xsl:text>\
+ <xsl:value-of select="name(//body)"/>\
+ <xsl:text>,</xsl:text>\
+ <xsl:value-of select="local-name(//body)"/>\
+ </xsl:template>\
+ </xsl:stylesheet>',
+ "text/xml");
+
+var processor = new XSLTProcessor;
+processor.importStylesheet(xsltdoc);
+var result = processor.transformToFragment(document, document);
+ok(result instanceof DocumentFragment, "returned a docfragment");
+is(result.firstChild.nodeValue, "1,1,0,BODY,body",
+ "correct treatment of HTML elements in XSLT");
+
+is(document.evaluate("count(//body)", document, null, XPathResult.ANY_TYPE, null).numberValue,
+ 1, "namespace-less node-test");
+is(document.evaluate("count(//a:body)", document,
+ function() { return "http://www.w3.org/1999/xhtml" },
+ XPathResult.ANY_TYPE, null).numberValue,
+ 1, "with-namespace node-test");
+is(document.evaluate("count(//a:body)", document,
+ function() { return "foo" },
+ XPathResult.ANY_TYPE, null).numberValue,
+ 0, "wrong-namespace node-test");
+is(document.evaluate("//bODy", document, null, XPathResult.ANY_TYPE, null).iterateNext(),
+ document.body, "case insensitive matching");
+is(document.evaluate("count(//a:bODy)", document,
+ function() { return "http://www.w3.org/1999/xhtml" },
+ XPathResult.ANY_TYPE, null).numberValue,
+ 0, "with-namespace but wrong casing node-test");
+is(document.evaluate("name(//body)", document, null, XPathResult.ANY_TYPE, null).stringValue,
+ "BODY", "uppercase name() function");
+is(document.evaluate("local-name(//body)", document, null, XPathResult.ANY_TYPE, null).stringValue,
+ "body", "lowercase local-name() function");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/xslt/tests/mochitest/test_bug566629.xhtml b/dom/xslt/tests/mochitest/test_bug566629.xhtml
new file mode 100644
index 0000000000..0880a36002
--- /dev/null
+++ b/dom/xslt/tests/mochitest/test_bug566629.xhtml
@@ -0,0 +1,73 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=566629
+-->
+<head>
+ <title>Test for Bug 566629</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=566629">Mozilla Bug 566629</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+<![CDATA[
+/** Test for Bug 566629 **/
+
+var xsltdoc = new DOMParser().parseFromString(
+ '<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"\
+ xmlns:xhtml="http://www.w3.org/1999/xhtml">\
+ <xsl:template match="/">\
+ <xsl:value-of select="count(//body)"/>\
+ <xsl:text>,</xsl:text>\
+ <xsl:value-of select="count(//xhtml:body)"/>\
+ <xsl:text>,</xsl:text>\
+ <xsl:value-of select="count(//xsl:body)"/>\
+ <xsl:text>,</xsl:text>\
+ <xsl:value-of select="name(//xhtml:body)"/>\
+ <xsl:text>,</xsl:text>\
+ <xsl:value-of select="local-name(//xhtml:body)"/>\
+ </xsl:template>\
+ </xsl:stylesheet>',
+ "text/xml");
+
+var processor = new XSLTProcessor;
+processor.importStylesheet(xsltdoc);
+var result = processor.transformToFragment(document, document);
+ok(result instanceof DocumentFragment, "returned a docfragment");
+is(result.firstChild.nodeValue, "0,1,0,body,body",
+ "correct treatment of HTML elements in XSLT");
+
+is(document.evaluate("count(//body)", document, null, XPathResult.ANY_TYPE, null).numberValue,
+ 0, "namespace-less node-test");
+is(document.evaluate("count(//a:body)", document,
+ function() { return "http://www.w3.org/1999/xhtml" },
+ XPathResult.ANY_TYPE, null).numberValue,
+ 1, "with-namespace node-test");
+is(document.evaluate("count(//a:body)", document,
+ function() { return "foo" },
+ XPathResult.ANY_TYPE, null).numberValue,
+ 0, "wrong-namespace node-test");
+is(document.evaluate("count(//a:bODy)", document,
+ function() { return "http://www.w3.org/1999/xhtml" },
+ XPathResult.ANY_TYPE, null).numberValue,
+ 0, "with-namespace wrong-casing node-test");
+is(document.evaluate("count(//bODy)", document, null, XPathResult.ANY_TYPE, null).numberValue,
+ 0, "without-namespace wrong-casing node-test");
+is(document.evaluate("name(//a:body)", document,
+ function() { return "http://www.w3.org/1999/xhtml" },
+ XPathResult.ANY_TYPE, null).stringValue,
+ "body", "name()");
+is(document.evaluate("local-name(//a:body)", document,
+ function() { return "http://www.w3.org/1999/xhtml" },
+ XPathResult.ANY_TYPE, null).stringValue,
+ "body", "local-name()");
+]]>
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/xslt/tests/mochitest/test_bug603159.html b/dom/xslt/tests/mochitest/test_bug603159.html
new file mode 100644
index 0000000000..95c4efa9c5
--- /dev/null
+++ b/dom/xslt/tests/mochitest/test_bug603159.html
@@ -0,0 +1,54 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=603159
+-->
+<head>
+ <title>Test for Bug 603159</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=603159">Mozilla Bug 603159</a>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 603159 **/
+
+ var style =
+ '<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" ' +
+ 'xmlns:date="http://exslt.org/dates-and-times" '+
+ 'version="1.0">' +
+ '<xsl:output method="html"/>' +
+ '<xsl:template match="/">' +
+ '<xsl:value-of select="date:date-time()" /> ' +
+ '</xsl:template>' +
+ '</xsl:stylesheet>';
+ var styleDoc = new DOMParser().parseFromString (style, "text/xml");
+
+ var data = '<root/>';
+ var originalDoc = new DOMParser().parseFromString(data, "text/xml");
+
+ var processor = new XSLTProcessor();
+ processor.importStylesheet(styleDoc);
+
+ var fragment = processor.transformToFragment(originalDoc, document);
+ var content = document.getElementById("content");
+ content.appendChild(fragment);
+
+ // use Gecko's Date.parse to parse, then compare the milliseconds since epoch
+ var xslt_ms = Date.parse(content.innerHTML);
+ var now_ms = new Date().getTime();
+ var accepted_diff = 30 * 60 * 1000; // 30 minutes
+ var diff = Math.abs(now_ms - xslt_ms);
+
+ ok(diff < accepted_diff, "generated timestamp should be not more than "
+ + accepted_diff + " ms before 'now', but the difference was: " + diff);
+
+ content.innerHTML = '';
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/xslt/tests/mochitest/test_bug616774.html b/dom/xslt/tests/mochitest/test_bug616774.html
new file mode 100644
index 0000000000..e970b778e9
--- /dev/null
+++ b/dom/xslt/tests/mochitest/test_bug616774.html
@@ -0,0 +1,28 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=616774-->
+<head>
+ <title>Test for Bug 616774</title>
+ <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=616774">Mozilla Bug 616774</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+ 42
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 616774 **/
+is(document.evaluate('- "8"', document, null, XPathResult.ANY_TYPE, null).numberValue, -8, "Negated string literal should evaluate to itself negated");
+is(document.evaluate('- - "999"', document, null, XPathResult.ANY_TYPE, null).numberValue, 999, "String literal should evaluate to itself");
+is(document.evaluate('- - id("content")', document, null, XPathResult.ANY_TYPE, null).numberValue, 42, "DOM element should evaluate to itself coerced to a number");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/xslt/tests/mochitest/test_bug667315.html b/dom/xslt/tests/mochitest/test_bug667315.html
new file mode 100644
index 0000000000..a54fa05f42
--- /dev/null
+++ b/dom/xslt/tests/mochitest/test_bug667315.html
@@ -0,0 +1,46 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=667315
+-->
+<head>
+ <title>Test for Bug 667315</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=667315">Mozilla Bug 667315</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 667315 **/
+
+var style =
+ '<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" ' +
+ 'version="1.0">' +
+ '<xsl:variable name="var">' +
+ '<html><p>a</p></html>' +
+ '</xsl:variable>' +
+ '<xsl:template match="/">' +
+ '<xsl:copy-of select="$var" />' +
+ '</xsl:template>' +
+ '</xsl:stylesheet>';
+var styleDoc = new DOMParser().parseFromString (style, "text/xml");
+
+var data = '<root/>';
+var originalDoc = new DOMParser().parseFromString(data, "text/xml");
+
+var processor = new XSLTProcessor();
+processor.importStylesheet(styleDoc);
+
+var doc = processor.transformToDocument(originalDoc);
+ok(doc instanceof HTMLDocument, "should have switched to html output method");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/xslt/tests/mochitest/test_exslt.html b/dom/xslt/tests/mochitest/test_exslt.html
new file mode 100644
index 0000000000..739d1c1988
--- /dev/null
+++ b/dom/xslt/tests/mochitest/test_exslt.html
@@ -0,0 +1,249 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Test EXSLT extensions
+http://www.exslt.org/
+-->
+<head>
+ <title>Test for EXSLT extensions</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test">
+<script type="application/javascript">
+ let tests = [
+ {
+ descr: "Testing common:node-set",
+ expr: "common:node-set($tree)",
+ useCopyOf: true,
+ expResult: "<abc>def</abc>",
+ },
+ {
+ descr: "Testing common:object-type(string)",
+ expr: "common:object-type($string)",
+ expResult: "string",
+ },
+ {
+ descr: "Testing common:object-type(number)",
+ expr: "common:object-type($number)",
+ expResult: "number",
+ },
+ {
+ descr: "Testing common:object-type(boolean)",
+ expr: "common:object-type($boolean)",
+ expResult: "boolean",
+ },
+ {
+ descr: "Testing common:object-type(node-set)",
+ expr: "common:object-type($node-set)",
+ expResult: "node-set",
+ },
+ {
+ descr: "Testing common:object-type(tree)",
+ expr: "common:object-type($tree)",
+ expResult: "RTF",
+ },
+ {
+ descr: "Testing math:max",
+ expr: "math:max(root/numbers/number)",
+ expResult: "11",
+ },
+ {
+ descr: "Testing math:min",
+ expr: "math:min(root/numbers/number)",
+ expResult: "4",
+ },
+ {
+ descr: "Testing math:highest",
+ expr: "math:highest(root/numbers/number)/@id",
+ expResult: "eleven",
+ },
+ {
+ descr: "Testing math:lowest",
+ expr: "math:lowest(root/numbers/number)/@id",
+ expResult: "four",
+ },
+ {
+ descr: "Testing regexp:test",
+ expr: "regexp:test('XSLT is great', 'XSLT', '')",
+ expResult: "true",
+ },
+ {
+ descr: "Testing regexp:match",
+ expr: "regexp:match('XSLT is great', 'XSL.', '')[1]",
+ expResult: "XSLT",
+ },
+ {
+ descr: "Testing regexp:replace",
+ expr: "regexp:replace('Food is great', 'Fo.d', '', 'XSLT')",
+ expResult: "XSLT is great",
+ },
+ {
+ descr: "Testing sets:difference",
+ expr: "sets:difference($i, $e)",
+ useCopyOf: true,
+ expResult: "<city name=\"Paris\" country=\"France\"></city><city name=\"Madrid\" country=\"Spain\"></city><city name=\"Calais\" country=\"France\"></city>",
+ },
+ {
+ descr: "Testing sets:distinct",
+ expr: "strings:concat(sets:distinct(//@country))",
+ expResult: "FranceSpainAustriaGermany",
+ },
+ {
+ descr: "Testing sets:hasSameNode",
+ expr: "sets:has-same-node($i, $e)",
+ expResult: "true",
+ },
+ {
+ descr: "Testing sets:hasSameNode",
+ expr: "sets:has-same-node($i, $o)",
+ expResult: "false",
+ },
+ {
+ descr: "Testing sets:intersection",
+ expr: "sets:intersection($i, $e)",
+ useCopyOf: true,
+ expResult: "<city name=\"Vienna\" country=\"Austria\"></city><city name=\"Berlin\" country=\"Germany\"></city>",
+ },
+ {
+ descr: "Testing sets:leading",
+ expr: "sets:leading($i, $e)",
+ useCopyOf: true,
+ expResult: "<city name=\"Paris\" country=\"France\"></city><city name=\"Madrid\" country=\"Spain\"></city>",
+ },
+ {
+ descr: "Testing sets:leading",
+ expr: "sets:leading($i, $o)",
+ useCopyOf: true,
+ expResult: "",
+ },
+ {
+ descr: "Testing sets:leading",
+ expr: "sets:leading($i, $empty)",
+ useCopyOf: true,
+ expResult: "<city name=\"Paris\" country=\"France\"></city><city name=\"Madrid\" country=\"Spain\"></city><city name=\"Vienna\" country=\"Austria\"></city><city name=\"Calais\" country=\"France\"></city><city name=\"Berlin\" country=\"Germany\"></city>",
+ },
+ {
+ descr: "Testing sets:trailing",
+ expr: "sets:trailing($i, $e)",
+ useCopyOf: true,
+ expResult: "<city name=\"Calais\" country=\"France\"></city><city name=\"Berlin\" country=\"Germany\"></city>",
+ },
+ {
+ descr: "Testing sets:trailing",
+ expr: "sets:trailing($i, $o)",
+ useCopyOf: true,
+ expResult: "",
+ },
+ {
+ descr: "Testing sets:trailing",
+ expr: "sets:trailing($i, $empty)",
+ useCopyOf: true,
+ expResult: "<city name=\"Paris\" country=\"France\"></city><city name=\"Madrid\" country=\"Spain\"></city><city name=\"Vienna\" country=\"Austria\"></city><city name=\"Calais\" country=\"France\"></city><city name=\"Berlin\" country=\"Germany\"></city>",
+ },
+ {
+ descr: "Testing strings:concat",
+ expr: "strings:concat(root/numbers/number/@id)",
+ expResult: "seveneleveneightfour",
+ },
+ {
+ descr: "Testing strings:split",
+ expr: "strings:split('a, simple, list', ', ')",
+ useCopyOf: true,
+ expResult: "<token>a</token><token>simple</token><token>list</token>",
+ },
+ {
+ descr: "Testing strings:split",
+ expr: "strings:split('date math str')",
+ useCopyOf: true,
+ expResult: "<token>date</token><token>math</token><token>str</token>",
+ },
+ {
+ descr: "Testing strings:split",
+ expr: "strings:split('foo', '')",
+ useCopyOf: true,
+ expResult: "<token>f</token><token>o</token><token>o</token>",
+ },
+ {
+ descr: "Testing strings:tokenize",
+ expr: "strings:tokenize('2001-06-03T11:40:23', '-T:')",
+ useCopyOf: true,
+ expResult: "<token>2001</token><token>06</token><token>03</token><token>11</token><token>40</token><token>23</token>",
+ },
+ {
+ descr: "Testing strings:tokenize",
+ expr: "strings:tokenize('date math str')",
+ useCopyOf: true,
+ expResult: "<token>date</token><token>math</token><token>str</token>",
+ },
+ {
+ descr: "Testing strings:tokenize",
+ expr: "strings:tokenize('foo', '')",
+ useCopyOf: true,
+ expResult: "<token>f</token><token>o</token><token>o</token>",
+ },
+ ];
+
+ let style =
+ `<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"
+ xmlns:common="http://exslt.org/common"
+ xmlns:math="http://exslt.org/math"
+ xmlns:regexp="http://exslt.org/regular-expressions"
+ xmlns:sets="http://exslt.org/sets"
+ xmlns:strings="http://exslt.org/strings">
+ <xsl:output method="html"/>
+ <xsl:variable name="tree"><abc>def</abc></xsl:variable>
+ <xsl:variable name="string" select="'abc'"/>
+ <xsl:variable name="number" select="123"/>
+ <xsl:variable name="boolean" select="true()"/>
+ <xsl:variable name="node-set" select="//*"/>
+ <xsl:variable name="i" select="/root/sets/city[contains(@name, 'i')]" />
+ <xsl:variable name="e" select="/root/sets/city[contains(@name, 'e')]" />
+ <xsl:variable name="o" select="/root/sets/city[contains(@name, 'o')]" />
+ <xsl:variable name="empty" select="/root/sets/city[contains(@name, 'x')]" />
+ <xsl:template match="/">
+ ${tests.map(({expr, useCopyOf}, i) => `<div id="${i}"><xsl:${useCopyOf ? "copy-of" : "value-of"} select="${expr}"/></div>`).join("\n")}
+ </xsl:template>
+ </xsl:stylesheet>`;
+
+ let styleDoc = new DOMParser().parseFromString(style, "text/xml");
+
+ let data = `<root>
+ <numbers>
+ <number id="seven">7</number>
+ <number id="eleven">11</number>
+ <number id="eight">8</number>
+ <number id="four">4</number>
+ </numbers>
+ <sets>
+ <city name="Paris" country="France" />
+ <city name="Madrid" country="Spain" />
+ <city name="Vienna" country="Austria" />
+ <city name="Barcelona" country="Spain" />
+ <city name="Salzburg" country="Austria" />
+ <city name="Bonn" country="Germany" />
+ <city name="Lyon" country="France" />
+ <city name="Hannover" country="Germany" />
+ <city name="Calais" country="France" />
+ <city name="Berlin" country="Germany" />
+ </sets>
+ </root>`;
+ let originalDoc = new DOMParser().parseFromString(data, "text/xml");
+
+ let processor = new XSLTProcessor();
+ processor.importStylesheet(styleDoc);
+
+ let fragment = processor.transformToFragment(originalDoc, document);
+
+ tests.forEach(({descr, expResult}, i) => {
+ let result = fragment.getElementById(i);
+ is(result.innerHTML, expResult, descr);
+ });
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/xslt/tests/mochitest/test_metaRefresh.html b/dom/xslt/tests/mochitest/test_metaRefresh.html
new file mode 100644
index 0000000000..21338bed82
--- /dev/null
+++ b/dom/xslt/tests/mochitest/test_metaRefresh.html
@@ -0,0 +1,48 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title><!-- TODO: insert title here --></title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+ <script>
+ SimpleTest.waitForExplicitFinish();
+ SimpleTest.requestFlakyTimeout("Use a timeout to check that a load doesn't happen.");
+
+ async function runTest() {
+ function refreshed(frame) {
+ return new Promise((resolve) => {
+ frame.addEventListener("load", () => {
+ let timeout = setTimeout(() => {
+ resolve(false);
+ }, 5000);
+ frame.addEventListener("load", () => {
+ clearTimeout(timeout);
+ resolve(true);
+ }, { once: true });
+ }, { once: true });
+ });
+ }
+
+ let frame = document.getElementById("frame");
+ let result = refreshed(frame);
+ frame.src = "file_metaRefresh.xml";
+ is(await result, true, "Meta refresh should work in iframe document created by XSLT.");
+
+ let sandBoxedFrame = document.getElementById("sandBoxedFrame");
+ result = refreshed(sandBoxedFrame);
+ sandBoxedFrame.src = "file_metaRefresh.xml";
+ is(await result, false, "Meta refresh shouldn't work in sandboxed iframe document created by XSLT.");
+
+ SimpleTest.finish();
+ }
+ </script>
+</head>
+<body onload="runTest();">
+<p id="display"></p>
+<iframe id="frame"></iframe>
+<iframe sandbox="allow-same-origin" id="sandBoxedFrame"></iframe>
+<div id="content" style="display: none"></div>
+<pre id="test"></pre>
+</body>
+</html>
diff --git a/dom/xslt/tests/mochitest/test_parameter.html b/dom/xslt/tests/mochitest/test_parameter.html
new file mode 100644
index 0000000000..32acd72fb2
--- /dev/null
+++ b/dom/xslt/tests/mochitest/test_parameter.html
@@ -0,0 +1,157 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for setParameter/getParameter</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none"><p id="number">123</p><p id="string">abc</p></div>
+<pre id="test">
+<script>
+ let processor = new XSLTProcessor();
+
+ processor.setParameter(null, "test", "hello");
+ is(processor.getParameter(null, "test"), "hello", "null namespace works");
+
+ processor.setParameter("foo", "bar", "foobar");
+ is(processor.getParameter("foo", "bar"), "foobar", "non-null namespace works");
+
+ processor.setParameter(null, "test", 123);
+ is(processor.getParameter(null, "test"), 123, "number value works");
+
+ processor.removeParameter(null, "test");
+ is(processor.getParameter(null, "test"), null, "removeParameter works");
+
+ is(processor.getParameter(null, "not-here"), null, "nonexistant parameter");
+
+ let parser = new DOMParser();
+ const style =
+ `<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"
+ xmlns:exslt="http://exslt.org/common">
+ <xsl:output method="html" />
+ <xsl:param name="test" />
+ <xsl:template match="/">
+ <p id="result"><xsl:value-of select="exslt:object-type($test)" /></p>
+ </xsl:template>
+ </xsl:stylesheet>`;
+ const styleDoc = parser.parseFromString(style, "text/xml");
+
+ const xml = `<root />`;
+ const sourceDoc = parser.parseFromString(xml, "text/xml");
+
+ function checkParameter(value, parameter) {
+ let valueType = typeof value;
+ switch (valueType) {
+ case "number":
+ case "boolean":
+ case "string":
+ is(typeof parameter, valueType, "Parameter has right type");
+ is(parameter, value, "Parameter has right value");
+ return;
+ case "object":
+ if (value instanceof Array || value instanceof NodeList) {
+ ok(parameter instanceof Array, "Parameter has right type");
+ ok(parameter.length == value.length &&
+ parameter.every((p, i) => value[i] === p),
+ "Parameter has right value");
+ return;
+ }
+
+ if (value instanceof Node) {
+ is(parameter, value, "Parameter has right value");
+ return;
+ }
+
+ if (value instanceof XPathResult) {
+ ok(parameter instanceof XPathResult, "Parameter has right type");
+ is(parameter.resultType, value.resultType, "Parameter has right inner type");
+ let valueProperty;
+ switch (value.resultType) {
+ case XPathResult.NUMBER_TYPE:
+ valueProperty = "numberValue";
+ break;
+ case XPathResult.BOOLEAN_TYPE:
+ valueProperty = "booleanValue";
+ break;
+ case XPathResult.STRING_TYPE:
+ valueProperty = "stringValue";
+ break;
+ case XPathResult.FIRST_ORDERED_NODE_TYPE:
+ valueProperty = "singleNodeValue";
+ break;
+ default:
+ ok(false, "Unexpected inner type");
+ return;
+ }
+ is(parameter[valueProperty], value[valueProperty], "Parameter has right value");
+ return;
+ }
+ }
+ ok(false, "Unexpected value");
+ }
+
+ function getXSLTType(value) {
+ let valueType = typeof value;
+ switch (valueType) {
+ case "number":
+ case "boolean":
+ case "string":
+ return valueType;
+ case "object":
+ if (value instanceof Array || value instanceof Node || value instanceof NodeList) {
+ return "node-set";
+ }
+ if (value instanceof XPathResult) {
+ switch (value.resultType) {
+ case XPathResult.NUMBER_TYPE:
+ return "number";
+ case XPathResult.BOOLEAN_TYPE:
+ return "boolean";
+ case XPathResult.STRING_TYPE:
+ return "string";
+ case XPathResult.FIRST_ORDERED_NODE_TYPE:
+ return "node-set";
+ }
+ }
+ }
+ ok(false, "Unexpected value");
+ throw new Error("Unexpected value");
+ }
+
+ function testParameter(value) {
+ let testProcessor = new XSLTProcessor();
+
+ testProcessor.setParameter(null, "test", value);
+ let parameter = testProcessor.getParameter(null, "test");
+
+ checkParameter(value, parameter);
+
+ testProcessor.importStylesheet(styleDoc);
+
+ let fragment = testProcessor.transformToFragment(sourceDoc, document);
+
+ is(
+ fragment.getElementById("result").textContent,
+ getXSLTType(value),
+ "Global parameter has right type in XSLT."
+ );
+ }
+
+ testParameter(123);
+ testParameter(true);
+ testParameter("abcd");
+ testParameter([document]);
+ testParameter(document.documentElement.childNodes);
+ testParameter(document);
+ testParameter(document.evaluate("id('number')", document, null, XPathResult.NUMBER_TYPE));
+ testParameter(document.evaluate("/", document, null, XPathResult.BOOLEAN_TYPE));
+ testParameter(document.evaluate("id('string')", document, null, XPathResult.STRING_TYPE));
+ testParameter(
+ document.evaluate("/", document, null, XPathResult.FIRST_ORDERED_NODE_TYPE)
+ );
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/xslt/tests/mochitest/test_sorting_invalid_lang.html b/dom/xslt/tests/mochitest/test_sorting_invalid_lang.html
new file mode 100644
index 0000000000..6044e43309
--- /dev/null
+++ b/dom/xslt/tests/mochitest/test_sorting_invalid_lang.html
@@ -0,0 +1,82 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1734679
+-->
+<head>
+ <title>Test sorting with invalid lang</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1734679">Mozilla Bug 1734679</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/**
+ * Test for Bug 1734679. Invalid language tags should not throw an error, and should
+ * fall back to the app's locale.
+ * */
+
+var xmldoc, xsltdoc;
+
+xmldoc = new DOMParser().parseFromString('<?xml version="1.0" encoding="UTF-8"?>\n\
+ <?xml-stylesheet type="text/xsl" href="sort-lang.xsl" ?>\n\
+ <list>\n\
+ <entry>\n\
+ <technology>CSS</technology>\n\
+ <term>text-direction</term>\n\
+ </entry>\n\
+ <entry>\n\
+ <technology>JavaScript</technology>\n\
+ <term>Array.prototype.sort</term>\n\
+ </entry>\n\
+ </list>\n\
+ ', "text/xml");
+
+xsltdoc = new DOMParser().parseFromString('<?xml version="1.0" encoding="UTF-8"?>\n\
+ <xsl:stylesheet version="1.0"\n\
+ xmlns:xsl="http://www.w3.org/1999/XSL/Transform">\n\
+ <xsl:template match="/">\n\
+ <html>\n\
+ <body>\n\
+ <table border="1">\n\
+ <tr bgcolor="#9acd32">\n\
+ <th>Technology</th>\n\
+ <th>Term</th>\n\
+ </tr>\n\
+ <xsl:for-each select="list/entry">\n\
+ <xsl:sort select="term" lang="$invalidLanguageTag"/>\n\
+ <tr>\n\
+ <td><xsl:value-of select="technology"/></td>\n\
+ <td><xsl:value-of select="term"/></td>\n\
+ </tr>\n\
+ </xsl:for-each>\n\
+ </table>\n\
+ </body>\n\
+ </html>\n\
+ </xsl:template>\n\
+ \n\
+ </xsl:stylesheet>\n\
+ ', "text/xml");
+
+var processor = new XSLTProcessor;
+processor.importStylesheet(xsltdoc);
+
+try
+{
+ var result = processor.transformToDocument(xmldoc);
+}
+catch (e)
+{
+ ok(false, "There was an error.");
+}
+ok(result && result instanceof Document, "XSLT transform should have created a document");
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/xslt/xml/moz.build b/dom/xslt/xml/moz.build
new file mode 100644
index 0000000000..a8f33bd350
--- /dev/null
+++ b/dom/xslt/xml/moz.build
@@ -0,0 +1,19 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+UNIFIED_SOURCES += [
+ "txXMLParser.cpp",
+ "txXMLUtils.cpp",
+]
+
+LOCAL_INCLUDES += [
+ "../base",
+ "../xpath",
+ "../xslt",
+ "/dom/base",
+]
+
+FINAL_LIBRARY = "xul"
diff --git a/dom/xslt/xml/txXMLParser.cpp b/dom/xslt/xml/txXMLParser.cpp
new file mode 100644
index 0000000000..b769d6f17e
--- /dev/null
+++ b/dom/xslt/xml/txXMLParser.cpp
@@ -0,0 +1,59 @@
+/* -*- 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 "txXMLParser.h"
+#include "txURIUtils.h"
+#include "txXPathTreeWalker.h"
+
+#include "mozilla/dom/Document.h"
+#include "nsSyncLoadService.h"
+#include "nsNetUtil.h"
+#include "nsIURI.h"
+
+using namespace mozilla::dom;
+
+nsresult txParseDocumentFromURI(const nsAString& aHref,
+ const txXPathNode& aLoader, nsAString& aErrMsg,
+ txXPathNode** aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = nullptr;
+ nsCOMPtr<nsIURI> documentURI;
+ nsresult rv = NS_NewURI(getter_AddRefs(documentURI), aHref);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ Document* loaderDocument = txXPathNativeNode::getDocument(aLoader);
+
+ nsCOMPtr<nsILoadGroup> loadGroup = loaderDocument->GetDocumentLoadGroup();
+
+ // For the system principal loaderUri will be null here, which is good
+ // since that means that chrome documents can load any uri.
+
+ // Raw pointer, we want the resulting txXPathNode to hold a reference to
+ // the document.
+ Document* theDocument = nullptr;
+ nsAutoSyncOperation sync(loaderDocument,
+ SyncOperationBehavior::eSuspendInput);
+ rv = nsSyncLoadService::LoadDocument(
+ documentURI, nsIContentPolicy::TYPE_INTERNAL_XMLHTTPREQUEST,
+ loaderDocument->NodePrincipal(),
+ nsILoadInfo::SEC_REQUIRE_CORS_INHERITS_SEC_CONTEXT, loadGroup,
+ loaderDocument->CookieJarSettings(), true,
+ loaderDocument->GetReferrerPolicy(), &theDocument);
+
+ if (NS_FAILED(rv)) {
+ aErrMsg.AppendLiteral("Document load of ");
+ aErrMsg.Append(aHref);
+ aErrMsg.AppendLiteral(" failed.");
+ return NS_FAILED(rv) ? rv : NS_ERROR_FAILURE;
+ }
+
+ *aResult = txXPathNativeNode::createXPathNode(theDocument);
+ if (!*aResult) {
+ NS_RELEASE(theDocument);
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
diff --git a/dom/xslt/xml/txXMLParser.h b/dom/xslt/xml/txXMLParser.h
new file mode 100644
index 0000000000..83f2221506
--- /dev/null
+++ b/dom/xslt/xml/txXMLParser.h
@@ -0,0 +1,27 @@
+/* -*- 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/. */
+
+#ifndef MITRE_XMLPARSER_H
+#define MITRE_XMLPARSER_H
+
+#include "txCore.h"
+
+class txXPathNode;
+
+/**
+ * API to load XML files into DOM datastructures.
+ * Parsing is either done by expat, or by expat via the syncloaderservice
+ */
+
+/**
+ * Parse a document from the aHref location, with referrer URI on behalf
+ * of the document aLoader.
+ */
+extern "C" nsresult txParseDocumentFromURI(const nsAString& aHref,
+ const txXPathNode& aLoader,
+ nsAString& aErrMsg,
+ txXPathNode** aResult);
+
+#endif
diff --git a/dom/xslt/xml/txXMLUtils.cpp b/dom/xslt/xml/txXMLUtils.cpp
new file mode 100644
index 0000000000..0688939ccb
--- /dev/null
+++ b/dom/xslt/xml/txXMLUtils.cpp
@@ -0,0 +1,164 @@
+/* -*- 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/. */
+
+/*
+ * XML utility classes
+ */
+
+#include "txXMLUtils.h"
+#include "nsString.h"
+#include "nsReadableUtils.h"
+#include "nsGkAtoms.h"
+#include "txStringUtils.h"
+#include "txNamespaceMap.h"
+#include "txXPathTreeWalker.h"
+#include "nsContentUtils.h"
+
+//------------------------------/
+//- Implementation of XMLUtils -/
+//------------------------------/
+
+// static
+nsresult XMLUtils::splitExpatName(const char16_t* aExpatName, nsAtom** aPrefix,
+ nsAtom** aLocalName, int32_t* aNameSpaceID) {
+ /**
+ * Expat can send the following:
+ * localName
+ * namespaceURI<separator>localName
+ * namespaceURI<separator>localName<separator>prefix
+ */
+
+ const char16_t* uriEnd = nullptr;
+ const char16_t* nameEnd = nullptr;
+ const char16_t* pos;
+ for (pos = aExpatName; *pos; ++pos) {
+ if (*pos == kExpatSeparatorChar) {
+ if (uriEnd) {
+ nameEnd = pos;
+ } else {
+ uriEnd = pos;
+ }
+ }
+ }
+
+ const char16_t* nameStart;
+ if (uriEnd) {
+ *aNameSpaceID = txNamespaceManager::getNamespaceID(
+ nsDependentSubstring(aExpatName, uriEnd));
+ if (*aNameSpaceID == kNameSpaceID_Unknown) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nameStart = (uriEnd + 1);
+ if (nameEnd) {
+ const char16_t* prefixStart = nameEnd + 1;
+ *aPrefix = NS_Atomize(Substring(prefixStart, pos)).take();
+ if (!*aPrefix) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ } else {
+ nameEnd = pos;
+ *aPrefix = nullptr;
+ }
+ } else {
+ *aNameSpaceID = kNameSpaceID_None;
+ nameStart = aExpatName;
+ nameEnd = pos;
+ *aPrefix = nullptr;
+ }
+
+ *aLocalName = NS_Atomize(Substring(nameStart, nameEnd)).take();
+
+ return *aLocalName ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
+}
+
+nsresult XMLUtils::splitQName(const nsAString& aName, nsAtom** aPrefix,
+ nsAtom** aLocalName) {
+ const char16_t* colon;
+ bool valid = XMLUtils::isValidQName(aName, &colon);
+ if (!valid) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (colon) {
+ const char16_t* end;
+ aName.EndReading(end);
+
+ *aPrefix = NS_Atomize(Substring(aName.BeginReading(), colon)).take();
+ *aLocalName = NS_Atomize(Substring(colon + 1, end)).take();
+ } else {
+ *aPrefix = nullptr;
+ *aLocalName = NS_Atomize(aName).take();
+ }
+
+ return NS_OK;
+}
+
+/**
+ * Returns true if the given string has only whitespace characters
+ */
+bool XMLUtils::isWhitespace(const nsAString& aText) {
+ nsString::const_char_iterator start, end;
+ aText.BeginReading(start);
+ aText.EndReading(end);
+ for (; start != end; ++start) {
+ if (!isWhitespace(*start)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+/**
+ * Normalizes the value of a XML processing instruction
+ **/
+void XMLUtils::normalizePIValue(nsAString& piValue) {
+ nsAutoString origValue(piValue);
+ uint32_t origLength = origValue.Length();
+ uint32_t conversionLoop = 0;
+ char16_t prevCh = 0;
+ piValue.Truncate();
+
+ while (conversionLoop < origLength) {
+ char16_t ch = origValue.CharAt(conversionLoop);
+ switch (ch) {
+ case '>': {
+ if (prevCh == '?') {
+ piValue.Append(char16_t(' '));
+ }
+ break;
+ }
+ default: {
+ break;
+ }
+ }
+ piValue.Append(ch);
+ prevCh = ch;
+ ++conversionLoop;
+ }
+}
+
+// static
+bool XMLUtils::isValidQName(const nsAString& aQName, const char16_t** aColon) {
+ return NS_SUCCEEDED(nsContentUtils::CheckQName(aQName, true, aColon));
+}
+
+// static
+bool XMLUtils::getXMLSpacePreserve(const txXPathNode& aNode) {
+ nsAutoString value;
+ txXPathTreeWalker walker(aNode);
+ do {
+ if (walker.getAttr(nsGkAtoms::space, kNameSpaceID_XML, value)) {
+ if (TX_StringEqualsAtom(value, nsGkAtoms::preserve)) {
+ return true;
+ }
+ if (TX_StringEqualsAtom(value, nsGkAtoms::_default)) {
+ return false;
+ }
+ }
+ } while (walker.moveToParent());
+
+ return false;
+}
diff --git a/dom/xslt/xml/txXMLUtils.h b/dom/xslt/xml/txXMLUtils.h
new file mode 100644
index 0000000000..e7723fbaca
--- /dev/null
+++ b/dom/xslt/xml/txXMLUtils.h
@@ -0,0 +1,75 @@
+/* -*- 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/. */
+
+/**
+ * An XML Utility class
+ **/
+
+#ifndef MITRE_XMLUTILS_H
+#define MITRE_XMLUTILS_H
+
+#include "txCore.h"
+#include "nsDependentSubstring.h"
+#include "txXPathNode.h"
+
+#define kExpatSeparatorChar 0xFFFF
+
+extern "C" int MOZ_XMLIsLetter(const char* ptr);
+extern "C" int MOZ_XMLIsNCNameChar(const char* ptr);
+
+class nsAtom;
+
+class XMLUtils {
+ public:
+ static nsresult splitExpatName(const char16_t* aExpatName, nsAtom** aPrefix,
+ nsAtom** aLocalName, int32_t* aNameSpaceID);
+ static nsresult splitQName(const nsAString& aName, nsAtom** aPrefix,
+ nsAtom** aLocalName);
+
+ /*
+ * Returns true if the given character is whitespace.
+ */
+ static bool isWhitespace(const char16_t& aChar) {
+ return (aChar <= ' ' &&
+ (aChar == ' ' || aChar == '\r' || aChar == '\n' || aChar == '\t'));
+ }
+
+ /**
+ * Returns true if the given string has only whitespace characters
+ */
+ static bool isWhitespace(const nsAString& aText);
+
+ /**
+ * Normalizes the value of a XML processingInstruction
+ **/
+ static void normalizePIValue(nsAString& attValue);
+
+ /**
+ * Returns true if the given string is a valid XML QName
+ */
+ static bool isValidQName(const nsAString& aQName, const char16_t** aColon);
+
+ /**
+ * Returns true if the given character represents an Alpha letter
+ */
+ static bool isLetter(char16_t aChar) {
+ return !!MOZ_XMLIsLetter(reinterpret_cast<const char*>(&aChar));
+ }
+
+ /**
+ * Returns true if the given character is an allowable NCName character
+ */
+ static bool isNCNameChar(char16_t aChar) {
+ return !!MOZ_XMLIsNCNameChar(reinterpret_cast<const char*>(&aChar));
+ }
+
+ /*
+ * Walks up the document tree and returns true if the closest xml:space
+ * attribute is "preserve"
+ */
+ static bool getXMLSpacePreserve(const txXPathNode& aNode);
+};
+
+#endif
diff --git a/dom/xslt/xpath/XPathEvaluator.cpp b/dom/xslt/xpath/XPathEvaluator.cpp
new file mode 100644
index 0000000000..269ea98ff6
--- /dev/null
+++ b/dom/xslt/xpath/XPathEvaluator.cpp
@@ -0,0 +1,187 @@
+/* -*- 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 "mozilla/dom/XPathEvaluator.h"
+
+#include <utility>
+
+#include "XPathResult.h"
+#include "mozilla/UniquePtrExtensions.h"
+#include "mozilla/dom/BindingUtils.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/XPathEvaluatorBinding.h"
+#include "mozilla/dom/XPathExpression.h"
+#include "mozilla/dom/XPathNSResolverBinding.h"
+#include "nsAtom.h"
+#include "nsCOMPtr.h"
+#include "nsContentCID.h"
+#include "nsContentUtils.h"
+#include "nsDOMString.h"
+#include "nsError.h"
+#include "nsNameSpaceManager.h"
+#include "txExpr.h"
+#include "txExprParser.h"
+#include "txIXPathContext.h"
+#include "txURIUtils.h"
+
+namespace mozilla::dom {
+
+// txIParseContext implementation
+class XPathEvaluatorParseContext : public txIParseContext {
+ public:
+ XPathEvaluatorParseContext(XPathNSResolver* aResolver, bool aIsCaseSensitive)
+ : mResolver(aResolver),
+ mResolverNode(nullptr),
+ mLastError(NS_OK),
+ mIsCaseSensitive(aIsCaseSensitive) {}
+ XPathEvaluatorParseContext(nsINode* aResolver, bool aIsCaseSensitive)
+ : mResolver(nullptr),
+ mResolverNode(aResolver),
+ mLastError(NS_OK),
+ mIsCaseSensitive(aIsCaseSensitive) {}
+
+ nsresult getError() { return mLastError; }
+
+ nsresult resolveNamespacePrefix(nsAtom* aPrefix, int32_t& aID) override;
+ nsresult resolveFunctionCall(nsAtom* aName, int32_t aID,
+ FunctionCall** aFunction) override;
+ bool caseInsensitiveNameTests() override;
+ void SetErrorOffset(uint32_t aOffset) override;
+
+ private:
+ XPathNSResolver* mResolver;
+ nsINode* mResolverNode;
+ nsresult mLastError;
+ bool mIsCaseSensitive;
+};
+
+XPathEvaluator::XPathEvaluator(Document* aDocument) : mDocument(aDocument) {}
+
+XPathEvaluator::~XPathEvaluator() = default;
+
+UniquePtr<XPathExpression> XPathEvaluator::CreateExpression(
+ const nsAString& aExpression, XPathNSResolver* aResolver,
+ ErrorResult& aRv) {
+ nsCOMPtr<Document> doc(mDocument);
+ XPathEvaluatorParseContext pContext(aResolver,
+ !(doc && doc->IsHTMLDocument()));
+ return CreateExpression(aExpression, &pContext, doc, aRv);
+}
+
+UniquePtr<XPathExpression> XPathEvaluator::CreateExpression(
+ const nsAString& aExpression, nsINode* aResolver, ErrorResult& aRv) {
+ nsCOMPtr<Document> doc(mDocument);
+ XPathEvaluatorParseContext pContext(aResolver,
+ !(doc && doc->IsHTMLDocument()));
+ return CreateExpression(aExpression, &pContext, doc, aRv);
+}
+
+UniquePtr<XPathExpression> XPathEvaluator::CreateExpression(
+ const nsAString& aExpression, txIParseContext* aContext,
+ Document* aDocument, ErrorResult& aRv) {
+ if (!mRecycler) {
+ mRecycler = new txResultRecycler;
+ }
+
+ UniquePtr<Expr> expression;
+ aRv = txExprParser::createExpr(PromiseFlatString(aExpression), aContext,
+ getter_Transfers(expression));
+ if (aRv.Failed()) {
+ if (!aRv.ErrorCodeIs(NS_ERROR_DOM_NAMESPACE_ERR)) {
+ aRv.SuppressException();
+ aRv.ThrowSyntaxError("The expression is not a legal expression");
+ }
+
+ return nullptr;
+ }
+
+ return MakeUnique<XPathExpression>(std::move(expression), mRecycler,
+ aDocument);
+}
+
+bool XPathEvaluator::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto,
+ JS::MutableHandle<JSObject*> aReflector) {
+ return dom::XPathEvaluator_Binding::Wrap(aCx, this, aGivenProto, aReflector);
+}
+
+/* static */
+UniquePtr<XPathEvaluator> XPathEvaluator::Constructor(
+ const GlobalObject& aGlobal) {
+ return MakeUnique<XPathEvaluator>(nullptr);
+}
+
+already_AddRefed<XPathResult> XPathEvaluator::Evaluate(
+ JSContext* aCx, const nsAString& aExpression, nsINode& aContextNode,
+ XPathNSResolver* aResolver, uint16_t aType, JS::Handle<JSObject*> aResult,
+ ErrorResult& rv) {
+ UniquePtr<XPathExpression> expression(
+ CreateExpression(aExpression, aResolver, rv));
+ if (rv.Failed()) {
+ return nullptr;
+ }
+ return expression->Evaluate(aCx, aContextNode, aType, aResult, rv);
+}
+
+/*
+ * Implementation of txIParseContext private to XPathEvaluator, based on a
+ * XPathNSResolver
+ */
+
+nsresult XPathEvaluatorParseContext::resolveNamespacePrefix(nsAtom* aPrefix,
+ int32_t& aID) {
+ aID = kNameSpaceID_Unknown;
+
+ if (!mResolver && !mResolverNode) {
+ return NS_ERROR_DOM_NAMESPACE_ERR;
+ }
+
+ nsAutoString prefix;
+ if (aPrefix) {
+ aPrefix->ToString(prefix);
+ }
+
+ nsAutoString ns;
+ if (mResolver) {
+ ErrorResult rv;
+ mResolver->LookupNamespaceURI(prefix, ns, rv);
+ if (rv.Failed()) {
+ return rv.StealNSResult();
+ }
+ } else {
+ if (aPrefix == nsGkAtoms::xml) {
+ ns.AssignLiteral("http://www.w3.org/XML/1998/namespace");
+ } else {
+ mResolverNode->LookupNamespaceURI(prefix, ns);
+ }
+ }
+
+ if (DOMStringIsNull(ns)) {
+ return NS_ERROR_DOM_NAMESPACE_ERR;
+ }
+
+ if (ns.IsEmpty()) {
+ aID = kNameSpaceID_None;
+
+ return NS_OK;
+ }
+
+ // get the namespaceID for the URI
+ return nsNameSpaceManager::GetInstance()->RegisterNameSpace(ns, aID);
+}
+
+nsresult XPathEvaluatorParseContext::resolveFunctionCall(nsAtom* aName,
+ int32_t aID,
+ FunctionCall** aFn) {
+ return NS_ERROR_XPATH_UNKNOWN_FUNCTION;
+}
+
+bool XPathEvaluatorParseContext::caseInsensitiveNameTests() {
+ return !mIsCaseSensitive;
+}
+
+void XPathEvaluatorParseContext::SetErrorOffset(uint32_t aOffset) {}
+
+} // namespace mozilla::dom
diff --git a/dom/xslt/xpath/XPathEvaluator.h b/dom/xslt/xpath/XPathEvaluator.h
new file mode 100644
index 0000000000..dbf79373b9
--- /dev/null
+++ b/dom/xslt/xpath/XPathEvaluator.h
@@ -0,0 +1,65 @@
+/* -*- 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/. */
+
+#ifndef mozilla_dom_XPathEvaluator_h
+#define mozilla_dom_XPathEvaluator_h
+
+#include "mozilla/dom/NonRefcountedDOMObject.h"
+#include "nsString.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/dom/Document.h"
+
+class nsINode;
+class txIParseContext;
+class txResultRecycler;
+
+namespace mozilla {
+class ErrorResult;
+
+namespace dom {
+
+class GlobalObject;
+class XPathExpression;
+class XPathNSResolver;
+class XPathResult;
+
+/**
+ * A class for evaluating an XPath expression string
+ */
+class XPathEvaluator final : public NonRefcountedDOMObject {
+ public:
+ explicit XPathEvaluator(Document* aDocument = nullptr);
+ ~XPathEvaluator();
+
+ // WebIDL API
+ bool WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto,
+ JS::MutableHandle<JSObject*> aReflector);
+ Document* GetParentObject() { return mDocument; }
+ static UniquePtr<XPathEvaluator> Constructor(const GlobalObject& aGlobal);
+ UniquePtr<XPathExpression> CreateExpression(const nsAString& aExpression,
+ XPathNSResolver* aResolver,
+ ErrorResult& rv);
+ UniquePtr<XPathExpression> CreateExpression(const nsAString& aExpression,
+ nsINode* aResolver,
+ ErrorResult& aRv);
+ UniquePtr<XPathExpression> CreateExpression(const nsAString& aExpression,
+ txIParseContext* aContext,
+ Document* aDocument,
+ ErrorResult& aRv);
+ nsINode* CreateNSResolver(nsINode& aNodeResolver) { return &aNodeResolver; }
+ already_AddRefed<XPathResult> Evaluate(
+ JSContext* aCx, const nsAString& aExpression, nsINode& aContextNode,
+ XPathNSResolver* aResolver, uint16_t aType, JS::Handle<JSObject*> aResult,
+ ErrorResult& rv);
+
+ private:
+ WeakPtr<Document> mDocument;
+ RefPtr<txResultRecycler> mRecycler;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif /* mozilla_dom_XPathEvaluator_h */
diff --git a/dom/xslt/xpath/XPathExpression.cpp b/dom/xslt/xpath/XPathExpression.cpp
new file mode 100644
index 0000000000..0706763d79
--- /dev/null
+++ b/dom/xslt/xpath/XPathExpression.cpp
@@ -0,0 +1,210 @@
+/* -*- 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 "XPathExpression.h"
+
+#include <utility>
+
+#include "XPathResult.h"
+#include "mozilla/dom/BindingUtils.h"
+#include "mozilla/dom/Text.h"
+#include "mozilla/dom/XPathResultBinding.h"
+#include "nsError.h"
+#include "nsINode.h"
+#include "txExpr.h"
+#include "txExprResult.h"
+#include "txIXPathContext.h"
+#include "txURIUtils.h"
+#include "txXPathTreeWalker.h"
+
+namespace mozilla::dom {
+
+class EvalContextImpl : public txIEvalContext {
+ public:
+ EvalContextImpl(const txXPathNode& aContextNode, uint32_t aContextPosition,
+ uint32_t aContextSize, txResultRecycler* aRecycler)
+ : mContextNode(aContextNode),
+ mContextPosition(aContextPosition),
+ mContextSize(aContextSize),
+ mLastError(NS_OK),
+ mRecycler(aRecycler) {}
+
+ nsresult getError() { return mLastError; }
+
+ TX_DECL_EVAL_CONTEXT;
+
+ private:
+ const txXPathNode& mContextNode;
+ uint32_t mContextPosition;
+ uint32_t mContextSize;
+ nsresult mLastError;
+ RefPtr<txResultRecycler> mRecycler;
+};
+
+XPathExpression::XPathExpression(UniquePtr<Expr>&& aExpression,
+ txResultRecycler* aRecycler,
+ Document* aDocument)
+ : mExpression(std::move(aExpression)),
+ mRecycler(aRecycler),
+ mDocument(do_GetWeakReference(aDocument)),
+ mCheckDocument(aDocument != nullptr) {}
+
+XPathExpression::~XPathExpression() = default;
+
+already_AddRefed<XPathResult> XPathExpression::EvaluateWithContext(
+ JSContext* aCx, nsINode& aContextNode, uint32_t aContextPosition,
+ uint32_t aContextSize, uint16_t aType, JS::Handle<JSObject*> aInResult,
+ ErrorResult& aRv) {
+ RefPtr<XPathResult> inResult;
+ if (aInResult) {
+ nsresult rv = UNWRAP_OBJECT(XPathResult, aInResult, inResult);
+ if (NS_FAILED(rv) && rv != NS_ERROR_XPC_BAD_CONVERT_JS) {
+ aRv.Throw(rv);
+ return nullptr;
+ }
+ }
+
+ return EvaluateWithContext(aContextNode, aContextPosition, aContextSize,
+ aType, inResult, aRv);
+}
+
+already_AddRefed<XPathResult> XPathExpression::EvaluateWithContext(
+ nsINode& aContextNode, uint32_t aContextPosition, uint32_t aContextSize,
+ uint16_t aType, XPathResult* aInResult, ErrorResult& aRv) {
+ if (aContextPosition > aContextSize) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ if (aType > XPathResult_Binding::FIRST_ORDERED_NODE_TYPE) {
+ aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+ return nullptr;
+ }
+
+ if (!nsContentUtils::LegacyIsCallerNativeCode() &&
+ !nsContentUtils::CanCallerAccess(&aContextNode)) {
+ aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return nullptr;
+ }
+
+ if (mCheckDocument) {
+ nsCOMPtr<Document> doc = do_QueryReferent(mDocument);
+ if (doc != aContextNode.OwnerDoc()) {
+ aRv.Throw(NS_ERROR_DOM_WRONG_DOCUMENT_ERR);
+ return nullptr;
+ }
+ }
+
+ uint16_t nodeType = aContextNode.NodeType();
+
+ if (nodeType == nsINode::TEXT_NODE ||
+ nodeType == nsINode::CDATA_SECTION_NODE) {
+ Text* textNode = aContextNode.GetAsText();
+ MOZ_ASSERT(textNode);
+
+ uint32_t textLength = textNode->Length();
+ if (textLength == 0) {
+ aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+ return nullptr;
+ }
+
+ // XXX Need to get logical XPath text node for CDATASection
+ // and Text nodes.
+ } else if (nodeType != nsINode::DOCUMENT_NODE &&
+ nodeType != nsINode::ELEMENT_NODE &&
+ nodeType != nsINode::ATTRIBUTE_NODE &&
+ nodeType != nsINode::COMMENT_NODE &&
+ nodeType != nsINode::PROCESSING_INSTRUCTION_NODE) {
+ aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+ return nullptr;
+ }
+
+ UniquePtr<txXPathNode> contextNode(
+ txXPathNativeNode::createXPathNode(&aContextNode));
+ if (!contextNode) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ EvalContextImpl eContext(*contextNode, aContextPosition, aContextSize,
+ mRecycler);
+ RefPtr<txAExprResult> exprResult;
+ aRv = mExpression->evaluate(&eContext, getter_AddRefs(exprResult));
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ uint16_t resultType = aType;
+ if (aType == XPathResult::ANY_TYPE) {
+ short exprResultType = exprResult->getResultType();
+ switch (exprResultType) {
+ 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;
+ case txAExprResult::RESULT_TREE_FRAGMENT:
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+ }
+
+ RefPtr<XPathResult> xpathResult = aInResult;
+ if (!xpathResult) {
+ xpathResult = new XPathResult(&aContextNode);
+ }
+
+ xpathResult->SetExprResult(exprResult, resultType, &aContextNode, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ return xpathResult.forget();
+}
+
+/*
+ * Implementation of the txIEvalContext private to XPathExpression
+ * EvalContextImpl bases on only one context node and no variables
+ */
+
+nsresult EvalContextImpl::getVariable(int32_t aNamespace, nsAtom* aLName,
+ txAExprResult*& aResult) {
+ aResult = 0;
+ return NS_ERROR_INVALID_ARG;
+}
+
+nsresult EvalContextImpl::isStripSpaceAllowed(const txXPathNode& aNode,
+ bool& aAllowed) {
+ aAllowed = false;
+
+ return NS_OK;
+}
+
+void* EvalContextImpl::getPrivateContext() {
+ // we don't have a private context here.
+ return nullptr;
+}
+
+txResultRecycler* EvalContextImpl::recycler() { return mRecycler; }
+
+void EvalContextImpl::receiveError(const nsAString& aMsg, nsresult aRes) {
+ mLastError = aRes;
+ // forward aMsg to console service?
+}
+
+const txXPathNode& EvalContextImpl::getContextNode() { return mContextNode; }
+
+uint32_t EvalContextImpl::size() { return mContextSize; }
+
+uint32_t EvalContextImpl::position() { return mContextPosition; }
+
+} // namespace mozilla::dom
diff --git a/dom/xslt/xpath/XPathExpression.h b/dom/xslt/xpath/XPathExpression.h
new file mode 100644
index 0000000000..e041649395
--- /dev/null
+++ b/dom/xslt/xpath/XPathExpression.h
@@ -0,0 +1,68 @@
+/* -*- 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/. */
+
+#ifndef mozilla_dom_XPathExpression_h
+#define mozilla_dom_XPathExpression_h
+
+#include "nsCycleCollectionParticipant.h"
+#include "nsIWeakReferenceUtils.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/dom/NonRefcountedDOMObject.h"
+#include "mozilla/dom/XPathExpressionBinding.h"
+#include "mozilla/UniquePtr.h"
+
+class Expr;
+
+class nsINode;
+class txResultRecycler;
+
+namespace mozilla::dom {
+
+class Document;
+class XPathResult;
+
+/**
+ * A class for evaluating an XPath expression string
+ */
+class XPathExpression final : public NonRefcountedDOMObject {
+ public:
+ XPathExpression(mozilla::UniquePtr<Expr>&& aExpression,
+ txResultRecycler* aRecycler, Document* aDocument);
+ ~XPathExpression();
+
+ bool WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto,
+ JS::MutableHandle<JSObject*> aReflector) {
+ return XPathExpression_Binding::Wrap(aCx, this, aGivenProto, aReflector);
+ }
+
+ already_AddRefed<XPathResult> Evaluate(JSContext* aCx, nsINode& aContextNode,
+ uint16_t aType,
+ JS::Handle<JSObject*> aInResult,
+ ErrorResult& aRv) {
+ return EvaluateWithContext(aCx, aContextNode, 1, 1, aType, aInResult, aRv);
+ }
+ already_AddRefed<XPathResult> EvaluateWithContext(
+ JSContext* aCx, nsINode& aContextNode, uint32_t aContextPosition,
+ uint32_t aContextSize, uint16_t aType, JS::Handle<JSObject*> aInResult,
+ ErrorResult& aRv);
+ already_AddRefed<XPathResult> Evaluate(nsINode& aContextNode, uint16_t aType,
+ XPathResult* aInResult,
+ ErrorResult& aRv) {
+ return EvaluateWithContext(aContextNode, 1, 1, aType, aInResult, aRv);
+ }
+ already_AddRefed<XPathResult> EvaluateWithContext(
+ nsINode& aContextNode, uint32_t aContextPosition, uint32_t aContextSize,
+ uint16_t aType, XPathResult* aInResult, ErrorResult& aRv);
+
+ private:
+ mozilla::UniquePtr<Expr> mExpression;
+ RefPtr<txResultRecycler> mRecycler;
+ nsWeakPtr mDocument;
+ bool mCheckDocument;
+};
+
+} // namespace mozilla::dom
+
+#endif /* mozilla_dom_XPathExpression_h */
diff --git a/dom/xslt/xpath/XPathResult.cpp b/dom/xslt/xpath/XPathResult.cpp
new file mode 100644
index 0000000000..091a83528f
--- /dev/null
+++ b/dom/xslt/xpath/XPathResult.cpp
@@ -0,0 +1,264 @@
+/* -*- 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 "XPathResult.h"
+#include "txExprResult.h"
+#include "txNodeSet.h"
+#include "nsError.h"
+#include "mozilla/dom/Attr.h"
+#include "mozilla/dom/Element.h"
+#include "nsDOMString.h"
+#include "txXPathTreeWalker.h"
+#include "nsCycleCollectionParticipant.h"
+#include "mozilla/dom/XPathResultBinding.h"
+
+namespace mozilla::dom {
+
+XPathResult::XPathResult(nsINode* aParent)
+ : mParent(aParent),
+ mDocument(nullptr),
+ mCurrentPos(0),
+ mResultType(ANY_TYPE),
+ mInvalidIteratorState(true),
+ mBooleanResult(false),
+ mNumberResult(0) {}
+
+XPathResult::XPathResult(const XPathResult& aResult)
+ : mParent(aResult.mParent),
+ mResult(aResult.mResult),
+ mResultNodes(aResult.mResultNodes.Clone()),
+ mDocument(aResult.mDocument),
+ mContextNode(aResult.mContextNode),
+ mCurrentPos(0),
+ mResultType(aResult.mResultType),
+ mInvalidIteratorState(aResult.mInvalidIteratorState),
+ mBooleanResult(aResult.mBooleanResult),
+ mNumberResult(aResult.mNumberResult),
+ mStringResult(aResult.mStringResult) {
+ if (mDocument) {
+ mDocument->AddMutationObserver(this);
+ }
+}
+
+XPathResult::~XPathResult() { RemoveObserver(); }
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(XPathResult)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(XPathResult)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mParent) { tmp->RemoveObserver(); }
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocument)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(XPathResult)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParent)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocument)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mResultNodes)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(XPathResult)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(XPathResult)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(XPathResult)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsIMutationObserver)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+JSObject* XPathResult::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return XPathResult_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+void XPathResult::RemoveObserver() {
+ if (mDocument) {
+ mDocument->RemoveMutationObserver(this);
+ }
+}
+
+nsINode* XPathResult::IterateNext(ErrorResult& aRv) {
+ if (!isIterator()) {
+ aRv.ThrowTypeError("Result is not an iterator");
+ return nullptr;
+ }
+
+ if (mDocument) {
+ mDocument->FlushPendingNotifications(FlushType::Content);
+ }
+
+ if (mInvalidIteratorState) {
+ aRv.ThrowInvalidStateError(
+ "The document has been mutated since the result was returned");
+ return nullptr;
+ }
+
+ return mResultNodes.SafeElementAt(mCurrentPos++);
+}
+
+void XPathResult::NodeWillBeDestroyed(nsINode* aNode) {
+ nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this);
+ // Set to null to avoid unregistring unnecessarily
+ mDocument = nullptr;
+ Invalidate(aNode->IsContent() ? aNode->AsContent() : nullptr);
+}
+
+void XPathResult::CharacterDataChanged(nsIContent* aContent,
+ const CharacterDataChangeInfo&) {
+ Invalidate(aContent);
+}
+
+void XPathResult::AttributeChanged(Element* aElement, int32_t aNameSpaceID,
+ nsAtom* aAttribute, int32_t aModType,
+ const nsAttrValue* aOldValue) {
+ Invalidate(aElement);
+}
+
+void XPathResult::ContentAppended(nsIContent* aFirstNewContent) {
+ Invalidate(aFirstNewContent->GetParent());
+}
+
+void XPathResult::ContentInserted(nsIContent* aChild) {
+ Invalidate(aChild->GetParent());
+}
+
+void XPathResult::ContentRemoved(nsIContent* aChild,
+ nsIContent* aPreviousSibling) {
+ Invalidate(aChild->GetParent());
+}
+
+void XPathResult::SetExprResult(txAExprResult* aExprResult,
+ uint16_t aResultType, nsINode* aContextNode,
+ ErrorResult& aRv) {
+ MOZ_ASSERT(aExprResult);
+
+ if ((isSnapshot(aResultType) || isIterator(aResultType) ||
+ isNode(aResultType)) &&
+ aExprResult->getResultType() != txAExprResult::NODESET) {
+ // The DOM spec doesn't really say what should happen when reusing an
+ // XPathResult and an error is thrown. Let's not touch the XPathResult
+ // in that case.
+ aRv.ThrowTypeError("Result type mismatch");
+ return;
+ }
+
+ mResultType = aResultType;
+ mContextNode = do_GetWeakReference(aContextNode);
+
+ if (mDocument) {
+ mDocument->RemoveMutationObserver(this);
+ mDocument = nullptr;
+ }
+
+ mResultNodes.Clear();
+
+ // XXX This will keep the recycler alive, should we clear it?
+ mResult = aExprResult;
+ switch (mResultType) {
+ case BOOLEAN_TYPE: {
+ mBooleanResult = mResult->booleanValue();
+ break;
+ }
+ case NUMBER_TYPE: {
+ mNumberResult = mResult->numberValue();
+ break;
+ }
+ case STRING_TYPE: {
+ mResult->stringValue(mStringResult);
+ break;
+ }
+ default: {
+ MOZ_ASSERT(isNode() || isIterator() || isSnapshot());
+ }
+ }
+
+ if (aExprResult->getResultType() == txAExprResult::NODESET) {
+ txNodeSet* nodeSet = static_cast<txNodeSet*>(aExprResult);
+ int32_t i, count = nodeSet->size();
+ for (i = 0; i < count; ++i) {
+ nsINode* node = txXPathNativeNode::getNode(nodeSet->get(i));
+ mResultNodes.AppendElement(node);
+ }
+
+ if (count > 0) {
+ mResult = nullptr;
+ }
+ }
+
+ if (!isIterator()) {
+ return;
+ }
+
+ mCurrentPos = 0;
+ mInvalidIteratorState = false;
+
+ if (!mResultNodes.IsEmpty()) {
+ // If we support the document() function in DOM-XPath we need to
+ // observe all documents that we have resultnodes in.
+ mDocument = mResultNodes[0]->OwnerDoc();
+ NS_ASSERTION(mDocument, "We need a document!");
+ if (mDocument) {
+ mDocument->AddMutationObserver(this);
+ }
+ }
+}
+
+void XPathResult::Invalidate(const nsIContent* aChangeRoot) {
+ nsCOMPtr<nsINode> contextNode = do_QueryReferent(mContextNode);
+ // If the changes are happening in a different anonymous trees, no
+ // invalidation should happen.
+ if (contextNode && aChangeRoot &&
+ !nsContentUtils::IsInSameAnonymousTree(contextNode, aChangeRoot)) {
+ return;
+ }
+
+ mInvalidIteratorState = true;
+ // Make sure nulling out mDocument is the last thing we do.
+ if (mDocument) {
+ mDocument->RemoveMutationObserver(this);
+ mDocument = nullptr;
+ }
+}
+
+nsresult XPathResult::GetExprResult(txAExprResult** aExprResult) {
+ if (isIterator() && mInvalidIteratorState) {
+ return NS_ERROR_DOM_INVALID_STATE_ERR;
+ }
+
+ if (mResult) {
+ NS_ADDREF(*aExprResult = mResult);
+
+ return NS_OK;
+ }
+
+ if (mResultNodes.IsEmpty()) {
+ return NS_ERROR_DOM_INVALID_STATE_ERR;
+ }
+
+ RefPtr<txNodeSet> nodeSet = new txNodeSet(nullptr);
+ uint32_t i, count = mResultNodes.Length();
+ for (i = 0; i < count; ++i) {
+ UniquePtr<txXPathNode> node(
+ txXPathNativeNode::createXPathNode(mResultNodes[i]));
+ if (!node) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ nodeSet->append(*node);
+ }
+
+ NS_ADDREF(*aExprResult = nodeSet);
+
+ return NS_OK;
+}
+
+already_AddRefed<XPathResult> XPathResult::Clone(ErrorResult& aError) {
+ if (isIterator() && mInvalidIteratorState) {
+ aError = NS_ERROR_DOM_INVALID_STATE_ERR;
+ return nullptr;
+ }
+
+ return do_AddRef(new XPathResult(*this));
+}
+
+} // namespace mozilla::dom
diff --git a/dom/xslt/xpath/XPathResult.h b/dom/xslt/xpath/XPathResult.h
new file mode 100644
index 0000000000..e073666a84
--- /dev/null
+++ b/dom/xslt/xpath/XPathResult.h
@@ -0,0 +1,156 @@
+/* -*- 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/. */
+
+#ifndef mozilla_dom_XPathResult_h
+#define mozilla_dom_XPathResult_h
+
+#include "nsStubMutationObserver.h"
+#include "nsCOMPtr.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsIWeakReferenceUtils.h"
+#include "nsTArray.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/ErrorResult.h"
+#include "nsString.h"
+#include "nsWrapperCache.h"
+#include "nsINode.h"
+
+class txAExprResult;
+
+namespace mozilla::dom {
+
+/**
+ * A class for evaluating an XPath expression string
+ */
+class XPathResult final : public nsStubMutationObserver, public nsWrapperCache {
+ ~XPathResult();
+
+ public:
+ explicit XPathResult(nsINode* aParent);
+ XPathResult(const XPathResult& aResult);
+
+ enum {
+ ANY_TYPE = 0U,
+ NUMBER_TYPE = 1U,
+ STRING_TYPE = 2U,
+ BOOLEAN_TYPE = 3U,
+ UNORDERED_NODE_ITERATOR_TYPE = 4U,
+ ORDERED_NODE_ITERATOR_TYPE = 5U,
+ UNORDERED_NODE_SNAPSHOT_TYPE = 6U,
+ ORDERED_NODE_SNAPSHOT_TYPE = 7U,
+ ANY_UNORDERED_NODE_TYPE = 8U,
+ FIRST_ORDERED_NODE_TYPE = 9U
+ };
+
+ // nsISupports interface
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(XPathResult)
+
+ virtual JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+ nsINode* GetParentObject() const { return mParent; }
+ uint16_t ResultType() const { return mResultType; }
+ double GetNumberValue(ErrorResult& aRv) const {
+ if (mResultType != NUMBER_TYPE) {
+ aRv.ThrowTypeError("Result is not a number");
+ return 0;
+ }
+
+ return mNumberResult;
+ }
+ void GetStringValue(nsAString& aStringValue, ErrorResult& aRv) const {
+ if (mResultType != STRING_TYPE) {
+ aRv.ThrowTypeError("Result is not a string");
+ return;
+ }
+
+ aStringValue = mStringResult;
+ }
+ bool GetBooleanValue(ErrorResult& aRv) const {
+ if (mResultType != BOOLEAN_TYPE) {
+ aRv.ThrowTypeError("Result is not a boolean");
+ return false;
+ }
+
+ return mBooleanResult;
+ }
+ nsINode* GetSingleNodeValue(ErrorResult& aRv) const {
+ if (!isNode()) {
+ aRv.ThrowTypeError("Result is not a node");
+ return nullptr;
+ }
+
+ return mResultNodes.SafeElementAt(0);
+ }
+ bool InvalidIteratorState() const {
+ return isIterator() && mInvalidIteratorState;
+ }
+ uint32_t GetSnapshotLength(ErrorResult& aRv) const {
+ if (!isSnapshot()) {
+ aRv.ThrowTypeError("Result is not a snapshot");
+ return 0;
+ }
+
+ return (uint32_t)mResultNodes.Length();
+ }
+ nsINode* IterateNext(ErrorResult& aRv);
+ nsINode* SnapshotItem(uint32_t aIndex, ErrorResult& aRv) const {
+ if (!isSnapshot()) {
+ aRv.ThrowTypeError("Result is not a snapshot");
+ return nullptr;
+ }
+
+ return mResultNodes.SafeElementAt(aIndex);
+ }
+
+ // nsIMutationObserver interface
+ NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATACHANGED
+ NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED
+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED
+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED
+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED
+ NS_DECL_NSIMUTATIONOBSERVER_NODEWILLBEDESTROYED
+
+ void SetExprResult(txAExprResult* aExprResult, uint16_t aResultType,
+ nsINode* aContextNode, ErrorResult& aRv);
+ nsresult GetExprResult(txAExprResult** aExprResult);
+ already_AddRefed<XPathResult> Clone(ErrorResult& aError);
+ void RemoveObserver();
+
+ private:
+ static bool isSnapshot(uint16_t aResultType) {
+ return aResultType == UNORDERED_NODE_SNAPSHOT_TYPE ||
+ aResultType == ORDERED_NODE_SNAPSHOT_TYPE;
+ }
+ static bool isIterator(uint16_t aResultType) {
+ return aResultType == UNORDERED_NODE_ITERATOR_TYPE ||
+ aResultType == ORDERED_NODE_ITERATOR_TYPE;
+ }
+ static bool isNode(uint16_t aResultType) {
+ return aResultType == FIRST_ORDERED_NODE_TYPE ||
+ aResultType == ANY_UNORDERED_NODE_TYPE;
+ }
+ bool isSnapshot() const { return isSnapshot(mResultType); }
+ bool isIterator() const { return isIterator(mResultType); }
+ bool isNode() const { return isNode(mResultType); }
+
+ void Invalidate(const nsIContent* aChangeRoot);
+
+ nsCOMPtr<nsINode> mParent;
+ RefPtr<txAExprResult> mResult;
+ nsTArray<nsCOMPtr<nsINode>> mResultNodes;
+ RefPtr<Document> mDocument;
+ nsWeakPtr mContextNode;
+ uint32_t mCurrentPos;
+ uint16_t mResultType;
+ bool mInvalidIteratorState;
+ bool mBooleanResult;
+ double mNumberResult;
+ nsString mStringResult;
+};
+
+} // namespace mozilla::dom
+
+#endif /* mozilla_dom_XPathResult_h */
diff --git a/dom/xslt/xpath/moz.build b/dom/xslt/xpath/moz.build
new file mode 100644
index 0000000000..9cf562c0af
--- /dev/null
+++ b/dom/xslt/xpath/moz.build
@@ -0,0 +1,58 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+EXPORTS.mozilla.dom += [
+ "txIXPathContext.h",
+ "XPathEvaluator.h",
+ "XPathExpression.h",
+ "XPathResult.h",
+]
+
+UNIFIED_SOURCES += [
+ "txBooleanExpr.cpp",
+ "txBooleanResult.cpp",
+ "txCoreFunctionCall.cpp",
+ "txErrorExpr.cpp",
+ "txExpr.cpp",
+ "txExprLexer.cpp",
+ "txExprParser.cpp",
+ "txFilterExpr.cpp",
+ "txForwardContext.cpp",
+ "txFunctionCall.cpp",
+ "txLiteralExpr.cpp",
+ "txLocationStep.cpp",
+ "txMozillaXPathTreeWalker.cpp",
+ "txNamedAttributeStep.cpp",
+ "txNameTest.cpp",
+ "txNodeSet.cpp",
+ "txNodeSetContext.cpp",
+ "txNodeTypeTest.cpp",
+ "txNumberExpr.cpp",
+ "txNumberResult.cpp",
+ "txPathExpr.cpp",
+ "txPredicatedNodeTest.cpp",
+ "txPredicateList.cpp",
+ "txRelationalExpr.cpp",
+ "txResultRecycler.cpp",
+ "txRootExpr.cpp",
+ "txStringResult.cpp",
+ "txUnaryExpr.cpp",
+ "txUnionExpr.cpp",
+ "txUnionNodeTest.cpp",
+ "txVariableRefExpr.cpp",
+ "txXPathOptimizer.cpp",
+ "XPathEvaluator.cpp",
+ "XPathExpression.cpp",
+ "XPathResult.cpp",
+]
+
+LOCAL_INCLUDES += [
+ "../base",
+ "../xml",
+ "../xslt",
+]
+
+FINAL_LIBRARY = "xul"
diff --git a/dom/xslt/xpath/txBooleanExpr.cpp b/dom/xslt/xpath/txBooleanExpr.cpp
new file mode 100644
index 0000000000..0f4366ff76
--- /dev/null
+++ b/dom/xslt/xpath/txBooleanExpr.cpp
@@ -0,0 +1,78 @@
+/* -*- 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/. */
+
+/**
+ * Represents a BooleanExpr, a binary expression that
+ * performs a boolean operation between its lvalue and rvalue.
+ **/
+
+#include "txExpr.h"
+#include "txIXPathContext.h"
+
+/**
+ * Evaluates this Expr based on the given context node and processor state
+ * @param context the context node for evaluation of this Expr
+ * @param ps the ContextState containing the stack information needed
+ * for evaluation
+ * @return the result of the evaluation
+ **/
+nsresult BooleanExpr::evaluate(txIEvalContext* aContext,
+ txAExprResult** aResult) {
+ *aResult = nullptr;
+
+ bool lval;
+ nsresult rv = leftExpr->evaluateToBool(aContext, lval);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // check for early decision
+ if (op == OR && lval) {
+ aContext->recycler()->getBoolResult(true, aResult);
+
+ return NS_OK;
+ }
+ if (op == AND && !lval) {
+ aContext->recycler()->getBoolResult(false, aResult);
+
+ return NS_OK;
+ }
+
+ bool rval;
+ rv = rightExpr->evaluateToBool(aContext, rval);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // just use rval, since we already checked lval
+ aContext->recycler()->getBoolResult(rval, aResult);
+
+ return NS_OK;
+} //-- evaluate
+
+TX_IMPL_EXPR_STUBS_2(BooleanExpr, BOOLEAN_RESULT, leftExpr, rightExpr)
+
+bool BooleanExpr::isSensitiveTo(ContextSensitivity aContext) {
+ return leftExpr->isSensitiveTo(aContext) ||
+ rightExpr->isSensitiveTo(aContext);
+}
+
+#ifdef TX_TO_STRING
+void BooleanExpr::toString(nsAString& str) {
+ if (leftExpr)
+ leftExpr->toString(str);
+ else
+ str.AppendLiteral("null");
+
+ switch (op) {
+ case OR:
+ str.AppendLiteral(" or ");
+ break;
+ default:
+ str.AppendLiteral(" and ");
+ break;
+ }
+ if (rightExpr)
+ rightExpr->toString(str);
+ else
+ str.AppendLiteral("null");
+}
+#endif
diff --git a/dom/xslt/xpath/txBooleanResult.cpp b/dom/xslt/xpath/txBooleanResult.cpp
new file mode 100644
index 0000000000..1d69a8c347
--- /dev/null
+++ b/dom/xslt/xpath/txBooleanResult.cpp
@@ -0,0 +1,48 @@
+/* -*- 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/. */
+
+/*
+ * Boolean Expression result
+ */
+
+#include "txExprResult.h"
+
+/**
+ * Creates a new BooleanResult with the value of the given bool parameter
+ * @param boolean the bool to use for initialization of this BooleanResult's
+ * value
+ **/
+BooleanResult::BooleanResult(bool boolean) : txAExprResult(nullptr) {
+ this->value = boolean;
+} //-- BooleanResult
+
+/*
+ * Virtual Methods from ExprResult
+ */
+
+short BooleanResult::getResultType() {
+ return txAExprResult::BOOLEAN;
+} //-- getResultType
+
+void BooleanResult::stringValue(nsString& aResult) {
+ if (value) {
+ aResult.AppendLiteral("true");
+ } else {
+ aResult.AppendLiteral("false");
+ }
+}
+
+const nsString* BooleanResult::stringValuePointer() {
+ // In theory we could set strings containing "true" and "false" somewhere,
+ // but most stylesheets never get the stringvalue of a bool so that won't
+ // really buy us anything.
+ return nullptr;
+}
+
+bool BooleanResult::booleanValue() { return this->value; } //-- toBoolean
+
+double BooleanResult::numberValue() {
+ return (value) ? 1.0 : 0.0;
+} //-- toNumber
diff --git a/dom/xslt/xpath/txCoreFunctionCall.cpp b/dom/xslt/xpath/txCoreFunctionCall.cpp
new file mode 100644
index 0000000000..6e2285ed18
--- /dev/null
+++ b/dom/xslt/xpath/txCoreFunctionCall.cpp
@@ -0,0 +1,665 @@
+/* -*- 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 "mozilla/ArrayUtils.h"
+#include "mozilla/FloatingPoint.h"
+
+#include "txExpr.h"
+#include "txNodeSet.h"
+#include "nsGkAtoms.h"
+#include "txIXPathContext.h"
+#include "nsWhitespaceTokenizer.h"
+#include "txXPathTreeWalker.h"
+#include <math.h>
+#include "txStringUtils.h"
+#include "txXMLUtils.h"
+
+using namespace mozilla;
+
+struct txCoreFunctionDescriptor {
+ const int8_t mMinParams;
+ const int8_t mMaxParams;
+ const Expr::ResultType mReturnType;
+ const nsStaticAtom* const mName;
+};
+
+// This must be ordered in the same order as txCoreFunctionCall::eType.
+// If you change one, change the other.
+static const txCoreFunctionDescriptor descriptTable[] = {
+ {1, 1, Expr::NUMBER_RESULT, nsGkAtoms::count}, // COUNT
+ {1, 1, Expr::NODESET_RESULT, nsGkAtoms::id}, // ID
+ {0, 0, Expr::NUMBER_RESULT, nsGkAtoms::last}, // LAST
+ {0, 1, Expr::STRING_RESULT, nsGkAtoms::localName}, // LOCAL_NAME
+ {0, 1, Expr::STRING_RESULT, nsGkAtoms::namespaceUri}, // NAMESPACE_URI
+ {0, 1, Expr::STRING_RESULT, nsGkAtoms::name}, // NAME
+ {0, 0, Expr::NUMBER_RESULT, nsGkAtoms::position}, // POSITION
+
+ {2, -1, Expr::STRING_RESULT, nsGkAtoms::concat}, // CONCAT
+ {2, 2, Expr::BOOLEAN_RESULT, nsGkAtoms::contains}, // CONTAINS
+ {0, 1, Expr::STRING_RESULT, nsGkAtoms::normalizeSpace}, // NORMALIZE_SPACE
+ {2, 2, Expr::BOOLEAN_RESULT, nsGkAtoms::startsWith}, // STARTS_WITH
+ {0, 1, Expr::STRING_RESULT, nsGkAtoms::string}, // STRING
+ {0, 1, Expr::NUMBER_RESULT, nsGkAtoms::stringLength}, // STRING_LENGTH
+ {2, 3, Expr::STRING_RESULT, nsGkAtoms::substring}, // SUBSTRING
+ {2, 2, Expr::STRING_RESULT, nsGkAtoms::substringAfter}, // SUBSTRING_AFTER
+ {2, 2, Expr::STRING_RESULT,
+ nsGkAtoms::substringBefore}, // SUBSTRING_BEFORE
+ {3, 3, Expr::STRING_RESULT, nsGkAtoms::translate}, // TRANSLATE
+
+ {0, 1, Expr::NUMBER_RESULT, nsGkAtoms::number}, // NUMBER
+ {1, 1, Expr::NUMBER_RESULT, nsGkAtoms::round}, // ROUND
+ {1, 1, Expr::NUMBER_RESULT, nsGkAtoms::floor}, // FLOOR
+ {1, 1, Expr::NUMBER_RESULT, nsGkAtoms::ceiling}, // CEILING
+ {1, 1, Expr::NUMBER_RESULT, nsGkAtoms::sum}, // SUM
+
+ {1, 1, Expr::BOOLEAN_RESULT, nsGkAtoms::boolean}, // BOOLEAN
+ {0, 0, Expr::BOOLEAN_RESULT, nsGkAtoms::_false}, // _FALSE
+ {1, 1, Expr::BOOLEAN_RESULT, nsGkAtoms::lang}, // LANG
+ {1, 1, Expr::BOOLEAN_RESULT, nsGkAtoms::_not}, // _NOT
+ {0, 0, Expr::BOOLEAN_RESULT, nsGkAtoms::_true} // _TRUE
+};
+
+/*
+ * Evaluates this Expr based on the given context node and processor state
+ * @param context the context node for evaluation of this Expr
+ * @param ps the ContextState containing the stack information needed
+ * for evaluation
+ * @return the result of the evaluation
+ */
+nsresult txCoreFunctionCall::evaluate(txIEvalContext* aContext,
+ txAExprResult** aResult) {
+ *aResult = nullptr;
+
+ if (!requireParams(descriptTable[mType].mMinParams,
+ descriptTable[mType].mMaxParams, aContext)) {
+ return NS_ERROR_XPATH_BAD_ARGUMENT_COUNT;
+ }
+
+ nsresult rv = NS_OK;
+ switch (mType) {
+ case COUNT: {
+ RefPtr<txNodeSet> nodes;
+ rv = evaluateToNodeSet(mParams[0], aContext, getter_AddRefs(nodes));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return aContext->recycler()->getNumberResult(nodes->size(), aResult);
+ }
+ case ID: {
+ RefPtr<txAExprResult> exprResult;
+ rv = mParams[0]->evaluate(aContext, getter_AddRefs(exprResult));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<txNodeSet> resultSet;
+ rv = aContext->recycler()->getNodeSet(getter_AddRefs(resultSet));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ txXPathTreeWalker walker(aContext->getContextNode());
+
+ if (exprResult->getResultType() == txAExprResult::NODESET) {
+ txNodeSet* nodes =
+ static_cast<txNodeSet*>(static_cast<txAExprResult*>(exprResult));
+ int32_t i;
+ for (i = 0; i < nodes->size(); ++i) {
+ nsAutoString idList;
+ txXPathNodeUtils::appendNodeValue(nodes->get(i), idList);
+ nsWhitespaceTokenizer tokenizer(idList);
+ while (tokenizer.hasMoreTokens()) {
+ if (walker.moveToElementById(tokenizer.nextToken())) {
+ resultSet->add(walker.getCurrentPosition());
+ }
+ }
+ }
+ } else {
+ nsAutoString idList;
+ exprResult->stringValue(idList);
+ nsWhitespaceTokenizer tokenizer(idList);
+ while (tokenizer.hasMoreTokens()) {
+ if (walker.moveToElementById(tokenizer.nextToken())) {
+ resultSet->add(walker.getCurrentPosition());
+ }
+ }
+ }
+
+ *aResult = resultSet;
+ NS_ADDREF(*aResult);
+
+ return NS_OK;
+ }
+ case LAST: {
+ return aContext->recycler()->getNumberResult(aContext->size(), aResult);
+ }
+ case LOCAL_NAME:
+ case NAME:
+ case NAMESPACE_URI: {
+ // Check for optional arg
+ RefPtr<txNodeSet> nodes;
+ if (!mParams.IsEmpty()) {
+ rv = evaluateToNodeSet(mParams[0], aContext, getter_AddRefs(nodes));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (nodes->isEmpty()) {
+ aContext->recycler()->getEmptyStringResult(aResult);
+
+ return NS_OK;
+ }
+ }
+
+ const txXPathNode& node =
+ nodes ? nodes->get(0) : aContext->getContextNode();
+ switch (mType) {
+ case LOCAL_NAME: {
+ StringResult* strRes = nullptr;
+ rv = aContext->recycler()->getStringResult(&strRes);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *aResult = strRes;
+ txXPathNodeUtils::getLocalName(node, strRes->mValue);
+
+ return NS_OK;
+ }
+ case NAMESPACE_URI: {
+ StringResult* strRes = nullptr;
+ rv = aContext->recycler()->getStringResult(&strRes);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *aResult = strRes;
+ txXPathNodeUtils::getNamespaceURI(node, strRes->mValue);
+
+ return NS_OK;
+ }
+ case NAME: {
+ // XXX Namespace: namespaces have a name
+ if (txXPathNodeUtils::isAttribute(node) ||
+ txXPathNodeUtils::isElement(node) ||
+ txXPathNodeUtils::isProcessingInstruction(node)) {
+ StringResult* strRes = nullptr;
+ rv = aContext->recycler()->getStringResult(&strRes);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *aResult = strRes;
+ txXPathNodeUtils::getNodeName(node, strRes->mValue);
+ } else {
+ aContext->recycler()->getEmptyStringResult(aResult);
+ }
+
+ return NS_OK;
+ }
+ default: {
+ MOZ_CRASH("Unexpected mType?!");
+ }
+ }
+ MOZ_CRASH("Inner mType switch should have returned!");
+ }
+ case POSITION: {
+ return aContext->recycler()->getNumberResult(aContext->position(),
+ aResult);
+ }
+
+ // String functions
+
+ case CONCAT: {
+ RefPtr<StringResult> strRes;
+ rv = aContext->recycler()->getStringResult(getter_AddRefs(strRes));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t i, len = mParams.Length();
+ for (i = 0; i < len; ++i) {
+ rv = mParams[i]->evaluateToString(aContext, strRes->mValue);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ NS_ADDREF(*aResult = strRes);
+
+ return NS_OK;
+ }
+ case CONTAINS: {
+ nsAutoString arg2;
+ rv = mParams[1]->evaluateToString(aContext, arg2);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (arg2.IsEmpty()) {
+ aContext->recycler()->getBoolResult(true, aResult);
+ } else {
+ nsAutoString arg1;
+ rv = mParams[0]->evaluateToString(aContext, arg1);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aContext->recycler()->getBoolResult(FindInReadable(arg2, arg1),
+ aResult);
+ }
+
+ return NS_OK;
+ }
+ case NORMALIZE_SPACE: {
+ nsAutoString resultStr;
+ if (!mParams.IsEmpty()) {
+ rv = mParams[0]->evaluateToString(aContext, resultStr);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ txXPathNodeUtils::appendNodeValue(aContext->getContextNode(),
+ resultStr);
+ }
+
+ RefPtr<StringResult> strRes;
+ rv = aContext->recycler()->getStringResult(getter_AddRefs(strRes));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool addSpace = false;
+ bool first = true;
+ strRes->mValue.SetCapacity(resultStr.Length());
+ char16_t c;
+ uint32_t src;
+ for (src = 0; src < resultStr.Length(); src++) {
+ c = resultStr.CharAt(src);
+ if (XMLUtils::isWhitespace(c)) {
+ addSpace = true;
+ } else {
+ if (addSpace && !first) strRes->mValue.Append(char16_t(' '));
+
+ strRes->mValue.Append(c);
+ addSpace = false;
+ first = false;
+ }
+ }
+ *aResult = strRes;
+ NS_ADDREF(*aResult);
+
+ return NS_OK;
+ }
+ case STARTS_WITH: {
+ nsAutoString arg2;
+ rv = mParams[1]->evaluateToString(aContext, arg2);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool result = false;
+ if (arg2.IsEmpty()) {
+ result = true;
+ } else {
+ nsAutoString arg1;
+ rv = mParams[0]->evaluateToString(aContext, arg1);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ result = StringBeginsWith(arg1, arg2);
+ }
+
+ aContext->recycler()->getBoolResult(result, aResult);
+
+ return NS_OK;
+ }
+ case STRING: {
+ RefPtr<StringResult> strRes;
+ rv = aContext->recycler()->getStringResult(getter_AddRefs(strRes));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!mParams.IsEmpty()) {
+ rv = mParams[0]->evaluateToString(aContext, strRes->mValue);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ txXPathNodeUtils::appendNodeValue(aContext->getContextNode(),
+ strRes->mValue);
+ }
+
+ NS_ADDREF(*aResult = strRes);
+
+ return NS_OK;
+ }
+ case STRING_LENGTH: {
+ nsAutoString resultStr;
+ if (!mParams.IsEmpty()) {
+ rv = mParams[0]->evaluateToString(aContext, resultStr);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ txXPathNodeUtils::appendNodeValue(aContext->getContextNode(),
+ resultStr);
+ }
+ rv = aContext->recycler()->getNumberResult(resultStr.Length(), aResult);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+ }
+ case SUBSTRING: {
+ nsAutoString src;
+ rv = mParams[0]->evaluateToString(aContext, src);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ double start;
+ rv = evaluateToNumber(mParams[1], aContext, &start);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // check for NaN or +/-Inf
+ if (std::isnan(start) || std::isinf(start) ||
+ start >= src.Length() + 0.5) {
+ aContext->recycler()->getEmptyStringResult(aResult);
+
+ return NS_OK;
+ }
+
+ start = floor(start + 0.5) - 1;
+
+ double end;
+ if (mParams.Length() == 3) {
+ rv = evaluateToNumber(mParams[2], aContext, &end);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ end += start;
+ if (std::isnan(end) || end < 0) {
+ aContext->recycler()->getEmptyStringResult(aResult);
+
+ return NS_OK;
+ }
+
+ if (end > src.Length())
+ end = src.Length();
+ else
+ end = floor(end + 0.5);
+ } else {
+ end = src.Length();
+ }
+
+ if (start < 0) start = 0;
+
+ if (start > end) {
+ aContext->recycler()->getEmptyStringResult(aResult);
+
+ return NS_OK;
+ }
+
+ return aContext->recycler()->getStringResult(
+ Substring(src, (uint32_t)start, (uint32_t)(end - start)), aResult);
+ }
+ case SUBSTRING_AFTER: {
+ nsAutoString arg1;
+ rv = mParams[0]->evaluateToString(aContext, arg1);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString arg2;
+ rv = mParams[1]->evaluateToString(aContext, arg2);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (arg2.IsEmpty()) {
+ return aContext->recycler()->getStringResult(arg1, aResult);
+ }
+
+ int32_t idx = arg1.Find(arg2);
+ if (idx == kNotFound) {
+ aContext->recycler()->getEmptyStringResult(aResult);
+
+ return NS_OK;
+ }
+
+ const nsAString& result = Substring(arg1, idx + arg2.Length());
+ return aContext->recycler()->getStringResult(result, aResult);
+ }
+ case SUBSTRING_BEFORE: {
+ nsAutoString arg2;
+ rv = mParams[1]->evaluateToString(aContext, arg2);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (arg2.IsEmpty()) {
+ aContext->recycler()->getEmptyStringResult(aResult);
+
+ return NS_OK;
+ }
+
+ nsAutoString arg1;
+ rv = mParams[0]->evaluateToString(aContext, arg1);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t idx = arg1.Find(arg2);
+ if (idx == kNotFound) {
+ aContext->recycler()->getEmptyStringResult(aResult);
+
+ return NS_OK;
+ }
+
+ return aContext->recycler()->getStringResult(StringHead(arg1, idx),
+ aResult);
+ }
+ case TRANSLATE: {
+ nsAutoString src;
+ rv = mParams[0]->evaluateToString(aContext, src);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (src.IsEmpty()) {
+ aContext->recycler()->getEmptyStringResult(aResult);
+
+ return NS_OK;
+ }
+
+ RefPtr<StringResult> strRes;
+ rv = aContext->recycler()->getStringResult(getter_AddRefs(strRes));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ strRes->mValue.SetCapacity(src.Length());
+
+ nsAutoString oldChars, newChars;
+ rv = mParams[1]->evaluateToString(aContext, oldChars);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mParams[2]->evaluateToString(aContext, newChars);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t i;
+ int32_t newCharsLength = (int32_t)newChars.Length();
+ for (i = 0; i < src.Length(); i++) {
+ int32_t idx = oldChars.FindChar(src.CharAt(i));
+ if (idx != kNotFound) {
+ if (idx < newCharsLength)
+ strRes->mValue.Append(newChars.CharAt((uint32_t)idx));
+ } else {
+ strRes->mValue.Append(src.CharAt(i));
+ }
+ }
+
+ NS_ADDREF(*aResult = strRes);
+
+ return NS_OK;
+ }
+
+ // Number functions
+
+ case NUMBER: {
+ double res;
+ if (!mParams.IsEmpty()) {
+ rv = evaluateToNumber(mParams[0], aContext, &res);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ nsAutoString resultStr;
+ txXPathNodeUtils::appendNodeValue(aContext->getContextNode(),
+ resultStr);
+ res = txDouble::toDouble(resultStr);
+ }
+ return aContext->recycler()->getNumberResult(res, aResult);
+ }
+ case ROUND: {
+ double dbl;
+ rv = evaluateToNumber(mParams[0], aContext, &dbl);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (std::isfinite(dbl)) {
+ if (mozilla::IsNegative(dbl) && dbl >= -0.5) {
+ dbl *= 0;
+ } else {
+ dbl = floor(dbl + 0.5);
+ }
+ }
+
+ return aContext->recycler()->getNumberResult(dbl, aResult);
+ }
+ case FLOOR: {
+ double dbl;
+ rv = evaluateToNumber(mParams[0], aContext, &dbl);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (std::isfinite(dbl) && !mozilla::IsNegativeZero(dbl)) dbl = floor(dbl);
+
+ return aContext->recycler()->getNumberResult(dbl, aResult);
+ }
+ case CEILING: {
+ double dbl;
+ rv = evaluateToNumber(mParams[0], aContext, &dbl);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (std::isfinite(dbl)) {
+ if (mozilla::IsNegative(dbl) && dbl > -1)
+ dbl *= 0;
+ else
+ dbl = ceil(dbl);
+ }
+
+ return aContext->recycler()->getNumberResult(dbl, aResult);
+ }
+ case SUM: {
+ RefPtr<txNodeSet> nodes;
+ nsresult rv =
+ evaluateToNodeSet(mParams[0], aContext, getter_AddRefs(nodes));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ double res = 0;
+ int32_t i;
+ for (i = 0; i < nodes->size(); ++i) {
+ nsAutoString resultStr;
+ txXPathNodeUtils::appendNodeValue(nodes->get(i), resultStr);
+ res += txDouble::toDouble(resultStr);
+ }
+ return aContext->recycler()->getNumberResult(res, aResult);
+ }
+
+ // Boolean functions
+
+ case BOOLEAN: {
+ bool result;
+ nsresult rv = mParams[0]->evaluateToBool(aContext, result);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aContext->recycler()->getBoolResult(result, aResult);
+
+ return NS_OK;
+ }
+ case _FALSE: {
+ aContext->recycler()->getBoolResult(false, aResult);
+
+ return NS_OK;
+ }
+ case LANG: {
+ txXPathTreeWalker walker(aContext->getContextNode());
+
+ nsAutoString lang;
+ bool found;
+ do {
+ found = walker.getAttr(nsGkAtoms::lang, kNameSpaceID_XML, lang);
+ } while (!found && walker.moveToParent());
+
+ if (!found) {
+ aContext->recycler()->getBoolResult(false, aResult);
+
+ return NS_OK;
+ }
+
+ nsAutoString arg;
+ rv = mParams[0]->evaluateToString(aContext, arg);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool result =
+ StringBeginsWith(lang, arg, nsCaseInsensitiveStringComparator) &&
+ (lang.Length() == arg.Length() || lang.CharAt(arg.Length()) == '-');
+
+ aContext->recycler()->getBoolResult(result, aResult);
+
+ return NS_OK;
+ }
+ case _NOT: {
+ bool result;
+ rv = mParams[0]->evaluateToBool(aContext, result);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aContext->recycler()->getBoolResult(!result, aResult);
+
+ return NS_OK;
+ }
+ case _TRUE: {
+ aContext->recycler()->getBoolResult(true, aResult);
+
+ return NS_OK;
+ }
+ }
+
+ aContext->receiveError(u"Internal error"_ns, NS_ERROR_UNEXPECTED);
+ return NS_ERROR_UNEXPECTED;
+}
+
+Expr::ResultType txCoreFunctionCall::getReturnType() {
+ return descriptTable[mType].mReturnType;
+}
+
+bool txCoreFunctionCall::isSensitiveTo(ContextSensitivity aContext) {
+ switch (mType) {
+ case COUNT:
+ case CONCAT:
+ case CONTAINS:
+ case STARTS_WITH:
+ case SUBSTRING:
+ case SUBSTRING_AFTER:
+ case SUBSTRING_BEFORE:
+ case TRANSLATE:
+ case ROUND:
+ case FLOOR:
+ case CEILING:
+ case SUM:
+ case BOOLEAN:
+ case _NOT:
+ case _FALSE:
+ case _TRUE: {
+ return argsSensitiveTo(aContext);
+ }
+ case ID: {
+ return (aContext & NODE_CONTEXT) || argsSensitiveTo(aContext);
+ }
+ case LAST: {
+ return !!(aContext & SIZE_CONTEXT);
+ }
+ case LOCAL_NAME:
+ case NAME:
+ case NAMESPACE_URI:
+ case NORMALIZE_SPACE:
+ case STRING:
+ case STRING_LENGTH:
+ case NUMBER: {
+ if (mParams.IsEmpty()) {
+ return !!(aContext & NODE_CONTEXT);
+ }
+ return argsSensitiveTo(aContext);
+ }
+ case POSITION: {
+ return !!(aContext & POSITION_CONTEXT);
+ }
+ case LANG: {
+ return (aContext & NODE_CONTEXT) || argsSensitiveTo(aContext);
+ }
+ }
+
+ MOZ_ASSERT_UNREACHABLE("how'd we get here?");
+ return true;
+}
+
+// static
+bool txCoreFunctionCall::getTypeFromAtom(nsAtom* aName, eType& aType) {
+ uint32_t i;
+ for (i = 0; i < ArrayLength(descriptTable); ++i) {
+ if (aName == descriptTable[i].mName) {
+ aType = static_cast<eType>(i);
+
+ return true;
+ }
+ }
+
+ return false;
+}
+
+#ifdef TX_TO_STRING
+void txCoreFunctionCall::appendName(nsAString& aDest) {
+ aDest.Append(descriptTable[mType].mName->GetUTF16String());
+}
+#endif
diff --git a/dom/xslt/xpath/txErrorExpr.cpp b/dom/xslt/xpath/txErrorExpr.cpp
new file mode 100644
index 0000000000..806b324259
--- /dev/null
+++ b/dom/xslt/xpath/txErrorExpr.cpp
@@ -0,0 +1,35 @@
+/* -*- 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 "nsError.h"
+#include "txExpr.h"
+#include "nsString.h"
+#include "txIXPathContext.h"
+
+nsresult txErrorExpr::evaluate(txIEvalContext* aContext,
+ txAExprResult** aResult) {
+ *aResult = nullptr;
+
+ nsAutoString err(u"Invalid expression evaluated"_ns);
+#ifdef TX_TO_STRING
+ err.AppendLiteral(": ");
+ toString(err);
+#endif
+ aContext->receiveError(err, NS_ERROR_XPATH_INVALID_EXPRESSION_EVALUATED);
+
+ return NS_ERROR_XPATH_INVALID_EXPRESSION_EVALUATED;
+}
+
+TX_IMPL_EXPR_STUBS_0(txErrorExpr, ANY_RESULT)
+
+bool txErrorExpr::isSensitiveTo(ContextSensitivity aContext) {
+ // It doesn't really matter what we return here, but it might
+ // be a good idea to try to keep this as unoptimizable as possible
+ return true;
+}
+
+#ifdef TX_TO_STRING
+void txErrorExpr::toString(nsAString& aStr) { aStr.Append(mStr); }
+#endif
diff --git a/dom/xslt/xpath/txExpr.cpp b/dom/xslt/xpath/txExpr.cpp
new file mode 100644
index 0000000000..d5ea774f62
--- /dev/null
+++ b/dom/xslt/xpath/txExpr.cpp
@@ -0,0 +1,26 @@
+/* -*- 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 "txExpr.h"
+
+nsresult Expr::evaluateToBool(txIEvalContext* aContext, bool& aResult) {
+ RefPtr<txAExprResult> exprRes;
+ nsresult rv = evaluate(aContext, getter_AddRefs(exprRes));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aResult = exprRes->booleanValue();
+
+ return NS_OK;
+}
+
+nsresult Expr::evaluateToString(txIEvalContext* aContext, nsString& aResult) {
+ RefPtr<txAExprResult> exprRes;
+ nsresult rv = evaluate(aContext, getter_AddRefs(exprRes));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ exprRes->stringValue(aResult);
+
+ return NS_OK;
+}
diff --git a/dom/xslt/xpath/txExpr.h b/dom/xslt/xpath/txExpr.h
new file mode 100644
index 0000000000..2622a3fe9f
--- /dev/null
+++ b/dom/xslt/xpath/txExpr.h
@@ -0,0 +1,847 @@
+/* -*- 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/. */
+
+#ifndef TRANSFRMX_EXPR_H
+#define TRANSFRMX_EXPR_H
+
+#include "mozilla/Attributes.h"
+#include "mozilla/UniquePtr.h"
+#include "txExprResult.h"
+#include "txCore.h"
+#include "nsString.h"
+#include "txOwningArray.h"
+#include "nsAtom.h"
+
+#ifdef DEBUG
+# define TX_TO_STRING
+#endif
+
+/*
+ XPath class definitions.
+ Much of this code was ported from XSL:P.
+*/
+
+class nsAtom;
+class txIMatchContext;
+class txIEvalContext;
+class txNodeSet;
+class txXPathNode;
+class txXPathTreeWalker;
+
+/**
+ * A Base Class for all XSL Expressions
+ **/
+class Expr {
+ public:
+ MOZ_COUNTED_DEFAULT_CTOR(Expr)
+ MOZ_COUNTED_DTOR_VIRTUAL(Expr)
+
+ /**
+ * Evaluates this Expr based on the given context node and processor state
+ * @param context the context node for evaluation of this Expr
+ * @param ps the ContextState containing the stack information needed
+ * for evaluation
+ * @return the result of the evaluation
+ **/
+ virtual nsresult evaluate(txIEvalContext* aContext,
+ txAExprResult** aResult) = 0;
+
+ /**
+ * Returns the type of this expression.
+ */
+ enum ExprType {
+ LOCATIONSTEP_EXPR,
+ PATH_EXPR,
+ UNION_EXPR,
+ LITERAL_EXPR,
+ OTHER_EXPR
+ };
+ virtual ExprType getType() { return OTHER_EXPR; }
+
+ /**
+ * Returns the type or types of results this Expr return.
+ */
+ using ResultType = uint16_t;
+ enum {
+ NODESET_RESULT = 0x01,
+ BOOLEAN_RESULT = 0x02,
+ NUMBER_RESULT = 0x04,
+ STRING_RESULT = 0x08,
+ RTF_RESULT = 0x10,
+ ANY_RESULT = 0xFFFF
+ };
+ virtual ResultType getReturnType() = 0;
+ bool canReturnType(ResultType aType) {
+ return (getReturnType() & aType) != 0;
+ }
+
+ using ContextSensitivity = uint16_t;
+ enum {
+ NO_CONTEXT = 0x00,
+ NODE_CONTEXT = 0x01,
+ POSITION_CONTEXT = 0x02,
+ SIZE_CONTEXT = 0x04,
+ NODESET_CONTEXT = POSITION_CONTEXT | SIZE_CONTEXT,
+ VARIABLES_CONTEXT = 0x08,
+ PRIVATE_CONTEXT = 0x10,
+ ANY_CONTEXT = 0xFFFF
+ };
+
+ /**
+ * Returns true if this expression is sensitive to *any* of
+ * the requested contexts in aContexts.
+ */
+ virtual bool isSensitiveTo(ContextSensitivity aContexts) = 0;
+
+ /**
+ * Returns sub-expression at given position
+ */
+ virtual Expr* getSubExprAt(uint32_t aPos) = 0;
+
+ /**
+ * Replace sub-expression at given position. Does not delete the old
+ * expression, that is the responsibility of the caller.
+ */
+ virtual void setSubExprAt(uint32_t aPos, Expr* aExpr) = 0;
+
+ virtual nsresult evaluateToBool(txIEvalContext* aContext, bool& aResult);
+
+ virtual nsresult evaluateToString(txIEvalContext* aContext,
+ nsString& aResult);
+
+#ifdef TX_TO_STRING
+ /**
+ * Returns the String representation of this Expr.
+ * @param dest the String to use when creating the String
+ * representation. The String representation will be appended to
+ * any data in the destination String, to allow cascading calls to
+ * other #toString() methods for Expressions.
+ * @return the String representation of this Expr.
+ **/
+ virtual void toString(nsAString& str) = 0;
+#endif
+}; //-- Expr
+
+#ifdef TX_TO_STRING
+# define TX_DECL_TOSTRING void toString(nsAString& aDest) override;
+# define TX_DECL_APPENDNAME void appendName(nsAString& aDest) override;
+#else
+# define TX_DECL_TOSTRING
+# define TX_DECL_APPENDNAME
+#endif
+
+#define TX_DECL_EXPR_BASE \
+ nsresult evaluate(txIEvalContext* aContext, txAExprResult** aResult) \
+ override; \
+ ResultType getReturnType() override; \
+ bool isSensitiveTo(ContextSensitivity aContexts) override;
+
+#define TX_DECL_EXPR \
+ TX_DECL_EXPR_BASE \
+ TX_DECL_TOSTRING \
+ Expr* getSubExprAt(uint32_t aPos) override; \
+ void setSubExprAt(uint32_t aPos, Expr* aExpr) override;
+
+#define TX_DECL_OPTIMIZABLE_EXPR \
+ TX_DECL_EXPR \
+ ExprType getType() override;
+
+#define TX_DECL_FUNCTION \
+ TX_DECL_APPENDNAME \
+ TX_DECL_EXPR_BASE
+
+#define TX_IMPL_EXPR_STUBS_BASE(_class, _ReturnType) \
+ Expr::ResultType _class::getReturnType() { return _ReturnType; }
+
+#define TX_IMPL_EXPR_STUBS_0(_class, _ReturnType) \
+ TX_IMPL_EXPR_STUBS_BASE(_class, _ReturnType) \
+ Expr* _class::getSubExprAt(uint32_t aPos) { return nullptr; } \
+ void _class::setSubExprAt(uint32_t aPos, Expr* aExpr) { \
+ MOZ_ASSERT_UNREACHABLE("setting bad subexpression index"); \
+ }
+
+#define TX_IMPL_EXPR_STUBS_1(_class, _ReturnType, _Expr1) \
+ TX_IMPL_EXPR_STUBS_BASE(_class, _ReturnType) \
+ Expr* _class::getSubExprAt(uint32_t aPos) { \
+ if (aPos == 0) { \
+ return _Expr1.get(); \
+ } \
+ return nullptr; \
+ } \
+ void _class::setSubExprAt(uint32_t aPos, Expr* aExpr) { \
+ NS_ASSERTION(aPos < 1, "setting bad subexpression index"); \
+ mozilla::Unused << _Expr1.release(); \
+ _Expr1 = mozilla::WrapUnique(aExpr); \
+ }
+
+#define TX_IMPL_EXPR_STUBS_2(_class, _ReturnType, _Expr1, _Expr2) \
+ TX_IMPL_EXPR_STUBS_BASE(_class, _ReturnType) \
+ Expr* _class::getSubExprAt(uint32_t aPos) { \
+ switch (aPos) { \
+ case 0: \
+ return _Expr1.get(); \
+ case 1: \
+ return _Expr2.get(); \
+ default: \
+ break; \
+ } \
+ return nullptr; \
+ } \
+ void _class::setSubExprAt(uint32_t aPos, Expr* aExpr) { \
+ NS_ASSERTION(aPos < 2, "setting bad subexpression index"); \
+ if (aPos == 0) { \
+ mozilla::Unused << _Expr1.release(); \
+ _Expr1 = mozilla::WrapUnique(aExpr); \
+ } else { \
+ mozilla::Unused << _Expr2.release(); \
+ _Expr2 = mozilla::WrapUnique(aExpr); \
+ } \
+ }
+
+#define TX_IMPL_EXPR_STUBS_LIST(_class, _ReturnType, _ExprList) \
+ TX_IMPL_EXPR_STUBS_BASE(_class, _ReturnType) \
+ Expr* _class::getSubExprAt(uint32_t aPos) { \
+ return _ExprList.SafeElementAt(aPos); \
+ } \
+ void _class::setSubExprAt(uint32_t aPos, Expr* aExpr) { \
+ NS_ASSERTION(aPos < _ExprList.Length(), \
+ "setting bad subexpression index"); \
+ _ExprList[aPos] = aExpr; \
+ }
+
+/**
+ * This class represents a FunctionCall as defined by the XPath 1.0
+ * Recommendation.
+ **/
+class FunctionCall : public Expr {
+ public:
+ /**
+ * Adds the given parameter to this FunctionCall's parameter list.
+ * The ownership of the given Expr is passed over to the FunctionCall,
+ * even on failure.
+ * @param aExpr the Expr to add to this FunctionCall's parameter list
+ */
+ void addParam(Expr* aExpr) { mParams.AppendElement(aExpr); }
+
+ /**
+ * Check if the number of parameters falls within a range.
+ *
+ * @param aParamCountMin minimum number of required parameters.
+ * @param aParamCountMax maximum number of parameters. If aParamCountMax
+ * is negative the maximum number is not checked.
+ * @return boolean representing whether the number of parameters falls
+ * within the expected range or not.
+ *
+ * XXX txIEvalContext should be txIParseContest, bug 143291
+ */
+ virtual bool requireParams(int32_t aParamCountMin, int32_t aParamCountMax,
+ txIEvalContext* aContext);
+
+ TX_DECL_TOSTRING
+ Expr* getSubExprAt(uint32_t aPos) override;
+ void setSubExprAt(uint32_t aPos, Expr* aExpr) override;
+
+ protected:
+ txOwningArray<Expr> mParams;
+
+ /*
+ * Evaluates the given Expression and converts its result to a number.
+ */
+ static nsresult evaluateToNumber(Expr* aExpr, txIEvalContext* aContext,
+ double* aResult);
+
+ /*
+ * Evaluates the given Expression and converts its result to a NodeSet.
+ * If the result is not a NodeSet an error is returned.
+ */
+ static nsresult evaluateToNodeSet(Expr* aExpr, txIEvalContext* aContext,
+ txNodeSet** aResult);
+
+ /**
+ * Returns true if any argument is sensitive to the given context.
+ */
+ bool argsSensitiveTo(ContextSensitivity aContexts);
+
+#ifdef TX_TO_STRING
+ /*
+ * Appends the name of the function to `aStr`.
+ */
+ virtual void appendName(nsAString& aStr) = 0;
+#endif
+};
+
+class txCoreFunctionCall : public FunctionCall {
+ public:
+ // This must be ordered in the same order as descriptTable in
+ // txCoreFunctionCall.cpp. If you change one, change the other.
+ enum eType {
+ COUNT = 0, // count()
+ ID, // id()
+ LAST, // last()
+ LOCAL_NAME, // local-name()
+ NAMESPACE_URI, // namespace-uri()
+ NAME, // name()
+ POSITION, // position()
+
+ CONCAT, // concat()
+ CONTAINS, // contains()
+ NORMALIZE_SPACE, // normalize-space()
+ STARTS_WITH, // starts-with()
+ STRING, // string()
+ STRING_LENGTH, // string-length()
+ SUBSTRING, // substring()
+ SUBSTRING_AFTER, // substring-after()
+ SUBSTRING_BEFORE, // substring-before()
+ TRANSLATE, // translate()
+
+ NUMBER, // number()
+ ROUND, // round()
+ FLOOR, // floor()
+ CEILING, // ceiling()
+ SUM, // sum()
+
+ BOOLEAN, // boolean()
+ _FALSE, // false()
+ LANG, // lang()
+ _NOT, // not()
+ _TRUE // true()
+ };
+
+ /*
+ * Creates a txCoreFunctionCall of the given type
+ */
+ explicit txCoreFunctionCall(eType aType) : mType(aType) {}
+
+ TX_DECL_FUNCTION
+
+ static bool getTypeFromAtom(nsAtom* aName, eType& aType);
+
+ private:
+ eType mType;
+};
+
+/*
+ * This class represents a NodeTest as defined by the XPath spec
+ */
+class txNodeTest {
+ public:
+ MOZ_COUNTED_DEFAULT_CTOR(txNodeTest)
+ MOZ_COUNTED_DTOR_VIRTUAL(txNodeTest)
+
+ /*
+ * Virtual methods
+ * pretty much a txPattern, but not supposed to be used
+ * standalone. The NodeTest node() is different to the
+ * Pattern "node()" (document node isn't matched)
+ */
+ virtual nsresult matches(const txXPathNode& aNode, txIMatchContext* aContext,
+ bool& aMatched) = 0;
+ virtual double getDefaultPriority() = 0;
+
+ /**
+ * Returns the type of this nodetest.
+ */
+ enum NodeTestType { NAME_TEST, NODETYPE_TEST, OTHER_TEST };
+ virtual NodeTestType getType() { return OTHER_TEST; }
+
+ /**
+ * Returns true if this expression is sensitive to *any* of
+ * the requested flags.
+ */
+ virtual bool isSensitiveTo(Expr::ContextSensitivity aContext) = 0;
+
+#ifdef TX_TO_STRING
+ virtual void toString(nsAString& aDest) = 0;
+#endif
+};
+
+#define TX_DECL_NODE_TEST \
+ TX_DECL_TOSTRING \
+ nsresult matches(const txXPathNode& aNode, txIMatchContext* aContext, \
+ bool& aMatched) override; \
+ double getDefaultPriority() override; \
+ bool isSensitiveTo(Expr::ContextSensitivity aContext) override;
+
+/*
+ * This class represents a NameTest as defined by the XPath spec
+ */
+class txNameTest : public txNodeTest {
+ public:
+ /*
+ * Creates a new txNameTest with the given type and the given
+ * principal node type
+ */
+ txNameTest(nsAtom* aPrefix, nsAtom* aLocalName, int32_t aNSID,
+ uint16_t aNodeType);
+
+ NodeTestType getType() override;
+
+ TX_DECL_NODE_TEST
+
+ RefPtr<nsAtom> mPrefix;
+ RefPtr<nsAtom> mLocalName;
+ int32_t mNamespace;
+
+ private:
+ uint16_t mNodeType;
+};
+
+/*
+ * This class represents a NodeType as defined by the XPath spec
+ */
+class txNodeTypeTest : public txNodeTest {
+ public:
+ enum NodeType { COMMENT_TYPE, TEXT_TYPE, PI_TYPE, NODE_TYPE };
+
+ /*
+ * Creates a new txNodeTypeTest of the given type
+ */
+ explicit txNodeTypeTest(NodeType aNodeType) : mNodeType(aNodeType) {}
+
+ /*
+ * Sets the name of the node to match. Only availible for pi nodes
+ */
+ void setNodeName(const nsAString& aName) { mNodeName = NS_Atomize(aName); }
+
+ NodeType getNodeTestType() { return mNodeType; }
+
+ NodeTestType getType() override;
+
+ TX_DECL_NODE_TEST
+
+ private:
+ NodeType mNodeType;
+ RefPtr<nsAtom> mNodeName;
+};
+
+/**
+ * Class representing a nodetest combined with a predicate. May only be used
+ * if the predicate is not sensitive to the context-nodelist.
+ */
+class txPredicatedNodeTest : public txNodeTest {
+ public:
+ txPredicatedNodeTest(txNodeTest* aNodeTest, Expr* aPredicate);
+ TX_DECL_NODE_TEST
+
+ private:
+ mozilla::UniquePtr<txNodeTest> mNodeTest;
+ mozilla::UniquePtr<Expr> mPredicate;
+};
+
+/**
+ * Represents an ordered list of Predicates,
+ * for use with Step and Filter Expressions
+ **/
+class PredicateList {
+ public:
+ /**
+ * Adds the given Expr to the list.
+ * The ownership of the given Expr is passed over the PredicateList,
+ * even on failure.
+ * @param aExpr the Expr to add to the list
+ */
+ void add(Expr* aExpr) {
+ NS_ASSERTION(aExpr, "missing expression");
+ mPredicates.AppendElement(aExpr);
+ }
+
+ nsresult evaluatePredicates(txNodeSet* aNodes, txIMatchContext* aContext);
+
+ /**
+ * Drops the first predicate without deleting it.
+ */
+ void dropFirst() { mPredicates.RemoveElementAt(0); }
+
+ /**
+ * returns true if this predicate list is empty
+ **/
+ bool isEmpty() { return mPredicates.IsEmpty(); }
+
+#ifdef TX_TO_STRING
+ /**
+ * Returns the String representation of this PredicateList.
+ * @param dest the String to use when creating the String
+ * representation. The String representation will be appended to
+ * any data in the destination String, to allow cascading calls to
+ * other #toString() methods for Expressions.
+ * @return the String representation of this PredicateList.
+ **/
+ void toString(nsAString& dest);
+#endif
+
+ protected:
+ bool isSensitiveTo(Expr::ContextSensitivity aContext);
+ Expr* getSubExprAt(uint32_t aPos) { return mPredicates.SafeElementAt(aPos); }
+ void setSubExprAt(uint32_t aPos, Expr* aExpr) {
+ NS_ASSERTION(aPos < mPredicates.Length(),
+ "setting bad subexpression index");
+ mPredicates[aPos] = aExpr;
+ }
+
+ //-- list of predicates
+ txOwningArray<Expr> mPredicates;
+}; //-- PredicateList
+
+class LocationStep : public Expr, public PredicateList {
+ public:
+ enum LocationStepType {
+ ANCESTOR_AXIS = 0,
+ ANCESTOR_OR_SELF_AXIS,
+ ATTRIBUTE_AXIS,
+ CHILD_AXIS,
+ DESCENDANT_AXIS,
+ DESCENDANT_OR_SELF_AXIS,
+ FOLLOWING_AXIS,
+ FOLLOWING_SIBLING_AXIS,
+ NAMESPACE_AXIS,
+ PARENT_AXIS,
+ PRECEDING_AXIS,
+ PRECEDING_SIBLING_AXIS,
+ SELF_AXIS
+ };
+
+ /**
+ * Creates a new LocationStep using the given NodeExpr and Axis Identifier
+ * @param nodeExpr the NodeExpr to use when matching Nodes
+ * @param axisIdentifier the Axis Identifier in which to search for nodes
+ **/
+ LocationStep(txNodeTest* aNodeTest, LocationStepType aAxisIdentifier)
+ : mNodeTest(aNodeTest), mAxisIdentifier(aAxisIdentifier) {}
+
+ TX_DECL_OPTIMIZABLE_EXPR
+
+ txNodeTest* getNodeTest() { return mNodeTest.get(); }
+ void setNodeTest(txNodeTest* aNodeTest) {
+ mozilla::Unused << mNodeTest.release();
+ mNodeTest = mozilla::WrapUnique(aNodeTest);
+ }
+ LocationStepType getAxisIdentifier() { return mAxisIdentifier; }
+ void setAxisIdentifier(LocationStepType aAxisIdentifier) {
+ mAxisIdentifier = aAxisIdentifier;
+ }
+
+ private:
+ /**
+ * Append the current position of aWalker to aNodes if it matches mNodeTest,
+ * using aContext as the context for matching.
+ */
+ nsresult appendIfMatching(const txXPathTreeWalker& aWalker,
+ txIMatchContext* aContext, txNodeSet* aNodes);
+
+ /**
+ * Append the descendants of the current position of aWalker to aNodes if
+ * they match mNodeTest, using aContext as the context for matching.
+ */
+ nsresult appendMatchingDescendants(const txXPathTreeWalker& aWalker,
+ txIMatchContext* aContext,
+ txNodeSet* aNodes);
+
+ /**
+ * Append the descendants of the current position of aWalker to aNodes in
+ * reverse order if they match mNodeTest, using aContext as the context for
+ * matching.
+ */
+ nsresult appendMatchingDescendantsRev(const txXPathTreeWalker& aWalker,
+ txIMatchContext* aContext,
+ txNodeSet* aNodes);
+
+ mozilla::UniquePtr<txNodeTest> mNodeTest;
+ LocationStepType mAxisIdentifier;
+};
+
+class FilterExpr : public Expr, public PredicateList {
+ public:
+ /**
+ * Creates a new FilterExpr using the given Expr
+ * @param expr the Expr to use for evaluation
+ */
+ explicit FilterExpr(Expr* aExpr) : expr(aExpr) {}
+
+ TX_DECL_EXPR
+
+ private:
+ mozilla::UniquePtr<Expr> expr;
+
+}; //-- FilterExpr
+
+class txLiteralExpr : public Expr {
+ public:
+ explicit txLiteralExpr(double aDbl)
+ : mValue(new NumberResult(aDbl, nullptr)) {}
+ explicit txLiteralExpr(const nsAString& aStr)
+ : mValue(new StringResult(aStr, nullptr)) {}
+ explicit txLiteralExpr(txAExprResult* aValue) : mValue(aValue) {}
+
+ TX_DECL_EXPR
+
+ private:
+ RefPtr<txAExprResult> mValue;
+};
+
+/**
+ * Represents an UnaryExpr. Returns the negative value of its expr.
+ **/
+class UnaryExpr : public Expr {
+ public:
+ explicit UnaryExpr(Expr* aExpr) : expr(aExpr) {}
+
+ TX_DECL_EXPR
+
+ private:
+ mozilla::UniquePtr<Expr> expr;
+}; //-- UnaryExpr
+
+/**
+ * Represents a BooleanExpr, a binary expression that
+ * performs a boolean operation between its lvalue and rvalue.
+ **/
+class BooleanExpr : public Expr {
+ public:
+ //-- BooleanExpr Types
+ enum _BooleanExprType { AND = 1, OR };
+
+ BooleanExpr(Expr* aLeftExpr, Expr* aRightExpr, short aOp)
+ : leftExpr(aLeftExpr), rightExpr(aRightExpr), op(aOp) {}
+
+ TX_DECL_EXPR
+
+ private:
+ mozilla::UniquePtr<Expr> leftExpr, rightExpr;
+ short op;
+}; //-- BooleanExpr
+
+/**
+ * Represents a MultiplicativeExpr, a binary expression that
+ * performs a multiplicative operation between its lvalue and rvalue:
+ * * : multiply
+ * mod : modulus
+ * div : divide
+ *
+ **/
+class txNumberExpr : public Expr {
+ public:
+ enum eOp { ADD, SUBTRACT, DIVIDE, MULTIPLY, MODULUS };
+
+ txNumberExpr(Expr* aLeftExpr, Expr* aRightExpr, eOp aOp)
+ : mLeftExpr(aLeftExpr), mRightExpr(aRightExpr), mOp(aOp) {}
+
+ TX_DECL_EXPR
+
+ private:
+ mozilla::UniquePtr<Expr> mLeftExpr, mRightExpr;
+ eOp mOp;
+}; //-- MultiplicativeExpr
+
+/**
+ * Represents a RelationalExpr, an expression that compares its lvalue
+ * to its rvalue using:
+ * = : equal to
+ * < : less than
+ * > : greater than
+ * <= : less than or equal to
+ * >= : greater than or equal to
+ *
+ **/
+class RelationalExpr : public Expr {
+ public:
+ enum RelationalExprType {
+ EQUAL,
+ NOT_EQUAL,
+ LESS_THAN,
+ GREATER_THAN,
+ LESS_OR_EQUAL,
+ GREATER_OR_EQUAL
+ };
+
+ RelationalExpr(Expr* aLeftExpr, Expr* aRightExpr, RelationalExprType aOp)
+ : mLeftExpr(aLeftExpr), mRightExpr(aRightExpr), mOp(aOp) {}
+
+ TX_DECL_EXPR
+
+ private:
+ bool compareResults(txIEvalContext* aContext, txAExprResult* aLeft,
+ txAExprResult* aRight);
+
+ mozilla::UniquePtr<Expr> mLeftExpr;
+ mozilla::UniquePtr<Expr> mRightExpr;
+ RelationalExprType mOp;
+};
+
+/**
+ * VariableRefExpr
+ * Represents a variable reference ($refname)
+ **/
+class VariableRefExpr : public Expr {
+ public:
+ VariableRefExpr(nsAtom* aPrefix, nsAtom* aLocalName, int32_t aNSID);
+
+ TX_DECL_EXPR
+
+ private:
+ RefPtr<nsAtom> mPrefix;
+ RefPtr<nsAtom> mLocalName;
+ int32_t mNamespace;
+};
+
+/**
+ * Represents a PathExpr
+ **/
+class PathExpr : public Expr {
+ public:
+ //-- Path Operators
+ //-- RELATIVE_OP is the default
+ //-- LF, changed from static const short to enum
+ enum PathOperator { RELATIVE_OP, DESCENDANT_OP };
+
+ /**
+ * Adds the Expr to this PathExpr
+ * The ownership of the given Expr is passed over the PathExpr,
+ * even on failure.
+ * @param aExpr the Expr to add to this PathExpr
+ */
+ void addExpr(Expr* aExpr, PathOperator pathOp);
+
+ /**
+ * Removes and deletes the expression at the given index.
+ */
+ void deleteExprAt(uint32_t aPos) {
+ NS_ASSERTION(aPos < mItems.Length(), "killing bad expression index");
+ mItems.RemoveElementAt(aPos);
+ }
+
+ TX_DECL_OPTIMIZABLE_EXPR
+
+ PathOperator getPathOpAt(uint32_t aPos) {
+ NS_ASSERTION(aPos < mItems.Length(), "getting bad pathop index");
+ return mItems[aPos].pathOp;
+ }
+ void setPathOpAt(uint32_t aPos, PathOperator aPathOp) {
+ NS_ASSERTION(aPos < mItems.Length(), "setting bad pathop index");
+ mItems[aPos].pathOp = aPathOp;
+ }
+
+ private:
+ class PathExprItem {
+ public:
+ mozilla::UniquePtr<Expr> expr;
+ PathOperator pathOp;
+ };
+
+ nsTArray<PathExprItem> mItems;
+
+ /*
+ * Selects from the descendants of the context node
+ * all nodes that match the Expr
+ */
+ nsresult evalDescendants(Expr* aStep, const txXPathNode& aNode,
+ txIMatchContext* aContext, txNodeSet* resNodes);
+};
+
+/**
+ * This class represents a RootExpr, which only matches the Document node
+ **/
+class RootExpr : public Expr {
+ public:
+ /**
+ * Creates a new RootExpr
+ */
+ RootExpr()
+#ifdef TX_TO_STRING
+ : mSerialize(true)
+#endif
+ {
+ }
+
+ TX_DECL_EXPR
+
+#ifdef TX_TO_STRING
+ public:
+ void setSerialize(bool aSerialize) { mSerialize = aSerialize; }
+
+ private:
+ // When a RootExpr is used in a PathExpr it shouldn't be serialized
+ bool mSerialize;
+#endif
+}; //-- RootExpr
+
+/**
+ * Represents a UnionExpr
+ **/
+class UnionExpr : public Expr {
+ public:
+ /**
+ * Adds the PathExpr to this UnionExpr
+ * The ownership of the given Expr is passed over the UnionExpr,
+ * even on failure.
+ * @param aExpr the Expr to add to this UnionExpr
+ */
+ void addExpr(Expr* aExpr) { mExpressions.AppendElement(aExpr); }
+
+ /**
+ * Removes and deletes the expression at the given index.
+ */
+ void deleteExprAt(uint32_t aPos) {
+ NS_ASSERTION(aPos < mExpressions.Length(), "killing bad expression index");
+
+ delete mExpressions[aPos];
+ mExpressions.RemoveElementAt(aPos);
+ }
+
+ TX_DECL_OPTIMIZABLE_EXPR
+
+ private:
+ txOwningArray<Expr> mExpressions;
+
+}; //-- UnionExpr
+
+/**
+ * Class specializing in executing expressions like "@foo" where we are
+ * interested in different result-types, and expressions like "@foo = 'hi'"
+ */
+class txNamedAttributeStep : public Expr {
+ public:
+ txNamedAttributeStep(int32_t aNsID, nsAtom* aPrefix, nsAtom* aLocalName);
+
+ TX_DECL_EXPR
+
+ private:
+ int32_t mNamespace;
+ RefPtr<nsAtom> mPrefix;
+ RefPtr<nsAtom> mLocalName;
+};
+
+/**
+ *
+ */
+class txUnionNodeTest : public txNodeTest {
+ public:
+ void addNodeTest(txNodeTest* aNodeTest) {
+ mNodeTests.AppendElement(aNodeTest);
+ }
+
+ TX_DECL_NODE_TEST
+
+ private:
+ txOwningArray<txNodeTest> mNodeTests;
+};
+
+/**
+ * Expression that failed to parse
+ */
+class txErrorExpr : public Expr {
+ public:
+#ifdef TX_TO_STRING
+ explicit txErrorExpr(const nsAString& aStr) : mStr(aStr) {}
+#endif
+
+ TX_DECL_EXPR
+
+#ifdef TX_TO_STRING
+ private:
+ nsString mStr;
+#endif
+};
+
+#endif
diff --git a/dom/xslt/xpath/txExprLexer.cpp b/dom/xslt/xpath/txExprLexer.cpp
new file mode 100644
index 0000000000..391dc2e875
--- /dev/null
+++ b/dom/xslt/xpath/txExprLexer.cpp
@@ -0,0 +1,333 @@
+/* -*- Mode: C++; tab-width: 2; 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/. */
+
+/**
+ * Lexical analyzer for XPath expressions
+ */
+
+#include "txExprLexer.h"
+#include "nsGkAtoms.h"
+#include "nsString.h"
+#include "nsError.h"
+#include "txXMLUtils.h"
+
+/**
+ * Creates a new ExprLexer
+ */
+txExprLexer::txExprLexer()
+ : mPosition(nullptr),
+ mCurrentItem(nullptr),
+ mFirstItem(nullptr),
+ mLastItem(nullptr),
+ mTokenCount(0) {}
+
+/**
+ * Destroys this instance of an txExprLexer
+ */
+txExprLexer::~txExprLexer() {
+ //-- delete tokens
+ Token* tok = mFirstItem;
+ while (tok) {
+ Token* temp = tok->mNext;
+ delete tok;
+ tok = temp;
+ }
+ mCurrentItem = nullptr;
+}
+
+Token* txExprLexer::nextToken() {
+ if (!mCurrentItem) {
+ MOZ_ASSERT_UNREACHABLE("nextToken called on uninitialized lexer");
+ return nullptr;
+ }
+
+ if (mCurrentItem->mType == Token::END) {
+ // Do not progress beyond the end token
+ return mCurrentItem;
+ }
+
+ Token* token = mCurrentItem;
+ mCurrentItem = mCurrentItem->mNext;
+ return token;
+}
+
+void txExprLexer::addToken(Token* aToken) {
+ if (mLastItem) {
+ mLastItem->mNext = aToken;
+ }
+ if (!mFirstItem) {
+ mFirstItem = aToken;
+ mCurrentItem = aToken;
+ }
+ mLastItem = aToken;
+ ++mTokenCount;
+}
+
+/**
+ * Returns true if the following Token should be an operator.
+ * This is a helper for the first bullet of [XPath 3.7]
+ * Lexical Structure
+ */
+bool txExprLexer::nextIsOperatorToken(Token* aToken) {
+ if (!aToken || aToken->mType == Token::NULL_TOKEN) {
+ return false;
+ }
+ /* This relies on the tokens having the right order in txExprLexer.h */
+ return aToken->mType < Token::COMMA || aToken->mType > Token::UNION_OP;
+}
+
+/**
+ * Parses the given string into a sequence of Tokens
+ */
+nsresult txExprLexer::parse(const nsAString& aPattern) {
+ iterator end;
+ aPattern.BeginReading(mPosition);
+ aPattern.EndReading(end);
+
+ //-- initialize previous token, this will automatically get
+ //-- deleted when it goes out of scope
+ Token nullToken(nullptr, nullptr, Token::NULL_TOKEN);
+
+ Token::Type defType;
+ Token* newToken = nullptr;
+ Token* prevToken = &nullToken;
+ bool isToken;
+
+ while (mPosition < end) {
+ defType = Token::CNAME;
+ isToken = true;
+
+ if (*mPosition == DOLLAR_SIGN) {
+ if (++mPosition == end || !XMLUtils::isLetter(*mPosition)) {
+ return NS_ERROR_XPATH_INVALID_VAR_NAME;
+ }
+ defType = Token::VAR_REFERENCE;
+ }
+ // just reuse the QName parsing, which will use defType
+ // the token to construct
+
+ if (XMLUtils::isLetter(*mPosition)) {
+ // NCName, can get QName or OperatorName;
+ // FunctionName, NodeName, and AxisSpecifier may want whitespace,
+ // and are dealt with below
+ iterator start = mPosition;
+ while (++mPosition < end && XMLUtils::isNCNameChar(*mPosition)) {
+ /* just go */
+ }
+ if (mPosition < end && *mPosition == COLON) {
+ // try QName or wildcard, might need to step back for axis
+ if (++mPosition == end) {
+ return NS_ERROR_XPATH_UNEXPECTED_END;
+ }
+ if (XMLUtils::isLetter(*mPosition)) {
+ while (++mPosition < end && XMLUtils::isNCNameChar(*mPosition)) {
+ /* just go */
+ }
+ } else if (*mPosition == '*' && defType != Token::VAR_REFERENCE) {
+ // eat wildcard for NameTest, bail for var ref at COLON
+ ++mPosition;
+ } else {
+ --mPosition; // step back
+ }
+ }
+ if (nextIsOperatorToken(prevToken)) {
+ nsDependentSubstring op(Substring(start, mPosition));
+ if (nsGkAtoms::_and->Equals(op)) {
+ defType = Token::AND_OP;
+ } else if (nsGkAtoms::_or->Equals(op)) {
+ defType = Token::OR_OP;
+ } else if (nsGkAtoms::mod->Equals(op)) {
+ defType = Token::MODULUS_OP;
+ } else if (nsGkAtoms::div->Equals(op)) {
+ defType = Token::DIVIDE_OP;
+ } else {
+ // XXX QUESTION: spec is not too precise
+ // badops is sure an error, but is bad:ops, too? We say yes!
+ return NS_ERROR_XPATH_OPERATOR_EXPECTED;
+ }
+ }
+ newToken = new Token(start, mPosition, defType);
+ } else if (isXPathDigit(*mPosition)) {
+ iterator start = mPosition;
+ while (++mPosition < end && isXPathDigit(*mPosition)) {
+ /* just go */
+ }
+ if (mPosition < end && *mPosition == '.') {
+ while (++mPosition < end && isXPathDigit(*mPosition)) {
+ /* just go */
+ }
+ }
+ newToken = new Token(start, mPosition, Token::NUMBER);
+ } else {
+ switch (*mPosition) {
+ //-- ignore whitespace
+ case SPACE:
+ case TX_TAB:
+ case TX_CR:
+ case TX_LF:
+ ++mPosition;
+ isToken = false;
+ break;
+ case S_QUOTE:
+ case D_QUOTE: {
+ iterator start = mPosition;
+ while (++mPosition < end && *mPosition != *start) {
+ // eat literal
+ }
+ if (mPosition == end) {
+ mPosition = start;
+ return NS_ERROR_XPATH_UNCLOSED_LITERAL;
+ }
+ newToken = new Token(start + 1, mPosition, Token::LITERAL);
+ ++mPosition;
+ } break;
+ case PERIOD:
+ // period can be .., .(DIGITS)+ or ., check next
+ if (++mPosition == end) {
+ newToken = new Token(mPosition - 1, Token::SELF_NODE);
+ } else if (isXPathDigit(*mPosition)) {
+ iterator start = mPosition - 1;
+ while (++mPosition < end && isXPathDigit(*mPosition)) {
+ /* just go */
+ }
+ newToken = new Token(start, mPosition, Token::NUMBER);
+ } else if (*mPosition == PERIOD) {
+ ++mPosition;
+ newToken = new Token(mPosition - 2, mPosition, Token::PARENT_NODE);
+ } else {
+ newToken = new Token(mPosition - 1, Token::SELF_NODE);
+ }
+ break;
+ case COLON: // QNames are dealt above, must be axis ident
+ if (++mPosition >= end || *mPosition != COLON ||
+ prevToken->mType != Token::CNAME) {
+ return NS_ERROR_XPATH_BAD_COLON;
+ }
+ prevToken->mType = Token::AXIS_IDENTIFIER;
+ ++mPosition;
+ isToken = false;
+ break;
+ case FORWARD_SLASH:
+ if (++mPosition < end && *mPosition == FORWARD_SLASH) {
+ ++mPosition;
+ newToken = new Token(mPosition - 2, mPosition, Token::ANCESTOR_OP);
+ } else {
+ newToken = new Token(mPosition - 1, Token::PARENT_OP);
+ }
+ break;
+ case BANG: // can only be !=
+ if (++mPosition < end && *mPosition == EQUAL) {
+ ++mPosition;
+ newToken = new Token(mPosition - 2, mPosition, Token::NOT_EQUAL_OP);
+ break;
+ }
+ // Error ! is not not()
+ return NS_ERROR_XPATH_BAD_BANG;
+ case EQUAL:
+ newToken = new Token(mPosition, Token::EQUAL_OP);
+ ++mPosition;
+ break;
+ case L_ANGLE:
+ if (++mPosition == end) {
+ return NS_ERROR_XPATH_UNEXPECTED_END;
+ }
+ if (*mPosition == EQUAL) {
+ ++mPosition;
+ newToken =
+ new Token(mPosition - 2, mPosition, Token::LESS_OR_EQUAL_OP);
+ } else {
+ newToken = new Token(mPosition - 1, Token::LESS_THAN_OP);
+ }
+ break;
+ case R_ANGLE:
+ if (++mPosition == end) {
+ return NS_ERROR_XPATH_UNEXPECTED_END;
+ }
+ if (*mPosition == EQUAL) {
+ ++mPosition;
+ newToken =
+ new Token(mPosition - 2, mPosition, Token::GREATER_OR_EQUAL_OP);
+ } else {
+ newToken = new Token(mPosition - 1, Token::GREATER_THAN_OP);
+ }
+ break;
+ case HYPHEN:
+ newToken = new Token(mPosition, Token::SUBTRACTION_OP);
+ ++mPosition;
+ break;
+ case ASTERISK:
+ if (nextIsOperatorToken(prevToken)) {
+ newToken = new Token(mPosition, Token::MULTIPLY_OP);
+ } else {
+ newToken = new Token(mPosition, Token::CNAME);
+ }
+ ++mPosition;
+ break;
+ case L_PAREN:
+ if (prevToken->mType == Token::CNAME) {
+ const nsDependentSubstring& val = prevToken->Value();
+ if (val.EqualsLiteral("comment")) {
+ prevToken->mType = Token::COMMENT_AND_PAREN;
+ } else if (val.EqualsLiteral("node")) {
+ prevToken->mType = Token::NODE_AND_PAREN;
+ } else if (val.EqualsLiteral("processing-instruction")) {
+ prevToken->mType = Token::PROC_INST_AND_PAREN;
+ } else if (val.EqualsLiteral("text")) {
+ prevToken->mType = Token::TEXT_AND_PAREN;
+ } else {
+ prevToken->mType = Token::FUNCTION_NAME_AND_PAREN;
+ }
+ isToken = false;
+ } else {
+ newToken = new Token(mPosition, Token::L_PAREN);
+ }
+ ++mPosition;
+ break;
+ case R_PAREN:
+ newToken = new Token(mPosition, Token::R_PAREN);
+ ++mPosition;
+ break;
+ case L_BRACKET:
+ newToken = new Token(mPosition, Token::L_BRACKET);
+ ++mPosition;
+ break;
+ case R_BRACKET:
+ newToken = new Token(mPosition, Token::R_BRACKET);
+ ++mPosition;
+ break;
+ case COMMA:
+ newToken = new Token(mPosition, Token::COMMA);
+ ++mPosition;
+ break;
+ case AT_SIGN:
+ newToken = new Token(mPosition, Token::AT_SIGN);
+ ++mPosition;
+ break;
+ case PLUS:
+ newToken = new Token(mPosition, Token::ADDITION_OP);
+ ++mPosition;
+ break;
+ case VERT_BAR:
+ newToken = new Token(mPosition, Token::UNION_OP);
+ ++mPosition;
+ break;
+ default:
+ // Error, don't grok character :-(
+ return NS_ERROR_XPATH_ILLEGAL_CHAR;
+ }
+ }
+ if (isToken) {
+ NS_ENSURE_TRUE(newToken != mLastItem, NS_ERROR_FAILURE);
+ prevToken = newToken;
+ addToken(newToken);
+ }
+ }
+
+ // add a endToken to the list
+ newToken = new Token(end, end, Token::END);
+ addToken(newToken);
+
+ return NS_OK;
+}
diff --git a/dom/xslt/xpath/txExprLexer.h b/dom/xslt/xpath/txExprLexer.h
new file mode 100644
index 0000000000..9d3ca2810e
--- /dev/null
+++ b/dom/xslt/xpath/txExprLexer.h
@@ -0,0 +1,200 @@
+/* -*- 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/. */
+
+#ifndef MITREXSL_EXPRLEXER_H
+#define MITREXSL_EXPRLEXER_H
+
+#include "txCore.h"
+#include "nsString.h"
+
+/**
+ * A Token class for the ExprLexer.
+ *
+ * This class was ported from XSL:P, an open source Java based
+ * XSLT processor, written by yours truly.
+ */
+class Token {
+ public:
+ /**
+ * Token types
+ */
+ enum Type {
+ //-- Trivial Tokens
+ NULL_TOKEN = 1,
+ LITERAL,
+ NUMBER,
+ CNAME,
+ VAR_REFERENCE,
+ PARENT_NODE,
+ SELF_NODE,
+ R_PAREN,
+ R_BRACKET, // 9
+ /**
+ * start of tokens for 3.7, bullet 1
+ * ExprLexer::nextIsOperatorToken bails if the tokens aren't
+ * consecutive.
+ */
+ COMMA,
+ AT_SIGN,
+ L_PAREN,
+ L_BRACKET,
+ AXIS_IDENTIFIER,
+
+ // These tokens include their following left parenthesis
+ FUNCTION_NAME_AND_PAREN, // 15
+ COMMENT_AND_PAREN,
+ NODE_AND_PAREN,
+ PROC_INST_AND_PAREN,
+ TEXT_AND_PAREN,
+
+ /**
+ * operators
+ */
+ //-- boolean ops
+ AND_OP, // 20
+ OR_OP,
+
+ //-- relational
+ EQUAL_OP, // 22
+ NOT_EQUAL_OP,
+ LESS_THAN_OP,
+ GREATER_THAN_OP,
+ LESS_OR_EQUAL_OP,
+ GREATER_OR_EQUAL_OP,
+ //-- additive operators
+ ADDITION_OP, // 28
+ SUBTRACTION_OP,
+ //-- multiplicative
+ DIVIDE_OP, // 30
+ MULTIPLY_OP,
+ MODULUS_OP,
+ //-- path operators
+ PARENT_OP, // 33
+ ANCESTOR_OP,
+ UNION_OP,
+ /**
+ * end of tokens for 3.7, bullet 1 -/
+ */
+ //-- Special endtoken
+ END // 36
+ };
+
+ /**
+ * Constructors
+ */
+ using iterator = nsAString::const_char_iterator;
+
+ Token(iterator aStart, iterator aEnd, Type aType)
+ : mStart(aStart), mEnd(aEnd), mType(aType), mNext(nullptr) {}
+ Token(iterator aChar, Type aType)
+ : mStart(aChar), mEnd(aChar + 1), mType(aType), mNext(nullptr) {}
+
+ const nsDependentSubstring Value() { return Substring(mStart, mEnd); }
+
+ iterator mStart, mEnd;
+ Type mType;
+ Token* mNext;
+};
+
+/**
+ * A class for splitting an "Expr" String into tokens and
+ * performing basic Lexical Analysis.
+ *
+ * This class was ported from XSL:P, an open source Java based XSL processor
+ */
+
+class txExprLexer {
+ public:
+ txExprLexer();
+ ~txExprLexer();
+
+ /**
+ * Parse the given string.
+ * returns an error result if lexing failed.
+ * The given string must outlive the use of the lexer, as the
+ * generated Tokens point to Substrings of it.
+ * mPosition points to the offending location in case of an error.
+ */
+ nsresult parse(const nsAString& aPattern);
+
+ using iterator = nsAString::const_char_iterator;
+ iterator mPosition;
+
+ /**
+ * Functions for iterating over the TokenList
+ */
+
+ Token* nextToken();
+ Token* peek() {
+ NS_ASSERTION(mCurrentItem, "peek called uninitialized lexer");
+ return mCurrentItem;
+ }
+ Token* peekAhead() {
+ NS_ASSERTION(mCurrentItem, "peekAhead called on uninitialized lexer");
+ // Don't peek past the end node
+ return (mCurrentItem && mCurrentItem->mNext) ? mCurrentItem->mNext
+ : mCurrentItem;
+ }
+ bool hasMoreTokens() {
+ NS_ASSERTION(mCurrentItem, "HasMoreTokens called on uninitialized lexer");
+ return (mCurrentItem && mCurrentItem->mType != Token::END);
+ }
+
+ /**
+ * Trivial Tokens
+ */
+ //-- LF, changed to enum
+ enum _TrivialTokens {
+ D_QUOTE = '\"',
+ S_QUOTE = '\'',
+ L_PAREN = '(',
+ R_PAREN = ')',
+ L_BRACKET = '[',
+ R_BRACKET = ']',
+ L_ANGLE = '<',
+ R_ANGLE = '>',
+ COMMA = ',',
+ PERIOD = '.',
+ ASTERISK = '*',
+ FORWARD_SLASH = '/',
+ EQUAL = '=',
+ BANG = '!',
+ VERT_BAR = '|',
+ AT_SIGN = '@',
+ DOLLAR_SIGN = '$',
+ PLUS = '+',
+ HYPHEN = '-',
+ COLON = ':',
+ //-- whitespace tokens
+ SPACE = ' ',
+ TX_TAB = '\t',
+ TX_CR = '\n',
+ TX_LF = '\r'
+ };
+
+ private:
+ Token* mCurrentItem;
+ Token* mFirstItem;
+ Token* mLastItem;
+
+ int mTokenCount;
+
+ void addToken(Token* aToken);
+
+ /**
+ * Returns true if the following Token should be an operator.
+ * This is a helper for the first bullet of [XPath 3.7]
+ * Lexical Structure
+ */
+ bool nextIsOperatorToken(Token* aToken);
+
+ /**
+ * Returns true if the given character represents a numeric letter (digit)
+ * Implemented in ExprLexerChars.cpp
+ */
+ static bool isXPathDigit(char16_t ch) { return (ch >= '0' && ch <= '9'); }
+};
+
+#endif
diff --git a/dom/xslt/xpath/txExprParser.cpp b/dom/xslt/xpath/txExprParser.cpp
new file mode 100644
index 0000000000..47f142d023
--- /dev/null
+++ b/dom/xslt/xpath/txExprParser.cpp
@@ -0,0 +1,855 @@
+/* -*- 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/. */
+
+/**
+ * ExprParser
+ * This class is used to parse XSL Expressions
+ * @see ExprLexer
+ **/
+
+#include "txExprParser.h"
+
+#include <utility>
+
+#include "mozilla/UniquePtrExtensions.h"
+#include "nsError.h"
+#include "nsGkAtoms.h"
+#include "txExpr.h"
+#include "txExprLexer.h"
+#include "txIXPathContext.h"
+#include "txStack.h"
+#include "txStringUtils.h"
+#include "txXPathNode.h"
+#include "txXPathOptimizer.h"
+
+using mozilla::MakeUnique;
+using mozilla::UniquePtr;
+using mozilla::Unused;
+using mozilla::WrapUnique;
+
+/**
+ * Creates an Attribute Value Template using the given value
+ * This should move to XSLProcessor class
+ */
+nsresult txExprParser::createAVT(const nsAString& aAttrValue,
+ txIParseContext* aContext, Expr** aResult) {
+ *aResult = nullptr;
+ nsresult rv = NS_OK;
+ UniquePtr<Expr> expr;
+ FunctionCall* concat = nullptr;
+
+ nsAutoString literalString;
+ bool inExpr = false;
+ nsAString::const_char_iterator iter, start, end, avtStart;
+ aAttrValue.BeginReading(iter);
+ aAttrValue.EndReading(end);
+ avtStart = iter;
+
+ while (iter != end) {
+ // Every iteration through this loop parses either a literal section
+ // or an expression
+ start = iter;
+ UniquePtr<Expr> newExpr;
+ if (!inExpr) {
+ // Parse literal section
+ literalString.Truncate();
+ while (iter != end) {
+ char16_t q = *iter;
+ if (q == '{' || q == '}') {
+ // Store what we've found so far and set a new |start| to
+ // skip the (first) brace
+ literalString.Append(Substring(start, iter));
+ start = ++iter;
+ // Unless another brace follows we've found the start of
+ // an expression (in case of '{') or an unbalanced brace
+ // (in case of '}')
+ if (iter == end || *iter != q) {
+ if (q == '}') {
+ aContext->SetErrorOffset(iter - avtStart);
+ return NS_ERROR_XPATH_UNBALANCED_CURLY_BRACE;
+ }
+
+ inExpr = true;
+ break;
+ }
+ // We found a second brace, let that be part of the next
+ // literal section being parsed and continue looping
+ }
+ ++iter;
+ }
+
+ if (start == iter && literalString.IsEmpty()) {
+ // Restart the loop since we didn't create an expression
+ continue;
+ }
+ newExpr =
+ MakeUnique<txLiteralExpr>(literalString + Substring(start, iter));
+ } else {
+ // Parse expressions, iter is already past the initial '{' when
+ // we get here.
+ while (iter != end) {
+ if (*iter == '}') {
+ rv = createExprInternal(Substring(start, iter), start - avtStart,
+ aContext, getter_Transfers(newExpr));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ inExpr = false;
+ ++iter; // skip closing '}'
+ break;
+ } else if (*iter == '\'' || *iter == '"') {
+ char16_t q = *iter;
+ while (++iter != end && *iter != q) {
+ } /* do nothing */
+ if (iter == end) {
+ break;
+ }
+ }
+ ++iter;
+ }
+
+ if (inExpr) {
+ aContext->SetErrorOffset(start - avtStart);
+ return NS_ERROR_XPATH_UNBALANCED_CURLY_BRACE;
+ }
+ }
+
+ // Add expression, create a concat() call if necessary
+ if (!expr) {
+ expr = std::move(newExpr);
+ } else {
+ if (!concat) {
+ concat = new txCoreFunctionCall(txCoreFunctionCall::CONCAT);
+ concat->addParam(expr.release());
+ expr = WrapUnique(concat);
+ }
+
+ concat->addParam(newExpr.release());
+ }
+ }
+
+ if (inExpr) {
+ aContext->SetErrorOffset(iter - avtStart);
+ return NS_ERROR_XPATH_UNBALANCED_CURLY_BRACE;
+ }
+
+ if (!expr) {
+ expr = MakeUnique<txLiteralExpr>(u""_ns);
+ }
+
+ *aResult = expr.release();
+
+ return NS_OK;
+}
+
+nsresult txExprParser::createExprInternal(const nsAString& aExpression,
+ uint32_t aSubStringPos,
+ txIParseContext* aContext,
+ Expr** aExpr) {
+ NS_ENSURE_ARG_POINTER(aExpr);
+ *aExpr = nullptr;
+ txExprLexer lexer;
+ nsresult rv = lexer.parse(aExpression);
+ if (NS_FAILED(rv)) {
+ nsAString::const_char_iterator start;
+ aExpression.BeginReading(start);
+ aContext->SetErrorOffset(lexer.mPosition - start + aSubStringPos);
+ return rv;
+ }
+ UniquePtr<Expr> expr;
+ rv = createExpr(lexer, aContext, getter_Transfers(expr));
+ if (NS_SUCCEEDED(rv) && lexer.peek()->mType != Token::END) {
+ rv = NS_ERROR_XPATH_BINARY_EXPECTED;
+ }
+ if (NS_FAILED(rv)) {
+ nsAString::const_char_iterator start;
+ aExpression.BeginReading(start);
+ aContext->SetErrorOffset(lexer.peek()->mStart - start + aSubStringPos);
+
+ return rv;
+ }
+
+ txXPathOptimizer optimizer;
+ Expr* newExpr = nullptr;
+ optimizer.optimize(expr.get(), &newExpr);
+
+ *aExpr = newExpr ? newExpr : expr.release();
+
+ return NS_OK;
+}
+
+/**
+ * Private Methods
+ */
+
+/**
+ * Creates a binary Expr for the given operator
+ */
+nsresult txExprParser::createBinaryExpr(UniquePtr<Expr>& left,
+ UniquePtr<Expr>& right, Token* op,
+ Expr** aResult) {
+ NS_ASSERTION(op, "internal error");
+ *aResult = nullptr;
+
+ Expr* expr = nullptr;
+ switch (op->mType) {
+ //-- math ops
+ case Token::ADDITION_OP:
+ expr = new txNumberExpr(left.get(), right.get(), txNumberExpr::ADD);
+ break;
+ case Token::SUBTRACTION_OP:
+ expr = new txNumberExpr(left.get(), right.get(), txNumberExpr::SUBTRACT);
+ break;
+ case Token::DIVIDE_OP:
+ expr = new txNumberExpr(left.get(), right.get(), txNumberExpr::DIVIDE);
+ break;
+ case Token::MODULUS_OP:
+ expr = new txNumberExpr(left.get(), right.get(), txNumberExpr::MODULUS);
+ break;
+ case Token::MULTIPLY_OP:
+ expr = new txNumberExpr(left.get(), right.get(), txNumberExpr::MULTIPLY);
+ break;
+
+ //-- case boolean ops
+ case Token::AND_OP:
+ expr = new BooleanExpr(left.get(), right.get(), BooleanExpr::AND);
+ break;
+ case Token::OR_OP:
+ expr = new BooleanExpr(left.get(), right.get(), BooleanExpr::OR);
+ break;
+
+ //-- equality ops
+ case Token::EQUAL_OP:
+ expr = new RelationalExpr(left.get(), right.get(), RelationalExpr::EQUAL);
+ break;
+ case Token::NOT_EQUAL_OP:
+ expr = new RelationalExpr(left.get(), right.get(),
+ RelationalExpr::NOT_EQUAL);
+ break;
+
+ //-- relational ops
+ case Token::LESS_THAN_OP:
+ expr = new RelationalExpr(left.get(), right.get(),
+ RelationalExpr::LESS_THAN);
+ break;
+ case Token::GREATER_THAN_OP:
+ expr = new RelationalExpr(left.get(), right.get(),
+ RelationalExpr::GREATER_THAN);
+ break;
+ case Token::LESS_OR_EQUAL_OP:
+ expr = new RelationalExpr(left.get(), right.get(),
+ RelationalExpr::LESS_OR_EQUAL);
+ break;
+ case Token::GREATER_OR_EQUAL_OP:
+ expr = new RelationalExpr(left.get(), right.get(),
+ RelationalExpr::GREATER_OR_EQUAL);
+ break;
+
+ default:
+ MOZ_ASSERT_UNREACHABLE("operator tokens should be already checked");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ Unused << left.release();
+ Unused << right.release();
+
+ *aResult = expr;
+ return NS_OK;
+}
+
+nsresult txExprParser::createExpr(txExprLexer& lexer, txIParseContext* aContext,
+ Expr** aResult) {
+ *aResult = nullptr;
+
+ nsresult rv = NS_OK;
+ bool done = false;
+
+ UniquePtr<Expr> expr;
+
+ txStack exprs;
+ txStack ops;
+
+ while (!done) {
+ uint16_t negations = 0;
+ while (lexer.peek()->mType == Token::SUBTRACTION_OP) {
+ negations++;
+ lexer.nextToken();
+ }
+
+ rv = createUnionExpr(lexer, aContext, getter_Transfers(expr));
+ if (NS_FAILED(rv)) {
+ break;
+ }
+
+ if (negations > 0) {
+ if (negations % 2 == 0) {
+ auto fcExpr =
+ MakeUnique<txCoreFunctionCall>(txCoreFunctionCall::NUMBER);
+
+ fcExpr->addParam(expr.release());
+ expr = std::move(fcExpr);
+ } else {
+ expr = MakeUnique<UnaryExpr>(expr.release());
+ }
+ }
+
+ short tokPrecedence = precedence(lexer.peek());
+ if (tokPrecedence != 0) {
+ Token* tok = lexer.nextToken();
+ while (!exprs.isEmpty() &&
+ tokPrecedence <= precedence(static_cast<Token*>(ops.peek()))) {
+ // can't use expr as argument due to order of evaluation
+ UniquePtr<Expr> left(static_cast<Expr*>(exprs.pop()));
+ UniquePtr<Expr> right(std::move(expr));
+ rv = createBinaryExpr(left, right, static_cast<Token*>(ops.pop()),
+ getter_Transfers(expr));
+ if (NS_FAILED(rv)) {
+ done = true;
+ break;
+ }
+ }
+ exprs.push(expr.release());
+ ops.push(tok);
+ } else {
+ done = true;
+ }
+ }
+
+ while (NS_SUCCEEDED(rv) && !exprs.isEmpty()) {
+ UniquePtr<Expr> left(static_cast<Expr*>(exprs.pop()));
+ UniquePtr<Expr> right(std::move(expr));
+ rv = createBinaryExpr(left, right, static_cast<Token*>(ops.pop()),
+ getter_Transfers(expr));
+ }
+ // clean up on error
+ while (!exprs.isEmpty()) {
+ delete static_cast<Expr*>(exprs.pop());
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *aResult = expr.release();
+ return NS_OK;
+}
+
+nsresult txExprParser::createFilterOrStep(txExprLexer& lexer,
+ txIParseContext* aContext,
+ Expr** aResult) {
+ *aResult = nullptr;
+
+ nsresult rv = NS_OK;
+ Token* tok = lexer.peek();
+
+ UniquePtr<Expr> expr;
+ switch (tok->mType) {
+ case Token::FUNCTION_NAME_AND_PAREN:
+ rv = createFunctionCall(lexer, aContext, getter_Transfers(expr));
+ NS_ENSURE_SUCCESS(rv, rv);
+ break;
+ case Token::VAR_REFERENCE:
+ lexer.nextToken();
+ {
+ RefPtr<nsAtom> prefix, lName;
+ int32_t nspace;
+ nsresult rv = resolveQName(tok->Value(), getter_AddRefs(prefix),
+ aContext, getter_AddRefs(lName), nspace);
+ NS_ENSURE_SUCCESS(rv, rv);
+ expr = MakeUnique<VariableRefExpr>(prefix, lName, nspace);
+ }
+ break;
+ case Token::L_PAREN:
+ lexer.nextToken();
+ rv = createExpr(lexer, aContext, getter_Transfers(expr));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (lexer.peek()->mType != Token::R_PAREN) {
+ return NS_ERROR_XPATH_PAREN_EXPECTED;
+ }
+ lexer.nextToken();
+ break;
+ case Token::LITERAL:
+ lexer.nextToken();
+ expr = MakeUnique<txLiteralExpr>(tok->Value());
+ break;
+ case Token::NUMBER: {
+ lexer.nextToken();
+ expr = MakeUnique<txLiteralExpr>(txDouble::toDouble(tok->Value()));
+ break;
+ }
+ default:
+ return createLocationStep(lexer, aContext, aResult);
+ }
+
+ if (lexer.peek()->mType == Token::L_BRACKET) {
+ UniquePtr<FilterExpr> filterExpr(new FilterExpr(expr.get()));
+
+ Unused << expr.release();
+
+ //-- handle predicates
+ rv = parsePredicates(filterExpr.get(), lexer, aContext);
+ NS_ENSURE_SUCCESS(rv, rv);
+ expr = std::move(filterExpr);
+ }
+
+ *aResult = expr.release();
+ return NS_OK;
+}
+
+nsresult txExprParser::createFunctionCall(txExprLexer& lexer,
+ txIParseContext* aContext,
+ Expr** aResult) {
+ *aResult = nullptr;
+
+ UniquePtr<FunctionCall> fnCall;
+
+ Token* tok = lexer.nextToken();
+ NS_ASSERTION(tok->mType == Token::FUNCTION_NAME_AND_PAREN,
+ "FunctionCall expected");
+
+ //-- compare function names
+ RefPtr<nsAtom> prefix, lName;
+ int32_t namespaceID;
+ nsresult rv = resolveQName(tok->Value(), getter_AddRefs(prefix), aContext,
+ getter_AddRefs(lName), namespaceID);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ txCoreFunctionCall::eType type;
+ if (namespaceID == kNameSpaceID_None &&
+ txCoreFunctionCall::getTypeFromAtom(lName, type)) {
+ // It is a known built-in function.
+ fnCall = MakeUnique<txCoreFunctionCall>(type);
+ }
+
+ // check extension functions and xslt
+ if (!fnCall) {
+ rv = aContext->resolveFunctionCall(lName, namespaceID,
+ getter_Transfers(fnCall));
+
+ if (rv == NS_ERROR_NOT_IMPLEMENTED) {
+ // this should just happen for unparsed-entity-uri()
+ NS_ASSERTION(!fnCall, "Now is it implemented or not?");
+ rv = parseParameters(0, lexer, aContext);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *aResult = new txLiteralExpr(tok->Value() + u" not implemented."_ns);
+
+ return NS_OK;
+ }
+
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ //-- handle parametes
+ rv = parseParameters(fnCall.get(), lexer, aContext);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *aResult = fnCall.release();
+ return NS_OK;
+}
+
+nsresult txExprParser::createLocationStep(txExprLexer& lexer,
+ txIParseContext* aContext,
+ Expr** aExpr) {
+ *aExpr = nullptr;
+
+ //-- child axis is default
+ LocationStep::LocationStepType axisIdentifier = LocationStep::CHILD_AXIS;
+ UniquePtr<txNodeTest> nodeTest;
+
+ //-- get Axis Identifier or AbbreviatedStep, if present
+ Token* tok = lexer.peek();
+ switch (tok->mType) {
+ case Token::AXIS_IDENTIFIER: {
+ //-- eat token
+ lexer.nextToken();
+ RefPtr<nsAtom> axis = NS_Atomize(tok->Value());
+ if (axis == nsGkAtoms::ancestor) {
+ axisIdentifier = LocationStep::ANCESTOR_AXIS;
+ } else if (axis == nsGkAtoms::ancestorOrSelf) {
+ axisIdentifier = LocationStep::ANCESTOR_OR_SELF_AXIS;
+ } else if (axis == nsGkAtoms::attribute) {
+ axisIdentifier = LocationStep::ATTRIBUTE_AXIS;
+ } else if (axis == nsGkAtoms::child) {
+ axisIdentifier = LocationStep::CHILD_AXIS;
+ } else if (axis == nsGkAtoms::descendant) {
+ axisIdentifier = LocationStep::DESCENDANT_AXIS;
+ } else if (axis == nsGkAtoms::descendantOrSelf) {
+ axisIdentifier = LocationStep::DESCENDANT_OR_SELF_AXIS;
+ } else if (axis == nsGkAtoms::following) {
+ axisIdentifier = LocationStep::FOLLOWING_AXIS;
+ } else if (axis == nsGkAtoms::followingSibling) {
+ axisIdentifier = LocationStep::FOLLOWING_SIBLING_AXIS;
+ } else if (axis == nsGkAtoms::_namespace) {
+ axisIdentifier = LocationStep::NAMESPACE_AXIS;
+ } else if (axis == nsGkAtoms::parent) {
+ axisIdentifier = LocationStep::PARENT_AXIS;
+ } else if (axis == nsGkAtoms::preceding) {
+ axisIdentifier = LocationStep::PRECEDING_AXIS;
+ } else if (axis == nsGkAtoms::precedingSibling) {
+ axisIdentifier = LocationStep::PRECEDING_SIBLING_AXIS;
+ } else if (axis == nsGkAtoms::self) {
+ axisIdentifier = LocationStep::SELF_AXIS;
+ } else {
+ return NS_ERROR_XPATH_INVALID_AXIS;
+ }
+ break;
+ }
+ case Token::AT_SIGN:
+ //-- eat token
+ lexer.nextToken();
+ axisIdentifier = LocationStep::ATTRIBUTE_AXIS;
+ break;
+ case Token::PARENT_NODE:
+ //-- eat token
+ lexer.nextToken();
+ axisIdentifier = LocationStep::PARENT_AXIS;
+ nodeTest = MakeUnique<txNodeTypeTest>(txNodeTypeTest::NODE_TYPE);
+ break;
+ case Token::SELF_NODE:
+ //-- eat token
+ lexer.nextToken();
+ axisIdentifier = LocationStep::SELF_AXIS;
+ nodeTest = MakeUnique<txNodeTypeTest>(txNodeTypeTest::NODE_TYPE);
+ break;
+ default:
+ break;
+ }
+
+ //-- get NodeTest unless an AbbreviatedStep was found
+ nsresult rv = NS_OK;
+ if (!nodeTest) {
+ tok = lexer.peek();
+
+ if (tok->mType == Token::CNAME) {
+ lexer.nextToken();
+ // resolve QName
+ RefPtr<nsAtom> prefix, lName;
+ int32_t nspace;
+ rv = resolveQName(tok->Value(), getter_AddRefs(prefix), aContext,
+ getter_AddRefs(lName), nspace, true);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nodeTest = MakeUnique<txNameTest>(
+ prefix, lName, nspace,
+ axisIdentifier == LocationStep::ATTRIBUTE_AXIS
+ ? static_cast<uint16_t>(txXPathNodeType::ATTRIBUTE_NODE)
+ : static_cast<uint16_t>(txXPathNodeType::ELEMENT_NODE));
+ } else {
+ rv = createNodeTypeTest(lexer, getter_Transfers(nodeTest));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ UniquePtr<LocationStep> lstep(
+ new LocationStep(nodeTest.get(), axisIdentifier));
+
+ Unused << nodeTest.release();
+
+ //-- handle predicates
+ rv = parsePredicates(lstep.get(), lexer, aContext);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *aExpr = lstep.release();
+ return NS_OK;
+}
+
+/**
+ * This method only handles comment(), text(), processing-instructing()
+ * and node()
+ */
+nsresult txExprParser::createNodeTypeTest(txExprLexer& lexer,
+ txNodeTest** aTest) {
+ *aTest = 0;
+ UniquePtr<txNodeTypeTest> nodeTest;
+
+ Token* nodeTok = lexer.peek();
+
+ switch (nodeTok->mType) {
+ case Token::COMMENT_AND_PAREN:
+ lexer.nextToken();
+ nodeTest = MakeUnique<txNodeTypeTest>(txNodeTypeTest::COMMENT_TYPE);
+ break;
+ case Token::NODE_AND_PAREN:
+ lexer.nextToken();
+ nodeTest = MakeUnique<txNodeTypeTest>(txNodeTypeTest::NODE_TYPE);
+ break;
+ case Token::PROC_INST_AND_PAREN:
+ lexer.nextToken();
+ nodeTest = MakeUnique<txNodeTypeTest>(txNodeTypeTest::PI_TYPE);
+ break;
+ case Token::TEXT_AND_PAREN:
+ lexer.nextToken();
+ nodeTest = MakeUnique<txNodeTypeTest>(txNodeTypeTest::TEXT_TYPE);
+ break;
+ default:
+ return NS_ERROR_XPATH_NO_NODE_TYPE_TEST;
+ }
+
+ if (nodeTok->mType == Token::PROC_INST_AND_PAREN &&
+ lexer.peek()->mType == Token::LITERAL) {
+ Token* tok = lexer.nextToken();
+ nodeTest->setNodeName(tok->Value());
+ }
+ if (lexer.peek()->mType != Token::R_PAREN) {
+ return NS_ERROR_XPATH_PAREN_EXPECTED;
+ }
+ lexer.nextToken();
+
+ *aTest = nodeTest.release();
+ return NS_OK;
+}
+
+/**
+ * Creates a PathExpr using the given txExprLexer
+ * @param lexer the txExprLexer for retrieving Tokens
+ */
+nsresult txExprParser::createPathExpr(txExprLexer& lexer,
+ txIParseContext* aContext,
+ Expr** aResult) {
+ *aResult = nullptr;
+
+ UniquePtr<Expr> expr;
+
+ Token* tok = lexer.peek();
+
+ // is this a root expression?
+ if (tok->mType == Token::PARENT_OP) {
+ if (!isLocationStepToken(lexer.peekAhead())) {
+ lexer.nextToken();
+ *aResult = new RootExpr();
+ return NS_OK;
+ }
+ }
+
+ // parse first step (possibly a FilterExpr)
+ nsresult rv = NS_OK;
+ if (tok->mType != Token::PARENT_OP && tok->mType != Token::ANCESTOR_OP) {
+ rv = createFilterOrStep(lexer, aContext, getter_Transfers(expr));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // is this a singlestep path expression?
+ tok = lexer.peek();
+ if (tok->mType != Token::PARENT_OP && tok->mType != Token::ANCESTOR_OP) {
+ *aResult = expr.release();
+ return NS_OK;
+ }
+ } else {
+ expr = MakeUnique<RootExpr>();
+
+#ifdef TX_TO_STRING
+ static_cast<RootExpr*>(expr.get())->setSerialize(false);
+#endif
+ }
+
+ // We have a PathExpr containing several steps
+ UniquePtr<PathExpr> pathExpr(new PathExpr());
+ pathExpr->addExpr(expr.release(), PathExpr::RELATIVE_OP);
+
+ // this is ugly
+ while (1) {
+ PathExpr::PathOperator pathOp;
+ switch (lexer.peek()->mType) {
+ case Token::ANCESTOR_OP:
+ pathOp = PathExpr::DESCENDANT_OP;
+ break;
+ case Token::PARENT_OP:
+ pathOp = PathExpr::RELATIVE_OP;
+ break;
+ default:
+ *aResult = pathExpr.release();
+ return NS_OK;
+ }
+ lexer.nextToken();
+
+ rv = createLocationStep(lexer, aContext, getter_Transfers(expr));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ pathExpr->addExpr(expr.release(), pathOp);
+ }
+ MOZ_ASSERT_UNREACHABLE("internal xpath parser error");
+ return NS_ERROR_UNEXPECTED;
+}
+
+/**
+ * Creates a PathExpr using the given txExprLexer
+ * @param lexer the txExprLexer for retrieving Tokens
+ */
+nsresult txExprParser::createUnionExpr(txExprLexer& lexer,
+ txIParseContext* aContext,
+ Expr** aResult) {
+ *aResult = nullptr;
+
+ UniquePtr<Expr> expr;
+ nsresult rv = createPathExpr(lexer, aContext, getter_Transfers(expr));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (lexer.peek()->mType != Token::UNION_OP) {
+ *aResult = expr.release();
+ return NS_OK;
+ }
+
+ UniquePtr<UnionExpr> unionExpr(new UnionExpr());
+ unionExpr->addExpr(expr.release());
+
+ while (lexer.peek()->mType == Token::UNION_OP) {
+ lexer.nextToken(); //-- eat token
+
+ rv = createPathExpr(lexer, aContext, getter_Transfers(expr));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ unionExpr->addExpr(expr.release());
+ }
+
+ *aResult = unionExpr.release();
+ return NS_OK;
+}
+
+bool txExprParser::isLocationStepToken(Token* aToken) {
+ // We could put these in consecutive order in ExprLexer.h for speed
+ return aToken->mType == Token::AXIS_IDENTIFIER ||
+ aToken->mType == Token::AT_SIGN ||
+ aToken->mType == Token::PARENT_NODE ||
+ aToken->mType == Token::SELF_NODE || aToken->mType == Token::CNAME ||
+ aToken->mType == Token::COMMENT_AND_PAREN ||
+ aToken->mType == Token::NODE_AND_PAREN ||
+ aToken->mType == Token::PROC_INST_AND_PAREN ||
+ aToken->mType == Token::TEXT_AND_PAREN;
+}
+
+/**
+ * Using the given lexer, parses the tokens if they represent a predicate list
+ * If an error occurs a non-zero String pointer will be returned containing the
+ * error message.
+ * @param predicateList, the PredicateList to add predicate expressions to
+ * @param lexer the txExprLexer to use for parsing tokens
+ * @return 0 if successful, or a String pointer to the error message
+ */
+nsresult txExprParser::parsePredicates(PredicateList* aPredicateList,
+ txExprLexer& lexer,
+ txIParseContext* aContext) {
+ UniquePtr<Expr> expr;
+ nsresult rv = NS_OK;
+ while (lexer.peek()->mType == Token::L_BRACKET) {
+ //-- eat Token
+ lexer.nextToken();
+
+ rv = createExpr(lexer, aContext, getter_Transfers(expr));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aPredicateList->add(expr.release());
+
+ if (lexer.peek()->mType != Token::R_BRACKET) {
+ return NS_ERROR_XPATH_BRACKET_EXPECTED;
+ }
+ lexer.nextToken();
+ }
+ return NS_OK;
+}
+
+/**
+ * Using the given lexer, parses the tokens if they represent a parameter list
+ * If an error occurs a non-zero String pointer will be returned containing the
+ * error message.
+ * @param list, the List to add parameter expressions to
+ * @param lexer the txExprLexer to use for parsing tokens
+ * @return NS_OK if successful, or another rv otherwise
+ */
+nsresult txExprParser::parseParameters(FunctionCall* aFnCall,
+ txExprLexer& lexer,
+ txIParseContext* aContext) {
+ if (lexer.peek()->mType == Token::R_PAREN) {
+ lexer.nextToken();
+ return NS_OK;
+ }
+
+ UniquePtr<Expr> expr;
+ nsresult rv = NS_OK;
+ while (1) {
+ rv = createExpr(lexer, aContext, getter_Transfers(expr));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (aFnCall) {
+ aFnCall->addParam(expr.release());
+ }
+
+ switch (lexer.peek()->mType) {
+ case Token::R_PAREN:
+ lexer.nextToken();
+ return NS_OK;
+ case Token::COMMA: //-- param separator
+ lexer.nextToken();
+ break;
+ default:
+ return NS_ERROR_XPATH_PAREN_EXPECTED;
+ }
+ }
+
+ MOZ_ASSERT_UNREACHABLE("internal xpath parser error");
+ return NS_ERROR_UNEXPECTED;
+}
+
+short txExprParser::precedence(Token* aToken) {
+ switch (aToken->mType) {
+ case Token::OR_OP:
+ return 1;
+ case Token::AND_OP:
+ return 2;
+ //-- equality
+ case Token::EQUAL_OP:
+ case Token::NOT_EQUAL_OP:
+ return 3;
+ //-- relational
+ case Token::LESS_THAN_OP:
+ case Token::GREATER_THAN_OP:
+ case Token::LESS_OR_EQUAL_OP:
+ case Token::GREATER_OR_EQUAL_OP:
+ return 4;
+ //-- additive operators
+ case Token::ADDITION_OP:
+ case Token::SUBTRACTION_OP:
+ return 5;
+ //-- multiplicative
+ case Token::DIVIDE_OP:
+ case Token::MULTIPLY_OP:
+ case Token::MODULUS_OP:
+ return 6;
+ default:
+ break;
+ }
+ return 0;
+}
+
+nsresult txExprParser::resolveQName(const nsAString& aQName, nsAtom** aPrefix,
+ txIParseContext* aContext,
+ nsAtom** aLocalName, int32_t& aNamespace,
+ bool aIsNameTest) {
+ aNamespace = kNameSpaceID_None;
+ int32_t idx = aQName.FindChar(':');
+ if (idx > 0) {
+ *aPrefix = NS_Atomize(StringHead(aQName, (uint32_t)idx)).take();
+ if (!*aPrefix) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ *aLocalName = NS_Atomize(Substring(aQName, (uint32_t)idx + 1,
+ aQName.Length() - (idx + 1)))
+ .take();
+ if (!*aLocalName) {
+ NS_RELEASE(*aPrefix);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ return aContext->resolveNamespacePrefix(*aPrefix, aNamespace);
+ }
+ // the lexer dealt with idx == 0
+ *aPrefix = 0;
+ if (aIsNameTest && aContext->caseInsensitiveNameTests()) {
+ nsAutoString lcname;
+ nsContentUtils::ASCIIToLower(aQName, lcname);
+ *aLocalName = NS_Atomize(lcname).take();
+ } else {
+ *aLocalName = NS_Atomize(aQName).take();
+ }
+ if (!*aLocalName) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ return NS_OK;
+}
diff --git a/dom/xslt/xpath/txExprParser.h b/dom/xslt/xpath/txExprParser.h
new file mode 100644
index 0000000000..06c07cf232
--- /dev/null
+++ b/dom/xslt/xpath/txExprParser.h
@@ -0,0 +1,95 @@
+/* -*- 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/. */
+
+/**
+ * ExprParser
+ * This class is used to parse XSL Expressions
+ * @see ExprLexer
+ **/
+
+#ifndef MITREXSL_EXPRPARSER_H
+#define MITREXSL_EXPRPARSER_H
+
+#include "txCore.h"
+#include "mozilla/UniquePtr.h"
+#include "nsString.h"
+
+class Expr;
+class txExprLexer;
+class FunctionCall;
+class LocationStep;
+class nsAtom;
+class PredicateList;
+class Token;
+class txIParseContext;
+class txNodeTest;
+
+class txExprParser {
+ public:
+ static nsresult createExpr(const nsAString& aExpression,
+ txIParseContext* aContext, Expr** aExpr) {
+ return createExprInternal(aExpression, 0, aContext, aExpr);
+ }
+
+ /**
+ * Creates an Attribute Value Template using the given value
+ */
+ static nsresult createAVT(const nsAString& aAttrValue,
+ txIParseContext* aContext, Expr** aResult);
+
+ protected:
+ static nsresult createExprInternal(const nsAString& aExpression,
+ uint32_t aSubStringPos,
+ txIParseContext* aContext, Expr** aExpr);
+ /**
+ * Using nsAutoPtr& to optimize passing the ownership to the
+ * created binary expression objects.
+ */
+ static nsresult createBinaryExpr(mozilla::UniquePtr<Expr>& left,
+ mozilla::UniquePtr<Expr>& right, Token* op,
+ Expr** aResult);
+ static nsresult createExpr(txExprLexer& lexer, txIParseContext* aContext,
+ Expr** aResult);
+ static nsresult createFilterOrStep(txExprLexer& lexer,
+ txIParseContext* aContext, Expr** aResult);
+ static nsresult createFunctionCall(txExprLexer& lexer,
+ txIParseContext* aContext, Expr** aResult);
+ static nsresult createLocationStep(txExprLexer& lexer,
+ txIParseContext* aContext, Expr** aResult);
+ static nsresult createNodeTypeTest(txExprLexer& lexer, txNodeTest** aResult);
+ static nsresult createPathExpr(txExprLexer& lexer, txIParseContext* aContext,
+ Expr** aResult);
+ static nsresult createUnionExpr(txExprLexer& lexer, txIParseContext* aContext,
+ Expr** aResult);
+
+ static bool isLocationStepToken(Token* aToken);
+
+ static short precedence(Token* aToken);
+
+ /**
+ * Resolve a QName, given the mContext parse context.
+ * Returns prefix and localName as well as namespace ID
+ */
+ static nsresult resolveQName(const nsAString& aQName, nsAtom** aPrefix,
+ txIParseContext* aContext, nsAtom** aLocalName,
+ int32_t& aNamespace, bool aIsNameTest = false);
+
+ /**
+ * Using the given lexer, parses the tokens if they represent a
+ * predicate list
+ * If an error occurs a non-zero String pointer will be returned
+ * containing the error message.
+ * @param predicateList, the PredicateList to add predicate expressions to
+ * @param lexer the ExprLexer to use for parsing tokens
+ * @return 0 if successful, or a String pointer to the error message
+ */
+ static nsresult parsePredicates(PredicateList* aPredicateList,
+ txExprLexer& lexer,
+ txIParseContext* aContext);
+ static nsresult parseParameters(FunctionCall* aFnCall, txExprLexer& lexer,
+ txIParseContext* aContext);
+};
+
+#endif
diff --git a/dom/xslt/xpath/txExprResult.h b/dom/xslt/xpath/txExprResult.h
new file mode 100644
index 0000000000..5572b222f5
--- /dev/null
+++ b/dom/xslt/xpath/txExprResult.h
@@ -0,0 +1,118 @@
+/* -*- 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/. */
+
+#ifndef TRANSFRMX_EXPRRESULT_H
+#define TRANSFRMX_EXPRRESULT_H
+
+#include "nsString.h"
+#include "txCore.h"
+#include "txResultRecycler.h"
+
+/*
+ * ExprResult
+ *
+ * Classes Represented:
+ * BooleanResult, ExprResult, NumberResult, StringResult
+ *
+ * Note: for NodeSet, see NodeSet.h
+ */
+
+class txAExprResult {
+ public:
+ friend class txResultRecycler;
+
+ // Update txLiteralExpr::getReturnType and sTypes in txEXSLTFunctions.cpp if
+ // this enum is changed.
+ enum ResultType {
+ NODESET = 0,
+ BOOLEAN,
+ NUMBER,
+ STRING,
+ RESULT_TREE_FRAGMENT
+ };
+
+ explicit txAExprResult(txResultRecycler* aRecycler) : mRecycler(aRecycler) {}
+ virtual ~txAExprResult() = default;
+
+ void AddRef() {
+ ++mRefCnt;
+ NS_LOG_ADDREF(this, mRefCnt, "txAExprResult", sizeof(*this));
+ }
+
+ void Release(); // Implemented in txResultRecycler.cpp
+
+ /**
+ * Returns the type of ExprResult represented
+ * @return the type of ExprResult represented
+ **/
+ virtual short getResultType() = 0;
+
+ /**
+ * Creates a String representation of this ExprResult
+ * @param aResult the destination string to append the String
+ * representation to.
+ **/
+ virtual void stringValue(nsString& aResult) = 0;
+
+ /**
+ * Returns a pointer to the stringvalue if possible. Otherwise null is
+ * returned.
+ */
+ virtual const nsString* stringValuePointer() = 0;
+
+ /**
+ * Converts this ExprResult to a Boolean (bool) value
+ * @return the Boolean value
+ **/
+ virtual bool booleanValue() = 0;
+
+ /**
+ * Converts this ExprResult to a Number (double) value
+ * @return the Number value
+ **/
+ virtual double numberValue() = 0;
+
+ private:
+ nsAutoRefCnt mRefCnt;
+ RefPtr<txResultRecycler> mRecycler;
+};
+
+#define TX_DECL_EXPRRESULT \
+ virtual short getResultType() override; \
+ virtual void stringValue(nsString& aString) override; \
+ virtual const nsString* stringValuePointer() override; \
+ virtual bool booleanValue() override; \
+ virtual double numberValue() override;
+
+class BooleanResult : public txAExprResult {
+ public:
+ explicit BooleanResult(bool aValue);
+
+ TX_DECL_EXPRRESULT
+
+ private:
+ bool value;
+};
+
+class NumberResult : public txAExprResult {
+ public:
+ NumberResult(double aValue, txResultRecycler* aRecycler);
+
+ TX_DECL_EXPRRESULT
+
+ double value;
+};
+
+class StringResult : public txAExprResult {
+ public:
+ explicit StringResult(txResultRecycler* aRecycler);
+ StringResult(const nsAString& aValue, txResultRecycler* aRecycler);
+
+ TX_DECL_EXPRRESULT
+
+ nsString mValue;
+};
+
+#endif
diff --git a/dom/xslt/xpath/txFilterExpr.cpp b/dom/xslt/xpath/txFilterExpr.cpp
new file mode 100644
index 0000000000..f632683290
--- /dev/null
+++ b/dom/xslt/xpath/txFilterExpr.cpp
@@ -0,0 +1,88 @@
+/* -*- 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 "txExpr.h"
+#include "txNodeSet.h"
+#include "txIXPathContext.h"
+
+using mozilla::Unused;
+using mozilla::WrapUnique;
+
+//-- Implementation of FilterExpr --/
+
+//-----------------------------/
+//- Virtual methods from Expr -/
+//-----------------------------/
+
+/**
+ * Evaluates this Expr based on the given context node and processor state
+ * @param context the context node for evaluation of this Expr
+ * @param ps the ProcessorState containing the stack information needed
+ * for evaluation
+ * @return the result of the evaluation
+ * @see Expr
+ **/
+nsresult FilterExpr::evaluate(txIEvalContext* aContext,
+ txAExprResult** aResult) {
+ *aResult = nullptr;
+
+ RefPtr<txAExprResult> exprRes;
+ nsresult rv = expr->evaluate(aContext, getter_AddRefs(exprRes));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ENSURE_TRUE(exprRes->getResultType() == txAExprResult::NODESET,
+ NS_ERROR_XSLT_NODESET_EXPECTED);
+
+ RefPtr<txNodeSet> nodes =
+ static_cast<txNodeSet*>(static_cast<txAExprResult*>(exprRes));
+ // null out exprRes so that we can test for shared-ness
+ exprRes = nullptr;
+
+ RefPtr<txNodeSet> nonShared;
+ rv = aContext->recycler()->getNonSharedNodeSet(nodes,
+ getter_AddRefs(nonShared));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = evaluatePredicates(nonShared, aContext);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *aResult = nonShared;
+ NS_ADDREF(*aResult);
+
+ return NS_OK;
+} //-- evaluate
+
+TX_IMPL_EXPR_STUBS_BASE(FilterExpr, NODESET_RESULT)
+
+Expr* FilterExpr::getSubExprAt(uint32_t aPos) {
+ if (aPos == 0) {
+ return expr.get();
+ }
+ return PredicateList::getSubExprAt(aPos - 1);
+}
+
+void FilterExpr::setSubExprAt(uint32_t aPos, Expr* aExpr) {
+ if (aPos == 0) {
+ Unused << expr.release();
+ expr = WrapUnique(aExpr);
+ } else {
+ PredicateList::setSubExprAt(aPos - 1, aExpr);
+ }
+}
+
+bool FilterExpr::isSensitiveTo(ContextSensitivity aContext) {
+ return expr->isSensitiveTo(aContext) ||
+ PredicateList::isSensitiveTo(aContext);
+}
+
+#ifdef TX_TO_STRING
+void FilterExpr::toString(nsAString& str) {
+ if (expr)
+ expr->toString(str);
+ else
+ str.AppendLiteral("null");
+ PredicateList::toString(str);
+}
+#endif
diff --git a/dom/xslt/xpath/txForwardContext.cpp b/dom/xslt/xpath/txForwardContext.cpp
new file mode 100644
index 0000000000..44b53fefbc
--- /dev/null
+++ b/dom/xslt/xpath/txForwardContext.cpp
@@ -0,0 +1,50 @@
+/* -*- 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 "txForwardContext.h"
+#include "txNodeSet.h"
+
+const txXPathNode& txForwardContext::getContextNode() { return mContextNode; }
+
+uint32_t txForwardContext::size() { return (uint32_t)mContextSet->size(); }
+
+uint32_t txForwardContext::position() {
+ int32_t pos = mContextSet->indexOf(mContextNode);
+ NS_ASSERTION(pos >= 0, "Context is not member of context node list.");
+ return (uint32_t)(pos + 1);
+}
+
+nsresult txForwardContext::getVariable(int32_t aNamespace, nsAtom* aLName,
+ txAExprResult*& aResult) {
+ NS_ASSERTION(mInner, "mInner is null!!!");
+ return mInner->getVariable(aNamespace, aLName, aResult);
+}
+
+nsresult txForwardContext::isStripSpaceAllowed(const txXPathNode& aNode,
+ bool& aAllowed) {
+ NS_ASSERTION(mInner, "mInner is null!!!");
+ return mInner->isStripSpaceAllowed(aNode, aAllowed);
+}
+
+void* txForwardContext::getPrivateContext() {
+ NS_ASSERTION(mInner, "mInner is null!!!");
+ return mInner->getPrivateContext();
+}
+
+txResultRecycler* txForwardContext::recycler() {
+ NS_ASSERTION(mInner, "mInner is null!!!");
+ return mInner->recycler();
+}
+
+void txForwardContext::receiveError(const nsAString& aMsg, nsresult aRes) {
+ NS_ASSERTION(mInner, "mInner is null!!!");
+#ifdef DEBUG
+ nsAutoString error(u"forwarded error: "_ns);
+ error.Append(aMsg);
+ mInner->receiveError(error, aRes);
+#else
+ mInner->receiveError(aMsg, aRes);
+#endif
+}
diff --git a/dom/xslt/xpath/txForwardContext.h b/dom/xslt/xpath/txForwardContext.h
new file mode 100644
index 0000000000..447f2fbfc1
--- /dev/null
+++ b/dom/xslt/xpath/txForwardContext.h
@@ -0,0 +1,28 @@
+/* -*- 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/. */
+
+#ifndef __TX_XPATH_CONTEXT
+#define __TX_XPATH_CONTEXT
+
+#include "txIXPathContext.h"
+#include "txNodeSet.h"
+
+class txForwardContext : public txIEvalContext {
+ public:
+ txForwardContext(txIMatchContext* aContext, const txXPathNode& aContextNode,
+ txNodeSet* aContextNodeSet)
+ : mInner(aContext),
+ mContextNode(aContextNode),
+ mContextSet(aContextNodeSet) {}
+
+ TX_DECL_EVAL_CONTEXT;
+
+ private:
+ txIMatchContext* mInner;
+ const txXPathNode& mContextNode;
+ RefPtr<txNodeSet> mContextSet;
+};
+
+#endif // __TX_XPATH_CONTEXT
diff --git a/dom/xslt/xpath/txFunctionCall.cpp b/dom/xslt/xpath/txFunctionCall.cpp
new file mode 100644
index 0000000000..17a325e2ad
--- /dev/null
+++ b/dom/xslt/xpath/txFunctionCall.cpp
@@ -0,0 +1,110 @@
+/* -*- 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 "txExpr.h"
+#include "nsAtom.h"
+#include "txIXPathContext.h"
+#include "txNodeSet.h"
+
+#ifdef TX_TO_STRING
+# include "nsReadableUtils.h"
+#endif
+
+/**
+ * This class represents a FunctionCall as defined by the XSL Working Draft
+ **/
+
+//------------------/
+//- Public Methods -/
+//------------------/
+
+/*
+ * Evaluates the given Expression and converts its result to a number.
+ */
+// static
+nsresult FunctionCall::evaluateToNumber(Expr* aExpr, txIEvalContext* aContext,
+ double* aResult) {
+ NS_ASSERTION(aExpr, "missing expression");
+ RefPtr<txAExprResult> exprResult;
+ nsresult rv = aExpr->evaluate(aContext, getter_AddRefs(exprResult));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *aResult = exprResult->numberValue();
+
+ return NS_OK;
+}
+
+/*
+ * Evaluates the given Expression and converts its result to a NodeSet.
+ * If the result is not a NodeSet nullptr is returned.
+ */
+nsresult FunctionCall::evaluateToNodeSet(Expr* aExpr, txIEvalContext* aContext,
+ txNodeSet** aResult) {
+ NS_ASSERTION(aExpr, "Missing expression to evaluate");
+ *aResult = nullptr;
+
+ RefPtr<txAExprResult> exprRes;
+ nsresult rv = aExpr->evaluate(aContext, getter_AddRefs(exprRes));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (exprRes->getResultType() != txAExprResult::NODESET) {
+ aContext->receiveError(u"NodeSet expected as argument"_ns,
+ NS_ERROR_XSLT_NODESET_EXPECTED);
+ return NS_ERROR_XSLT_NODESET_EXPECTED;
+ }
+
+ *aResult = static_cast<txNodeSet*>(static_cast<txAExprResult*>(exprRes));
+ NS_ADDREF(*aResult);
+
+ return NS_OK;
+}
+
+bool FunctionCall::requireParams(int32_t aParamCountMin, int32_t aParamCountMax,
+ txIEvalContext* aContext) {
+ int32_t argc = mParams.Length();
+ if (argc < aParamCountMin || (aParamCountMax > -1 && argc > aParamCountMax)) {
+ nsAutoString err(u"invalid number of parameters for function"_ns);
+#ifdef TX_TO_STRING
+ err.AppendLiteral(": ");
+ toString(err);
+#endif
+ aContext->receiveError(err, NS_ERROR_XPATH_INVALID_ARG);
+
+ return false;
+ }
+
+ return true;
+}
+
+Expr* FunctionCall::getSubExprAt(uint32_t aPos) {
+ return mParams.SafeElementAt(aPos);
+}
+
+void FunctionCall::setSubExprAt(uint32_t aPos, Expr* aExpr) {
+ NS_ASSERTION(aPos < mParams.Length(), "setting bad subexpression index");
+ mParams[aPos] = aExpr;
+}
+
+bool FunctionCall::argsSensitiveTo(ContextSensitivity aContext) {
+ uint32_t i, len = mParams.Length();
+ for (i = 0; i < len; ++i) {
+ if (mParams[i]->isSensitiveTo(aContext)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+#ifdef TX_TO_STRING
+void FunctionCall::toString(nsAString& aDest) {
+ appendName(aDest);
+ aDest.AppendLiteral("(");
+ StringJoinAppend(
+ aDest, u","_ns, mParams,
+ [](nsAString& dest, const auto& param) { param->toString(dest); });
+ aDest.Append(char16_t(')'));
+}
+#endif
diff --git a/dom/xslt/xpath/txIXPathContext.h b/dom/xslt/xpath/txIXPathContext.h
new file mode 100644
index 0000000000..8d5b5df4be
--- /dev/null
+++ b/dom/xslt/xpath/txIXPathContext.h
@@ -0,0 +1,132 @@
+/* -*- 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/. */
+
+#ifndef __TX_I_XPATH_CONTEXT
+#define __TX_I_XPATH_CONTEXT
+
+#include "nscore.h"
+#include "nsISupportsImpl.h"
+#include "nsStringFwd.h"
+
+class FunctionCall;
+class nsAtom;
+class txAExprResult;
+class txResultRecycler;
+class txXPathNode;
+
+/*
+ * txIParseContext
+ *
+ * This interface describes the context needed to create
+ * XPath Expressions and XSLT Patters.
+ * (not completely though. key() requires the ProcessorState, which is
+ * not part of this interface.)
+ */
+
+class txIParseContext {
+ public:
+ virtual ~txIParseContext() = default;
+
+ /*
+ * Return a namespaceID for a given prefix.
+ */
+ virtual nsresult resolveNamespacePrefix(nsAtom* aPrefix, int32_t& aID) = 0;
+
+ /*
+ * Create a FunctionCall, needed for extension function calls and
+ * XSLT. XPath function calls are resolved by the Parser.
+ */
+ virtual nsresult resolveFunctionCall(nsAtom* aName, int32_t aID,
+ FunctionCall** aFunction) = 0;
+
+ /**
+ * Should nametests parsed in this context be case-sensitive
+ */
+ virtual bool caseInsensitiveNameTests() = 0;
+
+ /*
+ * Callback to be used by the Parser if errors are detected.
+ */
+ virtual void SetErrorOffset(uint32_t aOffset) = 0;
+
+ enum Allowed { KEY_FUNCTION = 1 << 0 };
+ virtual bool allowed(Allowed aAllowed) { return true; }
+};
+
+/*
+ * txIMatchContext
+ *
+ * Interface used for matching XSLT Patters.
+ * This is the part of txIEvalContext (see below), that is independent
+ * of the context node when evaluating a XPath expression, too.
+ * When evaluating a XPath expression, |txIMatchContext|s are used
+ * to transport the information from Step to Step.
+ */
+class txIMatchContext {
+ public:
+ virtual ~txIMatchContext() = default;
+
+ /*
+ * Return the ExprResult associated with the variable with the
+ * given namespace and local name.
+ */
+ virtual nsresult getVariable(int32_t aNamespace, nsAtom* aLName,
+ txAExprResult*& aResult) = 0;
+
+ /*
+ * Is whitespace stripping allowed for the given node?
+ * See http://www.w3.org/TR/xslt#strip
+ */
+ virtual nsresult isStripSpaceAllowed(const txXPathNode& aNode,
+ bool& aAllowed) = 0;
+
+ /**
+ * Returns a pointer to the private context
+ */
+ virtual void* getPrivateContext() = 0;
+
+ virtual txResultRecycler* recycler() = 0;
+
+ /*
+ * Callback to be used by the expression/pattern if errors are detected.
+ */
+ virtual void receiveError(const nsAString& aMsg, nsresult aRes) = 0;
+};
+
+#define TX_DECL_MATCH_CONTEXT \
+ nsresult getVariable(int32_t aNamespace, nsAtom* aLName, \
+ txAExprResult*& aResult) override; \
+ nsresult isStripSpaceAllowed(const txXPathNode& aNode, bool& aAllowed) \
+ override; \
+ void* getPrivateContext() override; \
+ txResultRecycler* recycler() override; \
+ void receiveError(const nsAString& aMsg, nsresult aRes) override
+
+class txIEvalContext : public txIMatchContext {
+ public:
+ /*
+ * Get the context node.
+ */
+ virtual const txXPathNode& getContextNode() = 0;
+
+ /*
+ * Get the size of the context node set.
+ */
+ virtual uint32_t size() = 0;
+
+ /*
+ * Get the position of the context node in the context node set,
+ * starting with 1.
+ */
+ virtual uint32_t position() = 0;
+};
+
+#define TX_DECL_EVAL_CONTEXT \
+ TX_DECL_MATCH_CONTEXT; \
+ const txXPathNode& getContextNode() override; \
+ uint32_t size() override; \
+ uint32_t position() override
+
+#endif // __TX_I_XPATH_CONTEXT
diff --git a/dom/xslt/xpath/txLiteralExpr.cpp b/dom/xslt/xpath/txLiteralExpr.cpp
new file mode 100644
index 0000000000..93ea2c8f1d
--- /dev/null
+++ b/dom/xslt/xpath/txLiteralExpr.cpp
@@ -0,0 +1,74 @@
+/* -*- 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 "txExpr.h"
+
+nsresult txLiteralExpr::evaluate(txIEvalContext* aContext,
+ txAExprResult** aResult) {
+ NS_ENSURE_TRUE(mValue, NS_ERROR_OUT_OF_MEMORY);
+
+ *aResult = mValue;
+ NS_ADDREF(*aResult);
+
+ return NS_OK;
+}
+
+static Expr::ResultType resultTypes[] = {
+ Expr::NODESET_RESULT, // NODESET
+ Expr::BOOLEAN_RESULT, // BOOLEAN
+ Expr::NUMBER_RESULT, // NUMBER
+ Expr::STRING_RESULT, // STRING
+ Expr::RTF_RESULT // RESULT_TREE_FRAGMENT
+};
+
+Expr::ResultType txLiteralExpr::getReturnType() {
+ return resultTypes[mValue->getResultType()];
+}
+
+Expr* txLiteralExpr::getSubExprAt(uint32_t aPos) { return nullptr; }
+void txLiteralExpr::setSubExprAt(uint32_t aPos, Expr* aExpr) {
+ MOZ_ASSERT_UNREACHABLE("setting bad subexpression index");
+}
+
+bool txLiteralExpr::isSensitiveTo(ContextSensitivity aContext) { return false; }
+
+#ifdef TX_TO_STRING
+void txLiteralExpr::toString(nsAString& aStr) {
+ switch (mValue->getResultType()) {
+ case txAExprResult::NODESET: {
+ aStr.AppendLiteral(" { Nodeset literal } ");
+ return;
+ }
+ case txAExprResult::BOOLEAN: {
+ if (mValue->booleanValue()) {
+ aStr.AppendLiteral("true()");
+ } else {
+ aStr.AppendLiteral("false()");
+ }
+ return;
+ }
+ case txAExprResult::NUMBER: {
+ txDouble::toString(mValue->numberValue(), aStr);
+ return;
+ }
+ case txAExprResult::STRING: {
+ StringResult* strRes =
+ static_cast<StringResult*>(static_cast<txAExprResult*>(mValue));
+ char16_t ch = '\'';
+ if (strRes->mValue.Contains(ch)) {
+ ch = '\"';
+ }
+ aStr.Append(ch);
+ aStr.Append(strRes->mValue);
+ aStr.Append(ch);
+ return;
+ }
+ case txAExprResult::RESULT_TREE_FRAGMENT: {
+ aStr.AppendLiteral(" { RTF literal } ");
+ return;
+ }
+ }
+}
+#endif
diff --git a/dom/xslt/xpath/txLocationStep.cpp b/dom/xslt/xpath/txLocationStep.cpp
new file mode 100644
index 0000000000..c10d9e8cb1
--- /dev/null
+++ b/dom/xslt/xpath/txLocationStep.cpp
@@ -0,0 +1,308 @@
+/* -*- 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/. */
+
+/*
+ Implementation of an XPath LocationStep
+*/
+
+#include "txExpr.h"
+#include "txIXPathContext.h"
+#include "txNodeSet.h"
+#include "txXPathTreeWalker.h"
+
+//-----------------------------/
+//- Virtual methods from Expr -/
+//-----------------------------/
+
+/**
+ * Evaluates this Expr based on the given context node and processor state
+ * @param context the context node for evaluation of this Expr
+ * @param ps the ProcessorState containing the stack information needed
+ * for evaluation
+ * @return the result of the evaluation
+ * @see Expr
+ **/
+nsresult LocationStep::evaluate(txIEvalContext* aContext,
+ txAExprResult** aResult) {
+ NS_ASSERTION(aContext, "internal error");
+ *aResult = nullptr;
+
+ RefPtr<txNodeSet> nodes;
+ nsresult rv = aContext->recycler()->getNodeSet(getter_AddRefs(nodes));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ txXPathTreeWalker walker(aContext->getContextNode());
+
+ switch (mAxisIdentifier) {
+ case ANCESTOR_AXIS: {
+ if (!walker.moveToParent()) {
+ break;
+ }
+ [[fallthrough]];
+ }
+ case ANCESTOR_OR_SELF_AXIS: {
+ nodes->setReverse();
+
+ do {
+ rv = appendIfMatching(walker, aContext, nodes);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } while (walker.moveToParent());
+
+ break;
+ }
+ case ATTRIBUTE_AXIS: {
+ if (!walker.moveToFirstAttribute()) {
+ break;
+ }
+
+ do {
+ rv = appendIfMatching(walker, aContext, nodes);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } while (walker.moveToNextAttribute());
+ break;
+ }
+ case DESCENDANT_OR_SELF_AXIS: {
+ rv = appendIfMatching(walker, aContext, nodes);
+ NS_ENSURE_SUCCESS(rv, rv);
+ [[fallthrough]];
+ }
+ case DESCENDANT_AXIS: {
+ rv = appendMatchingDescendants(walker, aContext, nodes);
+ NS_ENSURE_SUCCESS(rv, rv);
+ break;
+ }
+ case FOLLOWING_AXIS: {
+ if (txXPathNodeUtils::isAttribute(walker.getCurrentPosition())) {
+ walker.moveToParent();
+ rv = appendMatchingDescendants(walker, aContext, nodes);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ bool cont = true;
+ while (!walker.moveToNextSibling()) {
+ if (!walker.moveToParent()) {
+ cont = false;
+ break;
+ }
+ }
+ while (cont) {
+ rv = appendIfMatching(walker, aContext, nodes);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = appendMatchingDescendants(walker, aContext, nodes);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ while (!walker.moveToNextSibling()) {
+ if (!walker.moveToParent()) {
+ cont = false;
+ break;
+ }
+ }
+ }
+ break;
+ }
+ case FOLLOWING_SIBLING_AXIS: {
+ while (walker.moveToNextSibling()) {
+ rv = appendIfMatching(walker, aContext, nodes);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ break;
+ }
+ case NAMESPACE_AXIS: //-- not yet implemented
+#if 0
+ // XXX DEBUG OUTPUT
+ cout << "namespace axis not yet implemented"<<endl;
+#endif
+ break;
+ case PARENT_AXIS: {
+ if (walker.moveToParent()) {
+ rv = appendIfMatching(walker, aContext, nodes);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ break;
+ }
+ case PRECEDING_AXIS: {
+ nodes->setReverse();
+
+ bool cont = true;
+ while (!walker.moveToPreviousSibling()) {
+ if (!walker.moveToParent()) {
+ cont = false;
+ break;
+ }
+ }
+ while (cont) {
+ rv = appendMatchingDescendantsRev(walker, aContext, nodes);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = appendIfMatching(walker, aContext, nodes);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ while (!walker.moveToPreviousSibling()) {
+ if (!walker.moveToParent()) {
+ cont = false;
+ break;
+ }
+ }
+ }
+ break;
+ }
+ case PRECEDING_SIBLING_AXIS: {
+ nodes->setReverse();
+
+ while (walker.moveToPreviousSibling()) {
+ rv = appendIfMatching(walker, aContext, nodes);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ break;
+ }
+ case SELF_AXIS: {
+ rv = appendIfMatching(walker, aContext, nodes);
+ NS_ENSURE_SUCCESS(rv, rv);
+ break;
+ }
+ default: // Children Axis
+ {
+ if (!walker.moveToFirstChild()) {
+ break;
+ }
+
+ do {
+ rv = appendIfMatching(walker, aContext, nodes);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } while (walker.moveToNextSibling());
+ break;
+ }
+ }
+
+ // Apply predicates
+ if (!isEmpty()) {
+ rv = evaluatePredicates(nodes, aContext);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ nodes->unsetReverse();
+
+ NS_ADDREF(*aResult = nodes);
+
+ return NS_OK;
+}
+
+nsresult LocationStep::appendIfMatching(const txXPathTreeWalker& aWalker,
+ txIMatchContext* aContext,
+ txNodeSet* aNodes) {
+ bool matched;
+ const txXPathNode& child = aWalker.getCurrentPosition();
+ nsresult rv = mNodeTest->matches(child, aContext, matched);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (matched) {
+ aNodes->append(child);
+ }
+ return NS_OK;
+}
+
+nsresult LocationStep::appendMatchingDescendants(
+ const txXPathTreeWalker& aWalker, txIMatchContext* aContext,
+ txNodeSet* aNodes) {
+ txXPathTreeWalker walker(aWalker);
+ if (!walker.moveToFirstChild()) {
+ return NS_OK;
+ }
+
+ do {
+ nsresult rv = appendIfMatching(walker, aContext, aNodes);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = appendMatchingDescendants(walker, aContext, aNodes);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } while (walker.moveToNextSibling());
+
+ return NS_OK;
+}
+
+nsresult LocationStep::appendMatchingDescendantsRev(
+ const txXPathTreeWalker& aWalker, txIMatchContext* aContext,
+ txNodeSet* aNodes) {
+ txXPathTreeWalker walker(aWalker);
+ if (!walker.moveToLastChild()) {
+ return NS_OK;
+ }
+
+ do {
+ nsresult rv = appendMatchingDescendantsRev(walker, aContext, aNodes);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = appendIfMatching(walker, aContext, aNodes);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } while (walker.moveToPreviousSibling());
+
+ return NS_OK;
+}
+
+Expr::ExprType LocationStep::getType() { return LOCATIONSTEP_EXPR; }
+
+TX_IMPL_EXPR_STUBS_BASE(LocationStep, NODESET_RESULT)
+
+Expr* LocationStep::getSubExprAt(uint32_t aPos) {
+ return PredicateList::getSubExprAt(aPos);
+}
+
+void LocationStep::setSubExprAt(uint32_t aPos, Expr* aExpr) {
+ PredicateList::setSubExprAt(aPos, aExpr);
+}
+
+bool LocationStep::isSensitiveTo(ContextSensitivity aContext) {
+ return (aContext & NODE_CONTEXT) || mNodeTest->isSensitiveTo(aContext) ||
+ PredicateList::isSensitiveTo(aContext);
+}
+
+#ifdef TX_TO_STRING
+void LocationStep::toString(nsAString& str) {
+ switch (mAxisIdentifier) {
+ case ANCESTOR_AXIS:
+ str.AppendLiteral("ancestor::");
+ break;
+ case ANCESTOR_OR_SELF_AXIS:
+ str.AppendLiteral("ancestor-or-self::");
+ break;
+ case ATTRIBUTE_AXIS:
+ str.Append(char16_t('@'));
+ break;
+ case DESCENDANT_AXIS:
+ str.AppendLiteral("descendant::");
+ break;
+ case DESCENDANT_OR_SELF_AXIS:
+ str.AppendLiteral("descendant-or-self::");
+ break;
+ case FOLLOWING_AXIS:
+ str.AppendLiteral("following::");
+ break;
+ case FOLLOWING_SIBLING_AXIS:
+ str.AppendLiteral("following-sibling::");
+ break;
+ case NAMESPACE_AXIS:
+ str.AppendLiteral("namespace::");
+ break;
+ case PARENT_AXIS:
+ str.AppendLiteral("parent::");
+ break;
+ case PRECEDING_AXIS:
+ str.AppendLiteral("preceding::");
+ break;
+ case PRECEDING_SIBLING_AXIS:
+ str.AppendLiteral("preceding-sibling::");
+ break;
+ case SELF_AXIS:
+ str.AppendLiteral("self::");
+ break;
+ default:
+ break;
+ }
+ NS_ASSERTION(mNodeTest, "mNodeTest is null, that's verboten");
+ mNodeTest->toString(str);
+
+ PredicateList::toString(str);
+}
+#endif
diff --git a/dom/xslt/xpath/txMozillaXPathTreeWalker.cpp b/dom/xslt/xpath/txMozillaXPathTreeWalker.cpp
new file mode 100644
index 0000000000..8150d7307e
--- /dev/null
+++ b/dom/xslt/xpath/txMozillaXPathTreeWalker.cpp
@@ -0,0 +1,674 @@
+/* -*- 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 "txXPathTreeWalker.h"
+#include "nsAtom.h"
+#include "nsINode.h"
+#include "nsPrintfCString.h"
+#include "nsReadableUtils.h"
+#include "nsString.h"
+#include "nsTextFragment.h"
+#include "txXMLUtils.h"
+#include "txLog.h"
+#include "nsUnicharUtils.h"
+#include "nsAttrName.h"
+#include "nsNameSpaceManager.h"
+#include "nsTArray.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/dom/Attr.h"
+#include "mozilla/dom/CharacterData.h"
+#include "mozilla/dom/Element.h"
+#include <stdint.h>
+#include <algorithm>
+
+using namespace mozilla::dom;
+using mozilla::Maybe;
+
+txXPathTreeWalker::txXPathTreeWalker(const txXPathTreeWalker& aOther) = default;
+
+txXPathTreeWalker::txXPathTreeWalker(const txXPathNode& aNode)
+ : mPosition(aNode) {}
+
+void txXPathTreeWalker::moveToRoot() {
+ if (mPosition.isDocument()) {
+ return;
+ }
+
+ Document* root = mPosition.mNode->GetUncomposedDoc();
+ if (root) {
+ mPosition.mIndex = txXPathNode::eDocument;
+ mPosition.mNode = root;
+ } else {
+ nsINode* rootNode = mPosition.Root();
+
+ NS_ASSERTION(rootNode->IsContent(), "root of subtree wasn't an nsIContent");
+
+ mPosition.mIndex = txXPathNode::eContent;
+ mPosition.mNode = rootNode;
+ }
+}
+
+bool txXPathTreeWalker::moveToElementById(const nsAString& aID) {
+ if (aID.IsEmpty()) {
+ return false;
+ }
+
+ Document* doc = mPosition.mNode->GetUncomposedDoc();
+
+ nsCOMPtr<nsIContent> content;
+ if (doc) {
+ content = doc->GetElementById(aID);
+ } else {
+ // We're in a disconnected subtree, search only that subtree.
+ nsINode* rootNode = mPosition.Root();
+
+ NS_ASSERTION(rootNode->IsContent(), "root of subtree wasn't an nsIContent");
+
+ content =
+ nsContentUtils::MatchElementId(static_cast<nsIContent*>(rootNode), aID);
+ }
+
+ if (!content) {
+ return false;
+ }
+
+ mPosition.mIndex = txXPathNode::eContent;
+ mPosition.mNode = content;
+
+ return true;
+}
+
+bool txXPathTreeWalker::moveToFirstAttribute() {
+ if (!mPosition.isContent()) {
+ return false;
+ }
+
+ return moveToValidAttribute(0);
+}
+
+bool txXPathTreeWalker::moveToNextAttribute() {
+ // XXX an assertion should be enough here with the current code
+ if (!mPosition.isAttribute()) {
+ return false;
+ }
+
+ return moveToValidAttribute(mPosition.mIndex + 1);
+}
+
+bool txXPathTreeWalker::moveToValidAttribute(uint32_t aStartIndex) {
+ NS_ASSERTION(!mPosition.isDocument(), "documents doesn't have attrs");
+
+ if (!mPosition.Content()->IsElement()) {
+ return false;
+ }
+
+ Element* element = mPosition.Content()->AsElement();
+ uint32_t total = element->GetAttrCount();
+ if (aStartIndex >= total) {
+ return false;
+ }
+
+ uint32_t index;
+ for (index = aStartIndex; index < total; ++index) {
+ const nsAttrName* name = element->GetAttrNameAt(index);
+
+ // We need to ignore XMLNS attributes.
+ if (name->NamespaceID() != kNameSpaceID_XMLNS) {
+ mPosition.mIndex = index;
+
+ return true;
+ }
+ }
+ return false;
+}
+
+bool txXPathTreeWalker::moveToNamedAttribute(nsAtom* aLocalName,
+ int32_t aNSID) {
+ if (!mPosition.isContent() || !mPosition.Content()->IsElement()) {
+ return false;
+ }
+
+ Element* element = mPosition.Content()->AsElement();
+
+ const nsAttrName* name;
+ uint32_t i;
+ for (i = 0; (name = element->GetAttrNameAt(i)); ++i) {
+ if (name->Equals(aLocalName, aNSID)) {
+ mPosition.mIndex = i;
+
+ return true;
+ }
+ }
+ return false;
+}
+
+bool txXPathTreeWalker::moveToFirstChild() {
+ if (mPosition.isAttribute()) {
+ return false;
+ }
+
+ nsIContent* child = mPosition.mNode->GetFirstChild();
+ if (!child) {
+ return false;
+ }
+ mPosition.mIndex = txXPathNode::eContent;
+ mPosition.mNode = child;
+
+ return true;
+}
+
+bool txXPathTreeWalker::moveToLastChild() {
+ if (mPosition.isAttribute()) {
+ return false;
+ }
+
+ nsIContent* child = mPosition.mNode->GetLastChild();
+ if (!child) {
+ return false;
+ }
+
+ mPosition.mIndex = txXPathNode::eContent;
+ mPosition.mNode = child;
+
+ return true;
+}
+
+bool txXPathTreeWalker::moveToNextSibling() {
+ if (!mPosition.isContent()) {
+ return false;
+ }
+
+ nsINode* sibling = mPosition.mNode->GetNextSibling();
+ if (!sibling) {
+ return false;
+ }
+
+ mPosition.mNode = sibling;
+
+ return true;
+}
+
+bool txXPathTreeWalker::moveToPreviousSibling() {
+ if (!mPosition.isContent()) {
+ return false;
+ }
+
+ nsINode* sibling = mPosition.mNode->GetPreviousSibling();
+ if (!sibling) {
+ return false;
+ }
+
+ mPosition.mNode = sibling;
+
+ return true;
+}
+
+bool txXPathTreeWalker::moveToParent() {
+ if (mPosition.isDocument()) {
+ return false;
+ }
+
+ if (mPosition.isAttribute()) {
+ mPosition.mIndex = txXPathNode::eContent;
+
+ return true;
+ }
+
+ nsINode* parent = mPosition.mNode->GetParentNode();
+ if (!parent) {
+ return false;
+ }
+
+ mPosition.mIndex = mPosition.mNode->GetParent() ? txXPathNode::eContent
+ : txXPathNode::eDocument;
+ mPosition.mNode = parent;
+
+ return true;
+}
+
+txXPathNode::txXPathNode(const txXPathNode& aNode)
+ : mNode(aNode.mNode),
+ mRefCountRoot(aNode.mRefCountRoot),
+ mIndex(aNode.mIndex) {
+ MOZ_COUNT_CTOR(txXPathNode);
+ if (mRefCountRoot) {
+ NS_ADDREF(Root());
+ }
+}
+
+txXPathNode::~txXPathNode() {
+ MOZ_COUNT_DTOR(txXPathNode);
+ if (mRefCountRoot) {
+ nsINode* root = Root();
+ NS_RELEASE(root);
+ }
+}
+
+/* static */
+bool txXPathNodeUtils::getAttr(const txXPathNode& aNode, nsAtom* aLocalName,
+ int32_t aNSID, nsAString& aValue) {
+ if (aNode.isDocument() || aNode.isAttribute() ||
+ !aNode.Content()->IsElement()) {
+ return false;
+ }
+
+ return aNode.Content()->AsElement()->GetAttr(aNSID, aLocalName, aValue);
+}
+
+/* static */
+already_AddRefed<nsAtom> txXPathNodeUtils::getLocalName(
+ const txXPathNode& aNode) {
+ if (aNode.isDocument()) {
+ return nullptr;
+ }
+
+ if (aNode.isContent()) {
+ if (aNode.mNode->IsElement()) {
+ RefPtr<nsAtom> localName = aNode.Content()->NodeInfo()->NameAtom();
+ return localName.forget();
+ }
+
+ if (aNode.mNode->IsProcessingInstruction()) {
+ return NS_Atomize(aNode.mNode->NodeName());
+ }
+
+ return nullptr;
+ }
+
+ // This is an attribute node, so we necessarily come from an element.
+ RefPtr<nsAtom> localName =
+ aNode.Content()->AsElement()->GetAttrNameAt(aNode.mIndex)->LocalName();
+
+ return localName.forget();
+}
+
+nsAtom* txXPathNodeUtils::getPrefix(const txXPathNode& aNode) {
+ if (aNode.isDocument()) {
+ return nullptr;
+ }
+
+ if (aNode.isContent()) {
+ // All other nsIContent node types but elements have a null prefix
+ // which is what we want here.
+ return aNode.Content()->NodeInfo()->GetPrefixAtom();
+ }
+
+ return aNode.Content()->AsElement()->GetAttrNameAt(aNode.mIndex)->GetPrefix();
+}
+
+/* static */
+void txXPathNodeUtils::getLocalName(const txXPathNode& aNode,
+ nsAString& aLocalName) {
+ if (aNode.isDocument()) {
+ aLocalName.Truncate();
+
+ return;
+ }
+
+ if (aNode.isContent()) {
+ if (aNode.mNode->IsElement()) {
+ mozilla::dom::NodeInfo* nodeInfo = aNode.Content()->NodeInfo();
+ nodeInfo->GetName(aLocalName);
+ return;
+ }
+
+ if (aNode.mNode->IsProcessingInstruction()) {
+ // PIs don't have a nodeinfo but do have a name
+ // XXXbz Not actually true, but this function looks like it wants
+ // different things from elements and PIs for "local name"...
+ aLocalName = aNode.mNode->NodeName();
+ return;
+ }
+
+ aLocalName.Truncate();
+
+ return;
+ }
+
+ aNode.Content()
+ ->AsElement()
+ ->GetAttrNameAt(aNode.mIndex)
+ ->LocalName()
+ ->ToString(aLocalName);
+
+ // Check for html
+ if (aNode.Content()->NodeInfo()->NamespaceEquals(kNameSpaceID_None) &&
+ aNode.Content()->IsHTMLElement()) {
+ nsContentUtils::ASCIIToUpper(aLocalName);
+ }
+}
+
+/* static */
+void txXPathNodeUtils::getNodeName(const txXPathNode& aNode, nsAString& aName) {
+ if (aNode.isDocument()) {
+ aName.Truncate();
+
+ return;
+ }
+
+ if (aNode.isContent()) {
+ // Elements and PIs have a name
+ if (aNode.mNode->IsElement() ||
+ aNode.mNode->NodeType() == nsINode::PROCESSING_INSTRUCTION_NODE) {
+ aName = aNode.Content()->NodeName();
+ return;
+ }
+
+ aName.Truncate();
+
+ return;
+ }
+
+ aNode.Content()
+ ->AsElement()
+ ->GetAttrNameAt(aNode.mIndex)
+ ->GetQualifiedName(aName);
+}
+
+/* static */
+int32_t txXPathNodeUtils::getNamespaceID(const txXPathNode& aNode) {
+ if (aNode.isDocument()) {
+ return kNameSpaceID_None;
+ }
+
+ if (aNode.isContent()) {
+ return aNode.Content()->GetNameSpaceID();
+ }
+
+ return aNode.Content()
+ ->AsElement()
+ ->GetAttrNameAt(aNode.mIndex)
+ ->NamespaceID();
+}
+
+/* static */
+void txXPathNodeUtils::getNamespaceURI(const txXPathNode& aNode,
+ nsAString& aURI) {
+ nsNameSpaceManager::GetInstance()->GetNameSpaceURI(getNamespaceID(aNode),
+ aURI);
+}
+
+/* static */
+uint16_t txXPathNodeUtils::getNodeType(const txXPathNode& aNode) {
+ if (aNode.isDocument()) {
+ return txXPathNodeType::DOCUMENT_NODE;
+ }
+
+ if (aNode.isContent()) {
+ return aNode.mNode->NodeType();
+ }
+
+ return txXPathNodeType::ATTRIBUTE_NODE;
+}
+
+/* static */
+void txXPathNodeUtils::appendNodeValue(const txXPathNode& aNode,
+ nsAString& aResult) {
+ if (aNode.isAttribute()) {
+ const nsAttrName* name =
+ aNode.Content()->AsElement()->GetAttrNameAt(aNode.mIndex);
+
+ if (aResult.IsEmpty()) {
+ aNode.Content()->AsElement()->GetAttr(name->NamespaceID(),
+ name->LocalName(), aResult);
+ } else {
+ nsAutoString result;
+ aNode.Content()->AsElement()->GetAttr(name->NamespaceID(),
+ name->LocalName(), result);
+ aResult.Append(result);
+ }
+
+ return;
+ }
+
+ if (aNode.isDocument() || aNode.mNode->IsElement() ||
+ aNode.mNode->IsDocumentFragment()) {
+ nsContentUtils::AppendNodeTextContent(aNode.mNode, true, aResult,
+ mozilla::fallible);
+
+ return;
+ }
+
+ MOZ_ASSERT(aNode.mNode->IsCharacterData());
+ static_cast<CharacterData*>(aNode.Content())->AppendTextTo(aResult);
+}
+
+/* static */
+bool txXPathNodeUtils::isWhitespace(const txXPathNode& aNode) {
+ NS_ASSERTION(aNode.isContent() && isText(aNode), "Wrong type!");
+
+ return aNode.Content()->TextIsOnlyWhitespace();
+}
+
+/* static */
+txXPathNode* txXPathNodeUtils::getOwnerDocument(const txXPathNode& aNode) {
+ return new txXPathNode(aNode.mNode->OwnerDoc());
+}
+
+const char gPrintfFmt[] = "id0x%" PRIxPTR;
+const char gPrintfFmtAttr[] = "id0x%" PRIxPTR "-%010i";
+
+/* static */
+nsresult txXPathNodeUtils::getXSLTId(const txXPathNode& aNode,
+ const txXPathNode& aBase,
+ nsAString& aResult) {
+ uintptr_t nodeid = ((uintptr_t)aNode.mNode) - ((uintptr_t)aBase.mNode);
+ if (!aNode.isAttribute()) {
+ CopyASCIItoUTF16(nsPrintfCString(gPrintfFmt, nodeid), aResult);
+ } else {
+ CopyASCIItoUTF16(nsPrintfCString(gPrintfFmtAttr, nodeid, aNode.mIndex),
+ aResult);
+ }
+
+ return NS_OK;
+}
+
+/* static */
+nsresult txXPathNodeUtils::getBaseURI(const txXPathNode& aNode,
+ nsAString& aURI) {
+ return aNode.mNode->GetBaseURI(aURI);
+}
+
+/* static */
+int txXPathNodeUtils::comparePosition(const txXPathNode& aNode,
+ const txXPathNode& aOtherNode) {
+ // First check for equal nodes or attribute-nodes on the same element.
+ if (aNode.mNode == aOtherNode.mNode) {
+ if (aNode.mIndex == aOtherNode.mIndex) {
+ return 0;
+ }
+
+ NS_ASSERTION(!aNode.isDocument() && !aOtherNode.isDocument(),
+ "documents should always have a set index");
+
+ if (aNode.isContent() ||
+ (!aOtherNode.isContent() && aNode.mIndex < aOtherNode.mIndex)) {
+ return -1;
+ }
+
+ return 1;
+ }
+
+ // Get document for both nodes.
+ Document* document = aNode.mNode->GetUncomposedDoc();
+ Document* otherDocument = aOtherNode.mNode->GetUncomposedDoc();
+
+ // If the nodes have different current documents, compare the document
+ // pointers.
+ if (document != otherDocument) {
+ return document < otherDocument ? -1 : 1;
+ }
+
+ // Now either both nodes are in orphan trees, or they are both in the
+ // same tree.
+
+ // Get parents up the tree.
+ AutoTArray<nsINode*, 8> parents, otherParents;
+ nsINode* node = aNode.mNode;
+ nsINode* otherNode = aOtherNode.mNode;
+ nsINode* parent;
+ nsINode* otherParent;
+ while (node && otherNode) {
+ parent = node->GetParentNode();
+ otherParent = otherNode->GetParentNode();
+
+ // Hopefully this is a common case.
+ if (parent == otherParent) {
+ if (!parent) {
+ // Both node and otherNode are root nodes in respective orphan
+ // tree.
+ return node < otherNode ? -1 : 1;
+ }
+
+ const Maybe<uint32_t> indexOfNode = parent->ComputeIndexOf(node);
+ const Maybe<uint32_t> indexOfOtherNode =
+ parent->ComputeIndexOf(otherNode);
+ if (MOZ_LIKELY(indexOfNode.isSome() && indexOfOtherNode.isSome())) {
+ return *indexOfNode < *indexOfOtherNode ? -1 : 1;
+ }
+ // XXX Keep the odd traditional behavior for now.
+ return indexOfNode.isNothing() && indexOfOtherNode.isSome() ? -1 : 1;
+ }
+
+ parents.AppendElement(node);
+ otherParents.AppendElement(otherNode);
+ node = parent;
+ otherNode = otherParent;
+ }
+
+ while (node) {
+ parents.AppendElement(node);
+ node = node->GetParentNode();
+ }
+ while (otherNode) {
+ otherParents.AppendElement(otherNode);
+ otherNode = otherNode->GetParentNode();
+ }
+
+ // Walk back down along the parent-chains until we find where they split.
+ int32_t total = parents.Length() - 1;
+ int32_t otherTotal = otherParents.Length() - 1;
+ NS_ASSERTION(total != otherTotal, "Can't have same number of parents");
+
+ int32_t lastIndex = std::min(total, otherTotal);
+ int32_t i;
+ parent = nullptr;
+ for (i = 0; i <= lastIndex; ++i) {
+ node = parents.ElementAt(total - i);
+ otherNode = otherParents.ElementAt(otherTotal - i);
+ if (node != otherNode) {
+ if (!parent) {
+ // The two nodes are in different orphan subtrees.
+ NS_ASSERTION(i == 0, "this shouldn't happen");
+ return node < otherNode ? -1 : 1;
+ }
+
+ const Maybe<uint32_t> index = parent->ComputeIndexOf(node);
+ const Maybe<uint32_t> otherIndex = parent->ComputeIndexOf(otherNode);
+ if (MOZ_LIKELY(index.isSome() && otherIndex.isSome())) {
+ NS_ASSERTION(*index != *otherIndex, "invalid index in comparePosition");
+ return *index < *otherIndex ? -1 : 1;
+ }
+ NS_ASSERTION(false, "invalid index in comparePosition");
+ // XXX Keep the odd traditional behavior for now.
+ return index.isNothing() && otherIndex.isSome() ? -1 : 1;
+ }
+
+ parent = node;
+ }
+
+ // One node is a descendant of the other. The one with the shortest
+ // parent-chain is first in the document.
+ return total < otherTotal ? -1 : 1;
+}
+
+/* static */
+txXPathNode* txXPathNativeNode::createXPathNode(nsIContent* aContent,
+ bool aKeepRootAlive) {
+ nsINode* root = aKeepRootAlive ? txXPathNode::RootOf(aContent) : nullptr;
+
+ return new txXPathNode(aContent, txXPathNode::eContent, root);
+}
+
+/* static */
+txXPathNode* txXPathNativeNode::createXPathNode(nsINode* aNode,
+ bool aKeepRootAlive) {
+ uint16_t nodeType = aNode->NodeType();
+ if (nodeType == nsINode::ATTRIBUTE_NODE) {
+ auto* attr = static_cast<Attr*>(aNode);
+
+ NodeInfo* nodeInfo = attr->NodeInfo();
+ Element* parent = attr->GetElement();
+ if (!parent) {
+ return nullptr;
+ }
+
+ nsINode* root = aKeepRootAlive ? txXPathNode::RootOf(parent) : nullptr;
+
+ uint32_t i, total = parent->GetAttrCount();
+ for (i = 0; i < total; ++i) {
+ const nsAttrName* name = parent->GetAttrNameAt(i);
+ if (nodeInfo->Equals(name->LocalName(), name->NamespaceID())) {
+ return new txXPathNode(parent, i, root);
+ }
+ }
+
+ NS_ERROR("Couldn't find the attribute in its parent!");
+
+ return nullptr;
+ }
+
+ uint32_t index;
+ nsINode* root = aKeepRootAlive ? aNode : nullptr;
+
+ if (nodeType == nsINode::DOCUMENT_NODE) {
+ index = txXPathNode::eDocument;
+ } else {
+ index = txXPathNode::eContent;
+ if (root) {
+ root = txXPathNode::RootOf(root);
+ }
+ }
+
+ return new txXPathNode(aNode, index, root);
+}
+
+/* static */
+txXPathNode* txXPathNativeNode::createXPathNode(Document* aDocument) {
+ return new txXPathNode(aDocument);
+}
+
+/* static */
+nsINode* txXPathNativeNode::getNode(const txXPathNode& aNode) {
+ if (!aNode.isAttribute()) {
+ return aNode.mNode;
+ }
+
+ const nsAttrName* name =
+ aNode.Content()->AsElement()->GetAttrNameAt(aNode.mIndex);
+
+ nsAutoString namespaceURI;
+ nsNameSpaceManager::GetInstance()->GetNameSpaceURI(name->NamespaceID(),
+ namespaceURI);
+
+ nsCOMPtr<Element> element = do_QueryInterface(aNode.mNode);
+ nsDOMAttributeMap* map = element->Attributes();
+ return map->GetNamedItemNS(namespaceURI,
+ nsDependentAtomString(name->LocalName()));
+}
+
+/* static */
+nsIContent* txXPathNativeNode::getContent(const txXPathNode& aNode) {
+ NS_ASSERTION(aNode.isContent(),
+ "Only call getContent on nsIContent wrappers!");
+ return aNode.Content();
+}
+
+/* static */
+Document* txXPathNativeNode::getDocument(const txXPathNode& aNode) {
+ NS_ASSERTION(aNode.isDocument(),
+ "Only call getDocument on Document wrappers!");
+ return aNode.Document();
+}
diff --git a/dom/xslt/xpath/txNameTest.cpp b/dom/xslt/xpath/txNameTest.cpp
new file mode 100644
index 0000000000..f76113146c
--- /dev/null
+++ b/dom/xslt/xpath/txNameTest.cpp
@@ -0,0 +1,92 @@
+/* -*- 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 "txExpr.h"
+#include "nsAtom.h"
+#include "nsGkAtoms.h"
+#include "txXPathTreeWalker.h"
+#include "txIXPathContext.h"
+
+txNameTest::txNameTest(nsAtom* aPrefix, nsAtom* aLocalName, int32_t aNSID,
+ uint16_t aNodeType)
+ : mPrefix(aPrefix),
+ mLocalName(aLocalName),
+ mNamespace(aNSID),
+ mNodeType(aNodeType) {
+ if (aPrefix == nsGkAtoms::_empty) mPrefix = nullptr;
+ NS_ASSERTION(aLocalName, "txNameTest without a local name?");
+ NS_ASSERTION(aNodeType == txXPathNodeType::DOCUMENT_NODE ||
+ aNodeType == txXPathNodeType::ELEMENT_NODE ||
+ aNodeType == txXPathNodeType::ATTRIBUTE_NODE,
+ "Go fix txNameTest::matches");
+}
+
+nsresult txNameTest::matches(const txXPathNode& aNode,
+ txIMatchContext* aContext, bool& aMatched) {
+ if ((mNodeType == txXPathNodeType::ELEMENT_NODE &&
+ !txXPathNodeUtils::isElement(aNode)) ||
+ (mNodeType == txXPathNodeType::ATTRIBUTE_NODE &&
+ !txXPathNodeUtils::isAttribute(aNode)) ||
+ (mNodeType == txXPathNodeType::DOCUMENT_NODE &&
+ !txXPathNodeUtils::isRoot(aNode))) {
+ aMatched = false;
+ return NS_OK;
+ }
+
+ // Totally wild?
+ if (mLocalName == nsGkAtoms::_asterisk && !mPrefix) {
+ aMatched = true;
+ return NS_OK;
+ }
+
+ // Compare namespaces
+ if (mNamespace != txXPathNodeUtils::getNamespaceID(aNode) &&
+ !(mNamespace == kNameSpaceID_None &&
+ txXPathNodeUtils::isHTMLElementInHTMLDocument(aNode))) {
+ aMatched = false;
+ return NS_OK;
+ }
+
+ // Name wild?
+ if (mLocalName == nsGkAtoms::_asterisk) {
+ aMatched = true;
+ return NS_OK;
+ }
+
+ // Compare local-names
+ aMatched = txXPathNodeUtils::localNameEquals(aNode, mLocalName);
+ return NS_OK;
+}
+
+/*
+ * Returns the default priority of this txNodeTest
+ */
+double txNameTest::getDefaultPriority() {
+ if (mLocalName == nsGkAtoms::_asterisk) {
+ if (!mPrefix) return -0.5;
+ return -0.25;
+ }
+ return 0;
+}
+
+txNodeTest::NodeTestType txNameTest::getType() { return NAME_TEST; }
+
+bool txNameTest::isSensitiveTo(Expr::ContextSensitivity aContext) {
+ return !!(aContext & Expr::NODE_CONTEXT);
+}
+
+#ifdef TX_TO_STRING
+void txNameTest::toString(nsAString& aDest) {
+ if (mPrefix) {
+ nsAutoString prefix;
+ mPrefix->ToString(prefix);
+ aDest.Append(prefix);
+ aDest.Append(char16_t(':'));
+ }
+ nsAutoString localName;
+ mLocalName->ToString(localName);
+ aDest.Append(localName);
+}
+#endif
diff --git a/dom/xslt/xpath/txNamedAttributeStep.cpp b/dom/xslt/xpath/txNamedAttributeStep.cpp
new file mode 100644
index 0000000000..18938e1dd3
--- /dev/null
+++ b/dom/xslt/xpath/txNamedAttributeStep.cpp
@@ -0,0 +1,53 @@
+/* -*- 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 "nsAtom.h"
+#include "txIXPathContext.h"
+#include "txNodeSet.h"
+#include "txExpr.h"
+#include "txXPathTreeWalker.h"
+
+txNamedAttributeStep::txNamedAttributeStep(int32_t aNsID, nsAtom* aPrefix,
+ nsAtom* aLocalName)
+ : mNamespace(aNsID), mPrefix(aPrefix), mLocalName(aLocalName) {}
+
+nsresult txNamedAttributeStep::evaluate(txIEvalContext* aContext,
+ txAExprResult** aResult) {
+ *aResult = nullptr;
+
+ RefPtr<txNodeSet> nodes;
+ nsresult rv = aContext->recycler()->getNodeSet(getter_AddRefs(nodes));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ txXPathTreeWalker walker(aContext->getContextNode());
+ if (walker.moveToNamedAttribute(mLocalName, mNamespace)) {
+ rv = nodes->append(walker.getCurrentPosition());
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ NS_ADDREF(*aResult = nodes);
+
+ return NS_OK;
+}
+
+TX_IMPL_EXPR_STUBS_0(txNamedAttributeStep, NODESET_RESULT)
+
+bool txNamedAttributeStep::isSensitiveTo(ContextSensitivity aContext) {
+ return !!(aContext & NODE_CONTEXT);
+}
+
+#ifdef TX_TO_STRING
+void txNamedAttributeStep::toString(nsAString& aDest) {
+ aDest.Append(char16_t('@'));
+ if (mPrefix) {
+ nsAutoString prefix;
+ mPrefix->ToString(prefix);
+ aDest.Append(prefix);
+ aDest.Append(char16_t(':'));
+ }
+ nsAutoString localName;
+ mLocalName->ToString(localName);
+ aDest.Append(localName);
+}
+#endif
diff --git a/dom/xslt/xpath/txNodeSet.cpp b/dom/xslt/xpath/txNodeSet.cpp
new file mode 100644
index 0000000000..60eed8952d
--- /dev/null
+++ b/dom/xslt/xpath/txNodeSet.cpp
@@ -0,0 +1,567 @@
+/* -*- 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 "txNodeSet.h"
+#include "txLog.h"
+#include "txXPathTreeWalker.h"
+#include <algorithm>
+
+/**
+ * Implementation of an XPath nodeset
+ */
+
+#ifdef NS_BUILD_REFCNT_LOGGING
+# define LOG_CHUNK_MOVE(_start, _new_start, _count) \
+ { \
+ txXPathNode* start = const_cast<txXPathNode*>(_start); \
+ while (start < _start + _count) { \
+ NS_LogDtor(start, "txXPathNode", sizeof(*start)); \
+ ++start; \
+ } \
+ start = const_cast<txXPathNode*>(_new_start); \
+ while (start < _new_start + _count) { \
+ NS_LogCtor(start, "txXPathNode", sizeof(*start)); \
+ ++start; \
+ } \
+ }
+#else
+# define LOG_CHUNK_MOVE(_start, _new_start, _count)
+#endif
+
+static const int32_t kTxNodeSetMinSize = 4;
+static const int32_t kTxNodeSetGrowFactor = 2;
+
+#define kForward 1
+#define kReversed -1
+
+txNodeSet::txNodeSet(txResultRecycler* aRecycler)
+ : txAExprResult(aRecycler),
+ mStart(nullptr),
+ mEnd(nullptr),
+ mStartBuffer(nullptr),
+ mEndBuffer(nullptr),
+ mDirection(kForward),
+ mMarks(nullptr) {}
+
+txNodeSet::txNodeSet(const txXPathNode& aNode, txResultRecycler* aRecycler)
+ : txAExprResult(aRecycler),
+ mStart(nullptr),
+ mEnd(nullptr),
+ mStartBuffer(nullptr),
+ mEndBuffer(nullptr),
+ mDirection(kForward),
+ mMarks(nullptr) {
+ if (!ensureGrowSize(1)) {
+ return;
+ }
+
+ new (mStart) txXPathNode(aNode);
+ ++mEnd;
+}
+
+txNodeSet::txNodeSet(const txNodeSet& aSource, txResultRecycler* aRecycler)
+ : txAExprResult(aRecycler),
+ mStart(nullptr),
+ mEnd(nullptr),
+ mStartBuffer(nullptr),
+ mEndBuffer(nullptr),
+ mDirection(kForward),
+ mMarks(nullptr) {
+ append(aSource);
+}
+
+txNodeSet::~txNodeSet() {
+ delete[] mMarks;
+
+ if (mStartBuffer) {
+ destroyElements(mStart, mEnd);
+
+ free(mStartBuffer);
+ }
+}
+
+nsresult txNodeSet::add(const txXPathNode& aNode) {
+ NS_ASSERTION(mDirection == kForward,
+ "only append(aNode) is supported on reversed nodesets");
+
+ if (isEmpty()) {
+ return append(aNode);
+ }
+
+ bool dupe;
+ txXPathNode* pos = findPosition(aNode, mStart, mEnd, dupe);
+
+ if (dupe) {
+ return NS_OK;
+ }
+
+ // save pos, ensureGrowSize messes with the pointers
+ int32_t moveSize = mEnd - pos;
+ int32_t offset = pos - mStart;
+ if (!ensureGrowSize(1)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ // set pos to where it was
+ pos = mStart + offset;
+
+ if (moveSize > 0) {
+ LOG_CHUNK_MOVE(pos, pos + 1, moveSize);
+ memmove(pos + 1, pos, moveSize * sizeof(txXPathNode));
+ }
+
+ new (pos) txXPathNode(aNode);
+ ++mEnd;
+
+ return NS_OK;
+}
+
+nsresult txNodeSet::add(const txNodeSet& aNodes) {
+ return add(aNodes, copyElements, nullptr);
+}
+
+nsresult txNodeSet::addAndTransfer(txNodeSet* aNodes) {
+ // failure is out-of-memory, transfer didn't happen
+ nsresult rv = add(*aNodes, transferElements, destroyElements);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+#ifdef TX_DONT_RECYCLE_BUFFER
+ if (aNodes->mStartBuffer) {
+ free(aNodes->mStartBuffer);
+ aNodes->mStartBuffer = aNodes->mEndBuffer = nullptr;
+ }
+#endif
+ aNodes->mStart = aNodes->mEnd = aNodes->mStartBuffer;
+
+ return NS_OK;
+}
+
+/**
+ * add(aNodeSet, aTransferOp)
+ *
+ * The code is optimized to make a minimum number of calls to
+ * Node::compareDocumentPosition. The idea is this:
+ * We have the two nodesets (number indicate "document position")
+ *
+ * 1 3 7 <- source 1
+ * 2 3 6 8 9 <- source 2
+ * _ _ _ _ _ _ _ _ <- result
+ *
+ *
+ * When merging these nodesets into the result, the nodes are transfered
+ * in chunks to the end of the buffer so that each chunk does not contain
+ * a node from the other nodeset, in document order.
+ *
+ * We select the last non-transfered node in the first nodeset and find
+ * where in the other nodeset it would be inserted. In this case we would
+ * take the 7 from the first nodeset and find the position between the
+ * 6 and 8 in the second. We then take the nodes after the insert-position
+ * and transfer them to the end of the resulting nodeset. Which in this case
+ * means that we first transfered the 8 and 9 nodes, giving us the following:
+ *
+ * 1 3 7 <- source 1
+ * 2 3 6 <- source 2
+ * _ _ _ _ _ _ 8 9 <- result
+ *
+ * The corresponding procedure is done for the second nodeset, that is
+ * the insertion position of the 6 in the first nodeset is found, which
+ * is between the 3 and the 7. The 7 is memmoved (as it stays within
+ * the same nodeset) to the result buffer.
+ *
+ * As the result buffer is filled from the end, it is safe to share the
+ * buffer between this nodeset and the result.
+ *
+ * This is repeated until both of the nodesets are empty.
+ *
+ * If we find a duplicate node when searching for where insertposition we
+ * check for sequences of duplicate nodes, which can be optimized.
+ *
+ */
+nsresult txNodeSet::add(const txNodeSet& aNodes, transferOp aTransfer,
+ destroyOp aDestroy) {
+ NS_ASSERTION(mDirection == kForward,
+ "only append(aNode) is supported on reversed nodesets");
+
+ if (aNodes.isEmpty()) {
+ return NS_OK;
+ }
+
+ if (!ensureGrowSize(aNodes.size())) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ // This is probably a rather common case, so lets try to shortcut.
+ if (mStart == mEnd ||
+ txXPathNodeUtils::comparePosition(mEnd[-1], *aNodes.mStart) < 0) {
+ aTransfer(mEnd, aNodes.mStart, aNodes.mEnd);
+ mEnd += aNodes.size();
+
+ return NS_OK;
+ }
+
+ // Last element in this nodeset
+ txXPathNode* thisPos = mEnd;
+
+ // Last element of the other nodeset
+ txXPathNode* otherPos = aNodes.mEnd;
+
+ // Pointer to the insertion point in this nodeset
+ txXPathNode* insertPos = mEndBuffer;
+
+ bool dupe;
+ txXPathNode* pos;
+ int32_t count;
+ while (thisPos > mStart || otherPos > aNodes.mStart) {
+ // Find where the last remaining node of this nodeset would
+ // be inserted in the other nodeset.
+ if (thisPos > mStart) {
+ pos = findPosition(thisPos[-1], aNodes.mStart, otherPos, dupe);
+
+ if (dupe) {
+ const txXPathNode* deletePos = thisPos;
+ --thisPos; // this is already added
+ // check dupe sequence
+ while (thisPos > mStart && pos > aNodes.mStart &&
+ thisPos[-1] == pos[-1]) {
+ --thisPos;
+ --pos;
+ }
+
+ if (aDestroy) {
+ aDestroy(thisPos, deletePos);
+ }
+ }
+ } else {
+ pos = aNodes.mStart;
+ }
+
+ // Transfer the otherNodes after the insertion point to the result
+ count = otherPos - pos;
+ if (count > 0) {
+ insertPos -= count;
+ aTransfer(insertPos, pos, otherPos);
+ otherPos -= count;
+ }
+
+ // Find where the last remaining node of the otherNodeset would
+ // be inserted in this nodeset.
+ if (otherPos > aNodes.mStart) {
+ pos = findPosition(otherPos[-1], mStart, thisPos, dupe);
+
+ if (dupe) {
+ const txXPathNode* deletePos = otherPos;
+ --otherPos; // this is already added
+ // check dupe sequence
+ while (otherPos > aNodes.mStart && pos > mStart &&
+ otherPos[-1] == pos[-1]) {
+ --otherPos;
+ --pos;
+ }
+
+ if (aDestroy) {
+ aDestroy(otherPos, deletePos);
+ }
+ }
+ } else {
+ pos = mStart;
+ }
+
+ // Move the nodes from this nodeset after the insertion point
+ // to the result
+ count = thisPos - pos;
+ if (count > 0) {
+ insertPos -= count;
+ LOG_CHUNK_MOVE(pos, insertPos, count);
+ memmove(insertPos, pos, count * sizeof(txXPathNode));
+ thisPos -= count;
+ }
+ }
+ mStart = insertPos;
+ mEnd = mEndBuffer;
+
+ return NS_OK;
+}
+
+/**
+ * Append API
+ * These functions should be used with care.
+ * They are intended to be used when the caller assures that the resulting
+ * nodeset remains in document order.
+ * Abuse will break document order, and cause errors in the result.
+ * These functions are significantly faster than the add API, as no
+ * order info operations will be performed.
+ */
+
+nsresult txNodeSet::append(const txXPathNode& aNode) {
+ if (!ensureGrowSize(1)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ if (mDirection == kForward) {
+ new (mEnd) txXPathNode(aNode);
+ ++mEnd;
+
+ return NS_OK;
+ }
+
+ new (--mStart) txXPathNode(aNode);
+
+ return NS_OK;
+}
+
+nsresult txNodeSet::append(const txNodeSet& aNodes) {
+ NS_ASSERTION(mDirection == kForward,
+ "only append(aNode) is supported on reversed nodesets");
+
+ if (aNodes.isEmpty()) {
+ return NS_OK;
+ }
+
+ int32_t appended = aNodes.size();
+ if (!ensureGrowSize(appended)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ copyElements(mEnd, aNodes.mStart, aNodes.mEnd);
+ mEnd += appended;
+
+ return NS_OK;
+}
+
+nsresult txNodeSet::mark(int32_t aIndex) {
+ NS_ASSERTION(aIndex >= 0 && mStart && mEnd - mStart > aIndex,
+ "index out of bounds");
+ if (!mMarks) {
+ int32_t length = size();
+ mMarks = new bool[length];
+ memset(mMarks, 0, length * sizeof(bool));
+ }
+ if (mDirection == kForward) {
+ mMarks[aIndex] = true;
+ } else {
+ mMarks[size() - aIndex - 1] = true;
+ }
+
+ return NS_OK;
+}
+
+nsresult txNodeSet::sweep() {
+ if (!mMarks) {
+ // sweep everything
+ clear();
+ }
+
+ int32_t chunk, pos = 0;
+ int32_t length = size();
+ txXPathNode* insertion = mStartBuffer;
+
+ while (pos < length) {
+ while (pos < length && !mMarks[pos]) {
+ // delete unmarked
+ mStart[pos].~txXPathNode();
+ ++pos;
+ }
+ // find chunk to move
+ chunk = 0;
+ while (pos < length && mMarks[pos]) {
+ ++pos;
+ ++chunk;
+ }
+ // move chunk
+ if (chunk > 0) {
+ LOG_CHUNK_MOVE(mStart + pos - chunk, insertion, chunk);
+ memmove(insertion, mStart + pos - chunk, chunk * sizeof(txXPathNode));
+ insertion += chunk;
+ }
+ }
+ mStart = mStartBuffer;
+ mEnd = insertion;
+ delete[] mMarks;
+ mMarks = nullptr;
+
+ return NS_OK;
+}
+
+void txNodeSet::clear() {
+ destroyElements(mStart, mEnd);
+#ifdef TX_DONT_RECYCLE_BUFFER
+ if (mStartBuffer) {
+ free(mStartBuffer);
+ mStartBuffer = mEndBuffer = nullptr;
+ }
+#endif
+ mStart = mEnd = mStartBuffer;
+ delete[] mMarks;
+ mMarks = nullptr;
+ mDirection = kForward;
+}
+
+int32_t txNodeSet::indexOf(const txXPathNode& aNode, uint32_t aStart) const {
+ NS_ASSERTION(mDirection == kForward,
+ "only append(aNode) is supported on reversed nodesets");
+
+ if (!mStart || mStart == mEnd) {
+ return -1;
+ }
+
+ txXPathNode* pos = mStart + aStart;
+ for (; pos < mEnd; ++pos) {
+ if (*pos == aNode) {
+ return pos - mStart;
+ }
+ }
+
+ return -1;
+}
+
+const txXPathNode& txNodeSet::get(int32_t aIndex) const {
+ if (mDirection == kForward) {
+ return mStart[aIndex];
+ }
+
+ return mEnd[-aIndex - 1];
+}
+
+short txNodeSet::getResultType() { return txAExprResult::NODESET; }
+
+bool txNodeSet::booleanValue() { return !isEmpty(); }
+double txNodeSet::numberValue() {
+ nsAutoString str;
+ stringValue(str);
+
+ return txDouble::toDouble(str);
+}
+
+void txNodeSet::stringValue(nsString& aStr) {
+ NS_ASSERTION(mDirection == kForward,
+ "only append(aNode) is supported on reversed nodesets");
+ if (isEmpty()) {
+ return;
+ }
+ txXPathNodeUtils::appendNodeValue(get(0), aStr);
+}
+
+const nsString* txNodeSet::stringValuePointer() { return nullptr; }
+
+bool txNodeSet::ensureGrowSize(int32_t aSize) {
+ // check if there is enough place in the buffer as is
+ if (mDirection == kForward && aSize <= mEndBuffer - mEnd) {
+ return true;
+ }
+
+ if (mDirection == kReversed && aSize <= mStart - mStartBuffer) {
+ return true;
+ }
+
+ // check if we just have to align mStart to have enough space
+ int32_t oldSize = mEnd - mStart;
+ int32_t oldLength = mEndBuffer - mStartBuffer;
+ int32_t ensureSize = oldSize + aSize;
+ if (ensureSize <= oldLength) {
+ // just move the buffer
+ txXPathNode* dest = mStartBuffer;
+ if (mDirection == kReversed) {
+ dest = mEndBuffer - oldSize;
+ }
+ LOG_CHUNK_MOVE(mStart, dest, oldSize);
+ memmove(dest, mStart, oldSize * sizeof(txXPathNode));
+ mStart = dest;
+ mEnd = dest + oldSize;
+
+ return true;
+ }
+
+ // This isn't 100% safe. But until someone manages to make a 1gig nodeset
+ // it should be ok.
+ int32_t newLength = std::max(oldLength, kTxNodeSetMinSize);
+
+ while (newLength < ensureSize) {
+ newLength *= kTxNodeSetGrowFactor;
+ }
+
+ txXPathNode* newArr =
+ static_cast<txXPathNode*>(moz_xmalloc(newLength * sizeof(txXPathNode)));
+
+ txXPathNode* dest = newArr;
+ if (mDirection == kReversed) {
+ dest += newLength - oldSize;
+ }
+
+ if (oldSize > 0) {
+ LOG_CHUNK_MOVE(mStart, dest, oldSize);
+ memcpy(dest, mStart, oldSize * sizeof(txXPathNode));
+ }
+
+ if (mStartBuffer) {
+#ifdef DEBUG
+ memset(mStartBuffer, 0, (mEndBuffer - mStartBuffer) * sizeof(txXPathNode));
+#endif
+ free(mStartBuffer);
+ }
+
+ mStartBuffer = newArr;
+ mEndBuffer = mStartBuffer + newLength;
+ mStart = dest;
+ mEnd = dest + oldSize;
+
+ return true;
+}
+
+txXPathNode* txNodeSet::findPosition(const txXPathNode& aNode,
+ txXPathNode* aFirst, txXPathNode* aLast,
+ bool& aDupe) const {
+ aDupe = false;
+ if (aLast - aFirst <= 2) {
+ // If we search 2 nodes or less there is no point in further divides
+ txXPathNode* pos = aFirst;
+ for (; pos < aLast; ++pos) {
+ int cmp = txXPathNodeUtils::comparePosition(aNode, *pos);
+ if (cmp < 0) {
+ return pos;
+ }
+
+ if (cmp == 0) {
+ aDupe = true;
+
+ return pos;
+ }
+ }
+ return pos;
+ }
+
+ // (cannot add two pointers)
+ txXPathNode* midpos = aFirst + (aLast - aFirst) / 2;
+ int cmp = txXPathNodeUtils::comparePosition(aNode, *midpos);
+ if (cmp == 0) {
+ aDupe = true;
+
+ return midpos;
+ }
+
+ if (cmp > 0) {
+ return findPosition(aNode, midpos + 1, aLast, aDupe);
+ }
+
+ // midpos excluded as end of range
+
+ return findPosition(aNode, aFirst, midpos, aDupe);
+}
+
+/* static */
+void txNodeSet::copyElements(txXPathNode* aDest, const txXPathNode* aStart,
+ const txXPathNode* aEnd) {
+ const txXPathNode* pos = aStart;
+ while (pos < aEnd) {
+ new (aDest) txXPathNode(*pos);
+ ++aDest;
+ ++pos;
+ }
+}
+
+/* static */
+void txNodeSet::transferElements(txXPathNode* aDest, const txXPathNode* aStart,
+ const txXPathNode* aEnd) {
+ LOG_CHUNK_MOVE(aStart, aDest, (aEnd - aStart));
+ memcpy(aDest, aStart, (aEnd - aStart) * sizeof(txXPathNode));
+}
diff --git a/dom/xslt/xpath/txNodeSet.h b/dom/xslt/xpath/txNodeSet.h
new file mode 100644
index 0000000000..8c5e805d6b
--- /dev/null
+++ b/dom/xslt/xpath/txNodeSet.h
@@ -0,0 +1,199 @@
+/* -*- 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/. */
+
+/**
+ * Implementation of an XPath NodeSet
+ */
+
+#ifndef txNodeSet_h__
+#define txNodeSet_h__
+
+#include "txExprResult.h"
+#include "nsError.h"
+#include "txXPathNode.h"
+
+class txNodeSet : public txAExprResult {
+ public:
+ /**
+ * Creates a new empty NodeSet
+ */
+ explicit txNodeSet(txResultRecycler* aRecycler);
+
+ /**
+ * Creates a new NodeSet with one node.
+ */
+ txNodeSet(const txXPathNode& aNode, txResultRecycler* aRecycler);
+
+ /**
+ * Creates a new txNodeSet, copying the node references from the source
+ * NodeSet.
+ */
+ txNodeSet(const txNodeSet& aSource, txResultRecycler* aRecycler);
+
+ /**
+ * Destructor for txNodeSet, deletes the nodes.
+ */
+ virtual ~txNodeSet();
+
+ /**
+ * Adds the specified txXPathNode to this NodeSet if it is not already
+ * in this NodeSet. The node is inserted according to document order.
+ *
+ * @param aNode the txXPathNode to add to the NodeSet
+ * @return errorcode.
+ */
+ nsresult add(const txXPathNode& aNode);
+
+ /**
+ * Adds the nodes in specified NodeSet to this NodeSet. The resulting
+ * NodeSet is sorted in document order and does not contain any duplicate
+ * nodes.
+ *
+ * @param aNodes the NodeSet to add, must be in document order.
+ * @return errorcode.
+ */
+ nsresult add(const txNodeSet& aNodes);
+ nsresult addAndTransfer(txNodeSet* aNodes);
+
+ /**
+ * Append API
+ * These functions should be used with care.
+ * They are intended to be used when the caller assures that the resulting
+ * NodeSet remains in document order.
+ * Abuse will break document order, and cause errors in the result.
+ * These functions are significantly faster than the add API, as no
+ * order info operations will be performed.
+ */
+
+ /**
+ * Appends the specified Node to the end of this NodeSet
+ * @param aNode the Node to append to the NodeSet
+ * @return errorcode.
+ */
+ nsresult append(const txXPathNode& aNode);
+
+ /**
+ * Appends the nodes in the specified NodeSet to the end of this NodeSet
+ * @param aNodes the NodeSet to append to the NodeSet
+ * @return errorcode.
+ */
+ nsresult append(const txNodeSet& aNodes);
+
+ /**
+ * API to implement reverse axes in LocationStep.
+ *
+ * Before adding nodes to the nodeset for a reversed axis, call
+ * setReverse(). This will make the append(aNode) and get() methods treat
+ * the nodeset as required. Do only call append(aNode), get(), mark()
+ * and sweep() while the nodeset is reversed.
+ * Afterwards, call unsetReverse(). The nodes are stored in document
+ * order internally.
+ */
+ void setReverse() { mDirection = -1; }
+ void unsetReverse() { mDirection = 1; }
+
+ /**
+ * API to implement predicates in PredicateExpr
+ *
+ * mark(aIndex) marks the specified member of the nodeset.
+ * sweep() clears all members of the nodeset that haven't been
+ * marked before and clear the mMarks array.
+ */
+ nsresult mark(int32_t aIndex);
+ nsresult sweep();
+
+ /**
+ * Removes all nodes from this nodeset
+ */
+ void clear();
+
+ /**
+ * Returns the index of the specified Node,
+ * or -1 if the Node is not contained in the NodeSet
+ * @param aNode the Node to get the index for
+ * @param aStart index to start searching at
+ * @return index of specified node or -1 if the node does not exist
+ */
+ int32_t indexOf(const txXPathNode& aNode, uint32_t aStart = 0) const;
+
+ /**
+ * Returns true if the specified Node is contained in the set.
+ * @param aNode the Node to search for
+ * @return true if specified Node is contained in the NodeSet
+ */
+ bool contains(const txXPathNode& aNode) const { return indexOf(aNode) >= 0; }
+
+ /**
+ * Returns the Node at the specified node in this NodeSet.
+ * @param aIndex the node of the Node to return
+ * @return Node at specified node
+ */
+ const txXPathNode& get(int32_t aIndex) const;
+
+ /**
+ * Returns true if there are no Nodes in the NodeSet.
+ * @return true if there are no Nodes in the NodeSet.
+ */
+ bool isEmpty() const { return mStart ? mStart == mEnd : true; }
+
+ /**
+ * Returns the number of elements in the NodeSet
+ * @return the number of elements in the NodeSet
+ */
+ int32_t size() const { return mStart ? mEnd - mStart : 0; }
+
+ TX_DECL_EXPRRESULT
+
+ private:
+ /**
+ * Ensure that this nodeset can take another aSize nodes.
+ *
+ * Changes mStart and mEnd as well as mBufferStart and mBufferEnd.
+ */
+ bool ensureGrowSize(int32_t aSize);
+
+ /**
+ * Finds position in the buffer where a node should be inserted
+ * to keep the nodeset in document order. Searches the positions
+ * aFirst-aLast, including aFirst, but not aLast.
+ * @param aNode Node to find insert position for.
+ * @param aFirst First item of the search range, included.
+ * @param aLast Last item of the search range, excluded.
+ * @param aDupe out-param. Will be set to true if the node already
+ * exists in the NodeSet, false if it should be
+ * inserted.
+ * @return pointer where to insert the node. The node should be inserted
+ * before the given node. This value is always set, even if aNode
+ * already exists in the NodeSet
+ */
+ txXPathNode* findPosition(const txXPathNode& aNode, txXPathNode* aFirst,
+ txXPathNode* aLast, bool& aDupe) const;
+
+ static void copyElements(txXPathNode* aDest, const txXPathNode* aStart,
+ const txXPathNode* aEnd);
+ static void transferElements(txXPathNode* aDest, const txXPathNode* aStart,
+ const txXPathNode* aEnd);
+ static void destroyElements(const txXPathNode* aStart,
+ const txXPathNode* aEnd) {
+ while (aStart < aEnd) {
+ aStart->~txXPathNode();
+ ++aStart;
+ }
+ }
+
+ using transferOp = void (*)(txXPathNode* aDest, const txXPathNode* aStart,
+ const txXPathNode* aEnd);
+ using destroyOp = void (*)(const txXPathNode* aStart,
+ const txXPathNode* aEnd);
+ nsresult add(const txNodeSet& aNodes, transferOp aTransfer,
+ destroyOp aDestroy);
+
+ txXPathNode *mStart, *mEnd, *mStartBuffer, *mEndBuffer;
+ int32_t mDirection;
+ // used for mark() and sweep() in predicates
+ bool* mMarks;
+};
+
+#endif
diff --git a/dom/xslt/xpath/txNodeSetContext.cpp b/dom/xslt/xpath/txNodeSetContext.cpp
new file mode 100644
index 0000000000..7565e12545
--- /dev/null
+++ b/dom/xslt/xpath/txNodeSetContext.cpp
@@ -0,0 +1,51 @@
+/* -*- 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 "txNodeSetContext.h"
+#include "txNodeSet.h"
+
+const txXPathNode& txNodeSetContext::getContextNode() {
+ return mContextSet->get(mPosition - 1);
+}
+
+uint32_t txNodeSetContext::size() { return (uint32_t)mContextSet->size(); }
+
+uint32_t txNodeSetContext::position() {
+ NS_ASSERTION(mPosition, "Should have called next() at least once");
+ return mPosition;
+}
+
+nsresult txNodeSetContext::getVariable(int32_t aNamespace, nsAtom* aLName,
+ txAExprResult*& aResult) {
+ NS_ASSERTION(mInner, "mInner is null!!!");
+ return mInner->getVariable(aNamespace, aLName, aResult);
+}
+
+nsresult txNodeSetContext::isStripSpaceAllowed(const txXPathNode& aNode,
+ bool& aAllowed) {
+ NS_ASSERTION(mInner, "mInner is null!!!");
+ return mInner->isStripSpaceAllowed(aNode, aAllowed);
+}
+
+void* txNodeSetContext::getPrivateContext() {
+ NS_ASSERTION(mInner, "mInner is null!!!");
+ return mInner->getPrivateContext();
+}
+
+txResultRecycler* txNodeSetContext::recycler() {
+ NS_ASSERTION(mInner, "mInner is null!!!");
+ return mInner->recycler();
+}
+
+void txNodeSetContext::receiveError(const nsAString& aMsg, nsresult aRes) {
+ NS_ASSERTION(mInner, "mInner is null!!!");
+#ifdef DEBUG
+ nsAutoString error(u"forwarded error: "_ns);
+ error.Append(aMsg);
+ mInner->receiveError(error, aRes);
+#else
+ mInner->receiveError(aMsg, aRes);
+#endif
+}
diff --git a/dom/xslt/xpath/txNodeSetContext.h b/dom/xslt/xpath/txNodeSetContext.h
new file mode 100644
index 0000000000..86cb14ab95
--- /dev/null
+++ b/dom/xslt/xpath/txNodeSetContext.h
@@ -0,0 +1,36 @@
+/* -*- 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/. */
+
+#ifndef __TX_XPATH_SET_CONTEXT
+#define __TX_XPATH_SET_CONTEXT
+
+#include "txIXPathContext.h"
+#include "txNodeSet.h"
+
+class txNodeSetContext : public txIEvalContext {
+ public:
+ txNodeSetContext(txNodeSet* aContextNodeSet, txIMatchContext* aContext)
+ : mContextSet(aContextNodeSet), mPosition(0), mInner(aContext) {}
+
+ // Iteration over the given NodeSet
+ bool hasNext() { return mPosition < size(); }
+ void next() {
+ NS_ASSERTION(mPosition < size(), "Out of bounds.");
+ mPosition++;
+ }
+ void setPosition(uint32_t aPosition) {
+ NS_ASSERTION(aPosition > 0 && aPosition <= size(), "Out of bounds.");
+ mPosition = aPosition;
+ }
+
+ TX_DECL_EVAL_CONTEXT;
+
+ protected:
+ RefPtr<txNodeSet> mContextSet;
+ uint32_t mPosition;
+ txIMatchContext* mInner;
+};
+
+#endif // __TX_XPATH_SET_CONTEXT
diff --git a/dom/xslt/xpath/txNodeTypeTest.cpp b/dom/xslt/xpath/txNodeTypeTest.cpp
new file mode 100644
index 0000000000..17d0c60483
--- /dev/null
+++ b/dom/xslt/xpath/txNodeTypeTest.cpp
@@ -0,0 +1,91 @@
+/* -*- 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 "txExpr.h"
+#include "nsAtom.h"
+#include "txIXPathContext.h"
+#include "txXPathTreeWalker.h"
+
+nsresult txNodeTypeTest::matches(const txXPathNode& aNode,
+ txIMatchContext* aContext, bool& aMatched) {
+ switch (mNodeType) {
+ case COMMENT_TYPE: {
+ aMatched = txXPathNodeUtils::isComment(aNode);
+ return NS_OK;
+ }
+ case TEXT_TYPE: {
+ aMatched = txXPathNodeUtils::isText(aNode);
+ if (aMatched) {
+ bool allowed;
+ nsresult rv = aContext->isStripSpaceAllowed(aNode, allowed);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aMatched = !allowed;
+ }
+ return NS_OK;
+ }
+ case PI_TYPE: {
+ aMatched =
+ txXPathNodeUtils::isProcessingInstruction(aNode) &&
+ (!mNodeName || txXPathNodeUtils::localNameEquals(aNode, mNodeName));
+ return NS_OK;
+ }
+ case NODE_TYPE: {
+ if (txXPathNodeUtils::isText(aNode)) {
+ bool allowed;
+ nsresult rv = aContext->isStripSpaceAllowed(aNode, allowed);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aMatched = !allowed;
+ } else {
+ aMatched = true;
+ }
+ return NS_OK;
+ }
+ }
+
+ MOZ_ASSERT_UNREACHABLE("Didn't deal with all values of the NodeType enum!");
+
+ aMatched = false;
+ return NS_OK;
+}
+
+txNodeTest::NodeTestType txNodeTypeTest::getType() { return NODETYPE_TEST; }
+
+/*
+ * Returns the default priority of this txNodeTest
+ */
+double txNodeTypeTest::getDefaultPriority() { return mNodeName ? 0 : -0.5; }
+
+bool txNodeTypeTest::isSensitiveTo(Expr::ContextSensitivity aContext) {
+ return !!(aContext & Expr::NODE_CONTEXT);
+}
+
+#ifdef TX_TO_STRING
+void txNodeTypeTest::toString(nsAString& aDest) {
+ switch (mNodeType) {
+ case COMMENT_TYPE:
+ aDest.AppendLiteral("comment()");
+ break;
+ case TEXT_TYPE:
+ aDest.AppendLiteral("text()");
+ break;
+ case PI_TYPE:
+ aDest.AppendLiteral("processing-instruction(");
+ if (mNodeName) {
+ nsAutoString str;
+ mNodeName->ToString(str);
+ aDest.Append(char16_t('\''));
+ aDest.Append(str);
+ aDest.Append(char16_t('\''));
+ }
+ aDest.Append(char16_t(')'));
+ break;
+ case NODE_TYPE:
+ aDest.AppendLiteral("node()");
+ break;
+ }
+}
+#endif
diff --git a/dom/xslt/xpath/txNumberExpr.cpp b/dom/xslt/xpath/txNumberExpr.cpp
new file mode 100644
index 0000000000..c3424a91f1
--- /dev/null
+++ b/dom/xslt/xpath/txNumberExpr.cpp
@@ -0,0 +1,108 @@
+/* -*- 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 "mozilla/FloatingPoint.h"
+
+#include "txExpr.h"
+#include <math.h>
+#include "txIXPathContext.h"
+
+nsresult txNumberExpr::evaluate(txIEvalContext* aContext,
+ txAExprResult** aResult) {
+ *aResult = nullptr;
+
+ RefPtr<txAExprResult> exprRes;
+ nsresult rv = mRightExpr->evaluate(aContext, getter_AddRefs(exprRes));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ double rightDbl = exprRes->numberValue();
+
+ rv = mLeftExpr->evaluate(aContext, getter_AddRefs(exprRes));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ double leftDbl = exprRes->numberValue();
+ double result = 0;
+
+ switch (mOp) {
+ case ADD:
+ result = leftDbl + rightDbl;
+ break;
+
+ case SUBTRACT:
+ result = leftDbl - rightDbl;
+ break;
+
+ case DIVIDE:
+ if (rightDbl == 0) {
+#if defined(XP_WIN)
+ /* XXX MSVC miscompiles such that (NaN == 0) */
+ if (std::isnan(rightDbl))
+ result = mozilla::UnspecifiedNaN<double>();
+ else
+#endif
+ if (leftDbl == 0 || std::isnan(leftDbl))
+ result = mozilla::UnspecifiedNaN<double>();
+ else if (mozilla::IsNegative(leftDbl) != mozilla::IsNegative(rightDbl))
+ result = mozilla::NegativeInfinity<double>();
+ else
+ result = mozilla::PositiveInfinity<double>();
+ } else
+ result = leftDbl / rightDbl;
+ break;
+
+ case MODULUS:
+ if (rightDbl == 0) {
+ result = mozilla::UnspecifiedNaN<double>();
+ } else {
+#if defined(XP_WIN)
+ /* Workaround MS fmod bug where 42 % (1/0) => NaN, not 42. */
+ if (!std::isinf(leftDbl) && std::isinf(rightDbl))
+ result = leftDbl;
+ else
+#endif
+ result = fmod(leftDbl, rightDbl);
+ }
+ break;
+
+ case MULTIPLY:
+ result = leftDbl * rightDbl;
+ break;
+ }
+
+ return aContext->recycler()->getNumberResult(result, aResult);
+} //-- evaluate
+
+TX_IMPL_EXPR_STUBS_2(txNumberExpr, NUMBER_RESULT, mLeftExpr, mRightExpr)
+
+bool txNumberExpr::isSensitiveTo(ContextSensitivity aContext) {
+ return mLeftExpr->isSensitiveTo(aContext) ||
+ mRightExpr->isSensitiveTo(aContext);
+}
+
+#ifdef TX_TO_STRING
+void txNumberExpr::toString(nsAString& str) {
+ mLeftExpr->toString(str);
+
+ switch (mOp) {
+ case ADD:
+ str.AppendLiteral(" + ");
+ break;
+ case SUBTRACT:
+ str.AppendLiteral(" - ");
+ break;
+ case DIVIDE:
+ str.AppendLiteral(" div ");
+ break;
+ case MODULUS:
+ str.AppendLiteral(" mod ");
+ break;
+ case MULTIPLY:
+ str.AppendLiteral(" * ");
+ break;
+ }
+
+ mRightExpr->toString(str);
+}
+#endif
diff --git a/dom/xslt/xpath/txNumberResult.cpp b/dom/xslt/xpath/txNumberResult.cpp
new file mode 100644
index 0000000000..370a7523d8
--- /dev/null
+++ b/dom/xslt/xpath/txNumberResult.cpp
@@ -0,0 +1,48 @@
+/* -*- 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/. */
+
+/**
+ * NumberResult
+ * Represents the a number as the result of evaluating an Expr
+ **/
+
+#include "mozilla/FloatingPoint.h"
+
+#include "txExprResult.h"
+
+/**
+ * Default Constructor
+ **/
+
+/**
+ * Creates a new NumberResult with the value of the given double parameter
+ * @param dbl the double to use for initialization of this NumberResult's value
+ **/
+NumberResult::NumberResult(double aValue, txResultRecycler* aRecycler)
+ : txAExprResult(aRecycler), value(aValue) {} //-- NumberResult
+
+/*
+ * Virtual Methods from ExprResult
+ */
+
+short NumberResult::getResultType() {
+ return txAExprResult::NUMBER;
+} //-- getResultType
+
+void NumberResult::stringValue(nsString& aResult) {
+ txDouble::toString(value, aResult);
+}
+
+const nsString* NumberResult::stringValuePointer() { return nullptr; }
+
+bool NumberResult::booleanValue() {
+ // OG+
+ // As per the XPath spec, the boolean value of a number is true if and only if
+ // it is neither positive 0 nor negative 0 nor NaN
+ return (bool)(value != 0.0 && !std::isnan(value));
+ // OG-
+} //-- booleanValue
+
+double NumberResult::numberValue() { return this->value; } //-- numberValue
diff --git a/dom/xslt/xpath/txPathExpr.cpp b/dom/xslt/xpath/txPathExpr.cpp
new file mode 100644
index 0000000000..79be4caa08
--- /dev/null
+++ b/dom/xslt/xpath/txPathExpr.cpp
@@ -0,0 +1,229 @@
+/* -*- 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 "txExpr.h"
+#include "txNodeSet.h"
+#include "txNodeSetContext.h"
+#include "txSingleNodeContext.h"
+#include "txXMLUtils.h"
+#include "txXPathTreeWalker.h"
+
+using mozilla::Unused;
+using mozilla::WrapUnique;
+
+//------------/
+//- PathExpr -/
+//------------/
+
+/**
+ * Adds the Expr to this PathExpr
+ * @param expr the Expr to add to this PathExpr
+ **/
+void PathExpr::addExpr(Expr* aExpr, PathOperator aPathOp) {
+ NS_ASSERTION(!mItems.IsEmpty() || aPathOp == RELATIVE_OP,
+ "First step has to be relative in PathExpr");
+ PathExprItem* pxi = mItems.AppendElement();
+ pxi->expr = WrapUnique(aExpr);
+ pxi->pathOp = aPathOp;
+}
+
+//-----------------------------/
+//- Virtual methods from Expr -/
+//-----------------------------/
+
+/**
+ * Evaluates this Expr based on the given context node and processor state
+ * @param context the context node for evaluation of this Expr
+ * @param ps the ContextState containing the stack information needed
+ * for evaluation
+ * @return the result of the evaluation
+ **/
+nsresult PathExpr::evaluate(txIEvalContext* aContext, txAExprResult** aResult) {
+ *aResult = nullptr;
+
+ // We need to evaluate the first step with the current context since it
+ // can depend on the context size and position. For example:
+ // key('books', concat('book', position()))
+ RefPtr<txAExprResult> res;
+ nsresult rv = mItems[0].expr->evaluate(aContext, getter_AddRefs(res));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ENSURE_TRUE(res->getResultType() == txAExprResult::NODESET,
+ NS_ERROR_XSLT_NODESET_EXPECTED);
+
+ RefPtr<txNodeSet> nodes =
+ static_cast<txNodeSet*>(static_cast<txAExprResult*>(res));
+ if (nodes->isEmpty()) {
+ res.forget(aResult);
+
+ return NS_OK;
+ }
+ res = nullptr; // To allow recycling
+
+ // Evaluate remaining steps
+ uint32_t i, len = mItems.Length();
+ for (i = 1; i < len; ++i) {
+ PathExprItem& pxi = mItems[i];
+ RefPtr<txNodeSet> tmpNodes;
+ txNodeSetContext eContext(nodes, aContext);
+ while (eContext.hasNext()) {
+ eContext.next();
+
+ RefPtr<txNodeSet> resNodes;
+ if (pxi.pathOp == DESCENDANT_OP) {
+ rv = aContext->recycler()->getNodeSet(getter_AddRefs(resNodes));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = evalDescendants(pxi.expr.get(), eContext.getContextNode(),
+ &eContext, resNodes);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ RefPtr<txAExprResult> res;
+ rv = pxi.expr->evaluate(&eContext, getter_AddRefs(res));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (res->getResultType() != txAExprResult::NODESET) {
+ // XXX ErrorReport: report nonnodeset error
+ return NS_ERROR_XSLT_NODESET_EXPECTED;
+ }
+ resNodes = static_cast<txNodeSet*>(static_cast<txAExprResult*>(res));
+ }
+
+ if (tmpNodes) {
+ if (!resNodes->isEmpty()) {
+ RefPtr<txNodeSet> oldSet;
+ oldSet.swap(tmpNodes);
+ rv = aContext->recycler()->getNonSharedNodeSet(
+ oldSet, getter_AddRefs(tmpNodes));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ oldSet.swap(resNodes);
+ rv = aContext->recycler()->getNonSharedNodeSet(
+ oldSet, getter_AddRefs(resNodes));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ tmpNodes->addAndTransfer(resNodes);
+ }
+ } else {
+ tmpNodes = resNodes;
+ }
+ }
+ nodes = tmpNodes;
+ if (nodes->isEmpty()) {
+ break;
+ }
+ }
+
+ *aResult = nodes;
+ NS_ADDREF(*aResult);
+
+ return NS_OK;
+} //-- evaluate
+
+/**
+ * Selects from the descendants of the context node
+ * all nodes that match the Expr
+ **/
+nsresult PathExpr::evalDescendants(Expr* aStep, const txXPathNode& aNode,
+ txIMatchContext* aContext,
+ txNodeSet* resNodes) {
+ txSingleNodeContext eContext(aNode, aContext);
+ RefPtr<txAExprResult> res;
+ nsresult rv = aStep->evaluate(&eContext, getter_AddRefs(res));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (res->getResultType() != txAExprResult::NODESET) {
+ // XXX ErrorReport: report nonnodeset error
+ return NS_ERROR_XSLT_NODESET_EXPECTED;
+ }
+
+ txNodeSet* oldSet = static_cast<txNodeSet*>(static_cast<txAExprResult*>(res));
+ RefPtr<txNodeSet> newSet;
+ rv =
+ aContext->recycler()->getNonSharedNodeSet(oldSet, getter_AddRefs(newSet));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ resNodes->addAndTransfer(newSet);
+
+ bool filterWS;
+ rv = aContext->isStripSpaceAllowed(aNode, filterWS);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ txXPathTreeWalker walker(aNode);
+ if (!walker.moveToFirstChild()) {
+ return NS_OK;
+ }
+
+ do {
+ const txXPathNode& node = walker.getCurrentPosition();
+ if (!(filterWS && txXPathNodeUtils::isText(node) &&
+ txXPathNodeUtils::isWhitespace(node))) {
+ rv = evalDescendants(aStep, node, aContext, resNodes);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ } while (walker.moveToNextSibling());
+
+ return NS_OK;
+} //-- evalDescendants
+
+Expr::ExprType PathExpr::getType() { return PATH_EXPR; }
+
+TX_IMPL_EXPR_STUBS_BASE(PathExpr, NODESET_RESULT)
+
+Expr* PathExpr::getSubExprAt(uint32_t aPos) {
+ return aPos < mItems.Length() ? mItems[aPos].expr.get() : nullptr;
+}
+void PathExpr::setSubExprAt(uint32_t aPos, Expr* aExpr) {
+ NS_ASSERTION(aPos < mItems.Length(), "setting bad subexpression index");
+ Unused << mItems[aPos].expr.release();
+ mItems[aPos].expr = WrapUnique(aExpr);
+}
+
+bool PathExpr::isSensitiveTo(ContextSensitivity aContext) {
+ if (mItems[0].expr->isSensitiveTo(aContext)) {
+ return true;
+ }
+
+ // We're creating a new node/nodeset so we can ignore those bits.
+ Expr::ContextSensitivity context =
+ aContext & ~(Expr::NODE_CONTEXT | Expr::NODESET_CONTEXT);
+ if (context == NO_CONTEXT) {
+ return false;
+ }
+
+ uint32_t i, len = mItems.Length();
+ for (i = 0; i < len; ++i) {
+ NS_ASSERTION(!mItems[i].expr->isSensitiveTo(Expr::NODESET_CONTEXT),
+ "Step cannot depend on nodeset-context");
+ if (mItems[i].expr->isSensitiveTo(context)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+#ifdef TX_TO_STRING
+void PathExpr::toString(nsAString& dest) {
+ if (!mItems.IsEmpty()) {
+ NS_ASSERTION(mItems[0].pathOp == RELATIVE_OP,
+ "First step should be relative");
+ mItems[0].expr->toString(dest);
+ }
+
+ uint32_t i, len = mItems.Length();
+ for (i = 1; i < len; ++i) {
+ switch (mItems[i].pathOp) {
+ case DESCENDANT_OP:
+ dest.AppendLiteral("//");
+ break;
+ case RELATIVE_OP:
+ dest.Append(char16_t('/'));
+ break;
+ }
+ mItems[i].expr->toString(dest);
+ }
+}
+#endif
diff --git a/dom/xslt/xpath/txPredicateList.cpp b/dom/xslt/xpath/txPredicateList.cpp
new file mode 100644
index 0000000000..ad2804caef
--- /dev/null
+++ b/dom/xslt/xpath/txPredicateList.cpp
@@ -0,0 +1,78 @@
+/* -*- 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 "txExpr.h"
+#include "txNodeSet.h"
+#include "txNodeSetContext.h"
+
+/*
+ * Represents an ordered list of Predicates,
+ * for use with Step and Filter Expressions
+ */
+
+nsresult PredicateList::evaluatePredicates(txNodeSet* nodes,
+ txIMatchContext* aContext) {
+ NS_ASSERTION(nodes, "called evaluatePredicates with nullptr NodeSet");
+ nsresult rv = NS_OK;
+
+ uint32_t i, len = mPredicates.Length();
+ for (i = 0; i < len && !nodes->isEmpty(); ++i) {
+ txNodeSetContext predContext(nodes, aContext);
+ /*
+ * add nodes to newNodes that match the expression
+ * or, if the result is a number, add the node with the right
+ * position
+ */
+ int32_t index = 0;
+ while (predContext.hasNext()) {
+ predContext.next();
+ RefPtr<txAExprResult> exprResult;
+ rv = mPredicates[i]->evaluate(&predContext, getter_AddRefs(exprResult));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // handle default, [position() == numberValue()]
+ if (exprResult->getResultType() == txAExprResult::NUMBER) {
+ if ((double)predContext.position() == exprResult->numberValue()) {
+ nodes->mark(index);
+ }
+ } else if (exprResult->booleanValue()) {
+ nodes->mark(index);
+ }
+ ++index;
+ }
+ // sweep the non-marked nodes
+ nodes->sweep();
+ }
+
+ return NS_OK;
+}
+
+bool PredicateList::isSensitiveTo(Expr::ContextSensitivity aContext) {
+ // We're creating a new node/nodeset so we can ignore those bits.
+ Expr::ContextSensitivity context =
+ aContext & ~(Expr::NODE_CONTEXT | Expr::NODESET_CONTEXT);
+ if (context == Expr::NO_CONTEXT) {
+ return false;
+ }
+
+ uint32_t i, len = mPredicates.Length();
+ for (i = 0; i < len; ++i) {
+ if (mPredicates[i]->isSensitiveTo(context)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+#ifdef TX_TO_STRING
+void PredicateList::toString(nsAString& dest) {
+ for (uint32_t i = 0; i < mPredicates.Length(); ++i) {
+ dest.Append(char16_t('['));
+ mPredicates[i]->toString(dest);
+ dest.Append(char16_t(']'));
+ }
+}
+#endif
diff --git a/dom/xslt/xpath/txPredicatedNodeTest.cpp b/dom/xslt/xpath/txPredicatedNodeTest.cpp
new file mode 100644
index 0000000000..6c3fd33cf5
--- /dev/null
+++ b/dom/xslt/xpath/txPredicatedNodeTest.cpp
@@ -0,0 +1,50 @@
+/* -*- 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 "txExpr.h"
+#include "txExprResult.h"
+#include "txSingleNodeContext.h"
+
+txPredicatedNodeTest::txPredicatedNodeTest(txNodeTest* aNodeTest,
+ Expr* aPredicate)
+ : mNodeTest(aNodeTest), mPredicate(aPredicate) {
+ NS_ASSERTION(!mPredicate->isSensitiveTo(Expr::NODESET_CONTEXT),
+ "predicate must not be context-nodeset-sensitive");
+}
+
+nsresult txPredicatedNodeTest::matches(const txXPathNode& aNode,
+ txIMatchContext* aContext,
+ bool& aMatched) {
+ nsresult rv = mNodeTest->matches(aNode, aContext, aMatched);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!aMatched) {
+ return NS_OK;
+ }
+
+ txSingleNodeContext context(aNode, aContext);
+ RefPtr<txAExprResult> res;
+ rv = mPredicate->evaluate(&context, getter_AddRefs(res));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aMatched = res->booleanValue();
+ return NS_OK;
+}
+
+double txPredicatedNodeTest::getDefaultPriority() { return 0.5; }
+
+bool txPredicatedNodeTest::isSensitiveTo(Expr::ContextSensitivity aContext) {
+ return mNodeTest->isSensitiveTo(aContext) ||
+ mPredicate->isSensitiveTo(aContext);
+}
+
+#ifdef TX_TO_STRING
+void txPredicatedNodeTest::toString(nsAString& aDest) {
+ mNodeTest->toString(aDest);
+ aDest.Append(char16_t('['));
+ mPredicate->toString(aDest);
+ aDest.Append(char16_t(']'));
+}
+#endif
diff --git a/dom/xslt/xpath/txRelationalExpr.cpp b/dom/xslt/xpath/txRelationalExpr.cpp
new file mode 100644
index 0000000000..df6da87ca8
--- /dev/null
+++ b/dom/xslt/xpath/txRelationalExpr.cpp
@@ -0,0 +1,184 @@
+/* -*- 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 "txExpr.h"
+#include "txNodeSet.h"
+#include "txIXPathContext.h"
+#include "txXPathTreeWalker.h"
+
+/**
+ * Compares the two ExprResults based on XPath 1.0 Recommendation (section 3.4)
+ */
+bool RelationalExpr::compareResults(txIEvalContext* aContext,
+ txAExprResult* aLeft,
+ txAExprResult* aRight) {
+ short ltype = aLeft->getResultType();
+ short rtype = aRight->getResultType();
+ nsresult rv = NS_OK;
+
+ // Handle case for just Left NodeSet or Both NodeSets
+ if (ltype == txAExprResult::NODESET) {
+ if (rtype == txAExprResult::BOOLEAN) {
+ BooleanResult leftBool(aLeft->booleanValue());
+ return compareResults(aContext, &leftBool, aRight);
+ }
+
+ txNodeSet* nodeSet = static_cast<txNodeSet*>(aLeft);
+ RefPtr<StringResult> strResult;
+ rv = aContext->recycler()->getStringResult(getter_AddRefs(strResult));
+ NS_ENSURE_SUCCESS(rv, false);
+
+ int32_t i;
+ for (i = 0; i < nodeSet->size(); ++i) {
+ strResult->mValue.Truncate();
+ txXPathNodeUtils::appendNodeValue(nodeSet->get(i), strResult->mValue);
+ if (compareResults(aContext, strResult, aRight)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ // Handle case for Just Right NodeSet
+ if (rtype == txAExprResult::NODESET) {
+ if (ltype == txAExprResult::BOOLEAN) {
+ BooleanResult rightBool(aRight->booleanValue());
+ return compareResults(aContext, aLeft, &rightBool);
+ }
+
+ txNodeSet* nodeSet = static_cast<txNodeSet*>(aRight);
+ RefPtr<StringResult> strResult;
+ rv = aContext->recycler()->getStringResult(getter_AddRefs(strResult));
+ NS_ENSURE_SUCCESS(rv, false);
+
+ int32_t i;
+ for (i = 0; i < nodeSet->size(); ++i) {
+ strResult->mValue.Truncate();
+ txXPathNodeUtils::appendNodeValue(nodeSet->get(i), strResult->mValue);
+ if (compareResults(aContext, aLeft, strResult)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ // Neither is a NodeSet
+ if (mOp == EQUAL || mOp == NOT_EQUAL) {
+ bool result;
+ const nsString *lString, *rString;
+
+ // If either is a bool, compare as bools.
+ if (ltype == txAExprResult::BOOLEAN || rtype == txAExprResult::BOOLEAN) {
+ result = aLeft->booleanValue() == aRight->booleanValue();
+ }
+
+ // If either is a number, compare as numbers.
+ else if (ltype == txAExprResult::NUMBER || rtype == txAExprResult::NUMBER) {
+ double lval = aLeft->numberValue();
+ double rval = aRight->numberValue();
+ result = (lval == rval);
+ }
+
+ // Otherwise compare as strings. Try to use the stringobject in
+ // StringResult if possible since that is a common case.
+ else if ((lString = aLeft->stringValuePointer())) {
+ if ((rString = aRight->stringValuePointer())) {
+ result = lString->Equals(*rString);
+ } else {
+ nsAutoString rStr;
+ aRight->stringValue(rStr);
+ result = lString->Equals(rStr);
+ }
+ } else if ((rString = aRight->stringValuePointer())) {
+ nsAutoString lStr;
+ aLeft->stringValue(lStr);
+ result = rString->Equals(lStr);
+ } else {
+ nsAutoString lStr, rStr;
+ aLeft->stringValue(lStr);
+ aRight->stringValue(rStr);
+ result = lStr.Equals(rStr);
+ }
+
+ return mOp == EQUAL ? result : !result;
+ }
+
+ double leftDbl = aLeft->numberValue();
+ double rightDbl = aRight->numberValue();
+ switch (mOp) {
+ case LESS_THAN: {
+ return (leftDbl < rightDbl);
+ }
+ case LESS_OR_EQUAL: {
+ return (leftDbl <= rightDbl);
+ }
+ case GREATER_THAN: {
+ return (leftDbl > rightDbl);
+ }
+ case GREATER_OR_EQUAL: {
+ return (leftDbl >= rightDbl);
+ }
+ default: {
+ MOZ_ASSERT_UNREACHABLE("We should have caught all cases");
+ }
+ }
+
+ return false;
+}
+
+nsresult RelationalExpr::evaluate(txIEvalContext* aContext,
+ txAExprResult** aResult) {
+ *aResult = nullptr;
+ RefPtr<txAExprResult> lResult;
+ nsresult rv = mLeftExpr->evaluate(aContext, getter_AddRefs(lResult));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<txAExprResult> rResult;
+ rv = mRightExpr->evaluate(aContext, getter_AddRefs(rResult));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aContext->recycler()->getBoolResult(
+ compareResults(aContext, lResult, rResult), aResult);
+
+ return NS_OK;
+}
+
+TX_IMPL_EXPR_STUBS_2(RelationalExpr, BOOLEAN_RESULT, mLeftExpr, mRightExpr)
+
+bool RelationalExpr::isSensitiveTo(ContextSensitivity aContext) {
+ return mLeftExpr->isSensitiveTo(aContext) ||
+ mRightExpr->isSensitiveTo(aContext);
+}
+
+#ifdef TX_TO_STRING
+void RelationalExpr::toString(nsAString& str) {
+ mLeftExpr->toString(str);
+
+ switch (mOp) {
+ case NOT_EQUAL:
+ str.AppendLiteral("!=");
+ break;
+ case LESS_THAN:
+ str.Append(char16_t('<'));
+ break;
+ case LESS_OR_EQUAL:
+ str.AppendLiteral("<=");
+ break;
+ case GREATER_THAN:
+ str.Append(char16_t('>'));
+ break;
+ case GREATER_OR_EQUAL:
+ str.AppendLiteral(">=");
+ break;
+ default:
+ str.Append(char16_t('='));
+ break;
+ }
+
+ mRightExpr->toString(str);
+}
+#endif
diff --git a/dom/xslt/xpath/txResultRecycler.cpp b/dom/xslt/xpath/txResultRecycler.cpp
new file mode 100644
index 0000000000..b52f91838a
--- /dev/null
+++ b/dom/xslt/xpath/txResultRecycler.cpp
@@ -0,0 +1,171 @@
+/* -*- 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 "txResultRecycler.h"
+#include "txExprResult.h"
+#include "txNodeSet.h"
+
+txResultRecycler::txResultRecycler()
+ : mEmptyStringResult(new StringResult(nullptr)),
+ mTrueResult(new BooleanResult(true)),
+ mFalseResult(new BooleanResult(false)) {}
+
+txResultRecycler::~txResultRecycler() {
+ txStackIterator stringIter(&mStringResults);
+ while (stringIter.hasNext()) {
+ delete static_cast<StringResult*>(stringIter.next());
+ }
+ txStackIterator nodesetIter(&mNodeSetResults);
+ while (nodesetIter.hasNext()) {
+ delete static_cast<txNodeSet*>(nodesetIter.next());
+ }
+ txStackIterator numberIter(&mNumberResults);
+ while (numberIter.hasNext()) {
+ delete static_cast<NumberResult*>(numberIter.next());
+ }
+}
+
+void txResultRecycler::recycle(txAExprResult* aResult) {
+ NS_ASSERTION(aResult->mRefCnt == 0, "In-use txAExprResult recycled");
+ RefPtr<txResultRecycler> kungFuDeathGrip;
+ aResult->mRecycler.swap(kungFuDeathGrip);
+
+ switch (aResult->getResultType()) {
+ case txAExprResult::STRING: {
+ mStringResults.push(static_cast<StringResult*>(aResult));
+ return;
+ }
+ case txAExprResult::NODESET: {
+ static_cast<txNodeSet*>(aResult)->clear();
+ mNodeSetResults.push(static_cast<txNodeSet*>(aResult));
+ return;
+ }
+ case txAExprResult::NUMBER: {
+ mNumberResults.push(static_cast<NumberResult*>(aResult));
+ return;
+ }
+ default: {
+ delete aResult;
+ }
+ }
+}
+
+nsresult txResultRecycler::getStringResult(StringResult** aResult) {
+ if (mStringResults.isEmpty()) {
+ *aResult = new StringResult(this);
+ } else {
+ *aResult = static_cast<StringResult*>(mStringResults.pop());
+ (*aResult)->mValue.Truncate();
+ (*aResult)->mRecycler = this;
+ }
+ NS_ADDREF(*aResult);
+
+ return NS_OK;
+}
+
+nsresult txResultRecycler::getStringResult(const nsAString& aValue,
+ txAExprResult** aResult) {
+ if (mStringResults.isEmpty()) {
+ *aResult = new StringResult(aValue, this);
+ } else {
+ StringResult* strRes = static_cast<StringResult*>(mStringResults.pop());
+ strRes->mValue = aValue;
+ strRes->mRecycler = this;
+ *aResult = strRes;
+ }
+ NS_ADDREF(*aResult);
+
+ return NS_OK;
+}
+
+void txResultRecycler::getEmptyStringResult(txAExprResult** aResult) {
+ *aResult = mEmptyStringResult;
+ NS_ADDREF(*aResult);
+}
+
+nsresult txResultRecycler::getNodeSet(txNodeSet** aResult) {
+ if (mNodeSetResults.isEmpty()) {
+ *aResult = new txNodeSet(this);
+ } else {
+ *aResult = static_cast<txNodeSet*>(mNodeSetResults.pop());
+ (*aResult)->mRecycler = this;
+ }
+ NS_ADDREF(*aResult);
+
+ return NS_OK;
+}
+
+nsresult txResultRecycler::getNodeSet(txNodeSet* aNodeSet,
+ txNodeSet** aResult) {
+ if (mNodeSetResults.isEmpty()) {
+ *aResult = new txNodeSet(*aNodeSet, this);
+ } else {
+ *aResult = static_cast<txNodeSet*>(mNodeSetResults.pop());
+ (*aResult)->append(*aNodeSet);
+ (*aResult)->mRecycler = this;
+ }
+ NS_ADDREF(*aResult);
+
+ return NS_OK;
+}
+
+nsresult txResultRecycler::getNodeSet(const txXPathNode& aNode,
+ txAExprResult** aResult) {
+ if (mNodeSetResults.isEmpty()) {
+ *aResult = new txNodeSet(aNode, this);
+ } else {
+ txNodeSet* nodes = static_cast<txNodeSet*>(mNodeSetResults.pop());
+ nodes->append(aNode);
+ nodes->mRecycler = this;
+ *aResult = nodes;
+ }
+ NS_ADDREF(*aResult);
+
+ return NS_OK;
+}
+
+nsresult txResultRecycler::getNumberResult(double aValue,
+ txAExprResult** aResult) {
+ if (mNumberResults.isEmpty()) {
+ *aResult = new NumberResult(aValue, this);
+ } else {
+ NumberResult* numRes = static_cast<NumberResult*>(mNumberResults.pop());
+ numRes->value = aValue;
+ numRes->mRecycler = this;
+ *aResult = numRes;
+ }
+ NS_ADDREF(*aResult);
+
+ return NS_OK;
+}
+
+void txResultRecycler::getBoolResult(bool aValue, txAExprResult** aResult) {
+ *aResult = aValue ? mTrueResult : mFalseResult;
+ NS_ADDREF(*aResult);
+}
+
+nsresult txResultRecycler::getNonSharedNodeSet(txNodeSet* aNodeSet,
+ txNodeSet** aResult) {
+ if (aNodeSet->mRefCnt > 1) {
+ return getNodeSet(aNodeSet, aResult);
+ }
+
+ *aResult = aNodeSet;
+ NS_ADDREF(*aResult);
+
+ return NS_OK;
+}
+
+void txAExprResult::Release() {
+ --mRefCnt;
+ NS_LOG_RELEASE(this, mRefCnt, "txAExprResult");
+ if (mRefCnt == 0) {
+ if (mRecycler) {
+ mRecycler->recycle(this);
+ } else {
+ delete this;
+ }
+ }
+}
diff --git a/dom/xslt/xpath/txResultRecycler.h b/dom/xslt/xpath/txResultRecycler.h
new file mode 100644
index 0000000000..f90687b70b
--- /dev/null
+++ b/dom/xslt/xpath/txResultRecycler.h
@@ -0,0 +1,76 @@
+/* -*- 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/. */
+
+#ifndef txResultRecycler_h__
+#define txResultRecycler_h__
+
+#include "nsCOMPtr.h"
+#include "txStack.h"
+
+class txAExprResult;
+class StringResult;
+class txNodeSet;
+class txXPathNode;
+class NumberResult;
+class BooleanResult;
+
+class txResultRecycler {
+ public:
+ txResultRecycler();
+ ~txResultRecycler();
+
+ void AddRef() {
+ ++mRefCnt;
+ NS_LOG_ADDREF(this, mRefCnt, "txResultRecycler", sizeof(*this));
+ }
+ void Release() {
+ --mRefCnt;
+ NS_LOG_RELEASE(this, mRefCnt, "txResultRecycler");
+ if (mRefCnt == 0) {
+ mRefCnt = 1; // stabilize
+ delete this;
+ }
+ }
+
+ /**
+ * Returns an txAExprResult to this recycler for reuse.
+ * @param aResult result to recycle
+ */
+ void recycle(txAExprResult* aResult);
+
+ /**
+ * Functions to return results that will be fully used by the caller.
+ * Returns nullptr on out-of-memory and an inited result otherwise.
+ */
+ nsresult getStringResult(StringResult** aResult);
+ nsresult getStringResult(const nsAString& aValue, txAExprResult** aResult);
+ nsresult getNodeSet(txNodeSet** aResult);
+ nsresult getNodeSet(txNodeSet* aNodeSet, txNodeSet** aResult);
+ nsresult getNodeSet(const txXPathNode& aNode, txAExprResult** aResult);
+ nsresult getNumberResult(double aValue, txAExprResult** aResult);
+
+ /**
+ * Functions to return a txAExprResult that is shared across several
+ * clients and must not be modified. Never returns nullptr.
+ */
+ void getEmptyStringResult(txAExprResult** aResult);
+ void getBoolResult(bool aValue, txAExprResult** aResult);
+
+ /**
+ * Functions that return non-shared resultsobjects
+ */
+ nsresult getNonSharedNodeSet(txNodeSet* aNodeSet, txNodeSet** aResult);
+
+ private:
+ nsAutoRefCnt mRefCnt;
+ txStack mStringResults;
+ txStack mNodeSetResults;
+ txStack mNumberResults;
+ RefPtr<StringResult> mEmptyStringResult;
+ RefPtr<BooleanResult> mTrueResult;
+ RefPtr<BooleanResult> mFalseResult;
+};
+
+#endif // txResultRecycler_h__
diff --git a/dom/xslt/xpath/txRootExpr.cpp b/dom/xslt/xpath/txRootExpr.cpp
new file mode 100644
index 0000000000..cabc0e1c13
--- /dev/null
+++ b/dom/xslt/xpath/txRootExpr.cpp
@@ -0,0 +1,35 @@
+/* -*- 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 "txExpr.h"
+#include "txNodeSet.h"
+#include "txIXPathContext.h"
+#include "txXPathTreeWalker.h"
+
+/**
+ * Evaluates this Expr based on the given context node and processor state
+ * @param context the context node for evaluation of this Expr
+ * @param ps the ContextState containing the stack information needed
+ * for evaluation
+ * @return the result of the evaluation
+ **/
+nsresult RootExpr::evaluate(txIEvalContext* aContext, txAExprResult** aResult) {
+ txXPathTreeWalker walker(aContext->getContextNode());
+ walker.moveToRoot();
+
+ return aContext->recycler()->getNodeSet(walker.getCurrentPosition(), aResult);
+}
+
+TX_IMPL_EXPR_STUBS_0(RootExpr, NODESET_RESULT)
+
+bool RootExpr::isSensitiveTo(ContextSensitivity aContext) {
+ return !!(aContext & NODE_CONTEXT);
+}
+
+#ifdef TX_TO_STRING
+void RootExpr::toString(nsAString& dest) {
+ if (mSerialize) dest.Append(char16_t('/'));
+}
+#endif
diff --git a/dom/xslt/xpath/txSingleNodeContext.h b/dom/xslt/xpath/txSingleNodeContext.h
new file mode 100644
index 0000000000..80b0680a2d
--- /dev/null
+++ b/dom/xslt/xpath/txSingleNodeContext.h
@@ -0,0 +1,64 @@
+/* -*- 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/. */
+
+#ifndef __TX_XPATH_SINGLENODE_CONTEXT
+#define __TX_XPATH_SINGLENODE_CONTEXT
+
+#include "mozilla/Attributes.h"
+#include "txIXPathContext.h"
+
+class txSingleNodeContext : public txIEvalContext {
+ public:
+ txSingleNodeContext(const txXPathNode& aContextNode,
+ txIMatchContext* aContext)
+ : mNode(aContextNode), mInner(aContext) {
+ NS_ASSERTION(aContext, "txIMatchContext must be given");
+ }
+
+ nsresult getVariable(int32_t aNamespace, nsAtom* aLName,
+ txAExprResult*& aResult) override {
+ NS_ASSERTION(mInner, "mInner is null!!!");
+ return mInner->getVariable(aNamespace, aLName, aResult);
+ }
+
+ nsresult isStripSpaceAllowed(const txXPathNode& aNode,
+ bool& aAllowed) override {
+ NS_ASSERTION(mInner, "mInner is null!!!");
+ return mInner->isStripSpaceAllowed(aNode, aAllowed);
+ }
+
+ void* getPrivateContext() override {
+ NS_ASSERTION(mInner, "mInner is null!!!");
+ return mInner->getPrivateContext();
+ }
+
+ txResultRecycler* recycler() override {
+ NS_ASSERTION(mInner, "mInner is null!!!");
+ return mInner->recycler();
+ }
+
+ void receiveError(const nsAString& aMsg, nsresult aRes) override {
+ NS_ASSERTION(mInner, "mInner is null!!!");
+#ifdef DEBUG
+ nsAutoString error(u"forwarded error: "_ns);
+ error.Append(aMsg);
+ mInner->receiveError(error, aRes);
+#else
+ mInner->receiveError(aMsg, aRes);
+#endif
+ }
+
+ const txXPathNode& getContextNode() override { return mNode; }
+
+ uint32_t size() override { return 1; }
+
+ uint32_t position() override { return 1; }
+
+ private:
+ const txXPathNode& mNode;
+ txIMatchContext* mInner;
+};
+
+#endif // __TX_XPATH_SINGLENODE_CONTEXT
diff --git a/dom/xslt/xpath/txStringResult.cpp b/dom/xslt/xpath/txStringResult.cpp
new file mode 100644
index 0000000000..740b6f1b34
--- /dev/null
+++ b/dom/xslt/xpath/txStringResult.cpp
@@ -0,0 +1,43 @@
+/* -*- 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/. */
+
+/**
+ * StringResult
+ * Represents a String as a Result of evaluating an Expr
+ **/
+#include "txExprResult.h"
+
+/**
+ * Default Constructor
+ **/
+StringResult::StringResult(txResultRecycler* aRecycler)
+ : txAExprResult(aRecycler) {}
+
+/**
+ * Creates a new StringResult with the value of the given String parameter
+ * @param str the String to use for initialization of this StringResult's value
+ **/
+StringResult::StringResult(const nsAString& aValue, txResultRecycler* aRecycler)
+ : txAExprResult(aRecycler), mValue(aValue) {}
+
+/*
+ * Virtual Methods from ExprResult
+ */
+
+short StringResult::getResultType() {
+ return txAExprResult::STRING;
+} //-- getResultType
+
+void StringResult::stringValue(nsString& aResult) { aResult.Append(mValue); }
+
+const nsString* StringResult::stringValuePointer() { return &mValue; }
+
+bool StringResult::booleanValue() {
+ return !mValue.IsEmpty();
+} //-- booleanValue
+
+double StringResult::numberValue() {
+ return txDouble::toDouble(mValue);
+} //-- numberValue
diff --git a/dom/xslt/xpath/txUnaryExpr.cpp b/dom/xslt/xpath/txUnaryExpr.cpp
new file mode 100644
index 0000000000..ec31a5fcb3
--- /dev/null
+++ b/dom/xslt/xpath/txUnaryExpr.cpp
@@ -0,0 +1,49 @@
+/* -*- 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 "txExpr.h"
+#include "txIXPathContext.h"
+
+/*
+ * Evaluates this Expr based on the given context node and processor state
+ * @param context the context node for evaluation of this Expr
+ * @param ps the ContextState containing the stack information needed
+ * for evaluation.
+ * @return the result of the evaluation.
+ */
+nsresult UnaryExpr::evaluate(txIEvalContext* aContext,
+ txAExprResult** aResult) {
+ *aResult = nullptr;
+
+ RefPtr<txAExprResult> exprRes;
+ nsresult rv = expr->evaluate(aContext, getter_AddRefs(exprRes));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ double value = exprRes->numberValue();
+#ifdef HPUX
+ /*
+ * Negation of a zero doesn't produce a negative
+ * zero on HPUX. Perform the operation by multiplying with
+ * -1.
+ */
+ return aContext->recycler()->getNumberResult(-1 * value, aResult);
+#else
+ return aContext->recycler()->getNumberResult(-value, aResult);
+#endif
+}
+
+TX_IMPL_EXPR_STUBS_1(UnaryExpr, NODESET_RESULT, expr)
+
+bool UnaryExpr::isSensitiveTo(ContextSensitivity aContext) {
+ return expr->isSensitiveTo(aContext);
+}
+
+#ifdef TX_TO_STRING
+void UnaryExpr::toString(nsAString& str) {
+ if (!expr) return;
+ str.Append(char16_t('-'));
+ expr->toString(str);
+}
+#endif
diff --git a/dom/xslt/xpath/txUnionExpr.cpp b/dom/xslt/xpath/txUnionExpr.cpp
new file mode 100644
index 0000000000..be8fa47781
--- /dev/null
+++ b/dom/xslt/xpath/txUnionExpr.cpp
@@ -0,0 +1,85 @@
+/* -*- 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 "txExpr.h"
+#include "txIXPathContext.h"
+#include "txNodeSet.h"
+
+#ifdef TX_TO_STRING
+# include "nsReadableUtils.h"
+#endif
+
+//-------------/
+//- UnionExpr -/
+//-------------/
+
+//-----------------------------/
+//- Virtual methods from Expr -/
+//-----------------------------/
+
+/**
+ * Evaluates this Expr based on the given context node and processor state
+ * @param context the context node for evaluation of this Expr
+ * @param ps the ContextState containing the stack information needed
+ * for evaluation
+ * @return the result of the evaluation
+ **/
+nsresult UnionExpr::evaluate(txIEvalContext* aContext,
+ txAExprResult** aResult) {
+ *aResult = nullptr;
+ RefPtr<txNodeSet> nodes;
+ nsresult rv = aContext->recycler()->getNodeSet(getter_AddRefs(nodes));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t i, len = mExpressions.Length();
+ for (i = 0; i < len; ++i) {
+ RefPtr<txAExprResult> exprResult;
+ rv = mExpressions[i]->evaluate(aContext, getter_AddRefs(exprResult));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (exprResult->getResultType() != txAExprResult::NODESET) {
+ // XXX ErrorReport: report nonnodeset error
+ return NS_ERROR_XSLT_NODESET_EXPECTED;
+ }
+
+ RefPtr<txNodeSet> resultSet, ownedSet;
+ resultSet =
+ static_cast<txNodeSet*>(static_cast<txAExprResult*>(exprResult));
+ exprResult = nullptr;
+ rv = aContext->recycler()->getNonSharedNodeSet(resultSet,
+ getter_AddRefs(ownedSet));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = nodes->addAndTransfer(ownedSet);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ *aResult = nodes;
+ NS_ADDREF(*aResult);
+
+ return NS_OK;
+} //-- evaluate
+
+Expr::ExprType UnionExpr::getType() { return UNION_EXPR; }
+
+TX_IMPL_EXPR_STUBS_LIST(UnionExpr, NODESET_RESULT, mExpressions)
+
+bool UnionExpr::isSensitiveTo(ContextSensitivity aContext) {
+ uint32_t i, len = mExpressions.Length();
+ for (i = 0; i < len; ++i) {
+ if (mExpressions[i]->isSensitiveTo(aContext)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+#ifdef TX_TO_STRING
+void UnionExpr::toString(nsAString& aDest) {
+ StringJoinAppend(aDest, u" | "_ns, mExpressions,
+ [](nsAString& dest, Expr* expr) { expr->toString(dest); });
+}
+#endif
diff --git a/dom/xslt/xpath/txUnionNodeTest.cpp b/dom/xslt/xpath/txUnionNodeTest.cpp
new file mode 100644
index 0000000000..4461b537e4
--- /dev/null
+++ b/dom/xslt/xpath/txUnionNodeTest.cpp
@@ -0,0 +1,59 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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 "mozilla/FloatingPoint.h"
+
+#include "txExpr.h"
+#include "txExprResult.h"
+#include "txSingleNodeContext.h"
+
+#ifdef TX_TO_STRING
+# include "nsReadableUtils.h"
+#endif
+
+nsresult txUnionNodeTest::matches(const txXPathNode& aNode,
+ txIMatchContext* aContext, bool& aMatched) {
+ uint32_t i, len = mNodeTests.Length();
+ for (i = 0; i < len; ++i) {
+ nsresult rv = mNodeTests[i]->matches(aNode, aContext, aMatched);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (aMatched) {
+ return NS_OK;
+ }
+ }
+
+ aMatched = false;
+ return NS_OK;
+}
+
+double txUnionNodeTest::getDefaultPriority() {
+ NS_ERROR("Don't call getDefaultPriority on txUnionPattern");
+ return mozilla::UnspecifiedNaN<double>();
+}
+
+bool txUnionNodeTest::isSensitiveTo(Expr::ContextSensitivity aContext) {
+ uint32_t i, len = mNodeTests.Length();
+ for (i = 0; i < len; ++i) {
+ if (mNodeTests[i]->isSensitiveTo(aContext)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+#ifdef TX_TO_STRING
+void txUnionNodeTest::toString(nsAString& aDest) {
+ aDest.Append('(');
+
+ StringJoinAppend(
+ aDest, u" | "_ns, mNodeTests,
+ [](nsAString& dest, txNodeTest* nodeTest) { nodeTest->toString(dest); });
+
+ aDest.Append(')');
+}
+#endif
diff --git a/dom/xslt/xpath/txVariableRefExpr.cpp b/dom/xslt/xpath/txVariableRefExpr.cpp
new file mode 100644
index 0000000000..4836913135
--- /dev/null
+++ b/dom/xslt/xpath/txVariableRefExpr.cpp
@@ -0,0 +1,62 @@
+/* -*- 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 "txExpr.h"
+#include "nsAtom.h"
+#include "txNodeSet.h"
+#include "nsGkAtoms.h"
+#include "txIXPathContext.h"
+
+//-------------------/
+//- VariableRefExpr -/
+//-------------------/
+
+/**
+ * Creates a VariableRefExpr with the given variable name
+ **/
+VariableRefExpr::VariableRefExpr(nsAtom* aPrefix, nsAtom* aLocalName,
+ int32_t aNSID)
+ : mPrefix(aPrefix), mLocalName(aLocalName), mNamespace(aNSID) {
+ NS_ASSERTION(mLocalName, "VariableRefExpr without local name?");
+ if (mPrefix == nsGkAtoms::_empty) mPrefix = nullptr;
+}
+
+/**
+ * Evaluates this Expr based on the given context node and processor state
+ * @param context the context node for evaluation of this Expr
+ * @param ps the ContextState containing the stack information needed
+ * for evaluation
+ * @return the result of the evaluation
+ **/
+nsresult VariableRefExpr::evaluate(txIEvalContext* aContext,
+ txAExprResult** aResult) {
+ nsresult rv = aContext->getVariable(mNamespace, mLocalName, *aResult);
+ if (NS_FAILED(rv)) {
+ // XXX report error, undefined variable
+ return rv;
+ }
+ return NS_OK;
+}
+
+TX_IMPL_EXPR_STUBS_0(VariableRefExpr, ANY_RESULT)
+
+bool VariableRefExpr::isSensitiveTo(ContextSensitivity aContext) {
+ return !!(aContext & VARIABLES_CONTEXT);
+}
+
+#ifdef TX_TO_STRING
+void VariableRefExpr::toString(nsAString& aDest) {
+ aDest.Append(char16_t('$'));
+ if (mPrefix) {
+ nsAutoString prefix;
+ mPrefix->ToString(prefix);
+ aDest.Append(prefix);
+ aDest.Append(char16_t(':'));
+ }
+ nsAutoString lname;
+ mLocalName->ToString(lname);
+ aDest.Append(lname);
+}
+#endif
diff --git a/dom/xslt/xpath/txXPathNode.h b/dom/xslt/xpath/txXPathNode.h
new file mode 100644
index 0000000000..8c5d6f5308
--- /dev/null
+++ b/dom/xslt/xpath/txXPathNode.h
@@ -0,0 +1,91 @@
+/* -*- 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/. */
+
+#ifndef txXPathNode_h__
+#define txXPathNode_h__
+
+#include "nsIContent.h"
+#include "mozilla/dom/Document.h"
+#include "nsINode.h"
+#include "nsNameSpaceManager.h"
+
+using txXPathNodeType = nsINode;
+
+class txXPathNode {
+ public:
+ bool operator==(const txXPathNode& aNode) const;
+ bool operator!=(const txXPathNode& aNode) const { return !(*this == aNode); }
+ ~txXPathNode();
+
+ private:
+ friend class txNodeSet;
+ friend class txXPathNativeNode;
+ friend class txXPathNodeUtils;
+ friend class txXPathTreeWalker;
+
+ txXPathNode(const txXPathNode& aNode);
+
+ explicit txXPathNode(mozilla::dom::Document* aDocument)
+ : mNode(aDocument), mRefCountRoot(0), mIndex(eDocument) {
+ MOZ_COUNT_CTOR(txXPathNode);
+ }
+ txXPathNode(nsINode* aNode, uint32_t aIndex, nsINode* aRoot)
+ : mNode(aNode), mRefCountRoot(aRoot ? 1 : 0), mIndex(aIndex) {
+ MOZ_COUNT_CTOR(txXPathNode);
+ if (aRoot) {
+ NS_ADDREF(aRoot);
+ }
+ }
+
+ static nsINode* RootOf(nsINode* aNode) { return aNode->SubtreeRoot(); }
+ nsINode* Root() const { return RootOf(mNode); }
+ nsINode* GetRootToAddRef() const { return mRefCountRoot ? Root() : nullptr; }
+
+ bool isDocument() const { return mIndex == eDocument; }
+ bool isContent() const { return mIndex == eContent; }
+ bool isAttribute() const { return mIndex != eDocument && mIndex != eContent; }
+
+ nsIContent* Content() const {
+ NS_ASSERTION(isContent() || isAttribute(), "wrong type");
+ return static_cast<nsIContent*>(mNode);
+ }
+ mozilla::dom::Document* Document() const {
+ NS_ASSERTION(isDocument(), "wrong type");
+ return static_cast<mozilla::dom::Document*>(mNode);
+ }
+
+ enum PositionType { eDocument = (1 << 30), eContent = eDocument - 1 };
+
+ nsINode* mNode;
+ uint32_t mRefCountRoot : 1;
+ uint32_t mIndex : 31;
+};
+
+class txNamespaceManager {
+ public:
+ static int32_t getNamespaceID(const nsAString& aNamespaceURI);
+ static nsresult getNamespaceURI(const int32_t aID, nsAString& aResult);
+};
+
+/* static */
+inline int32_t txNamespaceManager::getNamespaceID(
+ const nsAString& aNamespaceURI) {
+ int32_t namespaceID = kNameSpaceID_Unknown;
+ nsNameSpaceManager::GetInstance()->RegisterNameSpace(aNamespaceURI,
+ namespaceID);
+ return namespaceID;
+}
+
+/* static */
+inline nsresult txNamespaceManager::getNamespaceURI(const int32_t aID,
+ nsAString& aResult) {
+ return nsNameSpaceManager::GetInstance()->GetNameSpaceURI(aID, aResult);
+}
+
+inline bool txXPathNode::operator==(const txXPathNode& aNode) const {
+ return mIndex == aNode.mIndex && mNode == aNode.mNode;
+}
+
+#endif /* txXPathNode_h__ */
diff --git a/dom/xslt/xpath/txXPathOptimizer.cpp b/dom/xslt/xpath/txXPathOptimizer.cpp
new file mode 100644
index 0000000000..40a94f674c
--- /dev/null
+++ b/dom/xslt/xpath/txXPathOptimizer.cpp
@@ -0,0 +1,246 @@
+/* -*- 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 "mozilla/Assertions.h"
+#include "txXPathOptimizer.h"
+#include "txExprResult.h"
+#include "nsAtom.h"
+#include "nsGkAtoms.h"
+#include "txXPathNode.h"
+#include "txExpr.h"
+#include "txIXPathContext.h"
+
+using mozilla::UniquePtr;
+using mozilla::Unused;
+
+class txEarlyEvalContext : public txIEvalContext {
+ public:
+ explicit txEarlyEvalContext(txResultRecycler* aRecycler)
+ : mRecycler(aRecycler) {}
+
+ // txIEvalContext
+ nsresult getVariable(int32_t aNamespace, nsAtom* aLName,
+ txAExprResult*& aResult) override {
+ MOZ_CRASH("shouldn't depend on this context");
+ }
+ nsresult isStripSpaceAllowed(const txXPathNode& aNode,
+ bool& aAllowed) override {
+ MOZ_CRASH("shouldn't depend on this context");
+ }
+ void* getPrivateContext() override {
+ MOZ_CRASH("shouldn't depend on this context");
+ }
+ txResultRecycler* recycler() override { return mRecycler; }
+ void receiveError(const nsAString& aMsg, nsresult aRes) override {}
+ const txXPathNode& getContextNode() override {
+ MOZ_CRASH("shouldn't depend on this context");
+ }
+ uint32_t size() override { MOZ_CRASH("shouldn't depend on this context"); }
+ uint32_t position() override {
+ MOZ_CRASH("shouldn't depend on this context");
+ }
+
+ private:
+ txResultRecycler* mRecycler;
+};
+
+void txXPathOptimizer::optimize(Expr* aInExpr, Expr** aOutExpr) {
+ *aOutExpr = nullptr;
+
+ // First check if the expression will produce the same result
+ // under any context.
+ Expr::ExprType exprType = aInExpr->getType();
+ if (exprType != Expr::LITERAL_EXPR &&
+ !aInExpr->isSensitiveTo(Expr::ANY_CONTEXT)) {
+ RefPtr<txResultRecycler> recycler = new txResultRecycler;
+ txEarlyEvalContext context(recycler);
+ RefPtr<txAExprResult> exprRes;
+
+ // Don't throw if this fails since it could be that the expression
+ // is or contains an error-expression.
+ nsresult rv = aInExpr->evaluate(&context, getter_AddRefs(exprRes));
+ if (NS_SUCCEEDED(rv)) {
+ *aOutExpr = new txLiteralExpr(exprRes);
+ }
+
+ return;
+ }
+
+ // Then optimize sub expressions
+ uint32_t i = 0;
+ Expr* subExpr;
+ while ((subExpr = aInExpr->getSubExprAt(i))) {
+ Expr* newExpr = nullptr;
+ optimize(subExpr, &newExpr);
+ if (newExpr) {
+ delete subExpr;
+ aInExpr->setSubExprAt(i, newExpr);
+ }
+
+ ++i;
+ }
+
+ // Finally see if current expression can be optimized
+ switch (exprType) {
+ case Expr::LOCATIONSTEP_EXPR:
+ optimizeStep(aInExpr, aOutExpr);
+ return;
+
+ case Expr::PATH_EXPR:
+ optimizePath(aInExpr, aOutExpr);
+ return;
+
+ case Expr::UNION_EXPR:
+ optimizeUnion(aInExpr, aOutExpr);
+ return;
+
+ default:
+ return;
+ }
+}
+
+void txXPathOptimizer::optimizeStep(Expr* aInExpr, Expr** aOutExpr) {
+ LocationStep* step = static_cast<LocationStep*>(aInExpr);
+
+ if (step->getAxisIdentifier() == LocationStep::ATTRIBUTE_AXIS) {
+ // Test for @foo type steps.
+ txNameTest* nameTest = nullptr;
+ if (!step->getSubExprAt(0) &&
+ step->getNodeTest()->getType() == txNameTest::NAME_TEST &&
+ (nameTest = static_cast<txNameTest*>(step->getNodeTest()))
+ ->mLocalName != nsGkAtoms::_asterisk) {
+ *aOutExpr = new txNamedAttributeStep(
+ nameTest->mNamespace, nameTest->mPrefix, nameTest->mLocalName);
+ return; // return since we no longer have a step-object.
+ }
+ }
+
+ // Test for predicates that can be combined into the nodetest
+ Expr* pred;
+ while ((pred = step->getSubExprAt(0)) &&
+ !pred->canReturnType(Expr::NUMBER_RESULT) &&
+ !pred->isSensitiveTo(Expr::NODESET_CONTEXT)) {
+ txNodeTest* predTest = new txPredicatedNodeTest(step->getNodeTest(), pred);
+ step->dropFirst();
+ step->setNodeTest(predTest);
+ }
+}
+
+void txXPathOptimizer::optimizePath(Expr* aInExpr, Expr** aOutExpr) {
+ PathExpr* path = static_cast<PathExpr*>(aInExpr);
+
+ uint32_t i;
+ Expr* subExpr;
+ // look for steps like "//foo" that can be turned into "/descendant::foo"
+ // and "//." that can be turned into "/descendant-or-self::node()"
+ for (i = 0; (subExpr = path->getSubExprAt(i)); ++i) {
+ if (path->getPathOpAt(i) == PathExpr::DESCENDANT_OP &&
+ subExpr->getType() == Expr::LOCATIONSTEP_EXPR &&
+ !subExpr->getSubExprAt(0)) {
+ LocationStep* step = static_cast<LocationStep*>(subExpr);
+ if (step->getAxisIdentifier() == LocationStep::CHILD_AXIS) {
+ step->setAxisIdentifier(LocationStep::DESCENDANT_AXIS);
+ path->setPathOpAt(i, PathExpr::RELATIVE_OP);
+ } else if (step->getAxisIdentifier() == LocationStep::SELF_AXIS) {
+ step->setAxisIdentifier(LocationStep::DESCENDANT_OR_SELF_AXIS);
+ path->setPathOpAt(i, PathExpr::RELATIVE_OP);
+ }
+ }
+ }
+
+ // look for expressions that start with a "./"
+ subExpr = path->getSubExprAt(0);
+ LocationStep* step;
+ if (subExpr->getType() == Expr::LOCATIONSTEP_EXPR && path->getSubExprAt(1) &&
+ path->getPathOpAt(1) != PathExpr::DESCENDANT_OP) {
+ step = static_cast<LocationStep*>(subExpr);
+ if (step->getAxisIdentifier() == LocationStep::SELF_AXIS &&
+ !step->getSubExprAt(0)) {
+ txNodeTest* test = step->getNodeTest();
+ if (test->getType() == txNodeTest::NODETYPE_TEST &&
+ (static_cast<txNodeTypeTest*>(test))->getNodeTestType() ==
+ txNodeTypeTest::NODE_TYPE) {
+ // We have a '.' as first step followed by a single '/'.
+
+ // Check if there are only two steps. If so, return the second
+ // as resulting expression.
+ if (!path->getSubExprAt(2)) {
+ *aOutExpr = path->getSubExprAt(1);
+ path->setSubExprAt(1, nullptr);
+
+ return;
+ }
+
+ // Just delete the '.' step and leave the rest of the PathExpr
+ path->deleteExprAt(0);
+ }
+ }
+ }
+}
+
+void txXPathOptimizer::optimizeUnion(Expr* aInExpr, Expr** aOutExpr) {
+ UnionExpr* uni = static_cast<UnionExpr*>(aInExpr);
+
+ // Check for expressions like "foo | bar" and
+ // "descendant::foo | descendant::bar"
+
+ uint32_t current;
+ Expr* subExpr;
+ for (current = 0; (subExpr = uni->getSubExprAt(current)); ++current) {
+ if (subExpr->getType() != Expr::LOCATIONSTEP_EXPR ||
+ subExpr->getSubExprAt(0)) {
+ continue;
+ }
+
+ LocationStep* currentStep = static_cast<LocationStep*>(subExpr);
+ LocationStep::LocationStepType axis = currentStep->getAxisIdentifier();
+
+ txUnionNodeTest* unionTest = nullptr;
+
+ // Check if there are any other steps with the same axis and merge
+ // them with currentStep
+ uint32_t i;
+ for (i = current + 1; (subExpr = uni->getSubExprAt(i)); ++i) {
+ if (subExpr->getType() != Expr::LOCATIONSTEP_EXPR ||
+ subExpr->getSubExprAt(0)) {
+ continue;
+ }
+
+ LocationStep* step = static_cast<LocationStep*>(subExpr);
+ if (step->getAxisIdentifier() != axis) {
+ continue;
+ }
+
+ // Create a txUnionNodeTest if needed
+ if (!unionTest) {
+ UniquePtr<txNodeTest> owner(unionTest = new txUnionNodeTest);
+ unionTest->addNodeTest(currentStep->getNodeTest());
+
+ currentStep->setNodeTest(unionTest);
+ Unused << owner.release();
+ }
+
+ // Merge the nodetest into the union
+ unionTest->addNodeTest(step->getNodeTest());
+
+ step->setNodeTest(nullptr);
+
+ // Remove the step from the UnionExpr
+ uni->deleteExprAt(i);
+ --i;
+ }
+
+ // Check if all expressions were merged into a single step. If so,
+ // return the step as the new expression.
+ if (unionTest && current == 0 && !uni->getSubExprAt(1)) {
+ // Make sure the step doesn't get deleted when the UnionExpr is
+ uni->setSubExprAt(0, nullptr);
+ *aOutExpr = currentStep;
+
+ // Return right away since we no longer have a union
+ return;
+ }
+ }
+}
diff --git a/dom/xslt/xpath/txXPathOptimizer.h b/dom/xslt/xpath/txXPathOptimizer.h
new file mode 100644
index 0000000000..84f00191e7
--- /dev/null
+++ b/dom/xslt/xpath/txXPathOptimizer.h
@@ -0,0 +1,30 @@
+/* -*- 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/. */
+
+#ifndef txXPathOptimizer_h__
+#define txXPathOptimizer_h__
+
+#include "txCore.h"
+
+class Expr;
+
+class txXPathOptimizer {
+ public:
+ /**
+ * Optimize the given expression.
+ * @param aInExpr Expression to optimize.
+ * @param aOutExpr Resulting expression, null if optimization didn't
+ * result in a new expression.
+ */
+ void optimize(Expr* aInExpr, Expr** aOutExpr);
+
+ private:
+ // Helper methods for optimizing specific classes
+ void optimizeStep(Expr* aInExpr, Expr** aOutExpr);
+ void optimizePath(Expr* aInExpr, Expr** aOutExpr);
+ void optimizeUnion(Expr* aInExpr, Expr** aOutExpr);
+};
+
+#endif
diff --git a/dom/xslt/xpath/txXPathTreeWalker.h b/dom/xslt/xpath/txXPathTreeWalker.h
new file mode 100644
index 0000000000..6b74b1e18d
--- /dev/null
+++ b/dom/xslt/xpath/txXPathTreeWalker.h
@@ -0,0 +1,201 @@
+/* -*- 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/. */
+
+#ifndef txXPathTreeWalker_h__
+#define txXPathTreeWalker_h__
+
+#include "txCore.h"
+#include "txXPathNode.h"
+#include "nsIContentInlines.h"
+#include "nsTArray.h"
+
+class nsAtom;
+
+class txXPathTreeWalker {
+ public:
+ txXPathTreeWalker(const txXPathTreeWalker& aOther);
+ explicit txXPathTreeWalker(const txXPathNode& aNode);
+
+ bool getAttr(nsAtom* aLocalName, int32_t aNSID, nsAString& aValue) const;
+ int32_t getNamespaceID() const;
+ uint16_t getNodeType() const;
+ void appendNodeValue(nsAString& aResult) const;
+ void getNodeName(nsAString& aName) const;
+
+ void moveTo(const txXPathTreeWalker& aWalker);
+
+ void moveToRoot();
+ bool moveToParent();
+ bool moveToElementById(const nsAString& aID);
+ bool moveToFirstAttribute();
+ bool moveToNextAttribute();
+ bool moveToNamedAttribute(nsAtom* aLocalName, int32_t aNSID);
+ bool moveToFirstChild();
+ bool moveToLastChild();
+ bool moveToNextSibling();
+ bool moveToPreviousSibling();
+
+ bool isOnNode(const txXPathNode& aNode) const;
+
+ const txXPathNode& getCurrentPosition() const;
+
+ private:
+ txXPathNode mPosition;
+
+ bool moveToValidAttribute(uint32_t aStartIndex);
+};
+
+class txXPathNodeUtils {
+ public:
+ static bool getAttr(const txXPathNode& aNode, nsAtom* aLocalName,
+ int32_t aNSID, nsAString& aValue);
+ static already_AddRefed<nsAtom> getLocalName(const txXPathNode& aNode);
+ static nsAtom* getPrefix(const txXPathNode& aNode);
+ static void getLocalName(const txXPathNode& aNode, nsAString& aLocalName);
+ static void getNodeName(const txXPathNode& aNode, nsAString& aName);
+ static int32_t getNamespaceID(const txXPathNode& aNode);
+ static void getNamespaceURI(const txXPathNode& aNode, nsAString& aURI);
+ static uint16_t getNodeType(const txXPathNode& aNode);
+ static void appendNodeValue(const txXPathNode& aNode, nsAString& aResult);
+ static bool isWhitespace(const txXPathNode& aNode);
+ static txXPathNode* getOwnerDocument(const txXPathNode& aNode);
+ static int32_t getUniqueIdentifier(const txXPathNode& aNode);
+ static nsresult getXSLTId(const txXPathNode& aNode, const txXPathNode& aBase,
+ nsAString& aResult);
+ static void release(txXPathNode* aNode);
+ static nsresult getBaseURI(const txXPathNode& aNode, nsAString& aURI);
+ static int comparePosition(const txXPathNode& aNode,
+ const txXPathNode& aOtherNode);
+ static bool localNameEquals(const txXPathNode& aNode, nsAtom* aLocalName);
+ static bool isRoot(const txXPathNode& aNode);
+ static bool isElement(const txXPathNode& aNode);
+ static bool isAttribute(const txXPathNode& aNode);
+ static bool isProcessingInstruction(const txXPathNode& aNode);
+ static bool isComment(const txXPathNode& aNode);
+ static bool isText(const txXPathNode& aNode);
+ static inline bool isHTMLElementInHTMLDocument(const txXPathNode& aNode) {
+ if (!aNode.isContent()) {
+ return false;
+ }
+ nsIContent* content = aNode.Content();
+ return content->IsHTMLElement() && content->IsInHTMLDocument();
+ }
+};
+
+class txXPathNativeNode {
+ public:
+ static txXPathNode* createXPathNode(nsINode* aNode,
+ bool aKeepRootAlive = false);
+ static txXPathNode* createXPathNode(nsIContent* aContent,
+ bool aKeepRootAlive = false);
+ static txXPathNode* createXPathNode(mozilla::dom::Document* aDocument);
+ static nsINode* getNode(const txXPathNode& aNode);
+ static nsIContent* getContent(const txXPathNode& aNode);
+ static mozilla::dom::Document* getDocument(const txXPathNode& aNode);
+ static void addRef(const txXPathNode& aNode) { NS_ADDREF(aNode.mNode); }
+ static void release(const txXPathNode& aNode) {
+ nsINode* node = aNode.mNode;
+ NS_RELEASE(node);
+ }
+};
+
+inline const txXPathNode& txXPathTreeWalker::getCurrentPosition() const {
+ return mPosition;
+}
+
+inline bool txXPathTreeWalker::getAttr(nsAtom* aLocalName, int32_t aNSID,
+ nsAString& aValue) const {
+ return txXPathNodeUtils::getAttr(mPosition, aLocalName, aNSID, aValue);
+}
+
+inline int32_t txXPathTreeWalker::getNamespaceID() const {
+ return txXPathNodeUtils::getNamespaceID(mPosition);
+}
+
+inline void txXPathTreeWalker::appendNodeValue(nsAString& aResult) const {
+ txXPathNodeUtils::appendNodeValue(mPosition, aResult);
+}
+
+inline void txXPathTreeWalker::getNodeName(nsAString& aName) const {
+ txXPathNodeUtils::getNodeName(mPosition, aName);
+}
+
+inline void txXPathTreeWalker::moveTo(const txXPathTreeWalker& aWalker) {
+ nsINode* root = nullptr;
+ if (mPosition.mRefCountRoot) {
+ root = mPosition.Root();
+ }
+ mPosition.mIndex = aWalker.mPosition.mIndex;
+ mPosition.mRefCountRoot = aWalker.mPosition.mRefCountRoot;
+ mPosition.mNode = aWalker.mPosition.mNode;
+ nsINode* newRoot = nullptr;
+ if (mPosition.mRefCountRoot) {
+ newRoot = mPosition.Root();
+ }
+ if (root != newRoot) {
+ NS_IF_ADDREF(newRoot);
+ NS_IF_RELEASE(root);
+ }
+}
+
+inline bool txXPathTreeWalker::isOnNode(const txXPathNode& aNode) const {
+ return (mPosition == aNode);
+}
+
+/* static */
+inline int32_t txXPathNodeUtils::getUniqueIdentifier(const txXPathNode& aNode) {
+ MOZ_ASSERT(!aNode.isAttribute(), "Not implemented for attributes.");
+ return NS_PTR_TO_INT32(aNode.mNode);
+}
+
+/* static */
+inline void txXPathNodeUtils::release(txXPathNode* aNode) {
+ NS_RELEASE(aNode->mNode);
+}
+
+/* static */
+inline bool txXPathNodeUtils::localNameEquals(const txXPathNode& aNode,
+ nsAtom* aLocalName) {
+ if (aNode.isContent() && aNode.Content()->IsElement()) {
+ return aNode.Content()->NodeInfo()->Equals(aLocalName);
+ }
+
+ RefPtr<nsAtom> localName = txXPathNodeUtils::getLocalName(aNode);
+
+ return localName == aLocalName;
+}
+
+/* static */
+inline bool txXPathNodeUtils::isRoot(const txXPathNode& aNode) {
+ return !aNode.isAttribute() && !aNode.mNode->GetParentNode();
+}
+
+/* static */
+inline bool txXPathNodeUtils::isElement(const txXPathNode& aNode) {
+ return aNode.isContent() && aNode.Content()->IsElement();
+}
+
+/* static */
+inline bool txXPathNodeUtils::isAttribute(const txXPathNode& aNode) {
+ return aNode.isAttribute();
+}
+
+/* static */
+inline bool txXPathNodeUtils::isProcessingInstruction(
+ const txXPathNode& aNode) {
+ return aNode.isContent() && aNode.Content()->IsProcessingInstruction();
+}
+
+/* static */
+inline bool txXPathNodeUtils::isComment(const txXPathNode& aNode) {
+ return aNode.isContent() && aNode.Content()->IsComment();
+}
+
+/* static */
+inline bool txXPathNodeUtils::isText(const txXPathNode& aNode) {
+ return aNode.isContent() && aNode.Content()->IsText();
+}
+
+#endif /* txXPathTreeWalker_h__ */
diff --git a/dom/xslt/xslt/moz.build b/dom/xslt/xslt/moz.build
new file mode 100644
index 0000000000..ab0e6e4aa3
--- /dev/null
+++ b/dom/xslt/xslt/moz.build
@@ -0,0 +1,66 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+EXPORTS.mozilla.dom += [
+ "txMozillaXSLTProcessor.h",
+ "txXSLTMsgsURL.h",
+]
+
+XPIDL_MODULE = "dom_xslt"
+
+XPIDL_SOURCES += [
+ "txIEXSLTFunctions.idl",
+]
+
+UNIFIED_SOURCES += [
+ "txBufferingHandler.cpp",
+ "txCurrentFunctionCall.cpp",
+ "txDocumentFunctionCall.cpp",
+ "txExecutionState.cpp",
+ "txEXSLTFunctions.cpp",
+ "txFormatNumberFunctionCall.cpp",
+ "txGenerateIdFunctionCall.cpp",
+ "txInstructions.cpp",
+ "txKeyFunctionCall.cpp",
+ "txMozillaStylesheetCompiler.cpp",
+ "txMozillaTextOutput.cpp",
+ "txMozillaXMLOutput.cpp",
+ "txMozillaXSLTProcessor.cpp",
+ "txNodeSorter.cpp",
+ "txOutputFormat.cpp",
+ "txPatternOptimizer.cpp",
+ "txPatternParser.cpp",
+ "txRtfHandler.cpp",
+ "txStylesheet.cpp",
+ "txStylesheetCompileHandlers.cpp",
+ "txStylesheetCompiler.cpp",
+ "txTextHandler.cpp",
+ "txToplevelItems.cpp",
+ "txUnknownHandler.cpp",
+ "txXPathResultComparator.cpp",
+ "txXSLTEnvironmentFunctionCall.cpp",
+ "txXSLTNumber.cpp",
+ "txXSLTNumberCounters.cpp",
+ "txXSLTPatterns.cpp",
+ "txXSLTProcessor.cpp",
+]
+
+EXTRA_JS_MODULES += [
+ "txEXSLTRegExFunctions.sys.mjs",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+LOCAL_INCLUDES += [
+ "../base",
+ "../xml",
+ "../xpath",
+ "/dom/base",
+ "/js/xpconnect/src",
+ "/parser/htmlparser",
+]
+
+FINAL_LIBRARY = "xul"
diff --git a/dom/xslt/xslt/txBufferingHandler.cpp b/dom/xslt/xslt/txBufferingHandler.cpp
new file mode 100644
index 0000000000..a0c7a39f73
--- /dev/null
+++ b/dom/xslt/xslt/txBufferingHandler.cpp
@@ -0,0 +1,385 @@
+/* -*- 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 "txBufferingHandler.h"
+
+using mozilla::MakeUnique;
+
+class txOutputTransaction {
+ public:
+ enum txTransactionType {
+ eAttributeTransaction,
+ eAttributeAtomTransaction,
+ eCharacterTransaction,
+ eCharacterNoOETransaction,
+ eCommentTransaction,
+ eEndDocumentTransaction,
+ eEndElementTransaction,
+ ePITransaction,
+ eStartDocumentTransaction,
+ eStartElementAtomTransaction,
+ eStartElementTransaction
+ };
+ explicit txOutputTransaction(txTransactionType aType) : mType(aType) {
+ MOZ_COUNT_CTOR(txOutputTransaction);
+ }
+ MOZ_COUNTED_DTOR_VIRTUAL(txOutputTransaction)
+ txTransactionType mType;
+};
+
+class txCharacterTransaction : public txOutputTransaction {
+ public:
+ txCharacterTransaction(txTransactionType aType, uint32_t aLength)
+ : txOutputTransaction(aType), mLength(aLength) {
+ MOZ_COUNT_CTOR_INHERITED(txCharacterTransaction, txOutputTransaction);
+ }
+ virtual ~txCharacterTransaction() {
+ MOZ_COUNT_DTOR_INHERITED(txCharacterTransaction, txOutputTransaction);
+ }
+ uint32_t mLength;
+};
+
+class txCommentTransaction : public txOutputTransaction {
+ public:
+ explicit txCommentTransaction(const nsAString& aValue)
+ : txOutputTransaction(eCommentTransaction), mValue(aValue) {
+ MOZ_COUNT_CTOR_INHERITED(txCommentTransaction, txOutputTransaction);
+ }
+ virtual ~txCommentTransaction() {
+ MOZ_COUNT_DTOR_INHERITED(txCommentTransaction, txOutputTransaction);
+ }
+ nsString mValue;
+};
+
+class txPITransaction : public txOutputTransaction {
+ public:
+ txPITransaction(const nsAString& aTarget, const nsAString& aData)
+ : txOutputTransaction(ePITransaction), mTarget(aTarget), mData(aData) {
+ MOZ_COUNT_CTOR_INHERITED(txPITransaction, txOutputTransaction);
+ }
+ virtual ~txPITransaction() {
+ MOZ_COUNT_DTOR_INHERITED(txPITransaction, txOutputTransaction);
+ }
+ nsString mTarget;
+ nsString mData;
+};
+
+class txStartElementAtomTransaction : public txOutputTransaction {
+ public:
+ txStartElementAtomTransaction(nsAtom* aPrefix, nsAtom* aLocalName,
+ nsAtom* aLowercaseLocalName, int32_t aNsID)
+ : txOutputTransaction(eStartElementAtomTransaction),
+ mPrefix(aPrefix),
+ mLocalName(aLocalName),
+ mLowercaseLocalName(aLowercaseLocalName),
+ mNsID(aNsID) {
+ MOZ_COUNT_CTOR_INHERITED(txStartElementAtomTransaction,
+ txOutputTransaction);
+ }
+ virtual ~txStartElementAtomTransaction() {
+ MOZ_COUNT_DTOR_INHERITED(txStartElementAtomTransaction,
+ txOutputTransaction);
+ }
+ RefPtr<nsAtom> mPrefix;
+ RefPtr<nsAtom> mLocalName;
+ RefPtr<nsAtom> mLowercaseLocalName;
+ int32_t mNsID;
+};
+
+class txStartElementTransaction : public txOutputTransaction {
+ public:
+ txStartElementTransaction(nsAtom* aPrefix, const nsAString& aLocalName,
+ int32_t aNsID)
+ : txOutputTransaction(eStartElementTransaction),
+ mPrefix(aPrefix),
+ mLocalName(aLocalName),
+ mNsID(aNsID) {
+ MOZ_COUNT_CTOR_INHERITED(txStartElementTransaction, txOutputTransaction);
+ }
+ virtual ~txStartElementTransaction() {
+ MOZ_COUNT_DTOR_INHERITED(txStartElementTransaction, txOutputTransaction);
+ }
+ RefPtr<nsAtom> mPrefix;
+ nsString mLocalName;
+ int32_t mNsID;
+};
+
+class txAttributeTransaction : public txOutputTransaction {
+ public:
+ txAttributeTransaction(nsAtom* aPrefix, const nsAString& aLocalName,
+ int32_t aNsID, const nsString& aValue)
+ : txOutputTransaction(eAttributeTransaction),
+ mPrefix(aPrefix),
+ mLocalName(aLocalName),
+ mNsID(aNsID),
+ mValue(aValue) {
+ MOZ_COUNT_CTOR_INHERITED(txAttributeTransaction, txOutputTransaction);
+ }
+ virtual ~txAttributeTransaction() {
+ MOZ_COUNT_DTOR_INHERITED(txAttributeTransaction, txOutputTransaction);
+ }
+ RefPtr<nsAtom> mPrefix;
+ nsString mLocalName;
+ int32_t mNsID;
+ nsString mValue;
+};
+
+class txAttributeAtomTransaction : public txOutputTransaction {
+ public:
+ txAttributeAtomTransaction(nsAtom* aPrefix, nsAtom* aLocalName,
+ nsAtom* aLowercaseLocalName, int32_t aNsID,
+ const nsString& aValue)
+ : txOutputTransaction(eAttributeAtomTransaction),
+ mPrefix(aPrefix),
+ mLocalName(aLocalName),
+ mLowercaseLocalName(aLowercaseLocalName),
+ mNsID(aNsID),
+ mValue(aValue) {
+ MOZ_COUNT_CTOR_INHERITED(txAttributeAtomTransaction, txOutputTransaction);
+ }
+ virtual ~txAttributeAtomTransaction() {
+ MOZ_COUNT_DTOR_INHERITED(txAttributeAtomTransaction, txOutputTransaction);
+ }
+ RefPtr<nsAtom> mPrefix;
+ RefPtr<nsAtom> mLocalName;
+ RefPtr<nsAtom> mLowercaseLocalName;
+ int32_t mNsID;
+ nsString mValue;
+};
+
+txBufferingHandler::txBufferingHandler() : mCanAddAttribute(false) {
+ MOZ_COUNT_CTOR(txBufferingHandler);
+ mBuffer = MakeUnique<txResultBuffer>();
+}
+
+txBufferingHandler::~txBufferingHandler() {
+ MOZ_COUNT_DTOR(txBufferingHandler);
+}
+
+nsresult txBufferingHandler::attribute(nsAtom* aPrefix, nsAtom* aLocalName,
+ nsAtom* aLowercaseLocalName,
+ int32_t aNsID, const nsString& aValue) {
+ NS_ENSURE_TRUE(mBuffer, NS_ERROR_OUT_OF_MEMORY);
+
+ if (!mCanAddAttribute) {
+ // XXX ErrorReport: Can't add attributes without element
+ return NS_OK;
+ }
+
+ txOutputTransaction* transaction = new txAttributeAtomTransaction(
+ aPrefix, aLocalName, aLowercaseLocalName, aNsID, aValue);
+ return mBuffer->addTransaction(transaction);
+}
+
+nsresult txBufferingHandler::attribute(nsAtom* aPrefix,
+ const nsAString& aLocalName,
+ const int32_t aNsID,
+ const nsString& aValue) {
+ NS_ENSURE_TRUE(mBuffer, NS_ERROR_OUT_OF_MEMORY);
+
+ if (!mCanAddAttribute) {
+ // XXX ErrorReport: Can't add attributes without element
+ return NS_OK;
+ }
+
+ txOutputTransaction* transaction =
+ new txAttributeTransaction(aPrefix, aLocalName, aNsID, aValue);
+ return mBuffer->addTransaction(transaction);
+}
+
+nsresult txBufferingHandler::characters(const nsAString& aData, bool aDOE) {
+ NS_ENSURE_TRUE(mBuffer, NS_ERROR_OUT_OF_MEMORY);
+
+ mCanAddAttribute = false;
+
+ txOutputTransaction::txTransactionType type =
+ aDOE ? txOutputTransaction::eCharacterNoOETransaction
+ : txOutputTransaction::eCharacterTransaction;
+
+ txOutputTransaction* transaction = mBuffer->getLastTransaction();
+ if (transaction && transaction->mType == type) {
+ mBuffer->mStringValue.Append(aData);
+ static_cast<txCharacterTransaction*>(transaction)->mLength +=
+ aData.Length();
+ return NS_OK;
+ }
+
+ transaction = new txCharacterTransaction(type, aData.Length());
+ mBuffer->mStringValue.Append(aData);
+ return mBuffer->addTransaction(transaction);
+}
+
+nsresult txBufferingHandler::comment(const nsString& aData) {
+ NS_ENSURE_TRUE(mBuffer, NS_ERROR_OUT_OF_MEMORY);
+
+ mCanAddAttribute = false;
+
+ txOutputTransaction* transaction = new txCommentTransaction(aData);
+ return mBuffer->addTransaction(transaction);
+}
+
+nsresult txBufferingHandler::endDocument(nsresult aResult) {
+ NS_ENSURE_TRUE(mBuffer, NS_ERROR_OUT_OF_MEMORY);
+
+ txOutputTransaction* transaction =
+ new txOutputTransaction(txOutputTransaction::eEndDocumentTransaction);
+ return mBuffer->addTransaction(transaction);
+}
+
+nsresult txBufferingHandler::endElement() {
+ NS_ENSURE_TRUE(mBuffer, NS_ERROR_OUT_OF_MEMORY);
+
+ mCanAddAttribute = false;
+
+ txOutputTransaction* transaction =
+ new txOutputTransaction(txOutputTransaction::eEndElementTransaction);
+ return mBuffer->addTransaction(transaction);
+}
+
+nsresult txBufferingHandler::processingInstruction(const nsString& aTarget,
+ const nsString& aData) {
+ NS_ENSURE_TRUE(mBuffer, NS_ERROR_OUT_OF_MEMORY);
+
+ mCanAddAttribute = false;
+
+ txOutputTransaction* transaction = new txPITransaction(aTarget, aData);
+ return mBuffer->addTransaction(transaction);
+}
+
+nsresult txBufferingHandler::startDocument() {
+ NS_ENSURE_TRUE(mBuffer, NS_ERROR_OUT_OF_MEMORY);
+
+ txOutputTransaction* transaction =
+ new txOutputTransaction(txOutputTransaction::eStartDocumentTransaction);
+ return mBuffer->addTransaction(transaction);
+}
+
+nsresult txBufferingHandler::startElement(nsAtom* aPrefix, nsAtom* aLocalName,
+ nsAtom* aLowercaseLocalName,
+ int32_t aNsID) {
+ NS_ENSURE_TRUE(mBuffer, NS_ERROR_OUT_OF_MEMORY);
+
+ mCanAddAttribute = true;
+
+ txOutputTransaction* transaction = new txStartElementAtomTransaction(
+ aPrefix, aLocalName, aLowercaseLocalName, aNsID);
+ return mBuffer->addTransaction(transaction);
+}
+
+nsresult txBufferingHandler::startElement(nsAtom* aPrefix,
+ const nsAString& aLocalName,
+ const int32_t aNsID) {
+ NS_ENSURE_TRUE(mBuffer, NS_ERROR_OUT_OF_MEMORY);
+
+ mCanAddAttribute = true;
+
+ txOutputTransaction* transaction =
+ new txStartElementTransaction(aPrefix, aLocalName, aNsID);
+ return mBuffer->addTransaction(transaction);
+}
+
+txResultBuffer::txResultBuffer() { MOZ_COUNT_CTOR(txResultBuffer); }
+
+txResultBuffer::~txResultBuffer() {
+ MOZ_COUNT_DTOR(txResultBuffer);
+ for (uint32_t i = 0, len = mTransactions.Length(); i < len; ++i) {
+ delete mTransactions[i];
+ }
+}
+
+nsresult txResultBuffer::addTransaction(txOutputTransaction* aTransaction) {
+ // XXX(Bug 1631371) Check if this should use a fallible operation as it
+ // pretended earlier, or change the return type to void.
+ mTransactions.AppendElement(aTransaction);
+ return NS_OK;
+}
+
+static nsresult flushTransaction(txOutputTransaction* aTransaction,
+ txAXMLEventHandler* aHandler,
+ nsString::const_char_iterator& aIter) {
+ switch (aTransaction->mType) {
+ case txOutputTransaction::eAttributeAtomTransaction: {
+ txAttributeAtomTransaction* transaction =
+ static_cast<txAttributeAtomTransaction*>(aTransaction);
+ return aHandler->attribute(transaction->mPrefix, transaction->mLocalName,
+ transaction->mLowercaseLocalName,
+ transaction->mNsID, transaction->mValue);
+ }
+ case txOutputTransaction::eAttributeTransaction: {
+ txAttributeTransaction* attrTransaction =
+ static_cast<txAttributeTransaction*>(aTransaction);
+ return aHandler->attribute(
+ attrTransaction->mPrefix, attrTransaction->mLocalName,
+ attrTransaction->mNsID, attrTransaction->mValue);
+ }
+ case txOutputTransaction::eCharacterTransaction:
+ case txOutputTransaction::eCharacterNoOETransaction: {
+ txCharacterTransaction* charTransaction =
+ static_cast<txCharacterTransaction*>(aTransaction);
+ nsString::const_char_iterator start = aIter;
+ nsString::const_char_iterator end = start + charTransaction->mLength;
+ aIter = end;
+ return aHandler->characters(
+ Substring(start, end),
+ aTransaction->mType ==
+ txOutputTransaction::eCharacterNoOETransaction);
+ }
+ case txOutputTransaction::eCommentTransaction: {
+ txCommentTransaction* commentTransaction =
+ static_cast<txCommentTransaction*>(aTransaction);
+ return aHandler->comment(commentTransaction->mValue);
+ }
+ case txOutputTransaction::eEndElementTransaction: {
+ return aHandler->endElement();
+ }
+ case txOutputTransaction::ePITransaction: {
+ txPITransaction* piTransaction =
+ static_cast<txPITransaction*>(aTransaction);
+ return aHandler->processingInstruction(piTransaction->mTarget,
+ piTransaction->mData);
+ }
+ case txOutputTransaction::eStartDocumentTransaction: {
+ return aHandler->startDocument();
+ }
+ case txOutputTransaction::eStartElementAtomTransaction: {
+ txStartElementAtomTransaction* transaction =
+ static_cast<txStartElementAtomTransaction*>(aTransaction);
+ return aHandler->startElement(
+ transaction->mPrefix, transaction->mLocalName,
+ transaction->mLowercaseLocalName, transaction->mNsID);
+ }
+ case txOutputTransaction::eStartElementTransaction: {
+ txStartElementTransaction* transaction =
+ static_cast<txStartElementTransaction*>(aTransaction);
+ return aHandler->startElement(
+ transaction->mPrefix, transaction->mLocalName, transaction->mNsID);
+ }
+ default: {
+ MOZ_ASSERT_UNREACHABLE("Unexpected transaction type");
+ }
+ }
+
+ return NS_ERROR_UNEXPECTED;
+}
+
+nsresult txResultBuffer::flushToHandler(txAXMLEventHandler* aHandler) {
+ nsString::const_char_iterator iter;
+ mStringValue.BeginReading(iter);
+
+ for (uint32_t i = 0, len = mTransactions.Length(); i < len; ++i) {
+ nsresult rv = flushTransaction(mTransactions[i], aHandler, iter);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+txOutputTransaction* txResultBuffer::getLastTransaction() {
+ int32_t last = mTransactions.Length() - 1;
+ if (last < 0) {
+ return nullptr;
+ }
+ return mTransactions[last];
+}
diff --git a/dom/xslt/xslt/txBufferingHandler.h b/dom/xslt/xslt/txBufferingHandler.h
new file mode 100644
index 0000000000..5fc41f312c
--- /dev/null
+++ b/dom/xslt/xslt/txBufferingHandler.h
@@ -0,0 +1,45 @@
+/* -*- 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/. */
+
+#ifndef txBufferingHandler_h__
+#define txBufferingHandler_h__
+
+#include "mozilla/UniquePtr.h"
+#include "txXMLEventHandler.h"
+#include "nsString.h"
+#include "nsTArray.h"
+
+class txOutputTransaction;
+
+class txResultBuffer {
+ public:
+ txResultBuffer();
+ ~txResultBuffer();
+
+ nsresult addTransaction(txOutputTransaction* aTransaction);
+
+ nsresult flushToHandler(txAXMLEventHandler* aHandler);
+
+ txOutputTransaction* getLastTransaction();
+
+ nsString mStringValue;
+
+ private:
+ nsTArray<txOutputTransaction*> mTransactions;
+};
+
+class txBufferingHandler : public txAXMLEventHandler {
+ public:
+ txBufferingHandler();
+ virtual ~txBufferingHandler();
+
+ TX_DECL_TXAXMLEVENTHANDLER
+
+ protected:
+ mozilla::UniquePtr<txResultBuffer> mBuffer;
+ bool mCanAddAttribute;
+};
+
+#endif /* txBufferingHandler_h__ */
diff --git a/dom/xslt/xslt/txCurrentFunctionCall.cpp b/dom/xslt/xslt/txCurrentFunctionCall.cpp
new file mode 100644
index 0000000000..bf44d8eadd
--- /dev/null
+++ b/dom/xslt/xslt/txCurrentFunctionCall.cpp
@@ -0,0 +1,50 @@
+/* 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 "nsGkAtoms.h"
+#include "txXSLTFunctions.h"
+#include "txExecutionState.h"
+
+/*
+ Implementation of XSLT 1.0 extension function: current
+*/
+
+/**
+ * Creates a new current function call
+ **/
+CurrentFunctionCall::CurrentFunctionCall() = default;
+
+/*
+ * Evaluates this Expr
+ *
+ * @return NodeSet containing the context node used for the complete
+ * Expr or Pattern.
+ */
+nsresult CurrentFunctionCall::evaluate(txIEvalContext* aContext,
+ txAExprResult** aResult) {
+ *aResult = nullptr;
+
+ if (!requireParams(0, 0, aContext)) return NS_ERROR_XPATH_BAD_ARGUMENT_COUNT;
+
+ txExecutionState* es =
+ static_cast<txExecutionState*>(aContext->getPrivateContext());
+ if (!es) {
+ NS_ERROR("called xslt extension function \"current\" with wrong context");
+ return NS_ERROR_UNEXPECTED;
+ }
+ return aContext->recycler()->getNodeSet(
+ es->getEvalContext()->getContextNode(), aResult);
+}
+
+Expr::ResultType CurrentFunctionCall::getReturnType() { return NODESET_RESULT; }
+
+bool CurrentFunctionCall::isSensitiveTo(ContextSensitivity aContext) {
+ return !!(aContext & PRIVATE_CONTEXT);
+}
+
+#ifdef TX_TO_STRING
+void CurrentFunctionCall::appendName(nsAString& aDest) {
+ aDest.Append(nsGkAtoms::current->GetUTF16String());
+}
+#endif
diff --git a/dom/xslt/xslt/txDocumentFunctionCall.cpp b/dom/xslt/xslt/txDocumentFunctionCall.cpp
new file mode 100644
index 0000000000..94d7f04035
--- /dev/null
+++ b/dom/xslt/xslt/txDocumentFunctionCall.cpp
@@ -0,0 +1,150 @@
+/* -*- 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/. */
+
+/*
+ * DocumentFunctionCall
+ * A representation of the XSLT additional function: document()
+ */
+
+#include "nsGkAtoms.h"
+#include "txIXPathContext.h"
+#include "txXSLTFunctions.h"
+#include "txExecutionState.h"
+#include "txURIUtils.h"
+
+/*
+ * Creates a new DocumentFunctionCall.
+ */
+DocumentFunctionCall::DocumentFunctionCall(const nsAString& aBaseURI)
+ : mBaseURI(aBaseURI) {}
+
+static void retrieveNode(txExecutionState* aExecutionState,
+ const nsAString& aUri, const nsAString& aBaseUri,
+ txNodeSet* aNodeSet) {
+ nsAutoString absUrl;
+ URIUtils::resolveHref(aUri, aBaseUri, absUrl);
+
+ int32_t hash = absUrl.RFindChar(char16_t('#'));
+ uint32_t urlEnd, fragStart, fragEnd;
+ if (hash == kNotFound) {
+ urlEnd = absUrl.Length();
+ fragStart = 0;
+ fragEnd = 0;
+ } else {
+ urlEnd = hash;
+ fragStart = hash + 1;
+ fragEnd = absUrl.Length();
+ }
+
+ nsDependentSubstring docUrl(absUrl, 0, urlEnd);
+ nsDependentSubstring frag(absUrl, fragStart, fragEnd);
+
+ const txXPathNode* loadNode = aExecutionState->retrieveDocument(docUrl);
+ if (loadNode) {
+ if (frag.IsEmpty()) {
+ aNodeSet->add(*loadNode);
+ } else {
+ txXPathTreeWalker walker(*loadNode);
+ if (walker.moveToElementById(frag)) {
+ aNodeSet->add(walker.getCurrentPosition());
+ }
+ }
+ }
+}
+
+/*
+ * Evaluates this Expr based on the given context node and processor state
+ * NOTE: the implementation is incomplete since it does not make use of the
+ * second argument (base URI)
+ * @param context the context node for evaluation of this Expr
+ * @return the result of the evaluation
+ */
+nsresult DocumentFunctionCall::evaluate(txIEvalContext* aContext,
+ txAExprResult** aResult) {
+ *aResult = nullptr;
+ txExecutionState* es =
+ static_cast<txExecutionState*>(aContext->getPrivateContext());
+
+ RefPtr<txNodeSet> nodeSet;
+ nsresult rv = aContext->recycler()->getNodeSet(getter_AddRefs(nodeSet));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // document(object, node-set?)
+ if (!requireParams(1, 2, aContext)) {
+ return NS_ERROR_XPATH_BAD_ARGUMENT_COUNT;
+ }
+
+ RefPtr<txAExprResult> exprResult1;
+ rv = mParams[0]->evaluate(aContext, getter_AddRefs(exprResult1));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString baseURI;
+ bool baseURISet = false;
+
+ if (mParams.Length() == 2) {
+ // We have 2 arguments, get baseURI from the first node
+ // in the resulting nodeset
+ RefPtr<txNodeSet> nodeSet2;
+ rv = evaluateToNodeSet(mParams[1], aContext, getter_AddRefs(nodeSet2));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Make this true, even if nodeSet2 is empty. For relative URLs,
+ // we'll fail to load the document with an empty base URI, and for
+ // absolute URLs, the base URI doesn't matter
+ baseURISet = true;
+
+ if (!nodeSet2->isEmpty()) {
+ rv = txXPathNodeUtils::getBaseURI(nodeSet2->get(0), baseURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ if (exprResult1->getResultType() == txAExprResult::NODESET) {
+ // The first argument is a NodeSet, iterate on its nodes
+ txNodeSet* nodeSet1 =
+ static_cast<txNodeSet*>(static_cast<txAExprResult*>(exprResult1));
+ int32_t i;
+ for (i = 0; i < nodeSet1->size(); ++i) {
+ const txXPathNode& node = nodeSet1->get(i);
+ nsAutoString uriStr;
+ txXPathNodeUtils::appendNodeValue(node, uriStr);
+ if (!baseURISet) {
+ // if the second argument wasn't specified, use
+ // the baseUri of node itself
+ rv = txXPathNodeUtils::getBaseURI(node, baseURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ retrieveNode(es, uriStr, baseURI, nodeSet);
+ }
+
+ NS_ADDREF(*aResult = nodeSet);
+
+ return NS_OK;
+ }
+
+ // The first argument is not a NodeSet
+ nsAutoString uriStr;
+ exprResult1->stringValue(uriStr);
+ const nsAString* base = baseURISet ? &baseURI : &mBaseURI;
+ retrieveNode(es, uriStr, *base, nodeSet);
+
+ NS_ADDREF(*aResult = nodeSet);
+
+ return NS_OK;
+}
+
+Expr::ResultType DocumentFunctionCall::getReturnType() {
+ return NODESET_RESULT;
+}
+
+bool DocumentFunctionCall::isSensitiveTo(ContextSensitivity aContext) {
+ return (aContext & PRIVATE_CONTEXT) || argsSensitiveTo(aContext);
+}
+
+#ifdef TX_TO_STRING
+void DocumentFunctionCall::appendName(nsAString& aDest) {
+ aDest.Append(nsGkAtoms::document->GetUTF16String());
+}
+#endif
diff --git a/dom/xslt/xslt/txEXSLTFunctions.cpp b/dom/xslt/xslt/txEXSLTFunctions.cpp
new file mode 100644
index 0000000000..d4882d0b13
--- /dev/null
+++ b/dom/xslt/xslt/txEXSLTFunctions.cpp
@@ -0,0 +1,818 @@
+/* -*- 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 "mozilla/ArrayUtils.h"
+#include "mozilla/EnumeratedArray.h"
+#include "mozilla/EnumeratedRange.h"
+#include "mozilla/FloatingPoint.h"
+#include "mozilla/MacroArgs.h"
+#include "mozilla/MacroForEach.h"
+
+#include "nsAtom.h"
+#include "nsGkAtoms.h"
+#include "txExecutionState.h"
+#include "txExpr.h"
+#include "txIXPathContext.h"
+#include "txIEXSLTFunctions.h"
+#include "txNodeSet.h"
+#include "txOutputFormat.h"
+#include "txRtfHandler.h"
+#include "txXPathTreeWalker.h"
+#include "nsImportModule.h"
+#include "nsPrintfCString.h"
+#include "nsComponentManagerUtils.h"
+#include "nsContentCID.h"
+#include "nsContentCreatorFunctions.h"
+#include "nsIContent.h"
+#include "txMozillaXMLOutput.h"
+#include "nsTextNode.h"
+#include "mozilla/dom/DocumentFragmentBinding.h"
+#include "prtime.h"
+#include "xpcprivate.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+class txStylesheetCompilerState;
+
+// ------------------------------------------------------------------
+// Utility functions
+// ------------------------------------------------------------------
+
+static Document* getSourceDocument(txIEvalContext* aContext) {
+ txExecutionState* es =
+ static_cast<txExecutionState*>(aContext->getPrivateContext());
+ if (!es) {
+ NS_ERROR("Need txExecutionState!");
+
+ return nullptr;
+ }
+
+ const txXPathNode& document = es->getSourceDocument();
+ return txXPathNativeNode::getDocument(document);
+}
+
+static nsresult convertRtfToNode(txIEvalContext* aContext,
+ txResultTreeFragment* aRtf) {
+ Document* doc = getSourceDocument(aContext);
+ if (!doc) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ RefPtr<DocumentFragment> domFragment =
+ new (doc->NodeInfoManager()) DocumentFragment(doc->NodeInfoManager());
+
+ txOutputFormat format;
+ txMozillaXMLOutput mozHandler(&format, domFragment, true);
+
+ nsresult rv = aRtf->flushToHandler(&mozHandler);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mozHandler.closePrevious(true);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // The txResultTreeFragment will own this.
+ const txXPathNode* node =
+ txXPathNativeNode::createXPathNode(domFragment, true);
+ NS_ENSURE_TRUE(node, NS_ERROR_OUT_OF_MEMORY);
+
+ aRtf->setNode(node);
+
+ return NS_OK;
+}
+
+static nsresult createTextNode(txIEvalContext* aContext, nsString& aValue,
+ txXPathNode** aResult) {
+ Document* doc = getSourceDocument(aContext);
+ if (!doc) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ RefPtr<nsTextNode> text =
+ new (doc->NodeInfoManager()) nsTextNode(doc->NodeInfoManager());
+
+ nsresult rv = text->SetText(aValue, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *aResult = txXPathNativeNode::createXPathNode(text, true);
+ NS_ENSURE_TRUE(*aResult, NS_ERROR_OUT_OF_MEMORY);
+
+ return NS_OK;
+}
+
+static nsresult createAndAddToResult(nsAtom* aName, const nsAString& aValue,
+ txNodeSet* aResultSet,
+ DocumentFragment* aResultHolder) {
+ Document* doc = aResultHolder->OwnerDoc();
+ nsCOMPtr<Element> elem =
+ doc->CreateElem(nsDependentAtomString(aName), nullptr, kNameSpaceID_None);
+ NS_ENSURE_TRUE(elem, NS_ERROR_NULL_POINTER);
+
+ RefPtr<nsTextNode> text =
+ new (doc->NodeInfoManager()) nsTextNode(doc->NodeInfoManager());
+
+ nsresult rv = text->SetText(aValue, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ ErrorResult error;
+ elem->AppendChildTo(text, false, error);
+ if (error.Failed()) {
+ return error.StealNSResult();
+ }
+
+ aResultHolder->AppendChildTo(elem, false, error);
+ if (error.Failed()) {
+ return error.StealNSResult();
+ }
+
+ UniquePtr<txXPathNode> xpathNode(
+ txXPathNativeNode::createXPathNode(elem, true));
+ NS_ENSURE_TRUE(xpathNode, NS_ERROR_OUT_OF_MEMORY);
+
+ aResultSet->append(*xpathNode);
+
+ return NS_OK;
+}
+
+// Need to update this array if types are added to the ResultType enum in
+// txAExprResult.
+static const char* const sTypes[] = {"node-set", "boolean", "number", "string",
+ "RTF"};
+
+// ------------------------------------------------------------------
+// Function implementations
+// ------------------------------------------------------------------
+
+enum class txEXSLTType {
+ // http://exslt.org/common
+ NODE_SET,
+ OBJECT_TYPE,
+
+ // http://exslt.org/dates-and-times
+ DATE_TIME,
+
+ // http://exslt.org/math
+ MAX,
+ MIN,
+ HIGHEST,
+ LOWEST,
+
+ // http://exslt.org/regular-expressions
+ MATCH,
+ REPLACE,
+ TEST,
+
+ // http://exslt.org/sets
+ DIFFERENCE_, // not DIFFERENCE to avoid a conflict with a winuser.h macro
+ DISTINCT,
+ HAS_SAME_NODE,
+ INTERSECTION,
+ LEADING,
+ TRAILING,
+
+ // http://exslt.org/strings
+ CONCAT,
+ SPLIT,
+ TOKENIZE,
+
+ _LIMIT,
+};
+
+struct txEXSLTFunctionDescriptor {
+ int8_t mMinParams;
+ int8_t mMaxParams;
+ Expr::ResultType mReturnType;
+ nsStaticAtom* mName;
+ bool (*mCreator)(txEXSLTType, FunctionCall**);
+ int32_t mNamespaceID;
+};
+
+static EnumeratedArray<txEXSLTType, txEXSLTType::_LIMIT,
+ txEXSLTFunctionDescriptor>
+ descriptTable;
+
+class txEXSLTFunctionCall : public FunctionCall {
+ public:
+ explicit txEXSLTFunctionCall(txEXSLTType aType) : mType(aType) {}
+
+ static bool Create(txEXSLTType aType, FunctionCall** aFunction) {
+ *aFunction = new txEXSLTFunctionCall(aType);
+ return true;
+ }
+
+ TX_DECL_FUNCTION
+
+ private:
+ txEXSLTType mType;
+};
+
+class txEXSLTRegExFunctionCall : public FunctionCall {
+ public:
+ explicit txEXSLTRegExFunctionCall(txEXSLTType aType) : mType(aType) {}
+
+ static bool Create(txEXSLTType aType, FunctionCall** aFunction) {
+ *aFunction = new txEXSLTRegExFunctionCall(aType);
+ return true;
+ }
+
+ TX_DECL_FUNCTION
+
+ private:
+ txEXSLTType mType;
+};
+
+nsresult txEXSLTFunctionCall::evaluate(txIEvalContext* aContext,
+ txAExprResult** aResult) {
+ *aResult = nullptr;
+ if (!requireParams(descriptTable[mType].mMinParams,
+ descriptTable[mType].mMaxParams, aContext)) {
+ return NS_ERROR_XPATH_BAD_ARGUMENT_COUNT;
+ }
+
+ nsresult rv = NS_OK;
+ switch (mType) {
+ case txEXSLTType::NODE_SET: {
+ RefPtr<txAExprResult> exprResult;
+ rv = mParams[0]->evaluate(aContext, getter_AddRefs(exprResult));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (exprResult->getResultType() == txAExprResult::NODESET) {
+ exprResult.forget(aResult);
+ } else {
+ RefPtr<txNodeSet> resultSet;
+ rv = aContext->recycler()->getNodeSet(getter_AddRefs(resultSet));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (exprResult->getResultType() ==
+ txAExprResult::RESULT_TREE_FRAGMENT) {
+ txResultTreeFragment* rtf =
+ static_cast<txResultTreeFragment*>(exprResult.get());
+
+ const txXPathNode* node = rtf->getNode();
+ if (!node) {
+ rv = convertRtfToNode(aContext, rtf);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ node = rtf->getNode();
+ }
+
+ resultSet->append(*node);
+ } else {
+ nsAutoString value;
+ exprResult->stringValue(value);
+
+ UniquePtr<txXPathNode> node;
+ rv = createTextNode(aContext, value, getter_Transfers(node));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ resultSet->append(*node);
+ }
+
+ NS_ADDREF(*aResult = resultSet);
+ }
+
+ return NS_OK;
+ }
+ case txEXSLTType::OBJECT_TYPE: {
+ RefPtr<txAExprResult> exprResult;
+ nsresult rv = mParams[0]->evaluate(aContext, getter_AddRefs(exprResult));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<StringResult> strRes;
+ rv = aContext->recycler()->getStringResult(getter_AddRefs(strRes));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ AppendASCIItoUTF16(MakeStringSpan(sTypes[exprResult->getResultType()]),
+ strRes->mValue);
+
+ NS_ADDREF(*aResult = strRes);
+
+ return NS_OK;
+ }
+ case txEXSLTType::DIFFERENCE_:
+ case txEXSLTType::INTERSECTION: {
+ RefPtr<txNodeSet> nodes1;
+ rv = evaluateToNodeSet(mParams[0], aContext, getter_AddRefs(nodes1));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<txNodeSet> nodes2;
+ rv = evaluateToNodeSet(mParams[1], aContext, getter_AddRefs(nodes2));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<txNodeSet> resultSet;
+ rv = aContext->recycler()->getNodeSet(getter_AddRefs(resultSet));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool insertOnFound = mType == txEXSLTType::INTERSECTION;
+
+ int32_t searchPos = 0;
+ int32_t i, len = nodes1->size();
+ for (i = 0; i < len; ++i) {
+ const txXPathNode& node = nodes1->get(i);
+ int32_t foundPos = nodes2->indexOf(node, searchPos);
+ if (foundPos >= 0) {
+ searchPos = foundPos + 1;
+ }
+
+ if ((foundPos >= 0) == insertOnFound) {
+ rv = resultSet->append(node);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ NS_ADDREF(*aResult = resultSet);
+
+ return NS_OK;
+ }
+ case txEXSLTType::DISTINCT: {
+ RefPtr<txNodeSet> nodes;
+ rv = evaluateToNodeSet(mParams[0], aContext, getter_AddRefs(nodes));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<txNodeSet> resultSet;
+ rv = aContext->recycler()->getNodeSet(getter_AddRefs(resultSet));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsTHashSet<nsString> hash;
+
+ int32_t i, len = nodes->size();
+ for (i = 0; i < len; ++i) {
+ nsAutoString str;
+ const txXPathNode& node = nodes->get(i);
+ txXPathNodeUtils::appendNodeValue(node, str);
+ if (hash.EnsureInserted(str)) {
+ rv = resultSet->append(node);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ NS_ADDREF(*aResult = resultSet);
+
+ return NS_OK;
+ }
+ case txEXSLTType::HAS_SAME_NODE: {
+ RefPtr<txNodeSet> nodes1;
+ rv = evaluateToNodeSet(mParams[0], aContext, getter_AddRefs(nodes1));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<txNodeSet> nodes2;
+ rv = evaluateToNodeSet(mParams[1], aContext, getter_AddRefs(nodes2));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool found = false;
+ int32_t i, len = nodes1->size();
+ for (i = 0; i < len; ++i) {
+ if (nodes2->contains(nodes1->get(i))) {
+ found = true;
+ break;
+ }
+ }
+
+ aContext->recycler()->getBoolResult(found, aResult);
+
+ return NS_OK;
+ }
+ case txEXSLTType::LEADING:
+ case txEXSLTType::TRAILING: {
+ RefPtr<txNodeSet> nodes1;
+ rv = evaluateToNodeSet(mParams[0], aContext, getter_AddRefs(nodes1));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<txNodeSet> nodes2;
+ rv = evaluateToNodeSet(mParams[1], aContext, getter_AddRefs(nodes2));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (nodes2->isEmpty()) {
+ *aResult = nodes1;
+ NS_ADDREF(*aResult);
+
+ return NS_OK;
+ }
+
+ RefPtr<txNodeSet> resultSet;
+ rv = aContext->recycler()->getNodeSet(getter_AddRefs(resultSet));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t end = nodes1->indexOf(nodes2->get(0));
+ if (end >= 0) {
+ int32_t i = 0;
+ if (mType == txEXSLTType::TRAILING) {
+ i = end + 1;
+ end = nodes1->size();
+ }
+ for (; i < end; ++i) {
+ rv = resultSet->append(nodes1->get(i));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ NS_ADDREF(*aResult = resultSet);
+
+ return NS_OK;
+ }
+ case txEXSLTType::CONCAT: {
+ RefPtr<txNodeSet> nodes;
+ rv = evaluateToNodeSet(mParams[0], aContext, getter_AddRefs(nodes));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString str;
+ int32_t i, len = nodes->size();
+ for (i = 0; i < len; ++i) {
+ txXPathNodeUtils::appendNodeValue(nodes->get(i), str);
+ }
+
+ return aContext->recycler()->getStringResult(str, aResult);
+ }
+ case txEXSLTType::SPLIT:
+ case txEXSLTType::TOKENIZE: {
+ // Evaluate parameters
+ nsAutoString string;
+ rv = mParams[0]->evaluateToString(aContext, string);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString pattern;
+ if (mParams.Length() == 2) {
+ rv = mParams[1]->evaluateToString(aContext, pattern);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else if (mType == txEXSLTType::SPLIT) {
+ pattern.Assign(' ');
+ } else {
+ pattern.AssignLiteral("\t\r\n ");
+ }
+
+ // Set up holders for the result
+ Document* sourceDoc = getSourceDocument(aContext);
+ NS_ENSURE_STATE(sourceDoc);
+
+ RefPtr<DocumentFragment> docFrag = new (sourceDoc->NodeInfoManager())
+ DocumentFragment(sourceDoc->NodeInfoManager());
+
+ RefPtr<txNodeSet> resultSet;
+ rv = aContext->recycler()->getNodeSet(getter_AddRefs(resultSet));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t tailIndex;
+
+ // Start splitting
+ if (pattern.IsEmpty()) {
+ nsString::const_char_iterator start = string.BeginReading();
+ nsString::const_char_iterator end = string.EndReading();
+ for (; start < end; ++start) {
+ rv = createAndAddToResult(nsGkAtoms::token,
+ Substring(start, start + 1), resultSet,
+ docFrag);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ tailIndex = string.Length();
+ } else if (mType == txEXSLTType::SPLIT) {
+ nsAString::const_iterator strStart, strEnd;
+ string.BeginReading(strStart);
+ string.EndReading(strEnd);
+ nsAString::const_iterator start = strStart, end = strEnd;
+
+ while (FindInReadable(pattern, start, end)) {
+ if (start != strStart) {
+ rv = createAndAddToResult(nsGkAtoms::token,
+ Substring(strStart, start), resultSet,
+ docFrag);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ strStart = start = end;
+ end = strEnd;
+ }
+
+ tailIndex = strStart.get() - string.get();
+ } else {
+ int32_t found, start = 0;
+ while ((found = string.FindCharInSet(pattern, start)) != kNotFound) {
+ if (found != start) {
+ rv = createAndAddToResult(nsGkAtoms::token,
+ Substring(string, start, found - start),
+ resultSet, docFrag);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ start = found + 1;
+ }
+
+ tailIndex = start;
+ }
+
+ // Add tail if needed
+ if (tailIndex != (uint32_t)string.Length()) {
+ rv = createAndAddToResult(
+ nsGkAtoms::token, Substring(string, tailIndex), resultSet, docFrag);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ NS_ADDREF(*aResult = resultSet);
+
+ return NS_OK;
+ }
+ case txEXSLTType::MAX:
+ case txEXSLTType::MIN: {
+ RefPtr<txNodeSet> nodes;
+ rv = evaluateToNodeSet(mParams[0], aContext, getter_AddRefs(nodes));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (nodes->isEmpty()) {
+ return aContext->recycler()->getNumberResult(UnspecifiedNaN<double>(),
+ aResult);
+ }
+
+ bool findMax = mType == txEXSLTType::MAX;
+
+ double res = findMax ? mozilla::NegativeInfinity<double>()
+ : mozilla::PositiveInfinity<double>();
+ int32_t i, len = nodes->size();
+ for (i = 0; i < len; ++i) {
+ nsAutoString str;
+ txXPathNodeUtils::appendNodeValue(nodes->get(i), str);
+ double val = txDouble::toDouble(str);
+ if (std::isnan(val)) {
+ res = UnspecifiedNaN<double>();
+ break;
+ }
+
+ if (findMax ? (val > res) : (val < res)) {
+ res = val;
+ }
+ }
+
+ return aContext->recycler()->getNumberResult(res, aResult);
+ }
+ case txEXSLTType::HIGHEST:
+ case txEXSLTType::LOWEST: {
+ RefPtr<txNodeSet> nodes;
+ rv = evaluateToNodeSet(mParams[0], aContext, getter_AddRefs(nodes));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (nodes->isEmpty()) {
+ NS_ADDREF(*aResult = nodes);
+
+ return NS_OK;
+ }
+
+ RefPtr<txNodeSet> resultSet;
+ rv = aContext->recycler()->getNodeSet(getter_AddRefs(resultSet));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool findMax = mType == txEXSLTType::HIGHEST;
+ double res = findMax ? mozilla::NegativeInfinity<double>()
+ : mozilla::PositiveInfinity<double>();
+ int32_t i, len = nodes->size();
+ for (i = 0; i < len; ++i) {
+ nsAutoString str;
+ const txXPathNode& node = nodes->get(i);
+ txXPathNodeUtils::appendNodeValue(node, str);
+ double val = txDouble::toDouble(str);
+ if (std::isnan(val)) {
+ resultSet->clear();
+ break;
+ }
+ if (findMax ? (val > res) : (val < res)) {
+ resultSet->clear();
+ res = val;
+ }
+
+ if (res == val) {
+ rv = resultSet->append(node);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ NS_ADDREF(*aResult = resultSet);
+
+ return NS_OK;
+ }
+ case txEXSLTType::DATE_TIME: {
+ // http://exslt.org/date/functions/date-time/
+
+ PRExplodedTime prtime;
+ PR_ExplodeTime(PR_Now(), PR_LocalTimeParameters, &prtime);
+
+ int32_t offset =
+ (prtime.tm_params.tp_gmt_offset + prtime.tm_params.tp_dst_offset) /
+ 60;
+
+ bool isneg = offset < 0;
+ if (isneg) offset = -offset;
+
+ StringResult* strRes;
+ rv = aContext->recycler()->getStringResult(&strRes);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // format: YYYY-MM-DDTTHH:MM:SS.sss+00:00
+ CopyASCIItoUTF16(
+ nsPrintfCString("%04hd-%02" PRId32 "-%02" PRId32 "T%02" PRId32
+ ":%02" PRId32 ":%02" PRId32 ".%03" PRId32
+ "%c%02" PRId32 ":%02" PRId32,
+ prtime.tm_year, prtime.tm_month + 1, prtime.tm_mday,
+ prtime.tm_hour, prtime.tm_min, prtime.tm_sec,
+ prtime.tm_usec / 10000, isneg ? '-' : '+',
+ offset / 60, offset % 60),
+ strRes->mValue);
+
+ *aResult = strRes;
+
+ return NS_OK;
+ }
+ default: {
+ aContext->receiveError(u"Internal error"_ns, NS_ERROR_UNEXPECTED);
+ return NS_ERROR_UNEXPECTED;
+ }
+ }
+
+ MOZ_ASSERT_UNREACHABLE("Missing return?");
+ return NS_ERROR_UNEXPECTED;
+}
+
+Expr::ResultType txEXSLTFunctionCall::getReturnType() {
+ return descriptTable[mType].mReturnType;
+}
+
+bool txEXSLTFunctionCall::isSensitiveTo(ContextSensitivity aContext) {
+ if (mType == txEXSLTType::NODE_SET || mType == txEXSLTType::SPLIT ||
+ mType == txEXSLTType::TOKENIZE) {
+ return (aContext & PRIVATE_CONTEXT) || argsSensitiveTo(aContext);
+ }
+ return argsSensitiveTo(aContext);
+}
+
+#ifdef TX_TO_STRING
+void txEXSLTFunctionCall::appendName(nsAString& aDest) {
+ aDest.Append(descriptTable[mType].mName->GetUTF16String());
+}
+#endif
+
+nsresult txEXSLTRegExFunctionCall::evaluate(txIEvalContext* aContext,
+ txAExprResult** aResult) {
+ *aResult = nullptr;
+ if (!requireParams(descriptTable[mType].mMinParams,
+ descriptTable[mType].mMaxParams, aContext)) {
+ return NS_ERROR_XPATH_BAD_ARGUMENT_COUNT;
+ }
+
+ nsAutoString string;
+ nsresult rv = mParams[0]->evaluateToString(aContext, string);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString regex;
+ rv = mParams[1]->evaluateToString(aContext, regex);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString flags;
+ if (mParams.Length() >= 3) {
+ rv = mParams[2]->evaluateToString(aContext, flags);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ nsCOMPtr<txIEXSLTFunctions> funcs =
+ do_ImportESModule("resource://gre/modules/txEXSLTRegExFunctions.sys.mjs");
+ MOZ_ALWAYS_TRUE(funcs);
+
+ switch (mType) {
+ case txEXSLTType::MATCH: {
+ nsCOMPtr<Document> sourceDoc = getSourceDocument(aContext);
+ NS_ENSURE_STATE(sourceDoc);
+
+ RefPtr<DocumentFragment> docFrag;
+ rv = funcs->Match(string, regex, flags, sourceDoc,
+ getter_AddRefs(docFrag));
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_STATE(docFrag);
+
+ RefPtr<txNodeSet> resultSet;
+ rv = aContext->recycler()->getNodeSet(getter_AddRefs(resultSet));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ UniquePtr<txXPathNode> node;
+ for (nsIContent* result = docFrag->GetFirstChild(); result;
+ result = result->GetNextSibling()) {
+ node = WrapUnique(txXPathNativeNode::createXPathNode(result, true));
+ rv = resultSet->add(*node);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ resultSet.forget(aResult);
+
+ return NS_OK;
+ }
+ case txEXSLTType::REPLACE: {
+ nsAutoString replace;
+ rv = mParams[3]->evaluateToString(aContext, replace);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString result;
+ rv = funcs->Replace(string, regex, flags, replace, result);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aContext->recycler()->getStringResult(result, aResult);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+ }
+ case txEXSLTType::TEST: {
+ bool result;
+ rv = funcs->Test(string, regex, flags, &result);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aContext->recycler()->getBoolResult(result, aResult);
+
+ return NS_OK;
+ }
+ default: {
+ aContext->receiveError(u"Internal error"_ns, NS_ERROR_UNEXPECTED);
+ return NS_ERROR_UNEXPECTED;
+ }
+ }
+
+ MOZ_ASSERT_UNREACHABLE("Missing return?");
+ return NS_ERROR_UNEXPECTED;
+}
+
+Expr::ResultType txEXSLTRegExFunctionCall::getReturnType() {
+ return descriptTable[mType].mReturnType;
+}
+
+bool txEXSLTRegExFunctionCall::isSensitiveTo(ContextSensitivity aContext) {
+ if (mType == txEXSLTType::MATCH) {
+ return (aContext & PRIVATE_CONTEXT) || argsSensitiveTo(aContext);
+ }
+ return argsSensitiveTo(aContext);
+}
+
+#ifdef TX_TO_STRING
+void txEXSLTRegExFunctionCall::appendName(nsAString& aDest) {
+ aDest.Append(descriptTable[mType].mName->GetUTF16String());
+}
+#endif
+
+extern nsresult TX_ConstructEXSLTFunction(nsAtom* aName, int32_t aNamespaceID,
+ txStylesheetCompilerState* aState,
+ FunctionCall** aResult) {
+ for (auto i : MakeEnumeratedRange(txEXSLTType::_LIMIT)) {
+ const txEXSLTFunctionDescriptor& desc = descriptTable[i];
+ if (aName == desc.mName && aNamespaceID == desc.mNamespaceID) {
+ return desc.mCreator(i, aResult) ? NS_OK : NS_ERROR_FAILURE;
+ }
+ }
+
+ return NS_ERROR_XPATH_UNKNOWN_FUNCTION;
+}
+
+extern bool TX_InitEXSLTFunction() {
+#define EXSLT_FUNCS(NS, CLASS, ...) \
+ { \
+ int32_t nsid = txNamespaceManager::getNamespaceID(nsLiteralString(NS)); \
+ if (nsid == kNameSpaceID_Unknown) { \
+ return false; \
+ } \
+ MOZ_FOR_EACH(EXSLT_FUNC, (nsid, CLASS, ), (__VA_ARGS__)) \
+ }
+
+#define EXSLT_FUNC(NS, CLASS, ...) \
+ descriptTable[txEXSLTType::MOZ_ARG_1 __VA_ARGS__] = { \
+ MOZ_ARGS_AFTER_1 __VA_ARGS__, CLASS::Create, NS};
+
+ EXSLT_FUNCS(u"http://exslt.org/common", txEXSLTFunctionCall,
+ (NODE_SET, 1, 1, Expr::NODESET_RESULT, nsGkAtoms::nodeSet),
+ (OBJECT_TYPE, 1, 1, Expr::STRING_RESULT, nsGkAtoms::objectType))
+
+ EXSLT_FUNCS(u"http://exslt.org/dates-and-times", txEXSLTFunctionCall,
+ (DATE_TIME, 0, 0, Expr::STRING_RESULT, nsGkAtoms::dateTime))
+
+ EXSLT_FUNCS(u"http://exslt.org/math", txEXSLTFunctionCall,
+ (MAX, 1, 1, Expr::NUMBER_RESULT, nsGkAtoms::max),
+ (MIN, 1, 1, Expr::NUMBER_RESULT, nsGkAtoms::min),
+ (HIGHEST, 1, 1, Expr::NODESET_RESULT, nsGkAtoms::highest),
+ (LOWEST, 1, 1, Expr::NODESET_RESULT, nsGkAtoms::lowest))
+
+ EXSLT_FUNCS(u"http://exslt.org/regular-expressions", txEXSLTRegExFunctionCall,
+ (MATCH, 2, 3, Expr::NODESET_RESULT, nsGkAtoms::match),
+ (REPLACE, 4, 4, Expr::STRING_RESULT, nsGkAtoms::replace),
+ (TEST, 2, 3, Expr::BOOLEAN_RESULT, nsGkAtoms::test))
+
+ EXSLT_FUNCS(
+ u"http://exslt.org/sets", txEXSLTFunctionCall,
+ (DIFFERENCE_, 2, 2, Expr::NODESET_RESULT, nsGkAtoms::difference),
+ (DISTINCT, 1, 1, Expr::NODESET_RESULT, nsGkAtoms::distinct),
+ (HAS_SAME_NODE, 2, 2, Expr::BOOLEAN_RESULT, nsGkAtoms::hasSameNode),
+ (INTERSECTION, 2, 2, Expr::NODESET_RESULT, nsGkAtoms::intersection),
+ (LEADING, 2, 2, Expr::NODESET_RESULT, nsGkAtoms::leading),
+ (TRAILING, 2, 2, Expr::NODESET_RESULT, nsGkAtoms::trailing))
+
+ EXSLT_FUNCS(u"http://exslt.org/strings", txEXSLTFunctionCall,
+ (CONCAT, 1, 1, Expr::STRING_RESULT, nsGkAtoms::concat),
+ (SPLIT, 1, 2, Expr::STRING_RESULT, nsGkAtoms::split),
+ (TOKENIZE, 1, 2, Expr::STRING_RESULT, nsGkAtoms::tokenize))
+
+#undef EXSLT_FUNCS
+#undef EXSLT_FUNC
+#undef EXSLT_FUNC_HELPER
+#undef EXSLT_FUNC_HELPER2
+
+ return true;
+}
diff --git a/dom/xslt/xslt/txEXSLTRegExFunctions.sys.mjs b/dom/xslt/xslt/txEXSLTRegExFunctions.sys.mjs
new file mode 100644
index 0000000000..d13ea6bea5
--- /dev/null
+++ b/dom/xslt/xslt/txEXSLTRegExFunctions.sys.mjs
@@ -0,0 +1,32 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+export function match(str, regex, flags, doc) {
+ var docFrag = doc.createDocumentFragment();
+ var re = new RegExp(regex, flags);
+ var matches = str.match(re);
+ if (matches != null) {
+ for (var i = 0; i < matches.length; ++i) {
+ var match = matches[i];
+ var elem = doc.createElementNS(null, "match");
+ var text = doc.createTextNode(match ? match : "");
+ elem.appendChild(text);
+ docFrag.appendChild(elem);
+ }
+ }
+ return docFrag;
+}
+
+export function replace(str, regex, flags, replace) {
+ var re = new RegExp(regex, flags);
+
+ return str.replace(re, replace);
+}
+
+export function test(str, regex, flags) {
+ var re = new RegExp(regex, flags);
+
+ return re.test(str);
+}
diff --git a/dom/xslt/xslt/txExecutionState.cpp b/dom/xslt/xslt/txExecutionState.cpp
new file mode 100644
index 0000000000..a3cfddac1c
--- /dev/null
+++ b/dom/xslt/xslt/txExecutionState.cpp
@@ -0,0 +1,460 @@
+/* -*- 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 "txExecutionState.h"
+#include "txSingleNodeContext.h"
+#include "txInstructions.h"
+#include "txStylesheet.h"
+#include "txVariableMap.h"
+#include "txRtfHandler.h"
+#include "txXSLTProcessor.h"
+#include "txLog.h"
+#include "txURIUtils.h"
+#include "txXMLParser.h"
+
+using mozilla::UniquePtr;
+using mozilla::Unused;
+using mozilla::WrapUnique;
+
+const int32_t txExecutionState::kMaxRecursionDepth = 20000;
+
+nsresult txLoadedDocumentsHash::init(const txXPathNode& aSource) {
+ mSourceDocument = WrapUnique(txXPathNodeUtils::getOwnerDocument(aSource));
+
+ nsAutoString baseURI;
+ nsresult rv = txXPathNodeUtils::getBaseURI(*mSourceDocument, baseURI);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // Technically the hash holds documents, but we allow any node that we're
+ // transforming from. In particular, the document() function uses this hash
+ // and it can return the source document, but if we're transforming from a
+ // document fragment (through
+ // txMozillaXSLTProcessor::SetSourceContentModel/txMozillaXSLTProcessor::DoTransform)
+ // or from another type of node (through
+ // txMozillaXSLTProcessor::TransformToDocument or
+ // txMozillaXSLTProcessor::TransformToFragment) it makes more sense to return
+ // the real root of the source tree, which is the node where the transform
+ // started.
+ PutEntry(baseURI)->mDocument = WrapUnique(
+ txXPathNativeNode::createXPathNode(txXPathNativeNode::getNode(aSource)));
+ return NS_OK;
+}
+
+txLoadedDocumentsHash::~txLoadedDocumentsHash() {
+ if (mSourceDocument) {
+ nsAutoString baseURI;
+ nsresult rv = txXPathNodeUtils::getBaseURI(*mSourceDocument, baseURI);
+ if (NS_SUCCEEDED(rv)) {
+ txLoadedDocumentEntry* entry = GetEntry(baseURI);
+ if (entry) {
+ delete entry->mDocument.release();
+ }
+ }
+ }
+}
+
+txExecutionState::txExecutionState(txStylesheet* aStylesheet,
+ bool aDisableLoads)
+ : mOutputHandler(nullptr),
+ mResultHandler(nullptr),
+ mOutputHandlerFactory(nullptr),
+ mStylesheet(aStylesheet),
+ mNextInstruction(nullptr),
+ mLocalVariables(nullptr),
+ mRecursionDepth(0),
+ mEvalContext(nullptr),
+ mInitialEvalContext(nullptr),
+ mGlobalParams(nullptr),
+ mKeyHash(aStylesheet->getKeyMap()),
+ mDisableLoads(aDisableLoads) {
+ MOZ_COUNT_CTOR(txExecutionState);
+}
+
+txExecutionState::~txExecutionState() {
+ MOZ_COUNT_DTOR(txExecutionState);
+
+ delete mResultHandler;
+ delete mLocalVariables;
+ if (mEvalContext != mInitialEvalContext) {
+ delete mEvalContext;
+ }
+
+ txStackIterator varsIter(&mLocalVarsStack);
+ while (varsIter.hasNext()) {
+ delete (txVariableMap*)varsIter.next();
+ }
+
+ txStackIterator contextIter(&mEvalContextStack);
+ while (contextIter.hasNext()) {
+ txIEvalContext* context = (txIEvalContext*)contextIter.next();
+ if (context != mInitialEvalContext) {
+ delete context;
+ }
+ }
+
+ txStackIterator handlerIter(&mResultHandlerStack);
+ while (handlerIter.hasNext()) {
+ delete (txAXMLEventHandler*)handlerIter.next();
+ }
+
+ delete mInitialEvalContext;
+}
+
+nsresult txExecutionState::init(
+ const txXPathNode& aNode,
+ txOwningExpandedNameMap<txIGlobalParameter>* aGlobalParams) {
+ nsresult rv = NS_OK;
+
+ mGlobalParams = aGlobalParams;
+
+ // Set up initial context
+ mEvalContext = new txSingleNodeContext(aNode, this);
+ mInitialEvalContext = mEvalContext;
+
+ // Set up output and result-handler
+ txAXMLEventHandler* handler;
+ rv = mOutputHandlerFactory->createHandlerWith(mStylesheet->getOutputFormat(),
+ &handler);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mOutputHandler = handler;
+ mResultHandler = handler;
+ mOutputHandler->startDocument();
+
+ // Set up loaded-documents-hash
+ rv = mLoadedDocuments.init(aNode);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Init members
+ rv = mKeyHash.init();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mRecycler = new txResultRecycler;
+
+ // The actual value here doesn't really matter since noone should use this
+ // value. But lets put something errorlike in just in case
+ mGlobalVarPlaceholderValue = new StringResult(u"Error"_ns, nullptr);
+
+ // Initiate first instruction. This has to be done last since findTemplate
+ // might use us.
+ txStylesheet::ImportFrame* frame = 0;
+ txExpandedName nullName;
+ txInstruction* templ;
+ rv =
+ mStylesheet->findTemplate(aNode, nullName, this, nullptr, &templ, &frame);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ pushTemplateRule(frame, nullName, nullptr);
+
+ return runTemplate(templ);
+}
+
+nsresult txExecutionState::end(nsresult aResult) {
+ NS_ASSERTION(NS_FAILED(aResult) || mTemplateRules.Length() == 1,
+ "Didn't clean up template rules properly");
+ if (NS_SUCCEEDED(aResult)) {
+ popTemplateRule();
+ } else if (!mOutputHandler) {
+ return NS_OK;
+ }
+ return mOutputHandler->endDocument(aResult);
+}
+
+void txExecutionState::popAndDeleteEvalContextUntil(txIEvalContext* aContext) {
+ auto ctx = popEvalContext();
+ while (ctx && ctx != aContext) {
+ MOZ_RELEASE_ASSERT(ctx != mInitialEvalContext);
+ delete ctx;
+ ctx = popEvalContext();
+ }
+}
+
+nsresult txExecutionState::getVariable(int32_t aNamespace, nsAtom* aLName,
+ txAExprResult*& aResult) {
+ nsresult rv = NS_OK;
+ txExpandedName name(aNamespace, aLName);
+
+ // look for a local variable
+ if (mLocalVariables) {
+ mLocalVariables->getVariable(name, &aResult);
+ if (aResult) {
+ return NS_OK;
+ }
+ }
+
+ // look for an evaluated global variable
+ mGlobalVariableValues.getVariable(name, &aResult);
+ if (aResult) {
+ if (aResult == mGlobalVarPlaceholderValue) {
+ // XXX ErrorReport: cyclic variable-value
+ NS_RELEASE(aResult);
+ return NS_ERROR_XSLT_BAD_RECURSION;
+ }
+ return NS_OK;
+ }
+
+ // Is there perchance a global variable not evaluated yet?
+ txStylesheet::GlobalVariable* var = mStylesheet->getGlobalVariable(name);
+ if (!var) {
+ // XXX ErrorReport: variable doesn't exist in this scope
+ return NS_ERROR_FAILURE;
+ }
+
+ NS_ASSERTION((var->mExpr && !var->mFirstInstruction) ||
+ (!var->mExpr && var->mFirstInstruction),
+ "global variable should have either instruction or expression");
+
+ // Is this a stylesheet parameter that has a value?
+ if (var->mIsParam && mGlobalParams) {
+ txIGlobalParameter* param = mGlobalParams->get(name);
+ if (param) {
+ rv = param->getValue(&aResult);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mGlobalVariableValues.bindVariable(name, aResult);
+ if (NS_FAILED(rv)) {
+ NS_RELEASE(aResult);
+ return rv;
+ }
+
+ return NS_OK;
+ }
+ }
+
+ // Insert a placeholdervalue to protect against recursion
+ rv = mGlobalVariableValues.bindVariable(name, mGlobalVarPlaceholderValue);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // evaluate the global variable
+ pushEvalContext(mInitialEvalContext);
+ if (var->mExpr) {
+ txVariableMap* oldVars = mLocalVariables;
+ mLocalVariables = nullptr;
+ rv = var->mExpr->evaluate(getEvalContext(), &aResult);
+ mLocalVariables = oldVars;
+
+ if (NS_FAILED(rv)) {
+ popAndDeleteEvalContextUntil(mInitialEvalContext);
+ return rv;
+ }
+ } else {
+ pushResultHandler(new txRtfHandler);
+
+ txInstruction* prevInstr = mNextInstruction;
+ // set return to nullptr to stop execution
+ mNextInstruction = nullptr;
+ rv = runTemplate(var->mFirstInstruction.get());
+ if (NS_FAILED(rv)) {
+ popAndDeleteEvalContextUntil(mInitialEvalContext);
+ return rv;
+ }
+
+ pushTemplateRule(nullptr, txExpandedName(), nullptr);
+ rv = txXSLTProcessor::execute(*this);
+ if (NS_FAILED(rv)) {
+ popAndDeleteEvalContextUntil(mInitialEvalContext);
+ return rv;
+ }
+
+ popTemplateRule();
+
+ mNextInstruction = prevInstr;
+ UniquePtr<txRtfHandler> rtfHandler(
+ static_cast<txRtfHandler*>(popResultHandler()));
+ rv = rtfHandler->getAsRTF(&aResult);
+ if (NS_FAILED(rv)) {
+ popAndDeleteEvalContextUntil(mInitialEvalContext);
+ return rv;
+ }
+ }
+ popEvalContext();
+
+ // Remove the placeholder and insert the calculated value
+ mGlobalVariableValues.removeVariable(name);
+ rv = mGlobalVariableValues.bindVariable(name, aResult);
+ if (NS_FAILED(rv)) {
+ NS_RELEASE(aResult);
+
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+nsresult txExecutionState::isStripSpaceAllowed(const txXPathNode& aNode,
+ bool& aAllowed) {
+ return mStylesheet->isStripSpaceAllowed(aNode, this, aAllowed);
+}
+
+void* txExecutionState::getPrivateContext() { return this; }
+
+txResultRecycler* txExecutionState::recycler() { return mRecycler; }
+
+void txExecutionState::receiveError(const nsAString& aMsg, nsresult aRes) {
+ // XXX implement me
+}
+
+void txExecutionState::pushEvalContext(txIEvalContext* aContext) {
+ mEvalContextStack.push(mEvalContext);
+ mEvalContext = aContext;
+}
+
+txIEvalContext* txExecutionState::popEvalContext() {
+ txIEvalContext* prev = mEvalContext;
+ mEvalContext = (txIEvalContext*)mEvalContextStack.pop();
+
+ return prev;
+}
+
+void txExecutionState::pushBool(bool aBool) { mBoolStack.AppendElement(aBool); }
+
+bool txExecutionState::popBool() {
+ NS_ASSERTION(mBoolStack.Length(), "popping from empty stack");
+
+ return mBoolStack.IsEmpty() ? false : mBoolStack.PopLastElement();
+}
+
+void txExecutionState::pushResultHandler(txAXMLEventHandler* aHandler) {
+ mResultHandlerStack.push(mResultHandler);
+ mResultHandler = aHandler;
+}
+
+txAXMLEventHandler* txExecutionState::popResultHandler() {
+ txAXMLEventHandler* oldHandler = mResultHandler;
+ mResultHandler = (txAXMLEventHandler*)mResultHandlerStack.pop();
+
+ return oldHandler;
+}
+
+void txExecutionState::pushTemplateRule(txStylesheet::ImportFrame* aFrame,
+ const txExpandedName& aMode,
+ txParameterMap* aParams) {
+ TemplateRule* rule = mTemplateRules.AppendElement();
+ rule->mFrame = aFrame;
+ rule->mModeNsId = aMode.mNamespaceID;
+ rule->mModeLocalName = aMode.mLocalName;
+ rule->mParams = aParams;
+}
+
+void txExecutionState::popTemplateRule() {
+ MOZ_ASSERT(!mTemplateRules.IsEmpty(), "No rules to pop");
+ mTemplateRules.RemoveLastElement();
+}
+
+txIEvalContext* txExecutionState::getEvalContext() { return mEvalContext; }
+
+const txXPathNode* txExecutionState::retrieveDocument(const nsAString& aUri) {
+ NS_ASSERTION(!aUri.Contains(char16_t('#')), "Remove the fragment.");
+
+ if (mDisableLoads) {
+ return nullptr;
+ }
+
+ MOZ_LOG(txLog::xslt, mozilla::LogLevel::Debug,
+ ("Retrieve Document %s", NS_LossyConvertUTF16toASCII(aUri).get()));
+
+ // try to get already loaded document
+ txLoadedDocumentEntry* entry = mLoadedDocuments.PutEntry(aUri);
+ if (!entry) {
+ return nullptr;
+ }
+
+ if (!entry->mDocument && !entry->LoadingFailed()) {
+ // open URI
+ nsAutoString errMsg;
+ // XXX we should get the loader from the actual node
+ // triggering the load, but this will do for the time being
+ entry->mLoadResult =
+ txParseDocumentFromURI(aUri, *mLoadedDocuments.mSourceDocument, errMsg,
+ getter_Transfers(entry->mDocument));
+
+ if (entry->LoadingFailed()) {
+ receiveError(u"Couldn't load document '"_ns + aUri + u"': "_ns + errMsg,
+ entry->mLoadResult);
+ }
+ }
+
+ return entry->mDocument.get();
+}
+
+nsresult txExecutionState::getKeyNodes(const txExpandedName& aKeyName,
+ const txXPathNode& aRoot,
+ const nsAString& aKeyValue,
+ bool aIndexIfNotFound,
+ txNodeSet** aResult) {
+ return mKeyHash.getKeyNodes(aKeyName, aRoot, aKeyValue, aIndexIfNotFound,
+ *this, aResult);
+}
+
+txExecutionState::TemplateRule* txExecutionState::getCurrentTemplateRule() {
+ MOZ_ASSERT(!mTemplateRules.IsEmpty(), "No current rule!");
+ return &mTemplateRules[mTemplateRules.Length() - 1];
+}
+
+mozilla::Result<txInstruction*, nsresult>
+txExecutionState::getNextInstruction() {
+ if (mStopProcessing) {
+ return mozilla::Err(NS_ERROR_FAILURE);
+ }
+
+ txInstruction* instr = mNextInstruction;
+ if (instr) {
+ mNextInstruction = instr->mNext.get();
+ }
+
+ return instr;
+}
+
+nsresult txExecutionState::runTemplate(txInstruction* aTemplate) {
+ NS_ENSURE_TRUE(++mRecursionDepth < kMaxRecursionDepth,
+ NS_ERROR_XSLT_BAD_RECURSION);
+
+ mLocalVarsStack.push(mLocalVariables);
+ mReturnStack.push(mNextInstruction);
+
+ mLocalVariables = nullptr;
+ mNextInstruction = aTemplate;
+
+ return NS_OK;
+}
+
+void txExecutionState::gotoInstruction(txInstruction* aNext) {
+ mNextInstruction = aNext;
+}
+
+void txExecutionState::returnFromTemplate() {
+ --mRecursionDepth;
+ NS_ASSERTION(!mReturnStack.isEmpty() && !mLocalVarsStack.isEmpty(),
+ "return or variable stack is empty");
+ delete mLocalVariables;
+ mNextInstruction = (txInstruction*)mReturnStack.pop();
+ mLocalVariables = (txVariableMap*)mLocalVarsStack.pop();
+}
+
+nsresult txExecutionState::bindVariable(const txExpandedName& aName,
+ txAExprResult* aValue) {
+ if (!mLocalVariables) {
+ mLocalVariables = new txVariableMap;
+ }
+ return mLocalVariables->bindVariable(aName, aValue);
+}
+
+void txExecutionState::removeVariable(const txExpandedName& aName) {
+ mLocalVariables->removeVariable(aName);
+}
+
+void txExecutionState::pushParamMap(txParameterMap* aParams) {
+ mParamStack.AppendElement(mTemplateParams.forget());
+ mTemplateParams = aParams;
+}
+
+already_AddRefed<txParameterMap> txExecutionState::popParamMap() {
+ RefPtr<txParameterMap> oldParams = std::move(mTemplateParams);
+ mTemplateParams = mParamStack.PopLastElement();
+
+ return oldParams.forget();
+}
diff --git a/dom/xslt/xslt/txExecutionState.h b/dom/xslt/xslt/txExecutionState.h
new file mode 100644
index 0000000000..7a314cf8a4
--- /dev/null
+++ b/dom/xslt/xslt/txExecutionState.h
@@ -0,0 +1,166 @@
+/* -*- 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/. */
+
+#ifndef TRANSFRMX_TXEXECUTIONSTATE_H
+#define TRANSFRMX_TXEXECUTIONSTATE_H
+
+#include "txCore.h"
+#include "txStack.h"
+#include "txXMLUtils.h"
+#include "txIXPathContext.h"
+#include "txVariableMap.h"
+#include "nsTHashtable.h"
+#include "nsHashKeys.h"
+#include "txKey.h"
+#include "txStylesheet.h"
+#include "txXPathTreeWalker.h"
+#include "nsTArray.h"
+#include "mozilla/Result.h"
+
+class txAOutputHandlerFactory;
+class txAXMLEventHandler;
+class txInstruction;
+
+class txLoadedDocumentEntry : public nsStringHashKey {
+ public:
+ explicit txLoadedDocumentEntry(KeyTypePointer aStr)
+ : nsStringHashKey(aStr), mLoadResult(NS_OK) {}
+ txLoadedDocumentEntry(txLoadedDocumentEntry&& aOther)
+ : nsStringHashKey(std::move(aOther)),
+ mDocument(std::move(aOther.mDocument)),
+ mLoadResult(std::move(aOther.mLoadResult)) {
+ NS_ERROR("We're horked.");
+ }
+ ~txLoadedDocumentEntry() {
+ if (mDocument) {
+ txXPathNodeUtils::release(mDocument.get());
+ }
+ }
+ bool LoadingFailed() {
+ NS_ASSERTION(NS_SUCCEEDED(mLoadResult) || !mDocument,
+ "Load failed but we still got a document?");
+
+ return NS_FAILED(mLoadResult);
+ }
+
+ mozilla::UniquePtr<txXPathNode> mDocument;
+ nsresult mLoadResult;
+};
+
+class txLoadedDocumentsHash : public nsTHashtable<txLoadedDocumentEntry> {
+ public:
+ txLoadedDocumentsHash() : nsTHashtable<txLoadedDocumentEntry>(4) {}
+ ~txLoadedDocumentsHash();
+ [[nodiscard]] nsresult init(const txXPathNode& aSource);
+
+ private:
+ friend class txExecutionState;
+ mozilla::UniquePtr<txXPathNode> mSourceDocument;
+};
+
+class txExecutionState : public txIMatchContext {
+ public:
+ txExecutionState(txStylesheet* aStylesheet, bool aDisableLoads);
+ ~txExecutionState();
+ nsresult init(const txXPathNode& aNode,
+ txOwningExpandedNameMap<txIGlobalParameter>* aGlobalParams);
+ nsresult end(nsresult aResult);
+
+ TX_DECL_MATCH_CONTEXT;
+
+ /**
+ * Struct holding information about a current template rule
+ */
+ class TemplateRule {
+ public:
+ txStylesheet::ImportFrame* mFrame;
+ int32_t mModeNsId;
+ RefPtr<nsAtom> mModeLocalName;
+ RefPtr<txParameterMap> mParams;
+ };
+
+ // Stack functions
+ void pushEvalContext(txIEvalContext* aContext);
+ txIEvalContext* popEvalContext();
+
+ /**
+ * Helper that deletes all entries before |aContext| and then
+ * pops it off the stack. The caller must delete |aContext| if
+ * desired.
+ */
+ void popAndDeleteEvalContextUntil(txIEvalContext* aContext);
+
+ void pushBool(bool aBool);
+ bool popBool();
+ void pushResultHandler(txAXMLEventHandler* aHandler);
+ txAXMLEventHandler* popResultHandler();
+ void pushTemplateRule(txStylesheet::ImportFrame* aFrame,
+ const txExpandedName& aMode, txParameterMap* aParams);
+ void popTemplateRule();
+ void pushParamMap(txParameterMap* aParams);
+ already_AddRefed<txParameterMap> popParamMap();
+
+ // state-getting functions
+ txIEvalContext* getEvalContext();
+ const txXPathNode* retrieveDocument(const nsAString& aUri);
+ nsresult getKeyNodes(const txExpandedName& aKeyName, const txXPathNode& aRoot,
+ const nsAString& aKeyValue, bool aIndexIfNotFound,
+ txNodeSet** aResult);
+ TemplateRule* getCurrentTemplateRule();
+ const txXPathNode& getSourceDocument() {
+ NS_ASSERTION(mLoadedDocuments.mSourceDocument, "Need a source document!");
+
+ return *mLoadedDocuments.mSourceDocument;
+ }
+
+ // state-modification functions
+ mozilla::Result<txInstruction*, nsresult> getNextInstruction();
+ nsresult runTemplate(txInstruction* aInstruction);
+ nsresult runTemplate(txInstruction* aInstruction, txInstruction* aReturnTo);
+ void gotoInstruction(txInstruction* aNext);
+ void returnFromTemplate();
+ nsresult bindVariable(const txExpandedName& aName, txAExprResult* aValue);
+ void removeVariable(const txExpandedName& aName);
+ void stopProcessing() { mStopProcessing = true; }
+
+ txAXMLEventHandler* mOutputHandler;
+ txAXMLEventHandler* mResultHandler;
+ mozilla::UniquePtr<txAXMLEventHandler> mObsoleteHandler;
+ txAOutputHandlerFactory* mOutputHandlerFactory;
+
+ RefPtr<txParameterMap> mTemplateParams;
+
+ RefPtr<txStylesheet> mStylesheet;
+
+ private:
+ txStack mReturnStack;
+ txStack mLocalVarsStack;
+ txStack mEvalContextStack;
+ nsTArray<bool> mBoolStack;
+ txStack mResultHandlerStack;
+ nsTArray<RefPtr<txParameterMap>> mParamStack;
+ txInstruction* mNextInstruction;
+ txVariableMap* mLocalVariables;
+ txVariableMap mGlobalVariableValues;
+ RefPtr<txAExprResult> mGlobalVarPlaceholderValue;
+ int32_t mRecursionDepth;
+
+ AutoTArray<TemplateRule, 10> mTemplateRules;
+
+ txIEvalContext* mEvalContext;
+ txIEvalContext* mInitialEvalContext;
+ // Document* mRTFDocument;
+ txOwningExpandedNameMap<txIGlobalParameter>* mGlobalParams;
+
+ txLoadedDocumentsHash mLoadedDocuments;
+ txKeyHash mKeyHash;
+ RefPtr<txResultRecycler> mRecycler;
+ bool mDisableLoads;
+ bool mStopProcessing = false;
+
+ static const int32_t kMaxRecursionDepth;
+};
+
+#endif
diff --git a/dom/xslt/xslt/txFormatNumberFunctionCall.cpp b/dom/xslt/xslt/txFormatNumberFunctionCall.cpp
new file mode 100644
index 0000000000..e851db2d7e
--- /dev/null
+++ b/dom/xslt/xslt/txFormatNumberFunctionCall.cpp
@@ -0,0 +1,406 @@
+/* -*- 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 "mozilla/FloatingPoint.h"
+
+#include "txXSLTFunctions.h"
+#include "nsGkAtoms.h"
+#include "txIXPathContext.h"
+#include "txStylesheet.h"
+#include <math.h>
+#include "txNamespaceMap.h"
+
+#include "prdtoa.h"
+
+#define INVALID_PARAM_VALUE u"invalid parameter value for function"_ns
+
+const char16_t txFormatNumberFunctionCall::FORMAT_QUOTE = '\'';
+
+/*
+ * FormatNumberFunctionCall
+ * A representation of the XSLT additional function: format-number()
+ */
+
+/*
+ * Creates a new format-number function call
+ */
+txFormatNumberFunctionCall::txFormatNumberFunctionCall(
+ txStylesheet* aStylesheet, txNamespaceMap* aMappings)
+ : mStylesheet(aStylesheet), mMappings(aMappings) {}
+
+void txFormatNumberFunctionCall::ReportInvalidArg(txIEvalContext* aContext) {
+ nsAutoString err(INVALID_PARAM_VALUE);
+#ifdef TX_TO_STRING
+ err.AppendLiteral(": ");
+ toString(err);
+#endif
+ aContext->receiveError(err, NS_ERROR_XPATH_INVALID_ARG);
+}
+
+/*
+ * Evaluates this Expr based on the given context node and processor state
+ * @param context the context node for evaluation of this Expr
+ * @param cs the ContextState containing the stack information needed
+ * for evaluation
+ * @return the result of the evaluation
+ */
+nsresult txFormatNumberFunctionCall::evaluate(txIEvalContext* aContext,
+ txAExprResult** aResult) {
+ *aResult = nullptr;
+ if (!requireParams(2, 3, aContext)) return NS_ERROR_XPATH_BAD_ARGUMENT_COUNT;
+
+ // Get number and format
+ double value;
+ txExpandedName formatName;
+
+ nsresult rv = evaluateToNumber(mParams[0], aContext, &value);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString formatStr;
+ rv = mParams[1]->evaluateToString(aContext, formatStr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (mParams.Length() == 3) {
+ nsAutoString formatQName;
+ rv = mParams[2]->evaluateToString(aContext, formatQName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = formatName.init(formatQName, mMappings, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ txDecimalFormat* format = mStylesheet->getDecimalFormat(formatName);
+ if (!format) {
+ nsAutoString err(u"unknown decimal format"_ns);
+#ifdef TX_TO_STRING
+ err.AppendLiteral(" for: ");
+ toString(err);
+#endif
+ aContext->receiveError(err, NS_ERROR_XPATH_INVALID_ARG);
+ return NS_ERROR_XPATH_INVALID_ARG;
+ }
+
+ // Special cases
+ if (std::isnan(value)) {
+ return aContext->recycler()->getStringResult(format->mNaN, aResult);
+ }
+
+ if (value == mozilla::PositiveInfinity<double>()) {
+ return aContext->recycler()->getStringResult(format->mInfinity, aResult);
+ }
+
+ if (value == mozilla::NegativeInfinity<double>()) {
+ nsAutoString res;
+ res.Append(format->mMinusSign);
+ res.Append(format->mInfinity);
+ return aContext->recycler()->getStringResult(res, aResult);
+ }
+
+ // Value is a normal finite number
+ nsAutoString prefix;
+ nsAutoString suffix;
+ int minIntegerSize = 0;
+ int minFractionSize = 0;
+ int maxFractionSize = 0;
+ int multiplier = 1;
+ int groupSize = -1;
+
+ uint32_t pos = 0;
+ uint32_t formatLen = formatStr.Length();
+ bool inQuote;
+
+ // Get right subexpression
+ inQuote = false;
+ if (mozilla::IsNegative(value)) {
+ while (pos < formatLen &&
+ (inQuote || formatStr.CharAt(pos) != format->mPatternSeparator)) {
+ if (formatStr.CharAt(pos) == FORMAT_QUOTE) inQuote = !inQuote;
+ pos++;
+ }
+
+ if (pos == formatLen) {
+ pos = 0;
+ prefix.Append(format->mMinusSign);
+ } else
+ pos++;
+ }
+
+ // Parse the format string
+ FormatParseState pState = Prefix;
+ inQuote = false;
+
+ char16_t c = 0;
+ while (pos < formatLen && pState != Finished) {
+ c = formatStr.CharAt(pos++);
+
+ switch (pState) {
+ case Prefix:
+ case Suffix:
+ if (!inQuote) {
+ if (c == format->mPercent) {
+ if (multiplier == 1)
+ multiplier = 100;
+ else {
+ ReportInvalidArg(aContext);
+ return NS_ERROR_XPATH_INVALID_ARG;
+ }
+ } else if (c == format->mPerMille) {
+ if (multiplier == 1)
+ multiplier = 1000;
+ else {
+ ReportInvalidArg(aContext);
+ return NS_ERROR_XPATH_INVALID_ARG;
+ }
+ } else if (c == format->mDecimalSeparator ||
+ c == format->mGroupingSeparator ||
+ c == format->mZeroDigit || c == format->mDigit ||
+ c == format->mPatternSeparator) {
+ pState = pState == Prefix ? IntDigit : Finished;
+ pos--;
+ break;
+ }
+ }
+
+ if (c == FORMAT_QUOTE)
+ inQuote = !inQuote;
+ else if (pState == Prefix)
+ prefix.Append(c);
+ else
+ suffix.Append(c);
+ break;
+
+ case IntDigit:
+ if (c == format->mGroupingSeparator)
+ groupSize = 0;
+ else if (c == format->mDigit) {
+ if (groupSize >= 0) groupSize++;
+ } else {
+ pState = IntZero;
+ pos--;
+ }
+ break;
+
+ case IntZero:
+ if (c == format->mGroupingSeparator)
+ groupSize = 0;
+ else if (c == format->mZeroDigit) {
+ if (groupSize >= 0) groupSize++;
+ minIntegerSize++;
+ } else if (c == format->mDecimalSeparator) {
+ pState = FracZero;
+ } else {
+ pState = Suffix;
+ pos--;
+ }
+ break;
+
+ case FracZero:
+ if (c == format->mZeroDigit) {
+ maxFractionSize++;
+ minFractionSize++;
+ } else {
+ pState = FracDigit;
+ pos--;
+ }
+ break;
+
+ case FracDigit:
+ if (c == format->mDigit)
+ maxFractionSize++;
+ else {
+ pState = Suffix;
+ pos--;
+ }
+ break;
+
+ case Finished:
+ break;
+ }
+ }
+
+ // Did we manage to parse the entire formatstring and was it valid
+ if ((c != format->mPatternSeparator && pos < formatLen) || inQuote ||
+ groupSize == 0) {
+ ReportInvalidArg(aContext);
+ return NS_ERROR_XPATH_INVALID_ARG;
+ }
+
+ /*
+ * FINALLY we're done with the parsing
+ * now build the result string
+ */
+
+ value = fabs(value) * multiplier;
+
+ // Make sure the multiplier didn't push value to infinity.
+ if (value == mozilla::PositiveInfinity<double>()) {
+ return aContext->recycler()->getStringResult(format->mInfinity, aResult);
+ }
+
+ // Make sure the multiplier didn't push value to infinity.
+ if (value == mozilla::PositiveInfinity<double>()) {
+ return aContext->recycler()->getStringResult(format->mInfinity, aResult);
+ }
+
+ // Prefix
+ nsAutoString res(prefix);
+
+ int bufsize;
+ if (value > 1)
+ bufsize = (int)log10(value) + 30;
+ else
+ bufsize = 1 + 30;
+
+ auto buf = mozilla::MakeUnique<char[]>(bufsize);
+ int bufIntDigits, sign;
+ char* endp;
+ PR_dtoa(value, 0, 0, &bufIntDigits, &sign, &endp, buf.get(), bufsize - 1);
+
+ int buflen = endp - buf.get();
+ int intDigits;
+ intDigits = bufIntDigits > minIntegerSize ? bufIntDigits : minIntegerSize;
+
+ if (groupSize < 0) groupSize = intDigits + 10; // to simplify grouping
+
+ // XXX We shouldn't use SetLength.
+ res.SetLength(res.Length() + intDigits + // integer digits
+ 1 + // decimal separator
+ maxFractionSize + // fractions
+ (intDigits - 1) / groupSize); // group separators
+
+ int32_t i = bufIntDigits + maxFractionSize - 1;
+ bool carry = (0 <= i + 1) && (i + 1 < buflen) && (buf[i + 1] >= '5');
+ bool hasFraction = false;
+
+ // The number of characters in res that we haven't filled in.
+ mozilla::CheckedUint32 resRemain = mozilla::CheckedUint32(res.Length());
+
+#define CHECKED_SET_CHAR(c) \
+ --resRemain; \
+ if (!resRemain.isValid() || !res.SetCharAt(c, resRemain.value())) { \
+ ReportInvalidArg(aContext); \
+ return NS_ERROR_XPATH_INVALID_ARG; \
+ }
+
+#define CHECKED_TRUNCATE() \
+ --resRemain; \
+ if (!resRemain.isValid()) { \
+ ReportInvalidArg(aContext); \
+ return NS_ERROR_XPATH_INVALID_ARG; \
+ } \
+ res.Truncate(resRemain.value());
+
+ // Fractions
+ for (; i >= bufIntDigits; --i) {
+ int digit;
+ if (i >= buflen || i < 0) {
+ digit = 0;
+ } else {
+ digit = buf[i] - '0';
+ }
+
+ if (carry) {
+ digit = (digit + 1) % 10;
+ carry = digit == 0;
+ }
+
+ if (hasFraction || digit != 0 || i < bufIntDigits + minFractionSize) {
+ hasFraction = true;
+ CHECKED_SET_CHAR((char16_t)(digit + format->mZeroDigit));
+ } else {
+ CHECKED_TRUNCATE();
+ }
+ }
+
+ // Decimal separator
+ if (hasFraction) {
+ CHECKED_SET_CHAR(format->mDecimalSeparator);
+ } else {
+ CHECKED_TRUNCATE();
+ }
+
+ // Integer digits
+ for (i = 0; i < intDigits; ++i) {
+ int digit;
+ if (bufIntDigits - i - 1 >= buflen || bufIntDigits - i - 1 < 0) {
+ digit = 0;
+ } else {
+ digit = buf[bufIntDigits - i - 1] - '0';
+ }
+
+ if (carry) {
+ digit = (digit + 1) % 10;
+ carry = digit == 0;
+ }
+
+ if (i != 0 && i % groupSize == 0) {
+ CHECKED_SET_CHAR(format->mGroupingSeparator);
+ }
+
+ CHECKED_SET_CHAR((char16_t)(digit + format->mZeroDigit));
+ }
+
+#undef CHECKED_SET_CHAR
+#undef CHECKED_TRUNCATE
+
+ if (carry) {
+ if (i % groupSize == 0) {
+ res.Insert(format->mGroupingSeparator, resRemain.value());
+ }
+ res.Insert((char16_t)(1 + format->mZeroDigit), resRemain.value());
+ }
+
+ if (!hasFraction && !intDigits && !carry) {
+ // If we havn't added any characters we add a '0'
+ // This can only happen for formats like '##.##'
+ res.Append(format->mZeroDigit);
+ }
+
+ // Build suffix
+ res.Append(suffix);
+
+ return aContext->recycler()->getStringResult(res, aResult);
+} //-- evaluate
+
+Expr::ResultType txFormatNumberFunctionCall::getReturnType() {
+ return STRING_RESULT;
+}
+
+bool txFormatNumberFunctionCall::isSensitiveTo(ContextSensitivity aContext) {
+ return argsSensitiveTo(aContext);
+}
+
+#ifdef TX_TO_STRING
+void txFormatNumberFunctionCall::appendName(nsAString& aDest) {
+ aDest.Append(nsGkAtoms::formatNumber->GetUTF16String());
+}
+#endif
+
+/*
+ * txDecimalFormat
+ * A representation of the XSLT element <xsl:decimal-format>
+ */
+
+txDecimalFormat::txDecimalFormat()
+ : mInfinity(u"Infinity"_ns), mNaN(u"NaN"_ns) {
+ mDecimalSeparator = '.';
+ mGroupingSeparator = ',';
+ mMinusSign = '-';
+ mPercent = '%';
+ mPerMille = 0x2030;
+ mZeroDigit = '0';
+ mDigit = '#';
+ mPatternSeparator = ';';
+}
+
+bool txDecimalFormat::isEqual(txDecimalFormat* other) {
+ return mDecimalSeparator == other->mDecimalSeparator &&
+ mGroupingSeparator == other->mGroupingSeparator &&
+ mInfinity.Equals(other->mInfinity) &&
+ mMinusSign == other->mMinusSign && mNaN.Equals(other->mNaN) &&
+ mPercent == other->mPercent && mPerMille == other->mPerMille &&
+ mZeroDigit == other->mZeroDigit && mDigit == other->mDigit &&
+ mPatternSeparator == other->mPatternSeparator;
+}
diff --git a/dom/xslt/xslt/txGenerateIdFunctionCall.cpp b/dom/xslt/xslt/txGenerateIdFunctionCall.cpp
new file mode 100644
index 0000000000..775d8a0a81
--- /dev/null
+++ b/dom/xslt/xslt/txGenerateIdFunctionCall.cpp
@@ -0,0 +1,99 @@
+/* -*- 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 "nsGkAtoms.h"
+#include "txIXPathContext.h"
+#include "txNodeSet.h"
+#include "txXPathTreeWalker.h"
+#include "txXSLTFunctions.h"
+#include "txExecutionState.h"
+
+/*
+ Implementation of XSLT 1.0 extension function: generate-id
+*/
+
+/**
+ * Creates a new generate-id function call
+ **/
+GenerateIdFunctionCall::GenerateIdFunctionCall() = default;
+
+/**
+ * Evaluates this Expr based on the given context node and processor state
+ * @param context the context node for evaluation of this Expr
+ * @param ps the ContextState containing the stack information needed
+ * for evaluation
+ * @return the result of the evaluation
+ * @see FunctionCall.h
+ **/
+nsresult GenerateIdFunctionCall::evaluate(txIEvalContext* aContext,
+ txAExprResult** aResult) {
+ *aResult = nullptr;
+ if (!requireParams(0, 1, aContext)) return NS_ERROR_XPATH_BAD_ARGUMENT_COUNT;
+
+ txExecutionState* es =
+ static_cast<txExecutionState*>(aContext->getPrivateContext());
+ if (!es) {
+ NS_ERROR(
+ "called xslt extension function \"generate-id\" with wrong context");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ nsresult rv = NS_OK;
+ if (mParams.IsEmpty()) {
+ StringResult* strRes;
+ rv = aContext->recycler()->getStringResult(&strRes);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ txXPathNodeUtils::getXSLTId(aContext->getContextNode(),
+ es->getSourceDocument(), strRes->mValue);
+
+ *aResult = strRes;
+
+ return NS_OK;
+ }
+
+ RefPtr<txNodeSet> nodes;
+ rv = evaluateToNodeSet(mParams[0], aContext, getter_AddRefs(nodes));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (nodes->isEmpty()) {
+ aContext->recycler()->getEmptyStringResult(aResult);
+
+ return NS_OK;
+ }
+
+ StringResult* strRes;
+ rv = aContext->recycler()->getStringResult(&strRes);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ txXPathNodeUtils::getXSLTId(nodes->get(0), es->getSourceDocument(),
+ strRes->mValue);
+
+ *aResult = strRes;
+
+ return NS_OK;
+}
+
+Expr::ResultType GenerateIdFunctionCall::getReturnType() {
+ return STRING_RESULT;
+}
+
+bool GenerateIdFunctionCall::isSensitiveTo(ContextSensitivity aContext) {
+ if (aContext & PRIVATE_CONTEXT) {
+ return true;
+ }
+
+ if (mParams.IsEmpty()) {
+ return !!(aContext & NODE_CONTEXT);
+ }
+
+ return argsSensitiveTo(aContext);
+}
+
+#ifdef TX_TO_STRING
+void GenerateIdFunctionCall::appendName(nsAString& aDest) {
+ aDest.Append(nsGkAtoms::generateId->GetUTF16String());
+}
+#endif
diff --git a/dom/xslt/xslt/txIEXSLTFunctions.idl b/dom/xslt/xslt/txIEXSLTFunctions.idl
new file mode 100644
index 0000000000..7514a5e4a1
--- /dev/null
+++ b/dom/xslt/xslt/txIEXSLTFunctions.idl
@@ -0,0 +1,20 @@
+/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */
+/* 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 "nsISupports.idl"
+
+webidl Document;
+webidl DocumentFragment;
+
+[scriptable, uuid(21b1cfa4-00ce-4cc1-bfc1-92af1d00e580)]
+interface txIEXSLTFunctions : nsISupports {
+ DocumentFragment match(in AString str, in AString regex,
+ in AString flags, in Document doc);
+
+ AString replace(in AString str, in AString regex, in AString flags,
+ in AString replace);
+
+ boolean test(in AString str, in AString regex, in AString flags);
+};
diff --git a/dom/xslt/xslt/txInstructions.cpp b/dom/xslt/xslt/txInstructions.cpp
new file mode 100644
index 0000000000..df6e1ffeb0
--- /dev/null
+++ b/dom/xslt/xslt/txInstructions.cpp
@@ -0,0 +1,764 @@
+/* -*- 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 "txInstructions.h"
+
+#include <utility>
+
+#include "nsError.h"
+#include "nsGkAtoms.h"
+#include "nsIConsoleService.h"
+#include "nsServiceManagerUtils.h"
+#include "txExecutionState.h"
+#include "txExpr.h"
+#include "txNodeSetContext.h"
+#include "txNodeSorter.h"
+#include "txRtfHandler.h"
+#include "txStringUtils.h"
+#include "txStylesheet.h"
+#include "txTextHandler.h"
+#include "txXSLTNumber.h"
+
+using mozilla::MakeUnique;
+using mozilla::UniquePtr;
+
+nsresult txApplyDefaultElementTemplate::execute(txExecutionState& aEs) {
+ txExecutionState::TemplateRule* rule = aEs.getCurrentTemplateRule();
+ txExpandedName mode(rule->mModeNsId, rule->mModeLocalName);
+ txStylesheet::ImportFrame* frame = 0;
+ txInstruction* templ;
+ nsresult rv =
+ aEs.mStylesheet->findTemplate(aEs.getEvalContext()->getContextNode(),
+ mode, &aEs, nullptr, &templ, &frame);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aEs.pushTemplateRule(frame, mode, aEs.mTemplateParams);
+
+ return aEs.runTemplate(templ);
+}
+
+nsresult txApplyImportsEnd::execute(txExecutionState& aEs) {
+ aEs.popTemplateRule();
+ RefPtr<txParameterMap> paramMap = aEs.popParamMap();
+
+ return NS_OK;
+}
+
+nsresult txApplyImportsStart::execute(txExecutionState& aEs) {
+ txExecutionState::TemplateRule* rule = aEs.getCurrentTemplateRule();
+ // The frame is set to null when there is no current template rule, or
+ // when the current template rule is a default template. However this
+ // instruction isn't used in default templates.
+ if (!rule->mFrame) {
+ // XXX ErrorReport: apply-imports instantiated without a current rule
+ return NS_ERROR_XSLT_EXECUTION_FAILURE;
+ }
+
+ aEs.pushParamMap(rule->mParams);
+
+ txStylesheet::ImportFrame* frame = 0;
+ txExpandedName mode(rule->mModeNsId, rule->mModeLocalName);
+ txInstruction* templ;
+ nsresult rv =
+ aEs.mStylesheet->findTemplate(aEs.getEvalContext()->getContextNode(),
+ mode, &aEs, rule->mFrame, &templ, &frame);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aEs.pushTemplateRule(frame, mode, rule->mParams);
+
+ rv = aEs.runTemplate(templ);
+ if (NS_FAILED(rv)) {
+ aEs.popTemplateRule();
+ }
+
+ return rv;
+}
+
+txApplyTemplates::txApplyTemplates(const txExpandedName& aMode)
+ : mMode(aMode) {}
+
+nsresult txApplyTemplates::execute(txExecutionState& aEs) {
+ txStylesheet::ImportFrame* frame = 0;
+ txInstruction* templ;
+ nsresult rv =
+ aEs.mStylesheet->findTemplate(aEs.getEvalContext()->getContextNode(),
+ mMode, &aEs, nullptr, &templ, &frame);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aEs.pushTemplateRule(frame, mMode, aEs.mTemplateParams);
+
+ return aEs.runTemplate(templ);
+}
+
+txAttribute::txAttribute(UniquePtr<Expr>&& aName, UniquePtr<Expr>&& aNamespace,
+ txNamespaceMap* aMappings)
+ : mName(std::move(aName)),
+ mNamespace(std::move(aNamespace)),
+ mMappings(aMappings) {}
+
+nsresult txAttribute::execute(txExecutionState& aEs) {
+ UniquePtr<txTextHandler> handler(
+ static_cast<txTextHandler*>(aEs.popResultHandler()));
+
+ nsAutoString name;
+ nsresult rv = mName->evaluateToString(aEs.getEvalContext(), name);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ const char16_t* colon;
+ if (!XMLUtils::isValidQName(name, &colon) ||
+ TX_StringEqualsAtom(name, nsGkAtoms::xmlns)) {
+ return NS_OK;
+ }
+
+ RefPtr<nsAtom> prefix;
+ uint32_t lnameStart = 0;
+ if (colon) {
+ prefix = NS_Atomize(Substring(name.get(), colon));
+ lnameStart = colon - name.get() + 1;
+ }
+
+ int32_t nsId = kNameSpaceID_None;
+ if (mNamespace) {
+ nsAutoString nspace;
+ rv = mNamespace->evaluateToString(aEs.getEvalContext(), nspace);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!nspace.IsEmpty()) {
+ nsId = txNamespaceManager::getNamespaceID(nspace);
+ }
+ } else if (colon) {
+ nsId = mMappings->lookupNamespace(prefix);
+ }
+
+ // add attribute if everything was ok
+ return nsId != kNameSpaceID_Unknown
+ ? aEs.mResultHandler->attribute(
+ prefix, Substring(name, lnameStart), nsId, handler->mValue)
+ : NS_OK;
+}
+
+txCallTemplate::txCallTemplate(const txExpandedName& aName) : mName(aName) {}
+
+nsresult txCallTemplate::execute(txExecutionState& aEs) {
+ txInstruction* instr = aEs.mStylesheet->getNamedTemplate(mName);
+ NS_ENSURE_TRUE(instr, NS_ERROR_XSLT_EXECUTION_FAILURE);
+
+ nsresult rv = aEs.runTemplate(instr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+txCheckParam::txCheckParam(const txExpandedName& aName)
+ : mName(aName), mBailTarget(nullptr) {}
+
+nsresult txCheckParam::execute(txExecutionState& aEs) {
+ nsresult rv = NS_OK;
+ if (aEs.mTemplateParams) {
+ RefPtr<txAExprResult> exprRes;
+ aEs.mTemplateParams->getVariable(mName, getter_AddRefs(exprRes));
+ if (exprRes) {
+ rv = aEs.bindVariable(mName, exprRes);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aEs.gotoInstruction(mBailTarget);
+ }
+ }
+
+ return NS_OK;
+}
+
+txConditionalGoto::txConditionalGoto(UniquePtr<Expr>&& aCondition,
+ txInstruction* aTarget)
+ : mCondition(std::move(aCondition)), mTarget(aTarget) {}
+
+nsresult txConditionalGoto::execute(txExecutionState& aEs) {
+ bool exprRes;
+ nsresult rv = mCondition->evaluateToBool(aEs.getEvalContext(), exprRes);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!exprRes) {
+ aEs.gotoInstruction(mTarget);
+ }
+
+ return NS_OK;
+}
+
+nsresult txComment::execute(txExecutionState& aEs) {
+ UniquePtr<txTextHandler> handler(
+ static_cast<txTextHandler*>(aEs.popResultHandler()));
+ uint32_t length = handler->mValue.Length();
+ int32_t pos = 0;
+ while ((pos = handler->mValue.FindChar('-', (uint32_t)pos)) != kNotFound) {
+ ++pos;
+ if ((uint32_t)pos == length || handler->mValue.CharAt(pos) == '-') {
+ handler->mValue.Insert(char16_t(' '), pos++);
+ ++length;
+ }
+ }
+
+ return aEs.mResultHandler->comment(handler->mValue);
+}
+
+nsresult txCopyBase::copyNode(const txXPathNode& aNode, txExecutionState& aEs) {
+ switch (txXPathNodeUtils::getNodeType(aNode)) {
+ case txXPathNodeType::ATTRIBUTE_NODE: {
+ nsAutoString nodeValue;
+ txXPathNodeUtils::appendNodeValue(aNode, nodeValue);
+
+ RefPtr<nsAtom> localName = txXPathNodeUtils::getLocalName(aNode);
+ return aEs.mResultHandler->attribute(
+ txXPathNodeUtils::getPrefix(aNode), localName, nullptr,
+ txXPathNodeUtils::getNamespaceID(aNode), nodeValue);
+ }
+ case txXPathNodeType::COMMENT_NODE: {
+ nsAutoString nodeValue;
+ txXPathNodeUtils::appendNodeValue(aNode, nodeValue);
+ return aEs.mResultHandler->comment(nodeValue);
+ }
+ case txXPathNodeType::DOCUMENT_NODE:
+ case txXPathNodeType::DOCUMENT_FRAGMENT_NODE: {
+ // Copy children
+ txXPathTreeWalker walker(aNode);
+ bool hasChild = walker.moveToFirstChild();
+ while (hasChild) {
+ copyNode(walker.getCurrentPosition(), aEs);
+ hasChild = walker.moveToNextSibling();
+ }
+ break;
+ }
+ case txXPathNodeType::ELEMENT_NODE: {
+ RefPtr<nsAtom> localName = txXPathNodeUtils::getLocalName(aNode);
+ nsresult rv = aEs.mResultHandler->startElement(
+ txXPathNodeUtils::getPrefix(aNode), localName, nullptr,
+ txXPathNodeUtils::getNamespaceID(aNode));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Copy attributes
+ txXPathTreeWalker walker(aNode);
+ if (walker.moveToFirstAttribute()) {
+ do {
+ nsAutoString nodeValue;
+ walker.appendNodeValue(nodeValue);
+
+ const txXPathNode& attr = walker.getCurrentPosition();
+ localName = txXPathNodeUtils::getLocalName(attr);
+ rv = aEs.mResultHandler->attribute(
+ txXPathNodeUtils::getPrefix(attr), localName, nullptr,
+ txXPathNodeUtils::getNamespaceID(attr), nodeValue);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } while (walker.moveToNextAttribute());
+ walker.moveToParent();
+ }
+
+ // Copy children
+ bool hasChild = walker.moveToFirstChild();
+ while (hasChild) {
+ copyNode(walker.getCurrentPosition(), aEs);
+ hasChild = walker.moveToNextSibling();
+ }
+
+ return aEs.mResultHandler->endElement();
+ }
+ case txXPathNodeType::PROCESSING_INSTRUCTION_NODE: {
+ nsAutoString target, data;
+ txXPathNodeUtils::getNodeName(aNode, target);
+ txXPathNodeUtils::appendNodeValue(aNode, data);
+ return aEs.mResultHandler->processingInstruction(target, data);
+ }
+ case txXPathNodeType::TEXT_NODE:
+ case txXPathNodeType::CDATA_SECTION_NODE: {
+ nsAutoString nodeValue;
+ txXPathNodeUtils::appendNodeValue(aNode, nodeValue);
+ return aEs.mResultHandler->characters(nodeValue, false);
+ }
+ }
+
+ return NS_OK;
+}
+
+txCopy::txCopy() : mBailTarget(nullptr) {}
+
+nsresult txCopy::execute(txExecutionState& aEs) {
+ nsresult rv = NS_OK;
+ const txXPathNode& node = aEs.getEvalContext()->getContextNode();
+
+ switch (txXPathNodeUtils::getNodeType(node)) {
+ case txXPathNodeType::DOCUMENT_NODE:
+ case txXPathNodeType::DOCUMENT_FRAGMENT_NODE: {
+ // "close" current element to ensure that no attributes are added
+ rv = aEs.mResultHandler->characters(u""_ns, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aEs.pushBool(false);
+
+ break;
+ }
+ case txXPathNodeType::ELEMENT_NODE: {
+ RefPtr<nsAtom> localName = txXPathNodeUtils::getLocalName(node);
+ rv = aEs.mResultHandler->startElement(
+ txXPathNodeUtils::getPrefix(node), localName, nullptr,
+ txXPathNodeUtils::getNamespaceID(node));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // XXX copy namespace nodes once we have them
+
+ aEs.pushBool(true);
+
+ break;
+ }
+ default: {
+ rv = copyNode(node, aEs);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aEs.gotoInstruction(mBailTarget);
+ }
+ }
+
+ return NS_OK;
+}
+
+txCopyOf::txCopyOf(UniquePtr<Expr>&& aSelect) : mSelect(std::move(aSelect)) {}
+
+nsresult txCopyOf::execute(txExecutionState& aEs) {
+ RefPtr<txAExprResult> exprRes;
+ nsresult rv =
+ mSelect->evaluate(aEs.getEvalContext(), getter_AddRefs(exprRes));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ switch (exprRes->getResultType()) {
+ case txAExprResult::NODESET: {
+ txNodeSet* nodes =
+ static_cast<txNodeSet*>(static_cast<txAExprResult*>(exprRes));
+ int32_t i;
+ for (i = 0; i < nodes->size(); ++i) {
+ rv = copyNode(nodes->get(i), aEs);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ break;
+ }
+ case txAExprResult::RESULT_TREE_FRAGMENT: {
+ txResultTreeFragment* rtf = static_cast<txResultTreeFragment*>(
+ static_cast<txAExprResult*>(exprRes));
+ return rtf->flushToHandler(aEs.mResultHandler);
+ }
+ default: {
+ nsAutoString value;
+ exprRes->stringValue(value);
+ if (!value.IsEmpty()) {
+ return aEs.mResultHandler->characters(value, false);
+ }
+ break;
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult txEndElement::execute(txExecutionState& aEs) {
+ // This will return false if startElement was not called. This happens
+ // when <xsl:element> produces a bad name, or when <xsl:copy> copies a
+ // document node.
+ if (aEs.popBool()) {
+ return aEs.mResultHandler->endElement();
+ }
+
+ return NS_OK;
+}
+
+nsresult txErrorInstruction::execute(txExecutionState& aEs) {
+ // XXX ErrorReport: unknown instruction executed
+ return NS_ERROR_XSLT_EXECUTION_FAILURE;
+}
+
+txGoTo::txGoTo(txInstruction* aTarget) : mTarget(aTarget) {}
+
+nsresult txGoTo::execute(txExecutionState& aEs) {
+ aEs.gotoInstruction(mTarget);
+
+ return NS_OK;
+}
+
+txInsertAttrSet::txInsertAttrSet(const txExpandedName& aName) : mName(aName) {}
+
+nsresult txInsertAttrSet::execute(txExecutionState& aEs) {
+ txInstruction* instr = aEs.mStylesheet->getAttributeSet(mName);
+ NS_ENSURE_TRUE(instr, NS_ERROR_XSLT_EXECUTION_FAILURE);
+
+ nsresult rv = aEs.runTemplate(instr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+txLoopNodeSet::txLoopNodeSet(txInstruction* aTarget) : mTarget(aTarget) {}
+
+nsresult txLoopNodeSet::execute(txExecutionState& aEs) {
+ aEs.popTemplateRule();
+ txNodeSetContext* context =
+ static_cast<txNodeSetContext*>(aEs.getEvalContext());
+ if (!context->hasNext()) {
+ delete aEs.popEvalContext();
+
+ return NS_OK;
+ }
+
+ context->next();
+ aEs.gotoInstruction(mTarget);
+
+ return NS_OK;
+}
+
+txLREAttribute::txLREAttribute(int32_t aNamespaceID, nsAtom* aLocalName,
+ nsAtom* aPrefix, UniquePtr<Expr>&& aValue)
+ : mNamespaceID(aNamespaceID),
+ mLocalName(aLocalName),
+ mPrefix(aPrefix),
+ mValue(std::move(aValue)) {
+ if (aNamespaceID == kNameSpaceID_None) {
+ mLowercaseLocalName = TX_ToLowerCaseAtom(aLocalName);
+ }
+}
+
+nsresult txLREAttribute::execute(txExecutionState& aEs) {
+ RefPtr<txAExprResult> exprRes;
+ nsresult rv = mValue->evaluate(aEs.getEvalContext(), getter_AddRefs(exprRes));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ const nsString* value = exprRes->stringValuePointer();
+ if (value) {
+ return aEs.mResultHandler->attribute(
+ mPrefix, mLocalName, mLowercaseLocalName, mNamespaceID, *value);
+ }
+
+ nsAutoString valueStr;
+ exprRes->stringValue(valueStr);
+ return aEs.mResultHandler->attribute(mPrefix, mLocalName, mLowercaseLocalName,
+ mNamespaceID, valueStr);
+}
+
+txMessage::txMessage(bool aTerminate) : mTerminate(aTerminate) {}
+
+nsresult txMessage::execute(txExecutionState& aEs) {
+ UniquePtr<txTextHandler> handler(
+ static_cast<txTextHandler*>(aEs.popResultHandler()));
+
+ nsCOMPtr<nsIConsoleService> consoleSvc =
+ do_GetService("@mozilla.org/consoleservice;1");
+ if (consoleSvc) {
+ nsAutoString logString(u"xsl:message - "_ns);
+ logString.Append(handler->mValue);
+ consoleSvc->LogStringMessage(logString.get());
+ }
+
+ return mTerminate ? NS_ERROR_XSLT_ABORTED : NS_OK;
+}
+
+txNumber::txNumber(txXSLTNumber::LevelType aLevel,
+ UniquePtr<txPattern>&& aCount, UniquePtr<txPattern>&& aFrom,
+ UniquePtr<Expr>&& aValue, UniquePtr<Expr>&& aFormat,
+ UniquePtr<Expr>&& aGroupingSeparator,
+ UniquePtr<Expr>&& aGroupingSize)
+ : mLevel(aLevel),
+ mCount(std::move(aCount)),
+ mFrom(std::move(aFrom)),
+ mValue(std::move(aValue)),
+ mFormat(std::move(aFormat)),
+ mGroupingSeparator(std::move(aGroupingSeparator)),
+ mGroupingSize(std::move(aGroupingSize)) {}
+
+nsresult txNumber::execute(txExecutionState& aEs) {
+ nsAutoString res;
+ nsresult rv = txXSLTNumber::createNumber(
+ mValue.get(), mCount.get(), mFrom.get(), mLevel, mGroupingSize.get(),
+ mGroupingSeparator.get(), mFormat.get(), aEs.getEvalContext(), res);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return aEs.mResultHandler->characters(res, false);
+}
+
+nsresult txPopParams::execute(txExecutionState& aEs) {
+ RefPtr<txParameterMap> paramMap = aEs.popParamMap();
+
+ return NS_OK;
+}
+
+txProcessingInstruction::txProcessingInstruction(UniquePtr<Expr>&& aName)
+ : mName(std::move(aName)) {}
+
+nsresult txProcessingInstruction::execute(txExecutionState& aEs) {
+ UniquePtr<txTextHandler> handler(
+ static_cast<txTextHandler*>(aEs.popResultHandler()));
+ XMLUtils::normalizePIValue(handler->mValue);
+
+ nsAutoString name;
+ nsresult rv = mName->evaluateToString(aEs.getEvalContext(), name);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Check name validity (must be valid NCName and a PITarget)
+ // XXX Need to check for NCName and PITarget
+ const char16_t* colon;
+ if (!XMLUtils::isValidQName(name, &colon)) {
+ // XXX ErrorReport: bad PI-target
+ return NS_ERROR_FAILURE;
+ }
+
+ return aEs.mResultHandler->processingInstruction(name, handler->mValue);
+}
+
+txPushNewContext::txPushNewContext(UniquePtr<Expr>&& aSelect)
+ : mSelect(std::move(aSelect)), mBailTarget(nullptr) {}
+
+txPushNewContext::~txPushNewContext() = default;
+
+nsresult txPushNewContext::execute(txExecutionState& aEs) {
+ RefPtr<txAExprResult> exprRes;
+ nsresult rv =
+ mSelect->evaluate(aEs.getEvalContext(), getter_AddRefs(exprRes));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (exprRes->getResultType() != txAExprResult::NODESET) {
+ // XXX ErrorReport: nodeset expected
+ return NS_ERROR_XSLT_NODESET_EXPECTED;
+ }
+
+ txNodeSet* nodes =
+ static_cast<txNodeSet*>(static_cast<txAExprResult*>(exprRes));
+
+ if (nodes->isEmpty()) {
+ aEs.gotoInstruction(mBailTarget);
+
+ return NS_OK;
+ }
+
+ txNodeSorter sorter;
+ uint32_t i, count = mSortKeys.Length();
+ for (i = 0; i < count; ++i) {
+ SortKey& sort = mSortKeys[i];
+ rv = sorter.addSortElement(sort.mSelectExpr.get(), sort.mLangExpr.get(),
+ sort.mDataTypeExpr.get(), sort.mOrderExpr.get(),
+ sort.mCaseOrderExpr.get(), aEs.getEvalContext());
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ RefPtr<txNodeSet> sortedNodes;
+ rv = sorter.sortNodeSet(nodes, &aEs, getter_AddRefs(sortedNodes));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ auto context = MakeUnique<txNodeSetContext>(sortedNodes, &aEs);
+ context->next();
+
+ aEs.pushEvalContext(context.release());
+
+ return NS_OK;
+}
+
+void txPushNewContext::addSort(UniquePtr<Expr>&& aSelectExpr,
+ UniquePtr<Expr>&& aLangExpr,
+ UniquePtr<Expr>&& aDataTypeExpr,
+ UniquePtr<Expr>&& aOrderExpr,
+ UniquePtr<Expr>&& aCaseOrderExpr) {
+ SortKey* key = mSortKeys.AppendElement();
+ // workaround for not triggering the Copy Constructor
+ key->mSelectExpr = std::move(aSelectExpr);
+ key->mLangExpr = std::move(aLangExpr);
+ key->mDataTypeExpr = std::move(aDataTypeExpr);
+ key->mOrderExpr = std::move(aOrderExpr);
+ key->mCaseOrderExpr = std::move(aCaseOrderExpr);
+}
+
+nsresult txPushNullTemplateRule::execute(txExecutionState& aEs) {
+ aEs.pushTemplateRule(nullptr, txExpandedName(), nullptr);
+ return NS_OK;
+}
+
+nsresult txPushParams::execute(txExecutionState& aEs) {
+ aEs.pushParamMap(nullptr);
+ return NS_OK;
+}
+
+nsresult txPushRTFHandler::execute(txExecutionState& aEs) {
+ aEs.pushResultHandler(new txRtfHandler);
+
+ return NS_OK;
+}
+
+txPushStringHandler::txPushStringHandler(bool aOnlyText)
+ : mOnlyText(aOnlyText) {}
+
+nsresult txPushStringHandler::execute(txExecutionState& aEs) {
+ aEs.pushResultHandler(new txTextHandler(mOnlyText));
+
+ return NS_OK;
+}
+
+txRemoveVariable::txRemoveVariable(const txExpandedName& aName)
+ : mName(aName) {}
+
+nsresult txRemoveVariable::execute(txExecutionState& aEs) {
+ aEs.removeVariable(mName);
+
+ return NS_OK;
+}
+
+nsresult txReturn::execute(txExecutionState& aEs) {
+ NS_ASSERTION(!mNext, "instructions exist after txReturn");
+ aEs.returnFromTemplate();
+
+ return NS_OK;
+}
+
+txSetParam::txSetParam(const txExpandedName& aName, UniquePtr<Expr>&& aValue)
+ : mName(aName), mValue(std::move(aValue)) {}
+
+nsresult txSetParam::execute(txExecutionState& aEs) {
+ nsresult rv = NS_OK;
+ if (!aEs.mTemplateParams) {
+ aEs.mTemplateParams = new txParameterMap;
+ }
+
+ RefPtr<txAExprResult> exprRes;
+ if (mValue) {
+ rv = mValue->evaluate(aEs.getEvalContext(), getter_AddRefs(exprRes));
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ UniquePtr<txRtfHandler> rtfHandler(
+ static_cast<txRtfHandler*>(aEs.popResultHandler()));
+ rv = rtfHandler->getAsRTF(getter_AddRefs(exprRes));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ rv = aEs.mTemplateParams->bindVariable(mName, exprRes);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+txSetVariable::txSetVariable(const txExpandedName& aName,
+ UniquePtr<Expr>&& aValue)
+ : mName(aName), mValue(std::move(aValue)) {}
+
+nsresult txSetVariable::execute(txExecutionState& aEs) {
+ nsresult rv = NS_OK;
+ RefPtr<txAExprResult> exprRes;
+ if (mValue) {
+ rv = mValue->evaluate(aEs.getEvalContext(), getter_AddRefs(exprRes));
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ UniquePtr<txRtfHandler> rtfHandler(
+ static_cast<txRtfHandler*>(aEs.popResultHandler()));
+ rv = rtfHandler->getAsRTF(getter_AddRefs(exprRes));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return aEs.bindVariable(mName, exprRes);
+}
+
+txStartElement::txStartElement(UniquePtr<Expr>&& aName,
+ UniquePtr<Expr>&& aNamespace,
+ txNamespaceMap* aMappings)
+ : mName(std::move(aName)),
+ mNamespace(std::move(aNamespace)),
+ mMappings(aMappings) {}
+
+nsresult txStartElement::execute(txExecutionState& aEs) {
+ nsAutoString name;
+ nsresult rv = mName->evaluateToString(aEs.getEvalContext(), name);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t nsId = kNameSpaceID_None;
+ RefPtr<nsAtom> prefix;
+ uint32_t lnameStart = 0;
+
+ const char16_t* colon;
+ if (XMLUtils::isValidQName(name, &colon)) {
+ if (colon) {
+ prefix = NS_Atomize(Substring(name.get(), colon));
+ lnameStart = colon - name.get() + 1;
+ }
+
+ if (mNamespace) {
+ nsAutoString nspace;
+ rv = mNamespace->evaluateToString(aEs.getEvalContext(), nspace);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!nspace.IsEmpty()) {
+ nsId = txNamespaceManager::getNamespaceID(nspace);
+ }
+ } else {
+ nsId = mMappings->lookupNamespace(prefix);
+ }
+ } else {
+ nsId = kNameSpaceID_Unknown;
+ }
+
+ bool success = true;
+
+ if (nsId != kNameSpaceID_Unknown) {
+ rv = aEs.mResultHandler->startElement(prefix, Substring(name, lnameStart),
+ nsId);
+ } else {
+ rv = NS_ERROR_XSLT_BAD_NODE_NAME;
+ }
+
+ if (rv == NS_ERROR_XSLT_BAD_NODE_NAME) {
+ success = false;
+ // we call characters with an empty string to "close" any element to
+ // make sure that no attributes are added
+ rv = aEs.mResultHandler->characters(u""_ns, false);
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aEs.pushBool(success);
+
+ return NS_OK;
+}
+
+txStartLREElement::txStartLREElement(int32_t aNamespaceID, nsAtom* aLocalName,
+ nsAtom* aPrefix)
+ : mNamespaceID(aNamespaceID), mLocalName(aLocalName), mPrefix(aPrefix) {
+ if (aNamespaceID == kNameSpaceID_None) {
+ mLowercaseLocalName = TX_ToLowerCaseAtom(aLocalName);
+ }
+}
+
+nsresult txStartLREElement::execute(txExecutionState& aEs) {
+ nsresult rv = aEs.mResultHandler->startElement(
+ mPrefix, mLocalName, mLowercaseLocalName, mNamespaceID);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aEs.pushBool(true);
+
+ return NS_OK;
+}
+
+txText::txText(const nsAString& aStr, bool aDOE) : mStr(aStr), mDOE(aDOE) {}
+
+nsresult txText::execute(txExecutionState& aEs) {
+ return aEs.mResultHandler->characters(mStr, mDOE);
+}
+
+txValueOf::txValueOf(UniquePtr<Expr>&& aExpr, bool aDOE)
+ : mExpr(std::move(aExpr)), mDOE(aDOE) {}
+
+nsresult txValueOf::execute(txExecutionState& aEs) {
+ RefPtr<txAExprResult> exprRes;
+ nsresult rv = mExpr->evaluate(aEs.getEvalContext(), getter_AddRefs(exprRes));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ const nsString* value = exprRes->stringValuePointer();
+ if (value) {
+ if (!value->IsEmpty()) {
+ return aEs.mResultHandler->characters(*value, mDOE);
+ }
+ } else {
+ nsAutoString valueStr;
+ exprRes->stringValue(valueStr);
+ if (!valueStr.IsEmpty()) {
+ return aEs.mResultHandler->characters(valueStr, mDOE);
+ }
+ }
+
+ return NS_OK;
+}
diff --git a/dom/xslt/xslt/txInstructions.h b/dom/xslt/xslt/txInstructions.h
new file mode 100644
index 0000000000..5324303704
--- /dev/null
+++ b/dom/xslt/xslt/txInstructions.h
@@ -0,0 +1,362 @@
+/* -*- 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/. */
+
+#ifndef TRANSFRMX_TXINSTRUCTIONS_H
+#define TRANSFRMX_TXINSTRUCTIONS_H
+
+#include "mozilla/UniquePtr.h"
+#include "nsCOMPtr.h"
+#include "txCore.h"
+#include "nsString.h"
+#include "txXMLUtils.h"
+#include "txExpandedName.h"
+#include "txNamespaceMap.h"
+#include "txXSLTNumber.h"
+#include "nsTArray.h"
+
+class nsAtom;
+class txExecutionState;
+
+class txInstruction : public txObject {
+ public:
+ MOZ_COUNTED_DEFAULT_CTOR(txInstruction)
+
+ ~txInstruction() override {
+ MOZ_COUNT_DTOR(txInstruction);
+
+ mozilla::UniquePtr<txInstruction> next(std::move(mNext));
+ while (next) {
+ mozilla::UniquePtr<txInstruction> destroy(std::move(next));
+ next = std::move(destroy->mNext);
+ }
+ }
+
+ virtual nsresult execute(txExecutionState& aEs) = 0;
+
+ mozilla::UniquePtr<txInstruction> mNext;
+};
+
+#define TX_DECL_TXINSTRUCTION \
+ virtual nsresult execute(txExecutionState& aEs) override;
+
+class txApplyDefaultElementTemplate : public txInstruction {
+ public:
+ TX_DECL_TXINSTRUCTION
+};
+
+class txApplyImportsEnd : public txInstruction {
+ public:
+ TX_DECL_TXINSTRUCTION
+};
+
+class txApplyImportsStart : public txInstruction {
+ public:
+ TX_DECL_TXINSTRUCTION
+};
+
+class txApplyTemplates : public txInstruction {
+ public:
+ explicit txApplyTemplates(const txExpandedName& aMode);
+
+ TX_DECL_TXINSTRUCTION
+
+ txExpandedName mMode;
+};
+
+class txAttribute : public txInstruction {
+ public:
+ txAttribute(mozilla::UniquePtr<Expr>&& aName,
+ mozilla::UniquePtr<Expr>&& aNamespace, txNamespaceMap* aMappings);
+
+ TX_DECL_TXINSTRUCTION
+
+ mozilla::UniquePtr<Expr> mName;
+ mozilla::UniquePtr<Expr> mNamespace;
+ RefPtr<txNamespaceMap> mMappings;
+};
+
+class txCallTemplate : public txInstruction {
+ public:
+ explicit txCallTemplate(const txExpandedName& aName);
+
+ TX_DECL_TXINSTRUCTION
+
+ txExpandedName mName;
+};
+
+class txCheckParam : public txInstruction {
+ public:
+ explicit txCheckParam(const txExpandedName& aName);
+
+ TX_DECL_TXINSTRUCTION
+
+ txExpandedName mName;
+ txInstruction* mBailTarget;
+};
+
+class txConditionalGoto : public txInstruction {
+ public:
+ txConditionalGoto(mozilla::UniquePtr<Expr>&& aCondition,
+ txInstruction* aTarget);
+
+ TX_DECL_TXINSTRUCTION
+
+ mozilla::UniquePtr<Expr> mCondition;
+ txInstruction* mTarget;
+};
+
+class txComment : public txInstruction {
+ public:
+ TX_DECL_TXINSTRUCTION
+};
+
+class txCopyBase : public txInstruction {
+ protected:
+ nsresult copyNode(const txXPathNode& aNode, txExecutionState& aEs);
+};
+
+class txCopy : public txCopyBase {
+ public:
+ txCopy();
+
+ TX_DECL_TXINSTRUCTION
+
+ txInstruction* mBailTarget;
+};
+
+class txCopyOf : public txCopyBase {
+ public:
+ explicit txCopyOf(mozilla::UniquePtr<Expr>&& aSelect);
+
+ TX_DECL_TXINSTRUCTION
+
+ mozilla::UniquePtr<Expr> mSelect;
+};
+
+class txEndElement : public txInstruction {
+ public:
+ TX_DECL_TXINSTRUCTION
+};
+
+class txErrorInstruction : public txInstruction {
+ public:
+ TX_DECL_TXINSTRUCTION
+};
+
+class txGoTo : public txInstruction {
+ public:
+ explicit txGoTo(txInstruction* aTarget);
+
+ TX_DECL_TXINSTRUCTION
+
+ txInstruction* mTarget;
+};
+
+class txInsertAttrSet : public txInstruction {
+ public:
+ explicit txInsertAttrSet(const txExpandedName& aName);
+
+ TX_DECL_TXINSTRUCTION
+
+ txExpandedName mName;
+};
+
+class txLoopNodeSet : public txInstruction {
+ public:
+ explicit txLoopNodeSet(txInstruction* aTarget);
+
+ TX_DECL_TXINSTRUCTION
+
+ txInstruction* mTarget;
+};
+
+class txLREAttribute : public txInstruction {
+ public:
+ txLREAttribute(int32_t aNamespaceID, nsAtom* aLocalName, nsAtom* aPrefix,
+ mozilla::UniquePtr<Expr>&& aValue);
+
+ TX_DECL_TXINSTRUCTION
+
+ int32_t mNamespaceID;
+ RefPtr<nsAtom> mLocalName;
+ RefPtr<nsAtom> mLowercaseLocalName;
+ RefPtr<nsAtom> mPrefix;
+ mozilla::UniquePtr<Expr> mValue;
+};
+
+class txMessage : public txInstruction {
+ public:
+ explicit txMessage(bool aTerminate);
+
+ TX_DECL_TXINSTRUCTION
+
+ bool mTerminate;
+};
+
+class txNumber : public txInstruction {
+ public:
+ txNumber(txXSLTNumber::LevelType aLevel,
+ mozilla::UniquePtr<txPattern>&& aCount,
+ mozilla::UniquePtr<txPattern>&& aFrom,
+ mozilla::UniquePtr<Expr>&& aValue,
+ mozilla::UniquePtr<Expr>&& aFormat,
+ mozilla::UniquePtr<Expr>&& aGroupingSeparator,
+ mozilla::UniquePtr<Expr>&& aGroupingSize);
+
+ TX_DECL_TXINSTRUCTION
+
+ txXSLTNumber::LevelType mLevel;
+ mozilla::UniquePtr<txPattern> mCount;
+ mozilla::UniquePtr<txPattern> mFrom;
+ mozilla::UniquePtr<Expr> mValue;
+ mozilla::UniquePtr<Expr> mFormat;
+ mozilla::UniquePtr<Expr> mGroupingSeparator;
+ mozilla::UniquePtr<Expr> mGroupingSize;
+};
+
+class txPopParams : public txInstruction {
+ public:
+ TX_DECL_TXINSTRUCTION
+};
+
+class txProcessingInstruction : public txInstruction {
+ public:
+ explicit txProcessingInstruction(mozilla::UniquePtr<Expr>&& aName);
+
+ TX_DECL_TXINSTRUCTION
+
+ mozilla::UniquePtr<Expr> mName;
+};
+
+class txPushNewContext : public txInstruction {
+ public:
+ explicit txPushNewContext(mozilla::UniquePtr<Expr>&& aSelect);
+ ~txPushNewContext();
+
+ TX_DECL_TXINSTRUCTION
+
+ void addSort(mozilla::UniquePtr<Expr>&& aSelectExpr,
+ mozilla::UniquePtr<Expr>&& aLangExpr,
+ mozilla::UniquePtr<Expr>&& aDataTypeExpr,
+ mozilla::UniquePtr<Expr>&& aOrderExpr,
+ mozilla::UniquePtr<Expr>&& aCaseOrderExpr);
+
+ struct SortKey {
+ mozilla::UniquePtr<Expr> mSelectExpr;
+ mozilla::UniquePtr<Expr> mLangExpr;
+ mozilla::UniquePtr<Expr> mDataTypeExpr;
+ mozilla::UniquePtr<Expr> mOrderExpr;
+ mozilla::UniquePtr<Expr> mCaseOrderExpr;
+ };
+
+ nsTArray<SortKey> mSortKeys;
+ mozilla::UniquePtr<Expr> mSelect;
+ txInstruction* mBailTarget;
+};
+
+class txPushNullTemplateRule : public txInstruction {
+ public:
+ TX_DECL_TXINSTRUCTION
+};
+
+class txPushParams : public txInstruction {
+ public:
+ TX_DECL_TXINSTRUCTION
+};
+
+class txPushRTFHandler : public txInstruction {
+ public:
+ TX_DECL_TXINSTRUCTION
+};
+
+class txPushStringHandler : public txInstruction {
+ public:
+ explicit txPushStringHandler(bool aOnlyText);
+
+ TX_DECL_TXINSTRUCTION
+
+ bool mOnlyText;
+};
+
+class txRemoveVariable : public txInstruction {
+ public:
+ explicit txRemoveVariable(const txExpandedName& aName);
+
+ TX_DECL_TXINSTRUCTION
+
+ txExpandedName mName;
+};
+
+class txReturn : public txInstruction {
+ public:
+ TX_DECL_TXINSTRUCTION
+};
+
+class txSetParam : public txInstruction {
+ public:
+ txSetParam(const txExpandedName& aName, mozilla::UniquePtr<Expr>&& aValue);
+
+ TX_DECL_TXINSTRUCTION
+
+ txExpandedName mName;
+ mozilla::UniquePtr<Expr> mValue;
+};
+
+class txSetVariable : public txInstruction {
+ public:
+ txSetVariable(const txExpandedName& aName, mozilla::UniquePtr<Expr>&& aValue);
+
+ TX_DECL_TXINSTRUCTION
+
+ txExpandedName mName;
+ mozilla::UniquePtr<Expr> mValue;
+};
+
+class txStartElement : public txInstruction {
+ public:
+ txStartElement(mozilla::UniquePtr<Expr>&& aName,
+ mozilla::UniquePtr<Expr>&& aNamespace,
+ txNamespaceMap* aMappings);
+
+ TX_DECL_TXINSTRUCTION
+
+ mozilla::UniquePtr<Expr> mName;
+ mozilla::UniquePtr<Expr> mNamespace;
+ RefPtr<txNamespaceMap> mMappings;
+};
+
+class txStartLREElement : public txInstruction {
+ public:
+ txStartLREElement(int32_t aNamespaceID, nsAtom* aLocalName, nsAtom* aPrefix);
+
+ TX_DECL_TXINSTRUCTION
+
+ int32_t mNamespaceID;
+ RefPtr<nsAtom> mLocalName;
+ RefPtr<nsAtom> mLowercaseLocalName;
+ RefPtr<nsAtom> mPrefix;
+};
+
+class txText : public txInstruction {
+ public:
+ txText(const nsAString& aStr, bool aDOE);
+
+ TX_DECL_TXINSTRUCTION
+
+ nsString mStr;
+ bool mDOE;
+};
+
+class txValueOf : public txInstruction {
+ public:
+ txValueOf(mozilla::UniquePtr<Expr>&& aExpr, bool aDOE);
+
+ TX_DECL_TXINSTRUCTION
+
+ mozilla::UniquePtr<Expr> mExpr;
+ bool mDOE;
+};
+
+#endif // TRANSFRMX_TXINSTRUCTIONS_H
diff --git a/dom/xslt/xslt/txKey.h b/dom/xslt/xslt/txKey.h
new file mode 100644
index 0000000000..e3bf56ebaa
--- /dev/null
+++ b/dom/xslt/xslt/txKey.h
@@ -0,0 +1,186 @@
+/* -*- 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/. */
+
+#ifndef txKey_h__
+#define txKey_h__
+
+#include "nsTHashtable.h"
+#include "txExpandedNameMap.h"
+#include "txList.h"
+#include "txNodeSet.h"
+#include "txXSLTPatterns.h"
+#include "txXMLUtils.h"
+
+class txPattern;
+class Expr;
+class txExecutionState;
+
+class txKeyValueHashKey {
+ public:
+ txKeyValueHashKey(const txExpandedName& aKeyName, int32_t aRootIdentifier,
+ const nsAString& aKeyValue)
+ : mKeyName(aKeyName),
+ mKeyValue(aKeyValue),
+ mRootIdentifier(aRootIdentifier) {}
+
+ txExpandedName mKeyName;
+ nsString mKeyValue;
+ int32_t mRootIdentifier;
+};
+
+struct txKeyValueHashEntry : public PLDHashEntryHdr {
+ public:
+ using KeyType = const txKeyValueHashKey&;
+ using KeyTypePointer = const txKeyValueHashKey*;
+
+ explicit txKeyValueHashEntry(KeyTypePointer aKey)
+ : mKey(*aKey), mNodeSet(new txNodeSet(nullptr)) {}
+
+ txKeyValueHashEntry(const txKeyValueHashEntry& entry)
+ : mKey(entry.mKey), mNodeSet(entry.mNodeSet) {}
+
+ bool KeyEquals(KeyTypePointer aKey) const;
+
+ static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; }
+
+ static PLDHashNumber HashKey(KeyTypePointer aKey);
+
+ enum { ALLOW_MEMMOVE = true };
+
+ txKeyValueHashKey mKey;
+ RefPtr<txNodeSet> mNodeSet;
+};
+
+using txKeyValueHash = nsTHashtable<txKeyValueHashEntry>;
+
+class txIndexedKeyHashKey {
+ public:
+ txIndexedKeyHashKey(txExpandedName aKeyName, int32_t aRootIdentifier)
+ : mKeyName(aKeyName), mRootIdentifier(aRootIdentifier) {}
+
+ txExpandedName mKeyName;
+ int32_t mRootIdentifier;
+};
+
+struct txIndexedKeyHashEntry : public PLDHashEntryHdr {
+ public:
+ using KeyType = const txIndexedKeyHashKey&;
+ using KeyTypePointer = const txIndexedKeyHashKey*;
+
+ explicit txIndexedKeyHashEntry(KeyTypePointer aKey)
+ : mKey(*aKey), mIndexed(false) {}
+
+ txIndexedKeyHashEntry(const txIndexedKeyHashEntry& entry)
+ : mKey(entry.mKey), mIndexed(entry.mIndexed) {}
+
+ bool KeyEquals(KeyTypePointer aKey) const;
+
+ static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; }
+
+ static PLDHashNumber HashKey(KeyTypePointer aKey);
+
+ enum { ALLOW_MEMMOVE = true };
+
+ txIndexedKeyHashKey mKey;
+ bool mIndexed;
+};
+
+using txIndexedKeyHash = nsTHashtable<txIndexedKeyHashEntry>;
+
+/**
+ * Class holding all <xsl:key>s of a particular expanded name in the
+ * stylesheet.
+ */
+class txXSLKey {
+ public:
+ explicit txXSLKey(const txExpandedName& aName) : mName(aName) {}
+
+ /**
+ * Adds a match/use pair.
+ * @param aMatch match-pattern
+ * @param aUse use-expression
+ * @return false if an error occurred, true otherwise
+ */
+ bool addKey(mozilla::UniquePtr<txPattern>&& aMatch,
+ mozilla::UniquePtr<Expr>&& aUse);
+
+ /**
+ * Indexes a subtree and adds it to the hash of key values
+ * @param aRoot Subtree root to index and add
+ * @param aKeyValueHash Hash to add values to
+ * @param aEs txExecutionState to use for XPath evaluation
+ */
+ nsresult indexSubtreeRoot(const txXPathNode& aRoot,
+ txKeyValueHash& aKeyValueHash,
+ txExecutionState& aEs);
+
+ private:
+ /**
+ * Recursively searches a node, its attributes and its subtree for
+ * nodes matching any of the keys match-patterns.
+ * @param aNode Node to search
+ * @param aKey Key to use when adding into the hash
+ * @param aKeyValueHash Hash to add values to
+ * @param aEs txExecutionState to use for XPath evaluation
+ */
+ nsresult indexTree(const txXPathNode& aNode, txKeyValueHashKey& aKey,
+ txKeyValueHash& aKeyValueHash, txExecutionState& aEs);
+
+ /**
+ * Tests one node if it matches any of the keys match-patterns. If
+ * the node matches its values are added to the index.
+ * @param aNode Node to test
+ * @param aKey Key to use when adding into the hash
+ * @param aKeyValueHash Hash to add values to
+ * @param aEs txExecutionState to use for XPath evaluation
+ */
+ nsresult testNode(const txXPathNode& aNode, txKeyValueHashKey& aKey,
+ txKeyValueHash& aKeyValueHash, txExecutionState& aEs);
+
+ /**
+ * represents one match/use pair
+ */
+ struct Key {
+ mozilla::UniquePtr<txPattern> matchPattern;
+ mozilla::UniquePtr<Expr> useExpr;
+ };
+
+ /**
+ * List of all match/use pairs. The items as |Key|s
+ */
+ nsTArray<Key> mKeys;
+
+ /**
+ * Name of this key
+ */
+ txExpandedName mName;
+};
+
+class txKeyHash {
+ public:
+ explicit txKeyHash(const txOwningExpandedNameMap<txXSLKey>& aKeys)
+ : mKeyValues(4), mIndexedKeys(1), mKeys(aKeys) {}
+
+ nsresult init();
+
+ nsresult getKeyNodes(const txExpandedName& aKeyName, const txXPathNode& aRoot,
+ const nsAString& aKeyValue, bool aIndexIfNotFound,
+ txExecutionState& aEs, txNodeSet** aResult);
+
+ private:
+ // Hash of all indexed key-values
+ txKeyValueHash mKeyValues;
+
+ // Hash showing which keys+roots has been indexed
+ txIndexedKeyHash mIndexedKeys;
+
+ // Map of txXSLKeys
+ const txOwningExpandedNameMap<txXSLKey>& mKeys;
+
+ // Empty nodeset returned if no key is found
+ RefPtr<txNodeSet> mEmptyNodeSet;
+};
+
+#endif // txKey_h__
diff --git a/dom/xslt/xslt/txKeyFunctionCall.cpp b/dom/xslt/xslt/txKeyFunctionCall.cpp
new file mode 100644
index 0000000000..9219b335ca
--- /dev/null
+++ b/dom/xslt/xslt/txKeyFunctionCall.cpp
@@ -0,0 +1,345 @@
+/* -*- 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 <utility>
+
+#include "mozilla/HashFunctions.h"
+#include "nsGkAtoms.h"
+#include "nsReadableUtils.h"
+#include "txExecutionState.h"
+#include "txKey.h"
+#include "txNamespaceMap.h"
+#include "txSingleNodeContext.h"
+#include "txXSLTFunctions.h"
+#include "txXSLTPatterns.h"
+
+using namespace mozilla;
+
+/*
+ * txKeyFunctionCall
+ * A representation of the XSLT additional function: key()
+ */
+
+/*
+ * Creates a new key function call
+ */
+txKeyFunctionCall::txKeyFunctionCall(txNamespaceMap* aMappings)
+ : mMappings(aMappings) {}
+
+/*
+ * Evaluates a key() xslt-function call. First argument is name of key
+ * to use, second argument is value to look up.
+ * @param aContext the context node for evaluation of this Expr
+ * @param aCs the ContextState containing the stack information needed
+ * for evaluation
+ * @return the result of the evaluation
+ */
+nsresult txKeyFunctionCall::evaluate(txIEvalContext* aContext,
+ txAExprResult** aResult) {
+ if (!aContext || !requireParams(2, 2, aContext))
+ return NS_ERROR_XPATH_BAD_ARGUMENT_COUNT;
+
+ txExecutionState* es =
+ static_cast<txExecutionState*>(aContext->getPrivateContext());
+
+ nsAutoString keyQName;
+ nsresult rv = mParams[0]->evaluateToString(aContext, keyQName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ txExpandedName keyName;
+ rv = keyName.init(keyQName, mMappings, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<txAExprResult> exprResult;
+ rv = mParams[1]->evaluate(aContext, getter_AddRefs(exprResult));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ txXPathTreeWalker walker(aContext->getContextNode());
+ walker.moveToRoot();
+
+ RefPtr<txNodeSet> res;
+ txNodeSet* nodeSet;
+ if (exprResult->getResultType() == txAExprResult::NODESET &&
+ (nodeSet =
+ static_cast<txNodeSet*>(static_cast<txAExprResult*>(exprResult)))
+ ->size() > 1) {
+ rv = aContext->recycler()->getNodeSet(getter_AddRefs(res));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t i;
+ for (i = 0; i < nodeSet->size(); ++i) {
+ nsAutoString val;
+ txXPathNodeUtils::appendNodeValue(nodeSet->get(i), val);
+
+ RefPtr<txNodeSet> nodes;
+ rv = es->getKeyNodes(keyName, walker.getCurrentPosition(), val, i == 0,
+ getter_AddRefs(nodes));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ res->add(*nodes);
+ }
+ } else {
+ nsAutoString val;
+ exprResult->stringValue(val);
+ rv = es->getKeyNodes(keyName, walker.getCurrentPosition(), val, true,
+ getter_AddRefs(res));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ *aResult = res;
+ NS_ADDREF(*aResult);
+
+ return NS_OK;
+}
+
+Expr::ResultType txKeyFunctionCall::getReturnType() { return NODESET_RESULT; }
+
+bool txKeyFunctionCall::isSensitiveTo(ContextSensitivity aContext) {
+ return (aContext & NODE_CONTEXT) || argsSensitiveTo(aContext);
+}
+
+#ifdef TX_TO_STRING
+void txKeyFunctionCall::appendName(nsAString& aDest) {
+ aDest.Append(nsGkAtoms::key->GetUTF16String());
+}
+#endif
+
+/**
+ * Hash functions
+ */
+
+bool txKeyValueHashEntry::KeyEquals(KeyTypePointer aKey) const {
+ return mKey.mKeyName == aKey->mKeyName &&
+ mKey.mRootIdentifier == aKey->mRootIdentifier &&
+ mKey.mKeyValue.Equals(aKey->mKeyValue);
+}
+
+PLDHashNumber txKeyValueHashEntry::HashKey(KeyTypePointer aKey) {
+ const txKeyValueHashKey* key = static_cast<const txKeyValueHashKey*>(aKey);
+
+ return AddToHash(HashString(key->mKeyValue), key->mKeyName.mNamespaceID,
+ key->mRootIdentifier, key->mKeyName.mLocalName.get());
+}
+
+bool txIndexedKeyHashEntry::KeyEquals(KeyTypePointer aKey) const {
+ return mKey.mKeyName == aKey->mKeyName &&
+ mKey.mRootIdentifier == aKey->mRootIdentifier;
+}
+
+PLDHashNumber txIndexedKeyHashEntry::HashKey(KeyTypePointer aKey) {
+ const txIndexedKeyHashKey* key =
+ static_cast<const txIndexedKeyHashKey*>(aKey);
+ return HashGeneric(key->mKeyName.mNamespaceID, key->mRootIdentifier,
+ key->mKeyName.mLocalName.get());
+}
+
+/*
+ * Class managing XSLT-keys
+ */
+
+nsresult txKeyHash::getKeyNodes(const txExpandedName& aKeyName,
+ const txXPathNode& aRoot,
+ const nsAString& aKeyValue,
+ bool aIndexIfNotFound, txExecutionState& aEs,
+ txNodeSet** aResult) {
+ *aResult = nullptr;
+
+ int32_t identifier = txXPathNodeUtils::getUniqueIdentifier(aRoot);
+
+ txKeyValueHashKey valueKey(aKeyName, identifier, aKeyValue);
+ txKeyValueHashEntry* valueEntry = mKeyValues.GetEntry(valueKey);
+ if (valueEntry) {
+ *aResult = valueEntry->mNodeSet;
+ NS_ADDREF(*aResult);
+
+ return NS_OK;
+ }
+
+ // We didn't find a value. This could either mean that that key has no
+ // nodes with that value or that the key hasn't been indexed using this
+ // document.
+
+ if (!aIndexIfNotFound) {
+ // If aIndexIfNotFound is set then the caller knows this key is
+ // indexed, so don't bother investigating.
+ *aResult = mEmptyNodeSet;
+ NS_ADDREF(*aResult);
+
+ return NS_OK;
+ }
+
+ txIndexedKeyHashKey indexKey(aKeyName, identifier);
+ txIndexedKeyHashEntry* indexEntry = mIndexedKeys.PutEntry(indexKey);
+ NS_ENSURE_TRUE(indexEntry, NS_ERROR_OUT_OF_MEMORY);
+
+ if (indexEntry->mIndexed) {
+ // The key was indexed and apparently didn't contain this value so
+ // return the empty nodeset.
+ *aResult = mEmptyNodeSet;
+ NS_ADDREF(*aResult);
+
+ return NS_OK;
+ }
+
+ // The key needs to be indexed.
+ txXSLKey* xslKey = mKeys.get(aKeyName);
+ if (!xslKey) {
+ // The key didn't exist, so bail.
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ nsresult rv = xslKey->indexSubtreeRoot(aRoot, mKeyValues, aEs);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ indexEntry->mIndexed = true;
+
+ // Now that the key is indexed we can get its value.
+ valueEntry = mKeyValues.GetEntry(valueKey);
+ if (valueEntry) {
+ *aResult = valueEntry->mNodeSet;
+ NS_ADDREF(*aResult);
+ } else {
+ *aResult = mEmptyNodeSet;
+ NS_ADDREF(*aResult);
+ }
+
+ return NS_OK;
+}
+
+nsresult txKeyHash::init() {
+ mEmptyNodeSet = new txNodeSet(nullptr);
+
+ return NS_OK;
+}
+
+/**
+ * Adds a match/use pair.
+ * @param aMatch match-pattern
+ * @param aUse use-expression
+ * @return false if an error occurred, true otherwise
+ */
+bool txXSLKey::addKey(UniquePtr<txPattern>&& aMatch, UniquePtr<Expr>&& aUse) {
+ if (!aMatch || !aUse) return false;
+
+ Key* key = mKeys.AppendElement();
+ if (!key) return false;
+
+ key->matchPattern = std::move(aMatch);
+ key->useExpr = std::move(aUse);
+
+ return true;
+}
+
+/**
+ * Indexes a document and adds it to the hash of key values
+ * @param aRoot Subtree root to index and add
+ * @param aKeyValueHash Hash to add values to
+ * @param aEs txExecutionState to use for XPath evaluation
+ */
+nsresult txXSLKey::indexSubtreeRoot(const txXPathNode& aRoot,
+ txKeyValueHash& aKeyValueHash,
+ txExecutionState& aEs) {
+ txKeyValueHashKey key(mName, txXPathNodeUtils::getUniqueIdentifier(aRoot),
+ u""_ns);
+ return indexTree(aRoot, key, aKeyValueHash, aEs);
+}
+
+/**
+ * Recursively searches a node, its attributes and its subtree for
+ * nodes matching any of the keys match-patterns.
+ * @param aNode Node to search
+ * @param aKey Key to use when adding into the hash
+ * @param aKeyValueHash Hash to add values to
+ * @param aEs txExecutionState to use for XPath evaluation
+ */
+nsresult txXSLKey::indexTree(const txXPathNode& aNode, txKeyValueHashKey& aKey,
+ txKeyValueHash& aKeyValueHash,
+ txExecutionState& aEs) {
+ nsresult rv = testNode(aNode, aKey, aKeyValueHash, aEs);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // check if the node's attributes match
+ txXPathTreeWalker walker(aNode);
+ if (walker.moveToFirstAttribute()) {
+ do {
+ rv = testNode(walker.getCurrentPosition(), aKey, aKeyValueHash, aEs);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } while (walker.moveToNextAttribute());
+ walker.moveToParent();
+ }
+
+ // check if the node's descendants match
+ if (walker.moveToFirstChild()) {
+ do {
+ rv = indexTree(walker.getCurrentPosition(), aKey, aKeyValueHash, aEs);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } while (walker.moveToNextSibling());
+ }
+
+ return NS_OK;
+}
+
+/**
+ * Tests one node if it matches any of the keys match-patterns. If
+ * the node matches its values are added to the index.
+ * @param aNode Node to test
+ * @param aKey Key to use when adding into the hash
+ * @param aKeyValueHash Hash to add values to
+ * @param aEs txExecutionState to use for XPath evaluation
+ */
+nsresult txXSLKey::testNode(const txXPathNode& aNode, txKeyValueHashKey& aKey,
+ txKeyValueHash& aKeyValueHash,
+ txExecutionState& aEs) {
+ nsAutoString val;
+ uint32_t currKey, numKeys = mKeys.Length();
+ for (currKey = 0; currKey < numKeys; ++currKey) {
+ bool matched;
+ nsresult rv = mKeys[currKey].matchPattern->matches(aNode, &aEs, matched);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (matched) {
+ aEs.pushEvalContext(new txSingleNodeContext(aNode, &aEs));
+
+ RefPtr<txAExprResult> exprResult;
+ nsresult rv = mKeys[currKey].useExpr->evaluate(
+ aEs.getEvalContext(), getter_AddRefs(exprResult));
+
+ delete aEs.popEvalContext();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (exprResult->getResultType() == txAExprResult::NODESET) {
+ txNodeSet* res =
+ static_cast<txNodeSet*>(static_cast<txAExprResult*>(exprResult));
+ int32_t i;
+ for (i = 0; i < res->size(); ++i) {
+ val.Truncate();
+ txXPathNodeUtils::appendNodeValue(res->get(i), val);
+
+ aKey.mKeyValue.Assign(val);
+ txKeyValueHashEntry* entry = aKeyValueHash.PutEntry(aKey);
+ NS_ENSURE_TRUE(entry && entry->mNodeSet, NS_ERROR_OUT_OF_MEMORY);
+
+ if (entry->mNodeSet->isEmpty() ||
+ entry->mNodeSet->get(entry->mNodeSet->size() - 1) != aNode) {
+ entry->mNodeSet->append(aNode);
+ }
+ }
+ } else {
+ exprResult->stringValue(val);
+
+ aKey.mKeyValue.Assign(val);
+ txKeyValueHashEntry* entry = aKeyValueHash.PutEntry(aKey);
+ NS_ENSURE_TRUE(entry && entry->mNodeSet, NS_ERROR_OUT_OF_MEMORY);
+
+ if (entry->mNodeSet->isEmpty() ||
+ entry->mNodeSet->get(entry->mNodeSet->size() - 1) != aNode) {
+ entry->mNodeSet->append(aNode);
+ }
+ }
+ }
+ }
+
+ return NS_OK;
+}
diff --git a/dom/xslt/xslt/txMozillaStylesheetCompiler.cpp b/dom/xslt/xslt/txMozillaStylesheetCompiler.cpp
new file mode 100644
index 0000000000..252347492d
--- /dev/null
+++ b/dom/xslt/xslt/txMozillaStylesheetCompiler.cpp
@@ -0,0 +1,622 @@
+/* -*- 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 "nsIAuthPrompt.h"
+#include "mozilla/dom/Document.h"
+#include "nsIExpatSink.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsILoadGroup.h"
+#include "nsParser.h"
+#include "nsCharsetSource.h"
+#include "nsIRequestObserver.h"
+#include "nsContentPolicyUtils.h"
+#include "nsIStreamConverterService.h"
+#include "nsSyncLoadService.h"
+#include "nsIHttpChannel.h"
+#include "nsIURI.h"
+#include "nsIPrincipal.h"
+#include "nsIWindowWatcher.h"
+#include "nsIXMLContentSink.h"
+#include "nsMimeTypes.h"
+#include "nsNetUtil.h"
+#include "nsGkAtoms.h"
+#include "txLog.h"
+#include "txMozillaXSLTProcessor.h"
+#include "txStylesheetCompiler.h"
+#include "txXMLUtils.h"
+#include "nsAttrName.h"
+#include "nsComponentManagerUtils.h"
+#include "nsIScriptError.h"
+#include "nsError.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/Text.h"
+#include "mozilla/Encoding.h"
+#include "mozilla/UniquePtr.h"
+#include "ReferrerInfo.h"
+
+using namespace mozilla;
+using mozilla::dom::Document;
+using mozilla::dom::ReferrerPolicy;
+
+static void getSpec(nsIChannel* aChannel, nsAString& aSpec) {
+ if (!aChannel) {
+ return;
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ aChannel->GetOriginalURI(getter_AddRefs(uri));
+ if (!uri) {
+ return;
+ }
+
+ nsAutoCString spec;
+ uri->GetSpec(spec);
+ AppendUTF8toUTF16(spec, aSpec);
+}
+
+class txStylesheetSink final : public nsIXMLContentSink,
+ public nsIExpatSink,
+ public nsIStreamListener,
+ public nsIInterfaceRequestor {
+ public:
+ txStylesheetSink(txStylesheetCompiler* aCompiler, nsParser* aParser);
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIEXPATSINK
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSIINTERFACEREQUESTOR
+
+ // nsIContentSink
+ NS_IMETHOD WillParse(void) override { return NS_OK; }
+ NS_IMETHOD DidBuildModel(bool aTerminated) override;
+ NS_IMETHOD WillInterrupt(void) override { return NS_OK; }
+ void WillResume() override {}
+ NS_IMETHOD SetParser(nsParserBase* aParser) override { return NS_OK; }
+ virtual void FlushPendingNotifications(mozilla::FlushType aType) override {}
+ virtual void SetDocumentCharset(NotNull<const Encoding*> aEncoding) override {
+ }
+ virtual nsISupports* GetTarget() override { return nullptr; }
+
+ private:
+ RefPtr<txStylesheetCompiler> mCompiler;
+ nsCOMPtr<nsIStreamListener> mListener;
+ RefPtr<nsParser> mParser;
+ bool mCheckedForXML;
+
+ protected:
+ ~txStylesheetSink() = default;
+
+ // This exists solely to suppress a warning from nsDerivedSafe
+ txStylesheetSink();
+};
+
+txStylesheetSink::txStylesheetSink(txStylesheetCompiler* aCompiler,
+ nsParser* aParser)
+ : mCompiler(aCompiler),
+ mListener(aParser),
+ mParser(aParser),
+ mCheckedForXML(false) {}
+
+NS_IMPL_ISUPPORTS(txStylesheetSink, nsIXMLContentSink, nsIContentSink,
+ nsIExpatSink, nsIStreamListener, nsIRequestObserver,
+ nsIInterfaceRequestor)
+
+NS_IMETHODIMP
+txStylesheetSink::HandleStartElement(const char16_t* aName,
+ const char16_t** aAtts,
+ uint32_t aAttsCount, uint32_t aLineNumber,
+ uint32_t aColumnNumber) {
+ MOZ_ASSERT(aAttsCount % 2 == 0, "incorrect aAttsCount");
+
+ nsresult rv = mCompiler->startElement(aName, aAtts, aAttsCount / 2);
+ if (NS_FAILED(rv)) {
+ mCompiler->cancel(rv);
+
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+txStylesheetSink::HandleEndElement(const char16_t* aName) {
+ nsresult rv = mCompiler->endElement();
+ if (NS_FAILED(rv)) {
+ mCompiler->cancel(rv);
+
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+txStylesheetSink::HandleComment(const char16_t* aName) { return NS_OK; }
+
+NS_IMETHODIMP
+txStylesheetSink::HandleCDataSection(const char16_t* aData, uint32_t aLength) {
+ return HandleCharacterData(aData, aLength);
+}
+
+NS_IMETHODIMP
+txStylesheetSink::HandleDoctypeDecl(const nsAString& aSubset,
+ const nsAString& aName,
+ const nsAString& aSystemId,
+ const nsAString& aPublicId,
+ nsISupports* aCatalogData) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+txStylesheetSink::HandleCharacterData(const char16_t* aData, uint32_t aLength) {
+ nsresult rv = mCompiler->characters(Substring(aData, aData + aLength));
+ if (NS_FAILED(rv)) {
+ mCompiler->cancel(rv);
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+txStylesheetSink::HandleProcessingInstruction(const char16_t* aTarget,
+ const char16_t* aData) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+txStylesheetSink::HandleXMLDeclaration(const char16_t* aVersion,
+ const char16_t* aEncoding,
+ int32_t aStandalone) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+txStylesheetSink::ReportError(const char16_t* aErrorText,
+ const char16_t* aSourceText,
+ nsIScriptError* aError, bool* _retval) {
+ MOZ_ASSERT(aError && aSourceText && aErrorText, "Check arguments!!!");
+
+ // The expat driver should report the error.
+ *_retval = true;
+
+ mCompiler->cancel(NS_ERROR_FAILURE, aErrorText, aSourceText);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+txStylesheetSink::DidBuildModel(bool aTerminated) {
+ return mCompiler->doneLoading();
+}
+
+NS_IMETHODIMP
+txStylesheetSink::OnDataAvailable(nsIRequest* aRequest,
+ nsIInputStream* aInputStream,
+ uint64_t aOffset, uint32_t aCount) {
+ if (!mCheckedForXML) {
+ Maybe<bool> isForXML = mParser->IsForParsingXML();
+ mCheckedForXML = isForXML.isSome();
+ if (mCheckedForXML && !isForXML.value()) {
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
+ nsAutoString spec;
+ getSpec(channel, spec);
+ mCompiler->cancel(NS_ERROR_XSLT_WRONG_MIME_TYPE, nullptr, spec.get());
+
+ return NS_ERROR_XSLT_WRONG_MIME_TYPE;
+ }
+ }
+
+ return mListener->OnDataAvailable(aRequest, aInputStream, aOffset, aCount);
+}
+
+NS_IMETHODIMP
+txStylesheetSink::OnStartRequest(nsIRequest* aRequest) {
+ int32_t charsetSource = kCharsetFromDocTypeDefault;
+
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
+
+ // check channel's charset...
+ const Encoding* encoding = nullptr;
+ nsAutoCString charsetVal;
+ if (NS_SUCCEEDED(channel->GetContentCharset(charsetVal))) {
+ encoding = Encoding::ForLabel(charsetVal);
+ if (encoding) {
+ charsetSource = kCharsetFromChannel;
+ }
+ }
+
+ if (!encoding) {
+ encoding = UTF_8_ENCODING;
+ }
+
+ mParser->SetDocumentCharset(WrapNotNull(encoding), charsetSource, false);
+
+ nsAutoCString contentType;
+ channel->GetContentType(contentType);
+
+ // Time to sniff! Note: this should go away once file channels do
+ // sniffing themselves.
+ nsCOMPtr<nsIURI> uri;
+ channel->GetURI(getter_AddRefs(uri));
+ if (uri->SchemeIs("file") &&
+ contentType.EqualsLiteral(UNKNOWN_CONTENT_TYPE)) {
+ nsresult rv;
+ nsCOMPtr<nsIStreamConverterService> serv =
+ do_GetService("@mozilla.org/streamConverters;1", &rv);
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIStreamListener> converter;
+ rv = serv->AsyncConvertData(UNKNOWN_CONTENT_TYPE, "*/*", mListener,
+ NS_ISUPPORTS_CAST(nsIParser*, mParser),
+ getter_AddRefs(converter));
+ if (NS_SUCCEEDED(rv)) {
+ mListener = converter;
+ }
+ }
+ }
+
+ return mListener->OnStartRequest(aRequest);
+}
+
+NS_IMETHODIMP
+txStylesheetSink::OnStopRequest(nsIRequest* aRequest, nsresult aStatusCode) {
+ bool success = true;
+
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aRequest);
+ if (httpChannel) {
+ Unused << httpChannel->GetRequestSucceeded(&success);
+ }
+
+ nsresult result = aStatusCode;
+ if (!success) {
+ // XXX We sometimes want to use aStatusCode here, but the parser resets
+ // it to NS_ERROR_NOINTERFACE because we don't implement
+ // nsIHTMLContentSink.
+ result = NS_ERROR_XSLT_NETWORK_ERROR;
+ } else if (!mCheckedForXML) {
+ Maybe<bool> isForXML = mParser->IsForParsingXML();
+ if (isForXML.isSome() && !isForXML.value()) {
+ result = NS_ERROR_XSLT_WRONG_MIME_TYPE;
+ }
+ }
+
+ if (NS_FAILED(result)) {
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
+ nsAutoString spec;
+ getSpec(channel, spec);
+ mCompiler->cancel(result, nullptr, spec.get());
+ }
+
+ nsresult rv = mListener->OnStopRequest(aRequest, aStatusCode);
+ mListener = nullptr;
+ mParser = nullptr;
+ return rv;
+}
+
+NS_IMETHODIMP
+txStylesheetSink::GetInterface(const nsIID& aIID, void** aResult) {
+ if (aIID.Equals(NS_GET_IID(nsIAuthPrompt))) {
+ NS_ENSURE_ARG(aResult);
+ *aResult = nullptr;
+
+ nsresult rv;
+ nsCOMPtr<nsIWindowWatcher> wwatcher =
+ do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAuthPrompt> prompt;
+ rv = wwatcher->GetNewAuthPrompter(nullptr, getter_AddRefs(prompt));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ prompt.forget(aResult);
+
+ return NS_OK;
+ }
+
+ return NS_ERROR_NO_INTERFACE;
+}
+
+class txCompileObserver final : public txACompileObserver {
+ public:
+ txCompileObserver(txMozillaXSLTProcessor* aProcessor,
+ Document* aLoaderDocument);
+
+ TX_DECL_ACOMPILEOBSERVER
+ NS_INLINE_DECL_REFCOUNTING(txCompileObserver, override)
+
+ nsresult startLoad(nsIURI* aUri, txStylesheetCompiler* aCompiler,
+ nsIPrincipal* aSourcePrincipal,
+ ReferrerPolicy aReferrerPolicy);
+
+ private:
+ RefPtr<txMozillaXSLTProcessor> mProcessor;
+ nsCOMPtr<Document> mLoaderDocument;
+
+ // This exists solely to suppress a warning from nsDerivedSafe
+ txCompileObserver();
+
+ // Private destructor, to discourage deletion outside of Release():
+ ~txCompileObserver() = default;
+};
+
+txCompileObserver::txCompileObserver(txMozillaXSLTProcessor* aProcessor,
+ Document* aLoaderDocument)
+ : mProcessor(aProcessor), mLoaderDocument(aLoaderDocument) {}
+
+nsresult txCompileObserver::loadURI(const nsAString& aUri,
+ const nsAString& aReferrerUri,
+ ReferrerPolicy aReferrerPolicy,
+ txStylesheetCompiler* aCompiler) {
+ if (mProcessor->IsLoadDisabled()) {
+ return NS_ERROR_XSLT_LOAD_BLOCKED_ERROR;
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = NS_NewURI(getter_AddRefs(uri), aUri);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIURI> referrerUri;
+ rv = NS_NewURI(getter_AddRefs(referrerUri), aReferrerUri);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ OriginAttributes attrs;
+ nsCOMPtr<nsIPrincipal> referrerPrincipal =
+ BasePrincipal::CreateContentPrincipal(referrerUri, attrs);
+ NS_ENSURE_TRUE(referrerPrincipal, NS_ERROR_FAILURE);
+
+ return startLoad(uri, aCompiler, referrerPrincipal, aReferrerPolicy);
+}
+
+void txCompileObserver::onDoneCompiling(txStylesheetCompiler* aCompiler,
+ nsresult aResult,
+ const char16_t* aErrorText,
+ const char16_t* aParam) {
+ if (NS_SUCCEEDED(aResult)) {
+ mProcessor->setStylesheet(aCompiler->getStylesheet());
+ } else {
+ mProcessor->reportError(aResult, aErrorText, aParam);
+ }
+}
+
+nsresult txCompileObserver::startLoad(nsIURI* aUri,
+ txStylesheetCompiler* aCompiler,
+ nsIPrincipal* aReferrerPrincipal,
+ ReferrerPolicy aReferrerPolicy) {
+ nsCOMPtr<nsILoadGroup> loadGroup = mLoaderDocument->GetDocumentLoadGroup();
+ if (!loadGroup) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIChannel> channel;
+ nsresult rv = NS_NewChannelWithTriggeringPrincipal(
+ getter_AddRefs(channel), aUri, mLoaderDocument,
+ aReferrerPrincipal, // triggeringPrincipal
+ nsILoadInfo::SEC_REQUIRE_CORS_INHERITS_SEC_CONTEXT,
+ nsIContentPolicy::TYPE_XSLT,
+ nullptr, // aPerformanceStorage
+ loadGroup);
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ channel->SetContentType("text/xml"_ns);
+
+ nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(channel));
+ if (httpChannel) {
+ nsCOMPtr<nsIReferrerInfo> referrerInfo;
+ nsresult rv = aReferrerPrincipal->CreateReferrerInfo(
+ aReferrerPolicy, getter_AddRefs(referrerInfo));
+ if (NS_SUCCEEDED(rv)) {
+ rv = httpChannel->SetReferrerInfoWithoutClone(referrerInfo);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+ }
+
+ RefPtr<nsParser> parser = new nsParser();
+ RefPtr<txStylesheetSink> sink = new txStylesheetSink(aCompiler, parser);
+
+ channel->SetNotificationCallbacks(sink);
+
+ parser->SetCommand(kLoadAsData);
+ parser->SetContentSink(sink);
+ parser->Parse(aUri);
+
+ return channel->AsyncOpen(sink);
+}
+
+nsresult TX_LoadSheet(nsIURI* aUri, txMozillaXSLTProcessor* aProcessor,
+ Document* aLoaderDocument,
+ ReferrerPolicy aReferrerPolicy) {
+ nsIPrincipal* principal = aLoaderDocument->NodePrincipal();
+
+ nsAutoCString spec;
+ aUri->GetSpec(spec);
+ MOZ_LOG(txLog::xslt, LogLevel::Info, ("TX_LoadSheet: %s\n", spec.get()));
+
+ RefPtr<txCompileObserver> observer =
+ new txCompileObserver(aProcessor, aLoaderDocument);
+
+ RefPtr<txStylesheetCompiler> compiler = new txStylesheetCompiler(
+ NS_ConvertUTF8toUTF16(spec), aReferrerPolicy, observer);
+
+ return observer->startLoad(aUri, compiler, principal, aReferrerPolicy);
+}
+
+/**
+ * handling DOM->txStylesheet
+ * Observer needs to do synchronous loads.
+ */
+static nsresult handleNode(nsINode* aNode, txStylesheetCompiler* aCompiler) {
+ nsresult rv = NS_OK;
+
+ if (aNode->IsElement()) {
+ dom::Element* element = aNode->AsElement();
+
+ uint32_t attsCount = element->GetAttrCount();
+ UniquePtr<txStylesheetAttr[]> atts;
+ if (attsCount > 0) {
+ atts = MakeUnique<txStylesheetAttr[]>(attsCount);
+ uint32_t counter;
+ for (counter = 0; counter < attsCount; ++counter) {
+ txStylesheetAttr& att = atts[counter];
+ const nsAttrName* name = element->GetAttrNameAt(counter);
+ att.mNamespaceID = name->NamespaceID();
+ att.mLocalName = name->LocalName();
+ att.mPrefix = name->GetPrefix();
+ element->GetAttr(att.mNamespaceID, att.mLocalName, att.mValue);
+ }
+ }
+
+ mozilla::dom::NodeInfo* ni = element->NodeInfo();
+
+ rv = aCompiler->startElement(ni->NamespaceID(), ni->NameAtom(),
+ ni->GetPrefixAtom(), atts.get(), attsCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // explicitly destroy the attrs here since we no longer need it
+ atts = nullptr;
+
+ for (nsIContent* child = element->GetFirstChild(); child;
+ child = child->GetNextSibling()) {
+ rv = handleNode(child, aCompiler);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ rv = aCompiler->endElement();
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else if (dom::Text* text = aNode->GetAsText()) {
+ nsAutoString chars;
+ text->AppendTextTo(chars);
+ rv = aCompiler->characters(chars);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else if (aNode->IsDocument()) {
+ for (nsIContent* child = aNode->GetFirstChild(); child;
+ child = child->GetNextSibling()) {
+ rv = handleNode(child, aCompiler);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ return NS_OK;
+}
+
+class txSyncCompileObserver final : public txACompileObserver {
+ public:
+ explicit txSyncCompileObserver(txMozillaXSLTProcessor* aProcessor);
+
+ TX_DECL_ACOMPILEOBSERVER
+ NS_INLINE_DECL_REFCOUNTING(txSyncCompileObserver, override)
+
+ private:
+ // Private destructor, to discourage deletion outside of Release():
+ ~txSyncCompileObserver() = default;
+
+ RefPtr<txMozillaXSLTProcessor> mProcessor;
+};
+
+txSyncCompileObserver::txSyncCompileObserver(txMozillaXSLTProcessor* aProcessor)
+ : mProcessor(aProcessor) {}
+
+nsresult txSyncCompileObserver::loadURI(const nsAString& aUri,
+ const nsAString& aReferrerUri,
+ ReferrerPolicy aReferrerPolicy,
+ txStylesheetCompiler* aCompiler) {
+ if (mProcessor->IsLoadDisabled()) {
+ return NS_ERROR_XSLT_LOAD_BLOCKED_ERROR;
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = NS_NewURI(getter_AddRefs(uri), aUri);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIURI> referrerUri;
+ rv = NS_NewURI(getter_AddRefs(referrerUri), aReferrerUri);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIPrincipal> referrerPrincipal =
+ BasePrincipal::CreateContentPrincipal(referrerUri, OriginAttributes());
+ NS_ENSURE_TRUE(referrerPrincipal, NS_ERROR_FAILURE);
+
+ // This is probably called by js, a loadGroup for the channel doesn't
+ // make sense.
+ nsCOMPtr<nsINode> source;
+ if (mProcessor) {
+ source = mProcessor->GetSourceContentModel();
+ }
+ dom::nsAutoSyncOperation sync(source ? source->OwnerDoc() : nullptr,
+ dom::SyncOperationBehavior::eSuspendInput);
+ nsCOMPtr<Document> document;
+
+ rv = nsSyncLoadService::LoadDocument(
+ uri, nsIContentPolicy::TYPE_XSLT, referrerPrincipal,
+ nsILoadInfo::SEC_REQUIRE_CORS_INHERITS_SEC_CONTEXT, nullptr,
+ source ? source->OwnerDoc()->CookieJarSettings() : nullptr, false,
+ aReferrerPolicy, getter_AddRefs(document));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = handleNode(document, aCompiler);
+ if (NS_FAILED(rv)) {
+ nsAutoCString spec;
+ uri->GetSpec(spec);
+ aCompiler->cancel(rv, nullptr, NS_ConvertUTF8toUTF16(spec).get());
+ return rv;
+ }
+
+ rv = aCompiler->doneLoading();
+ return rv;
+}
+
+void txSyncCompileObserver::onDoneCompiling(txStylesheetCompiler* aCompiler,
+ nsresult aResult,
+ const char16_t* aErrorText,
+ const char16_t* aParam) {}
+
+nsresult TX_CompileStylesheet(nsINode* aNode,
+ txMozillaXSLTProcessor* aProcessor,
+ txStylesheet** aStylesheet) {
+ // If we move GetBaseURI to nsINode this can be simplified.
+ nsCOMPtr<Document> doc = aNode->OwnerDoc();
+
+ nsIURI* nodeBaseURI = aNode->GetBaseURI();
+ NS_ENSURE_TRUE(nodeBaseURI, NS_ERROR_FAILURE);
+
+ nsAutoCString spec;
+ nodeBaseURI->GetSpec(spec);
+ NS_ConvertUTF8toUTF16 baseURI(spec);
+
+ nsIURI* docUri = doc->GetDocumentURI();
+ NS_ENSURE_TRUE(docUri, NS_ERROR_FAILURE);
+
+ // We need to remove the ref, a URI with a ref would mean that we have an
+ // embedded stylesheet.
+ nsCOMPtr<nsIURI> uri;
+ NS_GetURIWithoutRef(docUri, getter_AddRefs(uri));
+ NS_ENSURE_TRUE(uri, NS_ERROR_FAILURE);
+
+ uri->GetSpec(spec);
+ NS_ConvertUTF8toUTF16 stylesheetURI(spec);
+
+ RefPtr<txSyncCompileObserver> obs = new txSyncCompileObserver(aProcessor);
+
+ RefPtr<txStylesheetCompiler> compiler =
+ new txStylesheetCompiler(stylesheetURI, doc->GetReferrerPolicy(), obs);
+
+ compiler->setBaseURI(baseURI);
+
+ nsresult rv = handleNode(aNode, compiler);
+ if (NS_FAILED(rv)) {
+ compiler->cancel(rv);
+ return rv;
+ }
+
+ rv = compiler->doneLoading();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *aStylesheet = compiler->getStylesheet();
+ NS_ADDREF(*aStylesheet);
+
+ return NS_OK;
+}
diff --git a/dom/xslt/xslt/txMozillaTextOutput.cpp b/dom/xslt/xslt/txMozillaTextOutput.cpp
new file mode 100644
index 0000000000..2104a0fd6b
--- /dev/null
+++ b/dom/xslt/xslt/txMozillaTextOutput.cpp
@@ -0,0 +1,251 @@
+/* -*- 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 "txMozillaTextOutput.h"
+#include "nsContentCID.h"
+#include "nsIContent.h"
+#include "mozilla/dom/Document.h"
+#include "nsIDocumentTransformer.h"
+#include "nsCharsetSource.h"
+#include "txURIUtils.h"
+#include "nsContentCreatorFunctions.h"
+#include "nsContentUtils.h"
+#include "nsGkAtoms.h"
+#include "mozilla/Encoding.h"
+#include "nsTextNode.h"
+#include "nsNameSpaceManager.h"
+#include "mozilla/dom/DocumentFragment.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+txMozillaTextOutput::txMozillaTextOutput(Document* aSourceDocument,
+ nsITransformObserver* aObserver)
+ : mSourceDocument(aSourceDocument),
+ mObserver(do_GetWeakReference(aObserver)),
+ mCreatedDocument(false) {
+ MOZ_COUNT_CTOR(txMozillaTextOutput);
+}
+
+txMozillaTextOutput::txMozillaTextOutput(DocumentFragment* aDest)
+ : mTextParent(aDest),
+ mDocument(mTextParent->OwnerDoc()),
+ mCreatedDocument(false) {
+ MOZ_COUNT_CTOR(txMozillaTextOutput);
+ mTextParent = aDest;
+ mDocument = mTextParent->OwnerDoc();
+}
+
+txMozillaTextOutput::~txMozillaTextOutput() {
+ MOZ_COUNT_DTOR(txMozillaTextOutput);
+}
+
+nsresult txMozillaTextOutput::attribute(nsAtom* aPrefix, nsAtom* aLocalName,
+ nsAtom* aLowercaseLocalName,
+ int32_t aNsID, const nsString& aValue) {
+ return NS_OK;
+}
+
+nsresult txMozillaTextOutput::attribute(nsAtom* aPrefix, const nsAString& aName,
+ const int32_t aNsID,
+ const nsString& aValue) {
+ return NS_OK;
+}
+
+nsresult txMozillaTextOutput::characters(const nsAString& aData, bool aDOE) {
+ mText.Append(aData);
+
+ return NS_OK;
+}
+
+nsresult txMozillaTextOutput::comment(const nsString& aData) { return NS_OK; }
+
+nsresult txMozillaTextOutput::endDocument(nsresult aResult) {
+ NS_ENSURE_TRUE(mDocument && mTextParent, NS_ERROR_FAILURE);
+
+ RefPtr<nsTextNode> text = new (mDocument->NodeInfoManager())
+ nsTextNode(mDocument->NodeInfoManager());
+
+ ErrorResult rv;
+ text->SetText(mText, false);
+ mTextParent->AppendChildTo(text, true, rv);
+ if (rv.Failed()) {
+ return rv.StealNSResult();
+ }
+
+ // This should really be handled by Document::EndLoad
+ if (mCreatedDocument) {
+ MOZ_ASSERT(mDocument->GetReadyStateEnum() == Document::READYSTATE_LOADING,
+ "Bad readyState");
+ } else {
+ MOZ_ASSERT(
+ mDocument->GetReadyStateEnum() == Document::READYSTATE_INTERACTIVE,
+ "Bad readyState");
+ }
+ mDocument->SetReadyStateInternal(Document::READYSTATE_INTERACTIVE);
+
+ if (NS_SUCCEEDED(aResult)) {
+ nsCOMPtr<nsITransformObserver> observer = do_QueryReferent(mObserver);
+ if (observer) {
+ observer->OnTransformDone(mSourceDocument, aResult, mDocument);
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult txMozillaTextOutput::endElement() { return NS_OK; }
+
+nsresult txMozillaTextOutput::processingInstruction(const nsString& aTarget,
+ const nsString& aData) {
+ return NS_OK;
+}
+
+nsresult txMozillaTextOutput::startDocument() { return NS_OK; }
+
+nsresult txMozillaTextOutput::createResultDocument(bool aLoadedAsData) {
+ /*
+ * Create an XHTML document to hold the text.
+ *
+ * <html>
+ * <head />
+ * <body>
+ * <pre id="transformiixResult"> * The text comes here * </pre>
+ * <body>
+ * </html>
+ *
+ * Except if we are transforming into a non-displayed document we create
+ * the following DOM
+ *
+ * <transformiix:result> * The text comes here * </transformiix:result>
+ */
+
+ // Create the document
+ nsresult rv = NS_NewXMLDocument(getter_AddRefs(mDocument), nullptr, nullptr,
+ aLoadedAsData);
+ NS_ENSURE_SUCCESS(rv, rv);
+ mCreatedDocument = true;
+ // This should really be handled by Document::BeginLoad
+ MOZ_ASSERT(
+ mDocument->GetReadyStateEnum() == Document::READYSTATE_UNINITIALIZED,
+ "Bad readyState");
+ mDocument->SetReadyStateInternal(Document::READYSTATE_LOADING);
+ bool hasHadScriptObject = false;
+ nsIScriptGlobalObject* sgo =
+ mSourceDocument->GetScriptHandlingObject(hasHadScriptObject);
+ NS_ENSURE_STATE(sgo || !hasHadScriptObject);
+
+ NS_ASSERTION(mDocument, "Need document");
+
+ // Reset and set up document
+ URIUtils::ResetWithSource(mDocument, mSourceDocument);
+ // Only do this after resetting the document to ensure we have the
+ // correct principal.
+ mDocument->SetScriptHandlingObject(sgo);
+
+ // Set the charset
+ if (!mOutputFormat.mEncoding.IsEmpty()) {
+ const Encoding* encoding = Encoding::ForLabel(mOutputFormat.mEncoding);
+ if (encoding) {
+ mDocument->SetDocumentCharacterSetSource(kCharsetFromOtherComponent);
+ mDocument->SetDocumentCharacterSet(WrapNotNull(encoding));
+ }
+ }
+
+ // Notify the contentsink that the document is created
+ nsCOMPtr<nsITransformObserver> observer = do_QueryReferent(mObserver);
+ if (observer) {
+ rv = observer->OnDocumentCreated(mSourceDocument, mDocument);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Create the content
+
+ // When transforming into a non-displayed document (i.e. when there is no
+ // observer) we only create a transformiix:result root element.
+ if (!observer) {
+ int32_t namespaceID;
+ rv = nsNameSpaceManager::GetInstance()->RegisterNameSpace(
+ nsLiteralString(kTXNameSpaceURI), namespaceID);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mTextParent =
+ mDocument->CreateElem(nsDependentAtomString(nsGkAtoms::result),
+ nsGkAtoms::transformiix, namespaceID);
+
+ ErrorResult error;
+ mDocument->AppendChildTo(mTextParent, true, error);
+ if (error.Failed()) {
+ return error.StealNSResult();
+ }
+ } else {
+ RefPtr<Element> html, head, body;
+ rv = createXHTMLElement(nsGkAtoms::html, getter_AddRefs(html));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = createXHTMLElement(nsGkAtoms::head, getter_AddRefs(head));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ ErrorResult error;
+ html->AppendChildTo(head, false, error);
+ if (error.Failed()) {
+ return error.StealNSResult();
+ }
+
+ rv = createXHTMLElement(nsGkAtoms::body, getter_AddRefs(body));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ html->AppendChildTo(body, false, error);
+ if (error.Failed()) {
+ return error.StealNSResult();
+ }
+
+ {
+ RefPtr<Element> textParent;
+ rv = createXHTMLElement(nsGkAtoms::pre, getter_AddRefs(textParent));
+ NS_ENSURE_SUCCESS(rv, rv);
+ mTextParent = std::move(textParent);
+ }
+
+ rv = mTextParent->AsElement()->SetAttr(kNameSpaceID_None, nsGkAtoms::id,
+ u"transformiixResult"_ns, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ body->AppendChildTo(mTextParent, false, error);
+ if (error.Failed()) {
+ return error.StealNSResult();
+ }
+
+ mDocument->AppendChildTo(html, true, error);
+ if (error.Failed()) {
+ return error.StealNSResult();
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult txMozillaTextOutput::startElement(nsAtom* aPrefix, nsAtom* aLocalName,
+ nsAtom* aLowercaseLocalName,
+ int32_t aNsID) {
+ return NS_OK;
+}
+
+nsresult txMozillaTextOutput::startElement(nsAtom* aPrefix,
+ const nsAString& aName,
+ const int32_t aNsID) {
+ return NS_OK;
+}
+
+void txMozillaTextOutput::getOutputDocument(Document** aDocument) {
+ NS_IF_ADDREF(*aDocument = mDocument);
+}
+
+nsresult txMozillaTextOutput::createXHTMLElement(nsAtom* aName,
+ Element** aResult) {
+ nsCOMPtr<Element> element = mDocument->CreateHTMLElement(aName);
+ element.forget(aResult);
+ return NS_OK;
+}
diff --git a/dom/xslt/xslt/txMozillaTextOutput.h b/dom/xslt/xslt/txMozillaTextOutput.h
new file mode 100644
index 0000000000..c3e493af37
--- /dev/null
+++ b/dom/xslt/xslt/txMozillaTextOutput.h
@@ -0,0 +1,47 @@
+/* -*- 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/. */
+
+#ifndef TRANSFRMX_MOZILLA_TEXT_OUTPUT_H
+#define TRANSFRMX_MOZILLA_TEXT_OUTPUT_H
+
+#include "txXMLEventHandler.h"
+#include "nsCOMPtr.h"
+#include "nsIWeakReferenceUtils.h"
+#include "txOutputFormat.h"
+
+class nsITransformObserver;
+class nsIContent;
+
+namespace mozilla::dom {
+class Document;
+class DocumentFragment;
+class Element;
+} // namespace mozilla::dom
+
+class txMozillaTextOutput : public txAOutputXMLEventHandler {
+ public:
+ explicit txMozillaTextOutput(mozilla::dom::Document* aSourceDocument,
+ nsITransformObserver* aObserver);
+ explicit txMozillaTextOutput(mozilla::dom::DocumentFragment* aDest);
+ virtual ~txMozillaTextOutput();
+
+ TX_DECL_TXAXMLEVENTHANDLER
+ TX_DECL_TXAOUTPUTXMLEVENTHANDLER
+
+ nsresult createResultDocument(bool aLoadedAsData);
+
+ private:
+ nsresult createXHTMLElement(nsAtom* aName, mozilla::dom::Element** aResult);
+
+ nsCOMPtr<mozilla::dom::Document> mSourceDocument;
+ nsCOMPtr<nsIContent> mTextParent;
+ nsWeakPtr mObserver;
+ RefPtr<mozilla::dom::Document> mDocument;
+ txOutputFormat mOutputFormat;
+ nsString mText;
+ bool mCreatedDocument;
+};
+
+#endif
diff --git a/dom/xslt/xslt/txMozillaXMLOutput.cpp b/dom/xslt/xslt/txMozillaXMLOutput.cpp
new file mode 100644
index 0000000000..4aa51d9928
--- /dev/null
+++ b/dom/xslt/xslt/txMozillaXMLOutput.cpp
@@ -0,0 +1,926 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "txMozillaXMLOutput.h"
+
+#include "mozilla/dom/Document.h"
+#include "nsIDocShell.h"
+#include "nsIScriptElement.h"
+#include "nsCharsetSource.h"
+#include "nsIRefreshURI.h"
+#include "nsPIDOMWindow.h"
+#include "nsIContent.h"
+#include "nsContentCID.h"
+#include "nsUnicharUtils.h"
+#include "nsGkAtoms.h"
+#include "txLog.h"
+#include "nsNameSpaceManager.h"
+#include "txStringUtils.h"
+#include "txURIUtils.h"
+#include "nsIDocumentTransformer.h"
+#include "mozilla/StyleSheetInlines.h"
+#include "mozilla/css/Loader.h"
+#include "mozilla/dom/DocumentType.h"
+#include "mozilla/dom/DocumentFragment.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/ScriptLoader.h"
+#include "mozilla/Encoding.h"
+#include "mozilla/Try.h"
+#include "nsContentUtils.h"
+#include "nsDocElementCreatedNotificationRunner.h"
+#include "txXMLUtils.h"
+#include "nsContentSink.h"
+#include "nsINode.h"
+#include "nsContentCreatorFunctions.h"
+#include "nsError.h"
+#include "nsStringFlags.h"
+#include "nsStyleUtil.h"
+#include "nsIFrame.h"
+#include <algorithm>
+#include "nsTextNode.h"
+#include "nsDocShell.h"
+#include "mozilla/dom/Comment.h"
+#include "mozilla/dom/ProcessingInstruction.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+#define TX_ENSURE_CURRENTNODE \
+ NS_ASSERTION(mCurrentNode, "mCurrentNode is nullptr"); \
+ if (!mCurrentNode) return NS_ERROR_UNEXPECTED
+
+txMozillaXMLOutput::txMozillaXMLOutput(Document* aSourceDocument,
+ txOutputFormat* aFormat,
+ nsITransformObserver* aObserver)
+ : mTreeDepth(0),
+ mBadChildLevel(0),
+ mTableState(NORMAL),
+ mCreatingNewDocument(true),
+ mOpenedElementIsHTML(false),
+ mRootContentCreated(false),
+ mNoFixup(false) {
+ MOZ_COUNT_CTOR(txMozillaXMLOutput);
+ if (aObserver) {
+ mNotifier = new txTransformNotifier(aSourceDocument);
+ if (mNotifier) {
+ mNotifier->Init(aObserver);
+ }
+ }
+
+ mOutputFormat.merge(*aFormat);
+ mOutputFormat.setFromDefaults();
+}
+
+txMozillaXMLOutput::txMozillaXMLOutput(txOutputFormat* aFormat,
+ DocumentFragment* aFragment,
+ bool aNoFixup)
+ : mTreeDepth(0),
+ mBadChildLevel(0),
+ mTableState(NORMAL),
+ mCreatingNewDocument(false),
+ mOpenedElementIsHTML(false),
+ mRootContentCreated(false),
+ mNoFixup(aNoFixup) {
+ MOZ_COUNT_CTOR(txMozillaXMLOutput);
+ mOutputFormat.merge(*aFormat);
+ mOutputFormat.setFromDefaults();
+
+ mCurrentNode = aFragment;
+ mDocument = mCurrentNode->OwnerDoc();
+ mNodeInfoManager = mDocument->NodeInfoManager();
+}
+
+txMozillaXMLOutput::~txMozillaXMLOutput() {
+ MOZ_COUNT_DTOR(txMozillaXMLOutput);
+}
+
+nsresult txMozillaXMLOutput::attribute(nsAtom* aPrefix, nsAtom* aLocalName,
+ nsAtom* aLowercaseLocalName,
+ const int32_t aNsID,
+ const nsString& aValue) {
+ RefPtr<nsAtom> owner;
+ if (mOpenedElementIsHTML && aNsID == kNameSpaceID_None) {
+ if (aLowercaseLocalName) {
+ aLocalName = aLowercaseLocalName;
+ } else {
+ owner = TX_ToLowerCaseAtom(aLocalName);
+ NS_ENSURE_TRUE(owner, NS_ERROR_OUT_OF_MEMORY);
+
+ aLocalName = owner;
+ }
+ }
+
+ return attributeInternal(aPrefix, aLocalName, aNsID, aValue);
+}
+
+nsresult txMozillaXMLOutput::attribute(nsAtom* aPrefix,
+ const nsAString& aLocalName,
+ const int32_t aNsID,
+ const nsString& aValue) {
+ RefPtr<nsAtom> lname;
+
+ if (mOpenedElementIsHTML && aNsID == kNameSpaceID_None) {
+ nsAutoString lnameStr;
+ nsContentUtils::ASCIIToLower(aLocalName, lnameStr);
+ lname = NS_Atomize(lnameStr);
+ } else {
+ lname = NS_Atomize(aLocalName);
+ }
+
+ NS_ENSURE_TRUE(lname, NS_ERROR_OUT_OF_MEMORY);
+
+ // Check that it's a valid name
+ if (!nsContentUtils::IsValidNodeName(lname, aPrefix, aNsID)) {
+ // Try without prefix
+ aPrefix = nullptr;
+ if (!nsContentUtils::IsValidNodeName(lname, aPrefix, aNsID)) {
+ // Don't return error here since the callers don't deal
+ return NS_OK;
+ }
+ }
+
+ return attributeInternal(aPrefix, lname, aNsID, aValue);
+}
+
+nsresult txMozillaXMLOutput::attributeInternal(nsAtom* aPrefix,
+ nsAtom* aLocalName,
+ int32_t aNsID,
+ const nsString& aValue) {
+ if (!mOpenedElement) {
+ // XXX Signal this? (can't add attributes after element closed)
+ return NS_OK;
+ }
+
+ NS_ASSERTION(!mBadChildLevel, "mBadChildLevel set when element is opened");
+
+ return mOpenedElement->SetAttr(aNsID, aLocalName, aPrefix, aValue, false);
+}
+
+nsresult txMozillaXMLOutput::characters(const nsAString& aData, bool aDOE) {
+ nsresult rv = closePrevious(false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!mBadChildLevel) {
+ mText.Append(aData);
+ }
+
+ return NS_OK;
+}
+
+nsresult txMozillaXMLOutput::comment(const nsString& aData) {
+ nsresult rv = closePrevious(true);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (mBadChildLevel) {
+ return NS_OK;
+ }
+
+ TX_ENSURE_CURRENTNODE;
+
+ RefPtr<Comment> comment = new (mNodeInfoManager) Comment(mNodeInfoManager);
+
+ rv = comment->SetText(aData, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ ErrorResult error;
+ mCurrentNode->AppendChildTo(comment, true, error);
+ return error.StealNSResult();
+}
+
+nsresult txMozillaXMLOutput::endDocument(nsresult aResult) {
+ TX_ENSURE_CURRENTNODE;
+
+ if (NS_FAILED(aResult)) {
+ if (mNotifier) {
+ mNotifier->OnTransformEnd(aResult);
+ }
+
+ return NS_OK;
+ }
+
+ nsresult rv = closePrevious(true);
+ if (NS_FAILED(rv)) {
+ if (mNotifier) {
+ mNotifier->OnTransformEnd(rv);
+ }
+
+ return rv;
+ }
+
+ if (mCreatingNewDocument) {
+ // This should really be handled by Document::EndLoad
+ MOZ_ASSERT(mDocument->GetReadyStateEnum() == Document::READYSTATE_LOADING,
+ "Bad readyState");
+ mDocument->SetReadyStateInternal(Document::READYSTATE_INTERACTIVE);
+ if (ScriptLoader* loader = mDocument->ScriptLoader()) {
+ loader->ParsingComplete(false);
+ }
+ }
+
+ if (mNotifier) {
+ mNotifier->OnTransformEnd();
+ }
+
+ return NS_OK;
+}
+
+nsresult txMozillaXMLOutput::endElement() {
+ TX_ENSURE_CURRENTNODE;
+
+ if (mBadChildLevel) {
+ --mBadChildLevel;
+ MOZ_LOG(txLog::xslt, LogLevel::Debug,
+ ("endElement, mBadChildLevel = %d\n", mBadChildLevel));
+ return NS_OK;
+ }
+
+ --mTreeDepth;
+
+ nsresult rv = closePrevious(true);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ASSERTION(mCurrentNode->IsElement(), "borked mCurrentNode");
+ NS_ENSURE_TRUE(mCurrentNode->IsElement(), NS_ERROR_UNEXPECTED);
+
+ Element* element = mCurrentNode->AsElement();
+
+ // Handle html-elements
+ if (!mNoFixup) {
+ if (element->IsHTMLElement()) {
+ endHTMLElement(element);
+ }
+
+ // Handle elements that are different when parser-created
+ if (nsIContent::RequiresDoneCreatingElement(
+ element->NodeInfo()->NamespaceID(),
+ element->NodeInfo()->NameAtom())) {
+ element->DoneCreatingElement();
+ } else if (element->IsSVGElement(nsGkAtoms::script) ||
+ element->IsHTMLElement(nsGkAtoms::script)) {
+ nsCOMPtr<nsIScriptElement> sele = do_QueryInterface(element);
+ if (sele) {
+ bool block = sele->AttemptToExecute();
+ // If the act of insertion evaluated the script, we're fine.
+ // Else, add this script element to the array of loading scripts.
+ if (block) {
+ mNotifier->AddScriptElement(sele);
+ }
+ } else {
+ MOZ_ASSERT(nsNameSpaceManager::GetInstance()->mSVGDisabled,
+ "Script elements need to implement nsIScriptElement and SVG "
+ "wasn't disabled.");
+ }
+ } else if (nsIContent::RequiresDoneAddingChildren(
+ element->NodeInfo()->NamespaceID(),
+ element->NodeInfo()->NameAtom())) {
+ element->DoneAddingChildren(true);
+ }
+ }
+
+ if (mCreatingNewDocument) {
+ // Handle all sorts of stylesheets
+ if (auto* linkStyle = LinkStyle::FromNode(*mCurrentNode)) {
+ auto updateOrError =
+ linkStyle->EnableUpdatesAndUpdateStyleSheet(mNotifier);
+ if (mNotifier && updateOrError.isOk() &&
+ updateOrError.unwrap().ShouldBlock()) {
+ mNotifier->AddPendingStylesheet();
+ }
+ }
+ }
+
+ // Add the element to the tree if it wasn't added before and take one step
+ // up the tree
+ MOZ_ASSERT(!mCurrentNodeStack.IsEmpty(), "empty stack");
+ nsCOMPtr<nsINode> parent;
+ if (!mCurrentNodeStack.IsEmpty()) {
+ parent = mCurrentNodeStack.PopLastElement();
+ }
+
+ if (mCurrentNode == mNonAddedNode) {
+ if (parent == mDocument) {
+ NS_ASSERTION(!mRootContentCreated,
+ "Parent to add to shouldn't be a document if we "
+ "have a root content");
+ mRootContentCreated = true;
+ }
+
+ // Check to make sure that script hasn't inserted the node somewhere
+ // else in the tree
+ if (!mCurrentNode->GetParentNode()) {
+ parent->AppendChildTo(mNonAddedNode, true, IgnoreErrors());
+ }
+ mNonAddedNode = nullptr;
+ }
+
+ mCurrentNode = parent;
+
+ mTableState =
+ static_cast<TableState>(NS_PTR_TO_INT32(mTableStateStack.pop()));
+
+ return NS_OK;
+}
+
+void txMozillaXMLOutput::getOutputDocument(Document** aDocument) {
+ NS_IF_ADDREF(*aDocument = mDocument);
+}
+
+nsresult txMozillaXMLOutput::processingInstruction(const nsString& aTarget,
+ const nsString& aData) {
+ nsresult rv = closePrevious(true);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (mOutputFormat.mMethod == eHTMLOutput) return NS_OK;
+
+ TX_ENSURE_CURRENTNODE;
+
+ rv = nsContentUtils::CheckQName(aTarget, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIContent> pi =
+ NS_NewXMLProcessingInstruction(mNodeInfoManager, aTarget, aData);
+
+ LinkStyle* linkStyle = nullptr;
+ if (mCreatingNewDocument) {
+ linkStyle = LinkStyle::FromNode(*pi);
+ if (linkStyle) {
+ linkStyle->DisableUpdates();
+ }
+ }
+
+ ErrorResult error;
+ mCurrentNode->AppendChildTo(pi, true, error);
+ if (error.Failed()) {
+ return error.StealNSResult();
+ }
+
+ if (linkStyle) {
+ auto updateOrError = linkStyle->EnableUpdatesAndUpdateStyleSheet(mNotifier);
+ if (mNotifier && updateOrError.isOk() &&
+ updateOrError.unwrap().ShouldBlock()) {
+ mNotifier->AddPendingStylesheet();
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult txMozillaXMLOutput::startDocument() {
+ if (mNotifier) {
+ mNotifier->OnTransformStart();
+ }
+
+ if (mCreatingNewDocument) {
+ ScriptLoader* loader = mDocument->ScriptLoader();
+ if (loader) {
+ loader->BeginDeferringScripts();
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult txMozillaXMLOutput::startElement(nsAtom* aPrefix, nsAtom* aLocalName,
+ nsAtom* aLowercaseLocalName,
+ const int32_t aNsID) {
+ MOZ_ASSERT(aNsID != kNameSpaceID_None || !aPrefix,
+ "Can't have prefix without namespace");
+
+ if (mOutputFormat.mMethod == eHTMLOutput && aNsID == kNameSpaceID_None) {
+ RefPtr<nsAtom> owner;
+ if (!aLowercaseLocalName) {
+ owner = TX_ToLowerCaseAtom(aLocalName);
+ NS_ENSURE_TRUE(owner, NS_ERROR_OUT_OF_MEMORY);
+
+ aLowercaseLocalName = owner;
+ }
+ return startElementInternal(nullptr, aLowercaseLocalName,
+ kNameSpaceID_XHTML);
+ }
+
+ return startElementInternal(aPrefix, aLocalName, aNsID);
+}
+
+nsresult txMozillaXMLOutput::startElement(nsAtom* aPrefix,
+ const nsAString& aLocalName,
+ const int32_t aNsID) {
+ int32_t nsId = aNsID;
+ RefPtr<nsAtom> lname;
+
+ if (mOutputFormat.mMethod == eHTMLOutput && aNsID == kNameSpaceID_None) {
+ nsId = kNameSpaceID_XHTML;
+
+ nsAutoString lnameStr;
+ nsContentUtils::ASCIIToLower(aLocalName, lnameStr);
+ lname = NS_Atomize(lnameStr);
+ } else {
+ lname = NS_Atomize(aLocalName);
+ }
+
+ // No biggie if we lose the prefix due to OOM
+ NS_ENSURE_TRUE(lname, NS_ERROR_OUT_OF_MEMORY);
+
+ // Check that it's a valid name
+ if (!nsContentUtils::IsValidNodeName(lname, aPrefix, nsId)) {
+ // Try without prefix
+ aPrefix = nullptr;
+ if (!nsContentUtils::IsValidNodeName(lname, aPrefix, nsId)) {
+ return NS_ERROR_XSLT_BAD_NODE_NAME;
+ }
+ }
+
+ return startElementInternal(aPrefix, lname, nsId);
+}
+
+nsresult txMozillaXMLOutput::startElementInternal(nsAtom* aPrefix,
+ nsAtom* aLocalName,
+ int32_t aNsID) {
+ TX_ENSURE_CURRENTNODE;
+
+ if (mBadChildLevel) {
+ ++mBadChildLevel;
+ MOZ_LOG(txLog::xslt, LogLevel::Debug,
+ ("startElement, mBadChildLevel = %d\n", mBadChildLevel));
+ return NS_OK;
+ }
+
+ MOZ_TRY(closePrevious(true));
+
+ // Push and init state
+ if (mTreeDepth == MAX_REFLOW_DEPTH) {
+ // eCloseElement couldn't add the parent so we fail as well or we've
+ // reached the limit of the depth of the tree that we allow.
+ ++mBadChildLevel;
+ MOZ_LOG(txLog::xslt, LogLevel::Debug,
+ ("startElement, mBadChildLevel = %d\n", mBadChildLevel));
+ return NS_OK;
+ }
+
+ ++mTreeDepth;
+
+ mTableStateStack.push(NS_INT32_TO_PTR(mTableState));
+
+ mCurrentNodeStack.AppendElement(mCurrentNode);
+
+ mTableState = NORMAL;
+ mOpenedElementIsHTML = false;
+
+ // Create the element
+ RefPtr<NodeInfo> ni = mNodeInfoManager->GetNodeInfo(
+ aLocalName, aPrefix, aNsID, nsINode::ELEMENT_NODE);
+
+ NS_NewElement(getter_AddRefs(mOpenedElement), ni.forget(),
+ mCreatingNewDocument ? FROM_PARSER_XSLT : FROM_PARSER_FRAGMENT);
+
+ // Set up the element and adjust state
+ if (!mNoFixup && aNsID == kNameSpaceID_XHTML) {
+ mOpenedElementIsHTML = (mOutputFormat.mMethod == eHTMLOutput);
+ MOZ_TRY(startHTMLElement(mOpenedElement, mOpenedElementIsHTML));
+ }
+
+ if (mCreatingNewDocument) {
+ // Handle all sorts of stylesheets
+ if (auto* linkStyle = LinkStyle::FromNode(*mOpenedElement)) {
+ linkStyle->DisableUpdates();
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult txMozillaXMLOutput::closePrevious(bool aFlushText) {
+ TX_ENSURE_CURRENTNODE;
+
+ if (mOpenedElement) {
+ bool currentIsDoc = mCurrentNode == mDocument;
+ if (currentIsDoc && mRootContentCreated) {
+ // We already have a document element, but the XSLT spec allows this.
+ // As a workaround, create a wrapper object and use that as the
+ // document element.
+
+ MOZ_TRY(createTxWrapper());
+ }
+
+ ErrorResult error;
+ mCurrentNode->AppendChildTo(mOpenedElement, true, error);
+ if (error.Failed()) {
+ return error.StealNSResult();
+ }
+
+ if (currentIsDoc) {
+ mRootContentCreated = true;
+ nsContentUtils::AddScriptRunner(
+ new nsDocElementCreatedNotificationRunner(mDocument));
+ }
+
+ mCurrentNode = mOpenedElement;
+ mOpenedElement = nullptr;
+ } else if (aFlushText && !mText.IsEmpty()) {
+ // Text can't appear in the root of a document
+ if (mDocument == mCurrentNode) {
+ if (XMLUtils::isWhitespace(mText)) {
+ mText.Truncate();
+
+ return NS_OK;
+ }
+
+ MOZ_TRY(createTxWrapper());
+ }
+ RefPtr<nsTextNode> text =
+ new (mNodeInfoManager) nsTextNode(mNodeInfoManager);
+
+ MOZ_TRY(text->SetText(mText, false));
+
+ ErrorResult error;
+ mCurrentNode->AppendChildTo(text, true, error);
+ if (error.Failed()) {
+ return error.StealNSResult();
+ }
+
+ mText.Truncate();
+ }
+
+ return NS_OK;
+}
+
+nsresult txMozillaXMLOutput::createTxWrapper() {
+ NS_ASSERTION(mDocument == mCurrentNode,
+ "creating wrapper when document isn't parent");
+
+ int32_t namespaceID;
+ MOZ_TRY(nsNameSpaceManager::GetInstance()->RegisterNameSpace(
+ nsLiteralString(kTXNameSpaceURI), namespaceID));
+
+ nsCOMPtr<Element> wrapper =
+ mDocument->CreateElem(nsDependentAtomString(nsGkAtoms::result),
+ nsGkAtoms::transformiix, namespaceID);
+
+#ifdef DEBUG
+ // Keep track of the location of the current documentElement, if there is
+ // one, so we can verify later
+ uint32_t j = 0, rootLocation = 0;
+#endif
+ for (nsCOMPtr<nsIContent> childContent = mDocument->GetFirstChild();
+ childContent; childContent = childContent->GetNextSibling()) {
+#ifdef DEBUG
+ if (childContent->IsElement()) {
+ rootLocation = j;
+ }
+#endif
+
+ if (childContent->NodeInfo()->NameAtom() ==
+ nsGkAtoms::documentTypeNodeName) {
+#ifdef DEBUG
+ // The new documentElement should go after the document type.
+ // This is needed for cases when there is no existing
+ // documentElement in the document.
+ rootLocation = std::max(rootLocation, j + 1);
+ ++j;
+#endif
+ } else {
+ mDocument->RemoveChildNode(childContent, true);
+
+ ErrorResult error;
+ wrapper->AppendChildTo(childContent, true, error);
+ if (error.Failed()) {
+ return error.StealNSResult();
+ }
+ break;
+ }
+ }
+
+ mCurrentNodeStack.AppendElement(wrapper);
+ mCurrentNode = wrapper;
+ mRootContentCreated = true;
+ NS_ASSERTION(rootLocation == mDocument->GetChildCount(),
+ "Incorrect root location");
+ ErrorResult error;
+ mDocument->AppendChildTo(wrapper, true, error);
+ return error.StealNSResult();
+}
+
+nsresult txMozillaXMLOutput::startHTMLElement(nsIContent* aElement,
+ bool aIsHTML) {
+ if ((!aElement->IsHTMLElement(nsGkAtoms::tr) || !aIsHTML) &&
+ NS_PTR_TO_INT32(mTableStateStack.peek()) == ADDED_TBODY) {
+ MOZ_ASSERT(!mCurrentNodeStack.IsEmpty(), "empty stack");
+ if (mCurrentNodeStack.IsEmpty()) {
+ mCurrentNode = nullptr;
+ } else {
+ mCurrentNode = mCurrentNodeStack.PopLastElement();
+ }
+ mTableStateStack.pop();
+ }
+
+ if (aElement->IsHTMLElement(nsGkAtoms::table) && aIsHTML) {
+ mTableState = TABLE;
+ } else if (aElement->IsHTMLElement(nsGkAtoms::tr) && aIsHTML &&
+ NS_PTR_TO_INT32(mTableStateStack.peek()) == TABLE) {
+ RefPtr<Element> tbody;
+ MOZ_TRY(createHTMLElement(nsGkAtoms::tbody, getter_AddRefs(tbody)));
+
+ ErrorResult error;
+ mCurrentNode->AppendChildTo(tbody, true, error);
+ if (error.Failed()) {
+ return error.StealNSResult();
+ }
+
+ mTableStateStack.push(NS_INT32_TO_PTR(ADDED_TBODY));
+
+ mCurrentNodeStack.AppendElement(tbody);
+ mCurrentNode = tbody;
+ } else if (aElement->IsHTMLElement(nsGkAtoms::head) &&
+ mOutputFormat.mMethod == eHTMLOutput) {
+ // Insert META tag, according to spec, 16.2, like
+ // <META http-equiv="Content-Type" content="text/html; charset=EUC-JP">
+ RefPtr<Element> meta;
+ MOZ_TRY(createHTMLElement(nsGkAtoms::meta, getter_AddRefs(meta)));
+
+ MOZ_TRY(meta->SetAttr(kNameSpaceID_None, nsGkAtoms::httpEquiv,
+ u"Content-Type"_ns, false));
+
+ nsAutoString metacontent;
+ CopyUTF8toUTF16(mOutputFormat.mMediaType, metacontent);
+ metacontent.AppendLiteral("; charset=");
+ metacontent.Append(mOutputFormat.mEncoding);
+ MOZ_TRY(meta->SetAttr(kNameSpaceID_None, nsGkAtoms::content, metacontent,
+ false));
+
+ // No need to notify since aElement hasn't been inserted yet
+ NS_ASSERTION(!aElement->IsInUncomposedDoc(), "should not be in doc");
+ ErrorResult error;
+ aElement->AppendChildTo(meta, false, error);
+ if (error.Failed()) {
+ return error.StealNSResult();
+ }
+ }
+
+ return NS_OK;
+}
+
+void txMozillaXMLOutput::endHTMLElement(nsIContent* aElement) {
+ if (mTableState == ADDED_TBODY) {
+ NS_ASSERTION(aElement->IsHTMLElement(nsGkAtoms::tbody),
+ "Element flagged as added tbody isn't a tbody");
+ MOZ_ASSERT(!mCurrentNodeStack.IsEmpty(), "empty stack");
+ if (mCurrentNodeStack.IsEmpty()) {
+ mCurrentNode = nullptr;
+ } else {
+ mCurrentNode = mCurrentNodeStack.PopLastElement();
+ }
+ mTableState =
+ static_cast<TableState>(NS_PTR_TO_INT32(mTableStateStack.pop()));
+ }
+}
+
+nsresult txMozillaXMLOutput::createResultDocument(const nsAString& aName,
+ int32_t aNsID,
+ Document* aSourceDocument,
+ bool aLoadedAsData) {
+ // Create the document
+ if (mOutputFormat.mMethod == eHTMLOutput) {
+ MOZ_TRY(NS_NewHTMLDocument(getter_AddRefs(mDocument), nullptr, nullptr,
+ aLoadedAsData));
+ } else {
+ // We should check the root name/namespace here and create the
+ // appropriate document
+ MOZ_TRY(NS_NewXMLDocument(getter_AddRefs(mDocument), nullptr, nullptr,
+ aLoadedAsData));
+ }
+ // This should really be handled by Document::BeginLoad
+ MOZ_ASSERT(
+ mDocument->GetReadyStateEnum() == Document::READYSTATE_UNINITIALIZED,
+ "Bad readyState");
+ mDocument->SetReadyStateInternal(Document::READYSTATE_LOADING);
+ mDocument->SetMayStartLayout(false);
+ bool hasHadScriptObject = false;
+ nsIScriptGlobalObject* sgo =
+ aSourceDocument->GetScriptHandlingObject(hasHadScriptObject);
+ NS_ENSURE_STATE(sgo || !hasHadScriptObject);
+
+ mCurrentNode = mDocument;
+ mNodeInfoManager = mDocument->NodeInfoManager();
+
+ // Reset and set up the document
+ URIUtils::ResetWithSource(mDocument, aSourceDocument);
+
+ // Make sure we set the script handling object after resetting with the
+ // source, so that we have the right principal.
+ mDocument->SetScriptHandlingObject(sgo);
+
+ mDocument->SetStateObjectFrom(aSourceDocument);
+
+ // Set the charset
+ if (!mOutputFormat.mEncoding.IsEmpty()) {
+ const Encoding* encoding = Encoding::ForLabel(mOutputFormat.mEncoding);
+ if (encoding) {
+ mDocument->SetDocumentCharacterSetSource(kCharsetFromOtherComponent);
+ mDocument->SetDocumentCharacterSet(WrapNotNull(encoding));
+ }
+ }
+
+ // Set the mime-type
+ if (!mOutputFormat.mMediaType.IsEmpty()) {
+ mDocument->SetContentType(mOutputFormat.mMediaType);
+ } else if (mOutputFormat.mMethod == eHTMLOutput) {
+ mDocument->SetContentType("text/html"_ns);
+ } else {
+ mDocument->SetContentType("application/xml"_ns);
+ }
+
+ if (mOutputFormat.mMethod == eXMLOutput &&
+ mOutputFormat.mOmitXMLDeclaration != eTrue) {
+ int32_t standalone;
+ if (mOutputFormat.mStandalone == eNotSet) {
+ standalone = -1;
+ } else if (mOutputFormat.mStandalone == eFalse) {
+ standalone = 0;
+ } else {
+ standalone = 1;
+ }
+
+ // Could use mOutputFormat.mVersion.get() when we support
+ // versions > 1.0.
+ static const char16_t kOneDotZero[] = {'1', '.', '0', '\0'};
+ mDocument->SetXMLDeclaration(kOneDotZero, mOutputFormat.mEncoding.get(),
+ standalone);
+ }
+
+ // Set up script loader of the result document.
+ ScriptLoader* loader = mDocument->ScriptLoader();
+ if (mNotifier) {
+ loader->AddObserver(mNotifier);
+ } else {
+ // Don't load scripts, we can't notify the caller when they're loaded.
+ loader->SetEnabled(false);
+ }
+
+ if (mNotifier) {
+ MOZ_TRY(mNotifier->SetOutputDocument(mDocument));
+ MOZ_TRY(mDocument->InitFeaturePolicy(mDocument->GetChannel()));
+ }
+
+ // Do this after calling OnDocumentCreated to ensure that the
+ // PresShell/PresContext has been hooked up and get notified.
+ mDocument->SetCompatibilityMode(eCompatibility_FullStandards);
+
+ // Add a doc-type if requested
+ if (!mOutputFormat.mSystemId.IsEmpty()) {
+ nsAutoString qName;
+ if (mOutputFormat.mMethod == eHTMLOutput) {
+ qName.AssignLiteral("html");
+ } else {
+ qName.Assign(aName);
+ }
+
+ nsresult rv = nsContentUtils::CheckQName(qName);
+ if (NS_SUCCEEDED(rv)) {
+ RefPtr<nsAtom> doctypeName = NS_Atomize(qName);
+ if (!doctypeName) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ // Indicate that there is no internal subset (not just an empty one)
+ RefPtr<DocumentType> documentType = NS_NewDOMDocumentType(
+ mNodeInfoManager, doctypeName, mOutputFormat.mPublicId,
+ mOutputFormat.mSystemId, VoidString());
+
+ ErrorResult error;
+ mDocument->AppendChildTo(documentType, true, error);
+ if (error.Failed()) {
+ return error.StealNSResult();
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult txMozillaXMLOutput::createHTMLElement(nsAtom* aName,
+ Element** aResult) {
+ NS_ASSERTION(mOutputFormat.mMethod == eHTMLOutput,
+ "need to adjust createHTMLElement");
+
+ *aResult = nullptr;
+
+ RefPtr<NodeInfo> ni;
+ ni = mNodeInfoManager->GetNodeInfo(aName, nullptr, kNameSpaceID_XHTML,
+ nsINode::ELEMENT_NODE);
+
+ nsCOMPtr<Element> el;
+ nsresult rv = NS_NewHTMLElement(
+ getter_AddRefs(el), ni.forget(),
+ mCreatingNewDocument ? FROM_PARSER_XSLT : FROM_PARSER_FRAGMENT);
+ el.forget(aResult);
+ return rv;
+}
+
+txTransformNotifier::txTransformNotifier(Document* aSourceDocument)
+ : mSourceDocument(aSourceDocument),
+ mPendingStylesheetCount(0),
+ mInTransform(false) {}
+
+txTransformNotifier::~txTransformNotifier() = default;
+
+NS_IMPL_ISUPPORTS(txTransformNotifier, nsIScriptLoaderObserver,
+ nsICSSLoaderObserver)
+
+NS_IMETHODIMP
+txTransformNotifier::ScriptAvailable(nsresult aResult,
+ nsIScriptElement* aElement,
+ bool aIsInlineClassicScript, nsIURI* aURI,
+ uint32_t aLineNo) {
+ if (NS_FAILED(aResult) && mScriptElements.RemoveElement(aElement)) {
+ SignalTransformEnd();
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+txTransformNotifier::ScriptEvaluated(nsresult aResult,
+ nsIScriptElement* aElement,
+ bool aIsInline) {
+ if (mScriptElements.RemoveElement(aElement)) {
+ SignalTransformEnd();
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+txTransformNotifier::StyleSheetLoaded(StyleSheet* aSheet, bool aWasDeferred,
+ nsresult aStatus) {
+ if (mPendingStylesheetCount == 0) {
+ // We weren't waiting on this stylesheet anyway. This can happen if
+ // SignalTransformEnd got called with an error aResult. See
+ // http://bugzilla.mozilla.org/show_bug.cgi?id=215465.
+ return NS_OK;
+ }
+
+ // We're never waiting for alternate stylesheets
+ if (!aWasDeferred) {
+ --mPendingStylesheetCount;
+ SignalTransformEnd();
+ }
+
+ return NS_OK;
+}
+
+void txTransformNotifier::Init(nsITransformObserver* aObserver) {
+ mObserver = aObserver;
+}
+
+void txTransformNotifier::AddScriptElement(nsIScriptElement* aElement) {
+ mScriptElements.AppendElement(aElement);
+}
+
+void txTransformNotifier::AddPendingStylesheet() { ++mPendingStylesheetCount; }
+
+void txTransformNotifier::OnTransformEnd(nsresult aResult) {
+ mInTransform = false;
+ SignalTransformEnd(aResult);
+}
+
+void txTransformNotifier::OnTransformStart() { mInTransform = true; }
+
+nsresult txTransformNotifier::SetOutputDocument(Document* aDocument) {
+ mDocument = aDocument;
+
+ // Notify the contentsink that the document is created
+ return mObserver->OnDocumentCreated(mSourceDocument, mDocument);
+}
+
+void txTransformNotifier::SignalTransformEnd(nsresult aResult) {
+ if (mInTransform ||
+ (NS_SUCCEEDED(aResult) &&
+ (!mScriptElements.IsEmpty() || mPendingStylesheetCount > 0))) {
+ return;
+ }
+
+ // mPendingStylesheetCount is nonzero at this point only if aResult is an
+ // error. Set it to 0 so we won't reenter this code when we stop the
+ // CSSLoader.
+ mPendingStylesheetCount = 0;
+ mScriptElements.Clear();
+
+ // Make sure that we don't get deleted while this function is executed and
+ // we remove ourselfs from the scriptloader
+ nsCOMPtr<nsIScriptLoaderObserver> kungFuDeathGrip(this);
+
+ if (mDocument) {
+ mDocument->ScriptLoader()->DeferCheckpointReached();
+ mDocument->ScriptLoader()->RemoveObserver(this);
+ // XXX Maybe we want to cancel script loads if NS_FAILED(rv)?
+
+ if (NS_FAILED(aResult)) {
+ mDocument->CSSLoader()->Stop();
+ }
+ }
+
+ if (NS_SUCCEEDED(aResult)) {
+ mObserver->OnTransformDone(mSourceDocument, aResult, mDocument);
+ }
+}
diff --git a/dom/xslt/xslt/txMozillaXMLOutput.h b/dom/xslt/xslt/txMozillaXMLOutput.h
new file mode 100644
index 0000000000..c77ecb6aae
--- /dev/null
+++ b/dom/xslt/xslt/txMozillaXMLOutput.h
@@ -0,0 +1,130 @@
+/* -*- 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/. */
+
+#ifndef TRANSFRMX_MOZILLA_XML_OUTPUT_H
+#define TRANSFRMX_MOZILLA_XML_OUTPUT_H
+
+#include "txXMLEventHandler.h"
+#include "nsIScriptLoaderObserver.h"
+#include "txOutputFormat.h"
+#include "nsTArray.h"
+#include "nsCOMPtr.h"
+#include "nsICSSLoaderObserver.h"
+#include "txStack.h"
+#include "mozilla/Attributes.h"
+
+class nsIContent;
+class nsAtom;
+class nsITransformObserver;
+class nsNodeInfoManager;
+class nsINode;
+
+namespace mozilla::dom {
+class Document;
+class DocumentFragment;
+class Element;
+} // namespace mozilla::dom
+
+class txTransformNotifier final : public nsIScriptLoaderObserver,
+ public nsICSSLoaderObserver {
+ public:
+ explicit txTransformNotifier(mozilla::dom::Document* aSourceDocument);
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISCRIPTLOADEROBSERVER
+
+ // nsICSSLoaderObserver
+ NS_IMETHOD StyleSheetLoaded(mozilla::StyleSheet* aSheet, bool aWasDeferred,
+ nsresult aStatus) override;
+
+ void Init(nsITransformObserver* aObserver);
+ void AddScriptElement(nsIScriptElement* aElement);
+ void AddPendingStylesheet();
+ void OnTransformEnd(nsresult aResult = NS_OK);
+ void OnTransformStart();
+ nsresult SetOutputDocument(mozilla::dom::Document* aDocument);
+
+ private:
+ ~txTransformNotifier();
+ void SignalTransformEnd(nsresult aResult = NS_OK);
+
+ nsCOMPtr<mozilla::dom::Document> mSourceDocument;
+ nsCOMPtr<mozilla::dom::Document> mDocument;
+ nsCOMPtr<nsITransformObserver> mObserver;
+ nsTArray<nsCOMPtr<nsIScriptElement>> mScriptElements;
+ uint32_t mPendingStylesheetCount;
+ bool mInTransform;
+};
+
+class txMozillaXMLOutput : public txAOutputXMLEventHandler {
+ public:
+ txMozillaXMLOutput(mozilla::dom::Document* aSourceDocument,
+ txOutputFormat* aFormat, nsITransformObserver* aObserver);
+ txMozillaXMLOutput(txOutputFormat* aFormat,
+ mozilla::dom::DocumentFragment* aFragment, bool aNoFixup);
+ ~txMozillaXMLOutput();
+
+ TX_DECL_TXAXMLEVENTHANDLER
+ TX_DECL_TXAOUTPUTXMLEVENTHANDLER
+
+ nsresult closePrevious(bool aFlushText);
+
+ nsresult createResultDocument(const nsAString& aName, int32_t aNsID,
+ mozilla::dom::Document* aSourceDocument,
+ bool aLoadedAsData);
+
+ private:
+ nsresult createTxWrapper();
+ nsresult startHTMLElement(nsIContent* aElement, bool aXHTML);
+ void endHTMLElement(nsIContent* aElement);
+ nsresult createHTMLElement(nsAtom* aName, mozilla::dom::Element** aResult);
+
+ nsresult attributeInternal(nsAtom* aPrefix, nsAtom* aLocalName, int32_t aNsID,
+ const nsString& aValue);
+ nsresult startElementInternal(nsAtom* aPrefix, nsAtom* aLocalName,
+ int32_t aNsID);
+
+ RefPtr<mozilla::dom::Document> mDocument;
+ nsCOMPtr<nsINode> mCurrentNode; // This is updated once an element is
+ // 'closed' (i.e. once we're done
+ // adding attributes to it).
+ // until then the opened element is
+ // kept in mOpenedElement
+ nsCOMPtr<mozilla::dom::Element> mOpenedElement;
+ RefPtr<nsNodeInfoManager> mNodeInfoManager;
+
+ nsTArray<nsCOMPtr<nsINode>> mCurrentNodeStack;
+
+ nsCOMPtr<nsIContent> mNonAddedNode;
+
+ RefPtr<txTransformNotifier> mNotifier;
+
+ uint32_t mTreeDepth, mBadChildLevel;
+
+ txStack mTableStateStack;
+ enum TableState {
+ NORMAL, // An element needing no special treatment
+ TABLE, // A HTML table element
+ ADDED_TBODY // An inserted tbody not coming from the stylesheet
+ };
+ TableState mTableState;
+
+ nsAutoString mText;
+
+ txOutputFormat mOutputFormat;
+
+ bool mCreatingNewDocument;
+
+ bool mOpenedElementIsHTML;
+
+ // Set to true when we know there's a root content in our document.
+ bool mRootContentCreated;
+
+ bool mNoFixup;
+
+ enum txAction { eCloseElement = 1, eFlushText = 2 };
+};
+
+#endif
diff --git a/dom/xslt/xslt/txMozillaXSLTProcessor.cpp b/dom/xslt/xslt/txMozillaXSLTProcessor.cpp
new file mode 100644
index 0000000000..db4de439ef
--- /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, &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 =
+ u"http://www.mozilla.org/newlayout/xml/parsererror.xml"_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);
+}
diff --git a/dom/xslt/xslt/txMozillaXSLTProcessor.h b/dom/xslt/xslt/txMozillaXSLTProcessor.h
new file mode 100644
index 0000000000..b000f804c6
--- /dev/null
+++ b/dom/xslt/xslt/txMozillaXSLTProcessor.h
@@ -0,0 +1,170 @@
+/* -*- 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/. */
+
+#ifndef TRANSFRMX_TXMOZILLAXSLTPROCESSOR_H
+#define TRANSFRMX_TXMOZILLAXSLTPROCESSOR_H
+
+#include "nsStubMutationObserver.h"
+#include "nsIDocumentTransformer.h"
+#include "txExpandedNameMap.h"
+#include "txNamespaceMap.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsWrapperCache.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/XSLTProcessorBinding.h"
+
+class nsINode;
+class nsIURI;
+class nsIVariant;
+class txStylesheet;
+class txResultRecycler;
+class txIGlobalParameter;
+
+namespace mozilla {
+class ErrorResult;
+
+namespace dom {
+
+class DocGroup;
+class Document;
+class DocumentFragment;
+class GlobalObject;
+enum class ReferrerPolicy : uint8_t;
+} // namespace dom
+} // namespace mozilla
+
+/**
+ * txMozillaXSLTProcessor is a front-end to the XSLT Processor.
+ */
+class txMozillaXSLTProcessor final : public nsIDocumentTransformer,
+ public nsStubMutationObserver,
+ public nsWrapperCache {
+ public:
+ typedef mozilla::dom::
+ UnrestrictedDoubleOrBooleanOrStringOrNodeOrNodeSequenceOrXPathResult
+ XSLTParameterValue;
+ typedef mozilla::dom::
+ OwningUnrestrictedDoubleOrBooleanOrStringOrNodeOrNodeSequenceOrXPathResult
+ OwningXSLTParameterValue;
+
+ /**
+ * Creates a new txMozillaXSLTProcessor
+ */
+ txMozillaXSLTProcessor();
+
+ // nsISupports interface
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS_AMBIGUOUS(txMozillaXSLTProcessor,
+ nsIDocumentTransformer)
+
+ // nsIDocumentTransformer interface
+ NS_IMETHOD SetTransformObserver(nsITransformObserver* aObserver) override;
+ NS_IMETHOD LoadStyleSheet(nsIURI* aUri,
+ mozilla::dom::Document* aLoaderDocument) override;
+ NS_IMETHOD SetSourceContentModel(nsINode* aSource) override;
+ NS_IMETHOD CancelLoads() override { return NS_OK; }
+ NS_IMETHOD AddXSLTParamNamespace(const nsString& aPrefix,
+ const nsString& aNamespace) override;
+ NS_IMETHOD AddXSLTParam(const nsString& aName, const nsString& aNamespace,
+ const nsString& aSelect, const nsString& aValue,
+ nsINode* aContext) override;
+
+ // nsIMutationObserver interface
+ NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATACHANGED
+ NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED
+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED
+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED
+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED
+ NS_DECL_NSIMUTATIONOBSERVER_NODEWILLBEDESTROYED
+
+ // nsWrapperCache
+ virtual JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ // WebIDL
+ nsISupports* GetParentObject() const { return mOwner; }
+
+ mozilla::dom::DocGroup* GetDocGroup() const;
+
+ static already_AddRefed<txMozillaXSLTProcessor> Constructor(
+ const mozilla::dom::GlobalObject& aGlobal);
+
+ void ImportStylesheet(nsINode& stylesheet, mozilla::ErrorResult& aRv);
+ already_AddRefed<mozilla::dom::DocumentFragment> TransformToFragment(
+ nsINode& source, mozilla::dom::Document& docVal,
+ mozilla::ErrorResult& aRv);
+ already_AddRefed<mozilla::dom::Document> TransformToDocument(
+ nsINode& source, mozilla::ErrorResult& aRv);
+
+ void SetParameter(const nsAString& aNamespaceURI, const nsAString& aLocalName,
+ const XSLTParameterValue& aValue,
+ mozilla::ErrorResult& aError);
+ void GetParameter(const nsAString& aNamespaceURI, const nsAString& aLocalName,
+ mozilla::dom::Nullable<OwningXSLTParameterValue>& aValue,
+ mozilla::ErrorResult& aRv);
+ void RemoveParameter(const nsAString& aNamespaceURI,
+ const nsAString& aLocalName, mozilla::ErrorResult& aRv);
+ void ClearParameters();
+ void Reset();
+
+ uint32_t Flags(mozilla::dom::SystemCallerGuarantee);
+ void SetFlags(uint32_t aFlags, mozilla::dom::SystemCallerGuarantee);
+
+ nsresult setStylesheet(txStylesheet* aStylesheet);
+ void reportError(nsresult aResult, const char16_t* aErrorText,
+ const char16_t* aSourceText);
+
+ nsINode* GetSourceContentModel() { return mSource; }
+
+ nsresult TransformToDoc(mozilla::dom::Document** aResult,
+ bool aCreateDataDocument);
+
+ bool IsLoadDisabled() {
+ return (mFlags & mozilla::dom::XSLTProcessor_Binding::DISABLE_ALL_LOADS) !=
+ 0;
+ }
+
+ static nsresult Startup();
+ static void Shutdown();
+
+ private:
+ explicit txMozillaXSLTProcessor(nsISupports* aOwner);
+ /**
+ * Default destructor for txMozillaXSLTProcessor
+ */
+ ~txMozillaXSLTProcessor();
+
+ nsresult DoTransform();
+ void notifyError();
+ nsresult ensureStylesheet();
+
+ nsCOMPtr<nsISupports> mOwner;
+
+ RefPtr<txStylesheet> mStylesheet;
+ mozilla::dom::Document* mStylesheetDocument; // weak
+ nsCOMPtr<nsIContent> mEmbeddedStylesheetRoot;
+
+ nsCOMPtr<nsINode> mSource;
+ nsresult mTransformResult;
+ nsresult mCompileResult;
+ nsString mErrorText, mSourceText;
+ nsCOMPtr<nsITransformObserver> mObserver;
+ txOwningExpandedNameMap<txIGlobalParameter> mVariables;
+ txNamespaceMap mParamNamespaceMap;
+ RefPtr<txResultRecycler> mRecycler;
+
+ uint32_t mFlags;
+};
+
+extern nsresult TX_LoadSheet(nsIURI* aUri, txMozillaXSLTProcessor* aProcessor,
+ mozilla::dom::Document* aLoaderDocument,
+ mozilla::dom::ReferrerPolicy aReferrerPolicy);
+
+extern nsresult TX_CompileStylesheet(nsINode* aNode,
+ txMozillaXSLTProcessor* aProcessor,
+ txStylesheet** aStylesheet);
+
+#endif
diff --git a/dom/xslt/xslt/txNodeSorter.cpp b/dom/xslt/xslt/txNodeSorter.cpp
new file mode 100644
index 0000000000..cc7ac3e354
--- /dev/null
+++ b/dom/xslt/xslt/txNodeSorter.cpp
@@ -0,0 +1,236 @@
+/* -*- 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 "txNodeSorter.h"
+#include "txExecutionState.h"
+#include "txXPathResultComparator.h"
+#include "nsGkAtoms.h"
+#include "txNodeSetContext.h"
+#include "txExpr.h"
+#include "txStringUtils.h"
+
+#include "mozilla/CheckedInt.h"
+#include "mozilla/UniquePtrExtensions.h"
+
+using mozilla::CheckedUint32;
+using mozilla::MakeUnique;
+using mozilla::MakeUniqueFallible;
+using mozilla::UniquePtr;
+
+/*
+ * Sorts Nodes as specified by the W3C XSLT 1.0 Recommendation
+ */
+
+txNodeSorter::txNodeSorter() : mNKeys(0) {}
+
+txNodeSorter::~txNodeSorter() {
+ txListIterator iter(&mSortKeys);
+ while (iter.hasNext()) {
+ SortKey* key = (SortKey*)iter.next();
+ delete key->mComparator;
+ delete key;
+ }
+}
+
+nsresult txNodeSorter::addSortElement(Expr* aSelectExpr, Expr* aLangExpr,
+ Expr* aDataTypeExpr, Expr* aOrderExpr,
+ Expr* aCaseOrderExpr,
+ txIEvalContext* aContext) {
+ UniquePtr<SortKey> key(new SortKey);
+ nsresult rv = NS_OK;
+
+ // Select
+ key->mExpr = aSelectExpr;
+
+ // Order
+ bool ascending = true;
+ if (aOrderExpr) {
+ nsAutoString attrValue;
+ rv = aOrderExpr->evaluateToString(aContext, attrValue);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (TX_StringEqualsAtom(attrValue, nsGkAtoms::descending)) {
+ ascending = false;
+ } else if (!TX_StringEqualsAtom(attrValue, nsGkAtoms::ascending)) {
+ // XXX ErrorReport: unknown value for order attribute
+ return NS_ERROR_XSLT_BAD_VALUE;
+ }
+ }
+
+ // Create comparator depending on datatype
+ nsAutoString dataType;
+ if (aDataTypeExpr) {
+ rv = aDataTypeExpr->evaluateToString(aContext, dataType);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (!aDataTypeExpr || TX_StringEqualsAtom(dataType, nsGkAtoms::text)) {
+ // Text comparator
+
+ // Language
+ nsAutoString lang;
+ if (aLangExpr) {
+ rv = aLangExpr->evaluateToString(aContext, lang);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Case-order
+ bool upperFirst = false;
+ if (aCaseOrderExpr) {
+ nsAutoString attrValue;
+
+ rv = aCaseOrderExpr->evaluateToString(aContext, attrValue);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (TX_StringEqualsAtom(attrValue, nsGkAtoms::upperFirst)) {
+ upperFirst = true;
+ } else if (!TX_StringEqualsAtom(attrValue, nsGkAtoms::lowerFirst)) {
+ // XXX ErrorReport: unknown value for case-order attribute
+ return NS_ERROR_XSLT_BAD_VALUE;
+ }
+ }
+
+ key->mComparator =
+ new txResultStringComparator(ascending, upperFirst, lang);
+ } else if (TX_StringEqualsAtom(dataType, nsGkAtoms::number)) {
+ // Number comparator
+ key->mComparator = new txResultNumberComparator(ascending);
+ } else {
+ // XXX ErrorReport: unknown data-type
+ return NS_ERROR_XSLT_BAD_VALUE;
+ }
+
+ // mSortKeys owns key now.
+ mSortKeys.add(key.release());
+ mNKeys++;
+
+ return NS_OK;
+}
+
+nsresult txNodeSorter::sortNodeSet(txNodeSet* aNodes, txExecutionState* aEs,
+ txNodeSet** aResult) {
+ if (mNKeys == 0 || aNodes->isEmpty()) {
+ RefPtr<txNodeSet> ref(aNodes);
+ ref.forget(aResult);
+
+ return NS_OK;
+ }
+
+ *aResult = nullptr;
+
+ RefPtr<txNodeSet> sortedNodes;
+ nsresult rv = aEs->recycler()->getNodeSet(getter_AddRefs(sortedNodes));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Create and set up memoryblock for sort-values and indexarray
+ CheckedUint32 len = aNodes->size();
+ CheckedUint32 numSortValues = len * mNKeys;
+ CheckedUint32 sortValuesSize = numSortValues * sizeof(txObject*);
+ if (!sortValuesSize.isValid()) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ nsTArray<uint32_t> indexes(len.value());
+ indexes.SetLengthAndRetainStorage(len.value());
+ nsTArray<txObject*> sortValues(numSortValues.value());
+ sortValues.SetLengthAndRetainStorage(numSortValues.value());
+ // txObject* has no null initializing constructor, so we init manually.
+ memset(sortValues.Elements(), 0, sortValuesSize.value());
+
+ uint32_t i;
+ for (i = 0; i < len.value(); ++i) {
+ indexes[i] = i;
+ }
+
+ auto nodeSetContext = MakeUnique<txNodeSetContext>(aNodes, aEs);
+
+ // Sort the indexarray
+ SortData sortData{};
+ sortData.mNodeSorter = this;
+ sortData.mContext = nodeSetContext.get();
+ sortData.mSortValues = sortValues.Elements();
+ sortData.mRv = NS_OK;
+
+ aEs->pushEvalContext(nodeSetContext.release());
+
+ indexes.StableSort([&sortData](uint32_t left, uint32_t right) {
+ return compareNodes(left, right, sortData);
+ });
+
+ // Delete these here so we don't have to deal with them at every possible
+ // failurepoint
+ for (i = 0; i < numSortValues.value(); ++i) {
+ delete sortValues[i];
+ }
+
+ if (NS_FAILED(sortData.mRv)) {
+ // The txExecutionState owns the evalcontext so no need to handle it
+ return sortData.mRv;
+ }
+
+ // Insert nodes in sorted order in new nodeset
+ for (i = 0; i < len.value(); ++i) {
+ rv = sortedNodes->append(aNodes->get(indexes[i]));
+ if (NS_FAILED(rv)) {
+ // The txExecutionState owns the evalcontext so no need to handle it
+ return rv;
+ }
+ }
+
+ delete aEs->popEvalContext();
+
+ sortedNodes.forget(aResult);
+
+ return NS_OK;
+}
+
+int txNodeSorter::compareNodes(uint32_t aIndexA, uint32_t aIndexB,
+ SortData& aSortData) {
+ NS_ENSURE_SUCCESS(aSortData.mRv, -1);
+
+ txListIterator iter(&aSortData.mNodeSorter->mSortKeys);
+ txObject** sortValuesA =
+ aSortData.mSortValues + aIndexA * aSortData.mNodeSorter->mNKeys;
+ txObject** sortValuesB =
+ aSortData.mSortValues + aIndexB * aSortData.mNodeSorter->mNKeys;
+
+ unsigned int i;
+ // Step through each key until a difference is found
+ for (i = 0; i < aSortData.mNodeSorter->mNKeys; ++i) {
+ SortKey* key = (SortKey*)iter.next();
+ // Lazy create sort values
+ if (!sortValuesA[i] &&
+ !calcSortValue(sortValuesA[i], key, &aSortData, aIndexA)) {
+ return -1;
+ }
+ if (!sortValuesB[i] &&
+ !calcSortValue(sortValuesB[i], key, &aSortData, aIndexB)) {
+ return 1;
+ }
+
+ // Compare node values
+ int compRes =
+ key->mComparator->compareValues(sortValuesA[i], sortValuesB[i]);
+ if (compRes != 0) return compRes;
+ }
+
+ // All keys have the same value for these nodes.
+ return 0;
+}
+
+// static
+bool txNodeSorter::calcSortValue(txObject*& aSortValue, SortKey* aKey,
+ SortData* aSortData, uint32_t aNodeIndex) {
+ aSortData->mContext->setPosition(aNodeIndex + 1); // position is 1-based
+
+ nsresult rv = aKey->mComparator->createSortableValue(
+ aKey->mExpr, aSortData->mContext, aSortValue);
+ if (NS_FAILED(rv)) {
+ aSortData->mRv = rv;
+ return false;
+ }
+
+ return true;
+}
diff --git a/dom/xslt/xslt/txNodeSorter.h b/dom/xslt/xslt/txNodeSorter.h
new file mode 100644
index 0000000000..b6c883b6ea
--- /dev/null
+++ b/dom/xslt/xslt/txNodeSorter.h
@@ -0,0 +1,55 @@
+/* -*- 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/. */
+
+#ifndef TRANSFRMX_NODESORTER_H
+#define TRANSFRMX_NODESORTER_H
+
+#include "txCore.h"
+#include "txList.h"
+
+class Expr;
+class txExecutionState;
+class txNodeSet;
+class txObject;
+class txXPathResultComparator;
+class txIEvalContext;
+class txNodeSetContext;
+
+/*
+ * Sorts Nodes as specified by the W3C XSLT 1.0 Recommendation
+ */
+
+class txNodeSorter {
+ public:
+ txNodeSorter();
+ ~txNodeSorter();
+
+ nsresult addSortElement(Expr* aSelectExpr, Expr* aLangExpr,
+ Expr* aDataTypeExpr, Expr* aOrderExpr,
+ Expr* aCaseOrderExpr, txIEvalContext* aContext);
+ nsresult sortNodeSet(txNodeSet* aNodes, txExecutionState* aEs,
+ txNodeSet** aResult);
+
+ private:
+ struct SortData {
+ txNodeSorter* mNodeSorter;
+ txNodeSetContext* mContext;
+ txObject** mSortValues;
+ nsresult mRv;
+ };
+ struct SortKey {
+ Expr* mExpr;
+ txXPathResultComparator* mComparator;
+ };
+
+ static int compareNodes(uint32_t aIndexA, uint32_t aIndexB,
+ SortData& aSortData);
+ static bool calcSortValue(txObject*& aSortValue, SortKey* aKey,
+ SortData* aSortData, uint32_t aNodeIndex);
+ txList mSortKeys;
+ unsigned int mNKeys;
+};
+
+#endif
diff --git a/dom/xslt/xslt/txOutputFormat.cpp b/dom/xslt/xslt/txOutputFormat.cpp
new file mode 100644
index 0000000000..fe4d068f6e
--- /dev/null
+++ b/dom/xslt/xslt/txOutputFormat.cpp
@@ -0,0 +1,101 @@
+/* -*- 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 "txOutputFormat.h"
+#include "txXMLUtils.h"
+#include "txExpandedName.h"
+
+txOutputFormat::txOutputFormat()
+ : mMethod(eMethodNotSet),
+ mOmitXMLDeclaration(eNotSet),
+ mStandalone(eNotSet),
+ mIndent(eNotSet) {}
+
+txOutputFormat::~txOutputFormat() {
+ txListIterator iter(&mCDATASectionElements);
+ while (iter.hasNext()) delete (txExpandedName*)iter.next();
+}
+
+void txOutputFormat::reset() {
+ mMethod = eMethodNotSet;
+ mVersion.Truncate();
+ if (mEncoding.IsEmpty()) mOmitXMLDeclaration = eNotSet;
+ mStandalone = eNotSet;
+ mPublicId.Truncate();
+ mSystemId.Truncate();
+ txListIterator iter(&mCDATASectionElements);
+ while (iter.hasNext()) delete (txExpandedName*)iter.next();
+ mIndent = eNotSet;
+ mMediaType.Truncate();
+}
+
+void txOutputFormat::merge(txOutputFormat& aOutputFormat) {
+ if (mMethod == eMethodNotSet) mMethod = aOutputFormat.mMethod;
+
+ if (mVersion.IsEmpty()) mVersion = aOutputFormat.mVersion;
+
+ if (mEncoding.IsEmpty()) mEncoding = aOutputFormat.mEncoding;
+
+ if (mOmitXMLDeclaration == eNotSet)
+ mOmitXMLDeclaration = aOutputFormat.mOmitXMLDeclaration;
+
+ if (mStandalone == eNotSet) mStandalone = aOutputFormat.mStandalone;
+
+ if (mPublicId.IsEmpty()) mPublicId = aOutputFormat.mPublicId;
+
+ if (mSystemId.IsEmpty()) mSystemId = aOutputFormat.mSystemId;
+
+ txListIterator iter(&aOutputFormat.mCDATASectionElements);
+ txExpandedName* qName;
+ while ((qName = (txExpandedName*)iter.next())) {
+ mCDATASectionElements.add(qName);
+ // XXX We need txList.clear()
+ iter.remove();
+ }
+
+ if (mIndent == eNotSet) mIndent = aOutputFormat.mIndent;
+
+ if (mMediaType.IsEmpty()) mMediaType = aOutputFormat.mMediaType;
+}
+
+void txOutputFormat::setFromDefaults() {
+ switch (mMethod) {
+ case eMethodNotSet: {
+ mMethod = eXMLOutput;
+ [[fallthrough]];
+ }
+ case eXMLOutput: {
+ if (mVersion.IsEmpty()) mVersion.AppendLiteral("1.0");
+
+ if (mEncoding.IsEmpty()) mEncoding.AppendLiteral("UTF-8");
+
+ if (mOmitXMLDeclaration == eNotSet) mOmitXMLDeclaration = eFalse;
+
+ if (mIndent == eNotSet) mIndent = eFalse;
+
+ if (mMediaType.IsEmpty()) mMediaType.AppendLiteral("text/xml");
+
+ break;
+ }
+ case eHTMLOutput: {
+ if (mVersion.IsEmpty()) mVersion.AppendLiteral("4.0");
+
+ if (mEncoding.IsEmpty()) mEncoding.AppendLiteral("UTF-8");
+
+ if (mIndent == eNotSet) mIndent = eTrue;
+
+ if (mMediaType.IsEmpty()) mMediaType.AppendLiteral("text/html");
+
+ break;
+ }
+ case eTextOutput: {
+ if (mEncoding.IsEmpty()) mEncoding.AppendLiteral("UTF-8");
+
+ if (mMediaType.IsEmpty()) mMediaType.AppendLiteral("text/plain");
+
+ break;
+ }
+ }
+}
diff --git a/dom/xslt/xslt/txOutputFormat.h b/dom/xslt/xslt/txOutputFormat.h
new file mode 100644
index 0000000000..f511381d44
--- /dev/null
+++ b/dom/xslt/xslt/txOutputFormat.h
@@ -0,0 +1,64 @@
+/* -*- 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/. */
+
+#ifndef TRANSFRMX_OUTPUTFORMAT_H
+#define TRANSFRMX_OUTPUTFORMAT_H
+
+#include "txList.h"
+#include "nsString.h"
+
+enum txOutputMethod { eMethodNotSet, eXMLOutput, eHTMLOutput, eTextOutput };
+
+enum txThreeState { eNotSet, eFalse, eTrue };
+
+class txOutputFormat {
+ public:
+ txOutputFormat();
+ ~txOutputFormat();
+
+ // "Unset" all values
+ void reset();
+
+ // Merges in the values of aOutputFormat, members that already
+ // have a value in this txOutputFormat will not be changed.
+ void merge(txOutputFormat& aOutputFormat);
+
+ // Sets members that have no value to their default value.
+ void setFromDefaults();
+
+ // The XSLT output method, which can be "xml", "html", or "text"
+ txOutputMethod mMethod;
+
+ // The xml version number that should be used when serializing
+ // xml documents
+ nsString mVersion;
+
+ // The XML character encoding that should be used when serializing
+ // xml documents
+ nsString mEncoding;
+
+ // Signals if we should output an XML declaration
+ txThreeState mOmitXMLDeclaration;
+
+ // Signals if we should output a standalone document declaration
+ txThreeState mStandalone;
+
+ // The public Id for creating a DOCTYPE
+ nsString mPublicId;
+
+ // The System Id for creating a DOCTYPE
+ nsString mSystemId;
+
+ // The elements whose text node children should be output as CDATA
+ txList mCDATASectionElements;
+
+ // Signals if output should be indented
+ txThreeState mIndent;
+
+ // The media type of the output
+ nsCString mMediaType;
+};
+
+#endif
diff --git a/dom/xslt/xslt/txPatternOptimizer.cpp b/dom/xslt/xslt/txPatternOptimizer.cpp
new file mode 100644
index 0000000000..4d0a68d188
--- /dev/null
+++ b/dom/xslt/xslt/txPatternOptimizer.cpp
@@ -0,0 +1,65 @@
+/* -*- 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 "txPatternOptimizer.h"
+#include "txXSLTPatterns.h"
+
+void txPatternOptimizer::optimize(txPattern* aInPattern,
+ txPattern** aOutPattern) {
+ *aOutPattern = nullptr;
+
+ // First optimize sub expressions
+ uint32_t i = 0;
+ Expr* subExpr;
+ while ((subExpr = aInPattern->getSubExprAt(i))) {
+ Expr* newExpr = nullptr;
+ mXPathOptimizer.optimize(subExpr, &newExpr);
+ if (newExpr) {
+ delete subExpr;
+ aInPattern->setSubExprAt(i, newExpr);
+ }
+
+ ++i;
+ }
+
+ // Then optimize sub patterns
+ txPattern* subPattern;
+ i = 0;
+ while ((subPattern = aInPattern->getSubPatternAt(i))) {
+ txPattern* newPattern = nullptr;
+ optimize(subPattern, &newPattern);
+ if (newPattern) {
+ delete subPattern;
+ aInPattern->setSubPatternAt(i, newPattern);
+ }
+
+ ++i;
+ }
+
+ // Finally see if current pattern can be optimized
+ switch (aInPattern->getType()) {
+ case txPattern::STEP_PATTERN:
+ optimizeStep(aInPattern, aOutPattern);
+ return;
+
+ default:
+ break;
+ }
+}
+
+void txPatternOptimizer::optimizeStep(txPattern* aInPattern,
+ txPattern** aOutPattern) {
+ txStepPattern* step = static_cast<txStepPattern*>(aInPattern);
+
+ // Test for predicates that can be combined into the nodetest
+ Expr* pred;
+ while ((pred = step->getSubExprAt(0)) &&
+ !pred->canReturnType(Expr::NUMBER_RESULT) &&
+ !pred->isSensitiveTo(Expr::NODESET_CONTEXT)) {
+ txNodeTest* predTest = new txPredicatedNodeTest(step->getNodeTest(), pred);
+ step->dropFirst();
+ step->setNodeTest(predTest);
+ }
+}
diff --git a/dom/xslt/xslt/txPatternOptimizer.h b/dom/xslt/xslt/txPatternOptimizer.h
new file mode 100644
index 0000000000..8ba3ffa625
--- /dev/null
+++ b/dom/xslt/xslt/txPatternOptimizer.h
@@ -0,0 +1,30 @@
+/* -*- 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/. */
+
+#ifndef txPatternOptimizer_h__
+#define txPatternOptimizer_h__
+
+#include "txXPathOptimizer.h"
+
+class txPattern;
+
+class txPatternOptimizer {
+ public:
+ /**
+ * Optimize the given pattern.
+ * @param aInPattern Pattern to optimize.
+ * @param aOutPattern Resulting pattern, null if optimization didn't
+ * result in a new pattern.
+ */
+ void optimize(txPattern* aInPattern, txPattern** aOutPattern);
+
+ private:
+ // Helper methods for optimizing specific classes
+ void optimizeStep(txPattern* aInPattern, txPattern** aOutPattern);
+
+ txXPathOptimizer mXPathOptimizer;
+};
+
+#endif // txPatternOptimizer_h__
diff --git a/dom/xslt/xslt/txPatternParser.cpp b/dom/xslt/xslt/txPatternParser.cpp
new file mode 100644
index 0000000000..d012c8d549
--- /dev/null
+++ b/dom/xslt/xslt/txPatternParser.cpp
@@ -0,0 +1,260 @@
+/* -*- 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 "txPatternParser.h"
+#include "txExprLexer.h"
+#include "nsGkAtoms.h"
+#include "nsError.h"
+#include "txStringUtils.h"
+#include "txXSLTPatterns.h"
+#include "txStylesheetCompiler.h"
+#include "txPatternOptimizer.h"
+
+#include "mozilla/UniquePtrExtensions.h"
+
+using mozilla::UniquePtr;
+
+nsresult txPatternParser::createPattern(const nsString& aPattern,
+ txIParseContext* aContext,
+ txPattern** aResult) {
+ txExprLexer lexer;
+ nsresult rv = lexer.parse(aPattern);
+ if (NS_FAILED(rv)) {
+ // XXX error report parsing error
+ return rv;
+ }
+ UniquePtr<txPattern> pattern;
+ rv = createUnionPattern(lexer, aContext, *getter_Transfers(pattern));
+ if (NS_FAILED(rv)) {
+ // XXX error report parsing error
+ return rv;
+ }
+
+ txPatternOptimizer optimizer;
+ txPattern* newPattern = nullptr;
+ optimizer.optimize(pattern.get(), &newPattern);
+
+ *aResult = newPattern ? newPattern : pattern.release();
+
+ return NS_OK;
+}
+
+nsresult txPatternParser::createUnionPattern(txExprLexer& aLexer,
+ txIParseContext* aContext,
+ txPattern*& aPattern) {
+ nsresult rv = NS_OK;
+ txPattern* locPath = 0;
+
+ rv = createLocPathPattern(aLexer, aContext, locPath);
+ if (NS_FAILED(rv)) return rv;
+
+ Token::Type type = aLexer.peek()->mType;
+ if (type == Token::END) {
+ aPattern = locPath;
+ return NS_OK;
+ }
+
+ if (type != Token::UNION_OP) {
+ delete locPath;
+ return NS_ERROR_XPATH_PARSE_FAILURE;
+ }
+
+ txUnionPattern* unionPattern = new txUnionPattern();
+ unionPattern->addPattern(locPath);
+
+ aLexer.nextToken();
+ do {
+ rv = createLocPathPattern(aLexer, aContext, locPath);
+ if (NS_FAILED(rv)) {
+ delete unionPattern;
+ return rv;
+ }
+ unionPattern->addPattern(locPath);
+ type = aLexer.nextToken()->mType;
+ } while (type == Token::UNION_OP);
+
+ if (type != Token::END) {
+ delete unionPattern;
+ return NS_ERROR_XPATH_PARSE_FAILURE;
+ }
+
+ aPattern = unionPattern;
+ return NS_OK;
+}
+
+nsresult txPatternParser::createLocPathPattern(txExprLexer& aLexer,
+ txIParseContext* aContext,
+ txPattern*& aPattern) {
+ nsresult rv = NS_OK;
+
+ bool isChild = true;
+ bool isAbsolute = false;
+ txPattern* stepPattern = 0;
+ txLocPathPattern* pathPattern = 0;
+
+ Token::Type type = aLexer.peek()->mType;
+ switch (type) {
+ case Token::ANCESTOR_OP:
+ isChild = false;
+ isAbsolute = true;
+ aLexer.nextToken();
+ break;
+ case Token::PARENT_OP:
+ aLexer.nextToken();
+ isAbsolute = true;
+ if (aLexer.peek()->mType == Token::END ||
+ aLexer.peek()->mType == Token::UNION_OP) {
+ aPattern = new txRootPattern();
+ return NS_OK;
+ }
+ break;
+ case Token::FUNCTION_NAME_AND_PAREN:
+ // id(Literal) or key(Literal, Literal)
+ {
+ RefPtr<nsAtom> nameAtom = NS_Atomize(aLexer.nextToken()->Value());
+ if (nameAtom == nsGkAtoms::id) {
+ rv = createIdPattern(aLexer, stepPattern);
+ } else if (nameAtom == nsGkAtoms::key) {
+ rv = createKeyPattern(aLexer, aContext, stepPattern);
+ }
+ if (NS_FAILED(rv)) return rv;
+ }
+ break;
+ default:
+ break;
+ }
+ if (!stepPattern) {
+ rv = createStepPattern(aLexer, aContext, stepPattern);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ type = aLexer.peek()->mType;
+ if (!isAbsolute && type != Token::PARENT_OP && type != Token::ANCESTOR_OP) {
+ aPattern = stepPattern;
+ return NS_OK;
+ }
+
+ pathPattern = new txLocPathPattern();
+ if (isAbsolute) {
+ txRootPattern* root = new txRootPattern();
+#ifdef TX_TO_STRING
+ root->setSerialize(false);
+#endif
+
+ pathPattern->addStep(root, isChild);
+ }
+
+ pathPattern->addStep(stepPattern, isChild);
+ stepPattern = 0; // stepPattern is part of pathPattern now
+
+ while (type == Token::PARENT_OP || type == Token::ANCESTOR_OP) {
+ isChild = type == Token::PARENT_OP;
+ aLexer.nextToken();
+ rv = createStepPattern(aLexer, aContext, stepPattern);
+ if (NS_FAILED(rv)) {
+ delete pathPattern;
+ return rv;
+ }
+ pathPattern->addStep(stepPattern, isChild);
+ stepPattern = 0; // stepPattern is part of pathPattern now
+ type = aLexer.peek()->mType;
+ }
+ aPattern = pathPattern;
+ return rv;
+}
+
+nsresult txPatternParser::createIdPattern(txExprLexer& aLexer,
+ txPattern*& aPattern) {
+ // check for '(' Literal ')'
+ if (aLexer.peek()->mType != Token::LITERAL)
+ return NS_ERROR_XPATH_PARSE_FAILURE;
+ const nsDependentSubstring& value = aLexer.nextToken()->Value();
+ if (aLexer.nextToken()->mType != Token::R_PAREN)
+ return NS_ERROR_XPATH_PARSE_FAILURE;
+ aPattern = new txIdPattern(value);
+ return NS_OK;
+}
+
+nsresult txPatternParser::createKeyPattern(txExprLexer& aLexer,
+ txIParseContext* aContext,
+ txPattern*& aPattern) {
+ // check for '(' Literal, Literal ')'
+ if (aLexer.peek()->mType != Token::LITERAL)
+ return NS_ERROR_XPATH_PARSE_FAILURE;
+ const nsDependentSubstring& key = aLexer.nextToken()->Value();
+ if (aLexer.nextToken()->mType != Token::COMMA &&
+ aLexer.peek()->mType != Token::LITERAL)
+ return NS_ERROR_XPATH_PARSE_FAILURE;
+ const nsDependentSubstring& value = aLexer.nextToken()->Value();
+ if (aLexer.nextToken()->mType != Token::R_PAREN)
+ return NS_ERROR_XPATH_PARSE_FAILURE;
+
+ if (!aContext->allowed(txIParseContext::KEY_FUNCTION))
+ return NS_ERROR_XSLT_CALL_TO_KEY_NOT_ALLOWED;
+
+ const char16_t* colon;
+ if (!XMLUtils::isValidQName(key, &colon)) {
+ return NS_ERROR_XPATH_PARSE_FAILURE;
+ }
+ RefPtr<nsAtom> prefix, localName;
+ int32_t namespaceID;
+ nsresult rv = resolveQName(key, getter_AddRefs(prefix), aContext,
+ getter_AddRefs(localName), namespaceID);
+ if (NS_FAILED(rv)) return rv;
+
+ aPattern = new txKeyPattern(prefix, localName, namespaceID, value);
+ return NS_OK;
+}
+
+nsresult txPatternParser::createStepPattern(txExprLexer& aLexer,
+ txIParseContext* aContext,
+ txPattern*& aPattern) {
+ nsresult rv = NS_OK;
+ bool isAttr = false;
+ Token* tok = aLexer.peek();
+ if (tok->mType == Token::AXIS_IDENTIFIER) {
+ if (TX_StringEqualsAtom(tok->Value(), nsGkAtoms::attribute)) {
+ isAttr = true;
+ } else if (!TX_StringEqualsAtom(tok->Value(), nsGkAtoms::child)) {
+ // all done already for CHILD_AXIS, for all others
+ // XXX report unexpected axis error
+ return NS_ERROR_XPATH_PARSE_FAILURE;
+ }
+ aLexer.nextToken();
+ } else if (tok->mType == Token::AT_SIGN) {
+ aLexer.nextToken();
+ isAttr = true;
+ }
+
+ txNodeTest* nodeTest;
+ if (aLexer.peek()->mType == Token::CNAME) {
+ tok = aLexer.nextToken();
+
+ // resolve QName
+ RefPtr<nsAtom> prefix, lName;
+ int32_t nspace;
+ rv = resolveQName(tok->Value(), getter_AddRefs(prefix), aContext,
+ getter_AddRefs(lName), nspace, true);
+ if (NS_FAILED(rv)) {
+ // XXX error report namespace resolve failed
+ return rv;
+ }
+
+ uint16_t nodeType = isAttr ? (uint16_t)txXPathNodeType::ATTRIBUTE_NODE
+ : (uint16_t)txXPathNodeType::ELEMENT_NODE;
+ nodeTest = new txNameTest(prefix, lName, nspace, nodeType);
+ } else {
+ rv = createNodeTypeTest(aLexer, &nodeTest);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ UniquePtr<txStepPattern> step(new txStepPattern(nodeTest, isAttr));
+ rv = parsePredicates(step.get(), aLexer, aContext);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aPattern = step.release();
+
+ return NS_OK;
+}
diff --git a/dom/xslt/xslt/txPatternParser.h b/dom/xslt/xslt/txPatternParser.h
new file mode 100644
index 0000000000..fcb9a1da0e
--- /dev/null
+++ b/dom/xslt/xslt/txPatternParser.h
@@ -0,0 +1,35 @@
+/* -*- 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/. */
+
+#ifndef TX_PATTERNPARSER_H
+#define TX_PATTERNPARSER_H
+
+#include "txXSLTPatterns.h"
+#include "txExprParser.h"
+
+class txStylesheetCompilerState;
+
+class txPatternParser : public txExprParser {
+ public:
+ static nsresult createPattern(const nsString& aPattern,
+ txIParseContext* aContext, txPattern** aResult);
+
+ protected:
+ static nsresult createUnionPattern(txExprLexer& aLexer,
+ txIParseContext* aContext,
+ txPattern*& aPattern);
+ static nsresult createLocPathPattern(txExprLexer& aLexer,
+ txIParseContext* aContext,
+ txPattern*& aPattern);
+ static nsresult createIdPattern(txExprLexer& aLexer, txPattern*& aPattern);
+ static nsresult createKeyPattern(txExprLexer& aLexer,
+ txIParseContext* aContext,
+ txPattern*& aPattern);
+ static nsresult createStepPattern(txExprLexer& aLexer,
+ txIParseContext* aContext,
+ txPattern*& aPattern);
+};
+
+#endif // TX_PATTERNPARSER_H
diff --git a/dom/xslt/xslt/txRtfHandler.cpp b/dom/xslt/xslt/txRtfHandler.cpp
new file mode 100644
index 0000000000..eaa40d8637
--- /dev/null
+++ b/dom/xslt/xslt/txRtfHandler.cpp
@@ -0,0 +1,54 @@
+/* -*- 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 "txRtfHandler.h"
+
+#include <utility>
+
+txResultTreeFragment::txResultTreeFragment(
+ mozilla::UniquePtr<txResultBuffer>&& aBuffer)
+ : txAExprResult(nullptr), mBuffer(std::move(aBuffer)) {}
+
+short txResultTreeFragment::getResultType() { return RESULT_TREE_FRAGMENT; }
+
+void txResultTreeFragment::stringValue(nsString& aResult) {
+ if (!mBuffer) {
+ return;
+ }
+
+ aResult.Append(mBuffer->mStringValue);
+}
+
+const nsString* txResultTreeFragment::stringValuePointer() {
+ return mBuffer ? &mBuffer->mStringValue : nullptr;
+}
+
+bool txResultTreeFragment::booleanValue() { return true; }
+
+double txResultTreeFragment::numberValue() {
+ if (!mBuffer) {
+ return 0;
+ }
+
+ return txDouble::toDouble(mBuffer->mStringValue);
+}
+
+nsresult txResultTreeFragment::flushToHandler(txAXMLEventHandler* aHandler) {
+ if (!mBuffer) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return mBuffer->flushToHandler(aHandler);
+}
+
+nsresult txRtfHandler::getAsRTF(txAExprResult** aResult) {
+ *aResult = new txResultTreeFragment(std::move(mBuffer));
+ NS_ADDREF(*aResult);
+ return NS_OK;
+}
+
+nsresult txRtfHandler::endDocument(nsresult aResult) { return NS_OK; }
+
+nsresult txRtfHandler::startDocument() { return NS_OK; }
diff --git a/dom/xslt/xslt/txRtfHandler.h b/dom/xslt/xslt/txRtfHandler.h
new file mode 100644
index 0000000000..ee0debf1a0
--- /dev/null
+++ b/dom/xslt/xslt/txRtfHandler.h
@@ -0,0 +1,42 @@
+/* -*- 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/. */
+
+#ifndef txRtfHandler_h___
+#define txRtfHandler_h___
+
+#include "mozilla/Attributes.h"
+#include "txBufferingHandler.h"
+#include "txExprResult.h"
+#include "txXPathNode.h"
+
+class txResultTreeFragment : public txAExprResult {
+ public:
+ explicit txResultTreeFragment(mozilla::UniquePtr<txResultBuffer>&& aBuffer);
+
+ TX_DECL_EXPRRESULT
+
+ nsresult flushToHandler(txAXMLEventHandler* aHandler);
+
+ void setNode(const txXPathNode* aNode) {
+ NS_ASSERTION(!mNode, "Already converted!");
+
+ mNode = mozilla::WrapUnique(aNode);
+ }
+ const txXPathNode* getNode() const { return mNode.get(); }
+
+ private:
+ mozilla::UniquePtr<txResultBuffer> mBuffer;
+ mozilla::UniquePtr<const txXPathNode> mNode;
+};
+
+class txRtfHandler : public txBufferingHandler {
+ public:
+ nsresult getAsRTF(txAExprResult** aResult);
+
+ nsresult endDocument(nsresult aResult) override;
+ nsresult startDocument() override;
+};
+
+#endif /* txRtfHandler_h___ */
diff --git a/dom/xslt/xslt/txStylesheet.cpp b/dom/xslt/xslt/txStylesheet.cpp
new file mode 100644
index 0000000000..5226ef5e08
--- /dev/null
+++ b/dom/xslt/xslt/txStylesheet.cpp
@@ -0,0 +1,550 @@
+/* -*- 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 "txStylesheet.h"
+
+#include <utility>
+
+#include "mozilla/FloatingPoint.h"
+#include "txExpr.h"
+#include "txInstructions.h"
+#include "txKey.h"
+#include "txLog.h"
+#include "txToplevelItems.h"
+#include "txXPathTreeWalker.h"
+#include "txXSLTFunctions.h"
+#include "txXSLTPatterns.h"
+
+using mozilla::LogLevel;
+using mozilla::MakeUnique;
+using mozilla::UniquePtr;
+using mozilla::Unused;
+using mozilla::WrapUnique;
+
+txStylesheet::txStylesheet() : mRootFrame(nullptr) {}
+
+nsresult txStylesheet::init() {
+ mRootFrame = new ImportFrame;
+
+ // Create default templates
+ // element/root template
+ mContainerTemplate = MakeUnique<txPushParams>();
+
+ UniquePtr<txNodeTest> nt(new txNodeTypeTest(txNodeTypeTest::NODE_TYPE));
+ UniquePtr<Expr> nodeExpr(
+ new LocationStep(nt.get(), LocationStep::CHILD_AXIS));
+ Unused << nt.release();
+
+ txPushNewContext* pushContext = new txPushNewContext(std::move(nodeExpr));
+ mContainerTemplate->mNext = WrapUnique(pushContext);
+
+ txApplyDefaultElementTemplate* applyTemplates =
+ new txApplyDefaultElementTemplate;
+ pushContext->mNext = WrapUnique(applyTemplates);
+
+ txLoopNodeSet* loopNodeSet = new txLoopNodeSet(applyTemplates);
+ applyTemplates->mNext = WrapUnique(loopNodeSet);
+
+ txPopParams* popParams = new txPopParams;
+ loopNodeSet->mNext = WrapUnique(popParams);
+ pushContext->mBailTarget = loopNodeSet->mNext.get();
+
+ popParams->mNext = MakeUnique<txReturn>();
+
+ // attribute/textnode template
+ nt = MakeUnique<txNodeTypeTest>(txNodeTypeTest::NODE_TYPE);
+ nodeExpr = MakeUnique<LocationStep>(nt.get(), LocationStep::SELF_AXIS);
+ Unused << nt.release();
+
+ mCharactersTemplate = MakeUnique<txValueOf>(std::move(nodeExpr), false);
+ mCharactersTemplate->mNext = MakeUnique<txReturn>();
+
+ // pi/comment/namespace template
+ mEmptyTemplate = MakeUnique<txReturn>();
+
+ return NS_OK;
+}
+
+txStylesheet::~txStylesheet() {
+ // Delete all ImportFrames
+ delete mRootFrame;
+ txListIterator frameIter(&mImportFrames);
+ while (frameIter.hasNext()) {
+ delete static_cast<ImportFrame*>(frameIter.next());
+ }
+
+ txListIterator instrIter(&mTemplateInstructions);
+ while (instrIter.hasNext()) {
+ delete static_cast<txInstruction*>(instrIter.next());
+ }
+
+ // We can't make the map own its values because then we wouldn't be able
+ // to merge attributesets of the same name
+ txExpandedNameMap<txInstruction>::iterator attrSetIter(mAttributeSets);
+ while (attrSetIter.next()) {
+ delete attrSetIter.value();
+ }
+}
+
+nsresult txStylesheet::findTemplate(const txXPathNode& aNode,
+ const txExpandedName& aMode,
+ txIMatchContext* aContext,
+ ImportFrame* aImportedBy,
+ txInstruction** aTemplate,
+ ImportFrame** aImportFrame) {
+ NS_ASSERTION(aImportFrame, "missing ImportFrame pointer");
+
+ *aTemplate = nullptr;
+ *aImportFrame = nullptr;
+ ImportFrame* endFrame = nullptr;
+ txListIterator frameIter(&mImportFrames);
+
+ if (aImportedBy) {
+ ImportFrame* curr = static_cast<ImportFrame*>(frameIter.next());
+ while (curr != aImportedBy) {
+ curr = static_cast<ImportFrame*>(frameIter.next());
+ }
+ endFrame = aImportedBy->mFirstNotImported;
+ }
+
+#if defined(TX_TO_STRING)
+ txPattern* match = 0;
+#endif
+
+ ImportFrame* frame;
+ while (!*aTemplate && (frame = static_cast<ImportFrame*>(frameIter.next())) &&
+ frame != endFrame) {
+ // get templatelist for this mode
+ nsTArray<MatchableTemplate>* templates =
+ frame->mMatchableTemplates.get(aMode);
+
+ if (templates) {
+ // Find template with highest priority
+ uint32_t i, len = templates->Length();
+ for (i = 0; i < len && !*aTemplate; ++i) {
+ MatchableTemplate& templ = (*templates)[i];
+ bool matched;
+ nsresult rv = templ.mMatch->matches(aNode, aContext, matched);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (matched) {
+ *aTemplate = templ.mFirstInstruction;
+ *aImportFrame = frame;
+#if defined(TX_TO_STRING)
+ match = templ.mMatch.get();
+#endif
+ }
+ }
+ }
+ }
+
+ if (MOZ_LOG_TEST(txLog::xslt, LogLevel::Debug)) {
+ nsAutoString mode, nodeName;
+ if (aMode.mLocalName) {
+ aMode.mLocalName->ToString(mode);
+ }
+ txXPathNodeUtils::getNodeName(aNode, nodeName);
+ if (*aTemplate) {
+ nsAutoString matchAttr;
+#ifdef TX_TO_STRING
+ match->toString(matchAttr);
+#endif
+ MOZ_LOG(txLog::xslt, LogLevel::Debug,
+ ("MatchTemplate, Pattern %s, Mode %s, Node %s\n",
+ NS_LossyConvertUTF16toASCII(matchAttr).get(),
+ NS_LossyConvertUTF16toASCII(mode).get(),
+ NS_LossyConvertUTF16toASCII(nodeName).get()));
+ } else {
+ MOZ_LOG(txLog::xslt, LogLevel::Debug,
+ ("No match, Node %s, Mode %s\n",
+ NS_LossyConvertUTF16toASCII(nodeName).get(),
+ NS_LossyConvertUTF16toASCII(mode).get()));
+ }
+ }
+
+ if (!*aTemplate) {
+ // Test for these first since a node can be both a text node
+ // and a root (if it is orphaned)
+ if (txXPathNodeUtils::isAttribute(aNode) ||
+ txXPathNodeUtils::isText(aNode)) {
+ *aTemplate = mCharactersTemplate.get();
+ } else if (txXPathNodeUtils::isElement(aNode) ||
+ txXPathNodeUtils::isRoot(aNode)) {
+ *aTemplate = mContainerTemplate.get();
+ } else {
+ *aTemplate = mEmptyTemplate.get();
+ }
+ }
+
+ return NS_OK;
+}
+
+txDecimalFormat* txStylesheet::getDecimalFormat(const txExpandedName& aName) {
+ return mDecimalFormats.get(aName);
+}
+
+txInstruction* txStylesheet::getAttributeSet(const txExpandedName& aName) {
+ return mAttributeSets.get(aName);
+}
+
+txInstruction* txStylesheet::getNamedTemplate(const txExpandedName& aName) {
+ return mNamedTemplates.get(aName);
+}
+
+txOutputFormat* txStylesheet::getOutputFormat() { return &mOutputFormat; }
+
+txStylesheet::GlobalVariable* txStylesheet::getGlobalVariable(
+ const txExpandedName& aName) {
+ return mGlobalVariables.get(aName);
+}
+
+const txOwningExpandedNameMap<txXSLKey>& txStylesheet::getKeyMap() {
+ return mKeys;
+}
+
+nsresult txStylesheet::isStripSpaceAllowed(const txXPathNode& aNode,
+ txIMatchContext* aContext,
+ bool& aAllowed) {
+ int32_t frameCount = mStripSpaceTests.Length();
+ if (frameCount == 0) {
+ aAllowed = false;
+
+ return NS_OK;
+ }
+
+ txXPathTreeWalker walker(aNode);
+
+ if (txXPathNodeUtils::isText(walker.getCurrentPosition()) &&
+ (!txXPathNodeUtils::isWhitespace(aNode) || !walker.moveToParent())) {
+ aAllowed = false;
+
+ return NS_OK;
+ }
+
+ const txXPathNode& node = walker.getCurrentPosition();
+
+ if (!txXPathNodeUtils::isElement(node)) {
+ aAllowed = false;
+
+ return NS_OK;
+ }
+
+ // check Whitespace stipping handling list against given Node
+ int32_t i;
+ for (i = 0; i < frameCount; ++i) {
+ const auto& sst = mStripSpaceTests[i];
+ bool matched;
+ nsresult rv = sst->matches(node, aContext, matched);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (matched) {
+ aAllowed = sst->stripsSpace() && !XMLUtils::getXMLSpacePreserve(node);
+
+ return NS_OK;
+ }
+ }
+
+ aAllowed = false;
+
+ return NS_OK;
+}
+
+nsresult txStylesheet::doneCompiling() {
+ nsresult rv = NS_OK;
+ // Collect all importframes into a single ordered list
+ txListIterator frameIter(&mImportFrames);
+ frameIter.addAfter(mRootFrame);
+
+ mRootFrame = nullptr;
+ frameIter.next();
+ rv = addFrames(frameIter);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Loop through importframes in decreasing-precedence-order and process
+ // all items
+ frameIter.reset();
+ ImportFrame* frame;
+ while ((frame = static_cast<ImportFrame*>(frameIter.next()))) {
+ nsTArray<txStripSpaceTest*> frameStripSpaceTests;
+
+ txListIterator itemIter(&frame->mToplevelItems);
+ itemIter.resetToEnd();
+ txToplevelItem* item;
+ while ((item = static_cast<txToplevelItem*>(itemIter.previous()))) {
+ switch (item->getType()) {
+ case txToplevelItem::attributeSet: {
+ rv = addAttributeSet(static_cast<txAttributeSetItem*>(item));
+ NS_ENSURE_SUCCESS(rv, rv);
+ break;
+ }
+ case txToplevelItem::dummy:
+ case txToplevelItem::import: {
+ break;
+ }
+ case txToplevelItem::output: {
+ mOutputFormat.merge(static_cast<txOutputItem*>(item)->mFormat);
+ break;
+ }
+ case txToplevelItem::stripSpace: {
+ rv = addStripSpace(static_cast<txStripSpaceItem*>(item),
+ frameStripSpaceTests);
+ NS_ENSURE_SUCCESS(rv, rv);
+ break;
+ }
+ case txToplevelItem::templ: {
+ rv = addTemplate(static_cast<txTemplateItem*>(item), frame);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ break;
+ }
+ case txToplevelItem::variable: {
+ rv = addGlobalVariable(static_cast<txVariableItem*>(item));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ break;
+ }
+ }
+ delete item;
+ itemIter.remove(); // remove() moves to the previous
+ itemIter.next();
+ }
+ mStripSpaceTests.AppendElements(frameStripSpaceTests);
+ frameStripSpaceTests.Clear();
+ }
+
+ if (!mDecimalFormats.get(txExpandedName())) {
+ UniquePtr<txDecimalFormat> format(new txDecimalFormat);
+ rv = mDecimalFormats.add(txExpandedName(), format.get());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ Unused << format.release();
+ }
+
+ return NS_OK;
+}
+
+nsresult txStylesheet::addTemplate(txTemplateItem* aTemplate,
+ ImportFrame* aImportFrame) {
+ NS_ASSERTION(aTemplate, "missing template");
+
+ txInstruction* instr = aTemplate->mFirstInstruction.get();
+ mTemplateInstructions.add(instr);
+
+ // mTemplateInstructions now owns the instructions
+ Unused << aTemplate->mFirstInstruction.release();
+
+ if (!aTemplate->mName.isNull()) {
+ nsresult rv = mNamedTemplates.add(aTemplate->mName, instr);
+ NS_ENSURE_TRUE(NS_SUCCEEDED(rv) || rv == NS_ERROR_XSLT_ALREADY_SET, rv);
+ }
+
+ if (!aTemplate->mMatch) {
+ // This is no error, see section 6 Named Templates
+
+ return NS_OK;
+ }
+
+ // get the txList for the right mode
+ nsTArray<MatchableTemplate>* templates =
+ aImportFrame->mMatchableTemplates.get(aTemplate->mMode);
+
+ if (!templates) {
+ UniquePtr<nsTArray<MatchableTemplate>> newList(
+ new nsTArray<MatchableTemplate>);
+ nsresult rv =
+ aImportFrame->mMatchableTemplates.set(aTemplate->mMode, newList.get());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ templates = newList.release();
+ }
+
+ // Add the simple patterns to the list of matchable templates, according
+ // to default priority
+ UniquePtr<txPattern> simple = std::move(aTemplate->mMatch);
+ UniquePtr<txPattern> unionPattern;
+ if (simple->getType() == txPattern::UNION_PATTERN) {
+ unionPattern = std::move(simple);
+ simple = WrapUnique(unionPattern->getSubPatternAt(0));
+ unionPattern->setSubPatternAt(0, nullptr);
+ }
+
+ uint32_t unionPos = 1; // only used when unionPattern is set
+ while (simple) {
+ double priority = aTemplate->mPrio;
+ if (std::isnan(priority)) {
+ priority = simple->getDefaultPriority();
+ NS_ASSERTION(!std::isnan(priority),
+ "simple pattern without default priority");
+ }
+
+ uint32_t i, len = templates->Length();
+ for (i = 0; i < len; ++i) {
+ if (priority > (*templates)[i].mPriority) {
+ break;
+ }
+ }
+
+ MatchableTemplate* nt = templates->InsertElementAt(i);
+ nt->mFirstInstruction = instr;
+ nt->mMatch = std::move(simple);
+ nt->mPriority = priority;
+
+ if (unionPattern) {
+ simple = WrapUnique(unionPattern->getSubPatternAt(unionPos));
+ if (simple) {
+ unionPattern->setSubPatternAt(unionPos, nullptr);
+ }
+ ++unionPos;
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult txStylesheet::addFrames(txListIterator& aInsertIter) {
+ ImportFrame* frame = static_cast<ImportFrame*>(aInsertIter.current());
+ nsresult rv = NS_OK;
+ txListIterator iter(&frame->mToplevelItems);
+ txToplevelItem* item;
+ while ((item = static_cast<txToplevelItem*>(iter.next()))) {
+ if (item->getType() == txToplevelItem::import) {
+ txImportItem* import = static_cast<txImportItem*>(item);
+ import->mFrame->mFirstNotImported =
+ static_cast<ImportFrame*>(aInsertIter.next());
+ aInsertIter.addBefore(import->mFrame.release());
+ aInsertIter.previous();
+ rv = addFrames(aInsertIter);
+ NS_ENSURE_SUCCESS(rv, rv);
+ aInsertIter.previous();
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult txStylesheet::addStripSpace(
+ txStripSpaceItem* aStripSpaceItem,
+ nsTArray<txStripSpaceTest*>& aFrameStripSpaceTests) {
+ int32_t testCount = aStripSpaceItem->mStripSpaceTests.Length();
+ for (; testCount > 0; --testCount) {
+ txStripSpaceTest* sst = aStripSpaceItem->mStripSpaceTests[testCount - 1];
+ double priority = sst->getDefaultPriority();
+ int32_t i, frameCount = aFrameStripSpaceTests.Length();
+ for (i = 0; i < frameCount; ++i) {
+ if (aFrameStripSpaceTests[i]->getDefaultPriority() < priority) {
+ break;
+ }
+ }
+ aFrameStripSpaceTests.InsertElementAt(i, sst);
+ aStripSpaceItem->mStripSpaceTests.RemoveElementAt(testCount - 1);
+ }
+
+ return NS_OK;
+}
+
+nsresult txStylesheet::addAttributeSet(txAttributeSetItem* aAttributeSetItem) {
+ nsresult rv = NS_OK;
+ txInstruction* oldInstr = mAttributeSets.get(aAttributeSetItem->mName);
+ if (!oldInstr) {
+ rv = mAttributeSets.add(aAttributeSetItem->mName,
+ aAttributeSetItem->mFirstInstruction.get());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ Unused << aAttributeSetItem->mFirstInstruction.release();
+
+ return NS_OK;
+ }
+
+ // We need to prepend the new instructions before the existing ones.
+ txInstruction* instr = aAttributeSetItem->mFirstInstruction.get();
+ txInstruction* lastNonReturn = nullptr;
+ while (instr->mNext) {
+ lastNonReturn = instr;
+ instr = instr->mNext.get();
+ }
+
+ if (!lastNonReturn) {
+ // The new attributeset is empty, so lets just ignore it.
+ return NS_OK;
+ }
+
+ rv = mAttributeSets.set(aAttributeSetItem->mName,
+ aAttributeSetItem->mFirstInstruction.get());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ Unused << aAttributeSetItem->mFirstInstruction.release();
+
+ lastNonReturn->mNext =
+ WrapUnique(oldInstr); // ...and link up the old instructions.
+
+ return NS_OK;
+}
+
+nsresult txStylesheet::addGlobalVariable(txVariableItem* aVariable) {
+ if (mGlobalVariables.get(aVariable->mName)) {
+ return NS_OK;
+ }
+ UniquePtr<GlobalVariable> var(new GlobalVariable(
+ std::move(aVariable->mValue), std::move(aVariable->mFirstInstruction),
+ aVariable->mIsParam));
+ nsresult rv = mGlobalVariables.add(aVariable->mName, var.get());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ Unused << var.release();
+
+ return NS_OK;
+}
+
+nsresult txStylesheet::addKey(const txExpandedName& aName,
+ UniquePtr<txPattern> aMatch,
+ UniquePtr<Expr> aUse) {
+ nsresult rv = NS_OK;
+
+ txXSLKey* xslKey = mKeys.get(aName);
+ if (!xslKey) {
+ xslKey = new txXSLKey(aName);
+ rv = mKeys.add(aName, xslKey);
+ if (NS_FAILED(rv)) {
+ delete xslKey;
+ return rv;
+ }
+ }
+ if (!xslKey->addKey(std::move(aMatch), std::move(aUse))) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ return NS_OK;
+}
+
+nsresult txStylesheet::addDecimalFormat(const txExpandedName& aName,
+ UniquePtr<txDecimalFormat>&& aFormat) {
+ txDecimalFormat* existing = mDecimalFormats.get(aName);
+ if (existing) {
+ NS_ENSURE_TRUE(existing->isEqual(aFormat.get()),
+ NS_ERROR_XSLT_PARSE_FAILURE);
+
+ return NS_OK;
+ }
+
+ nsresult rv = mDecimalFormats.add(aName, aFormat.get());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ Unused << aFormat.release();
+
+ return NS_OK;
+}
+
+txStylesheet::ImportFrame::~ImportFrame() {
+ txListIterator tlIter(&mToplevelItems);
+ while (tlIter.hasNext()) {
+ delete static_cast<txToplevelItem*>(tlIter.next());
+ }
+}
+
+txStylesheet::GlobalVariable::GlobalVariable(UniquePtr<Expr>&& aExpr,
+ UniquePtr<txInstruction>&& aInstr,
+ bool aIsParam)
+ : mExpr(std::move(aExpr)),
+ mFirstInstruction(std::move(aInstr)),
+ mIsParam(aIsParam) {}
diff --git a/dom/xslt/xslt/txStylesheet.h b/dom/xslt/xslt/txStylesheet.h
new file mode 100644
index 0000000000..f37b8acdbc
--- /dev/null
+++ b/dom/xslt/xslt/txStylesheet.h
@@ -0,0 +1,185 @@
+/* -*- 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/. */
+
+#ifndef TX_TXSTYLESHEET_H
+#define TX_TXSTYLESHEET_H
+
+#include "txOutputFormat.h"
+#include "txExpandedNameMap.h"
+#include "txList.h"
+#include "txXSLTPatterns.h"
+#include "nsISupportsImpl.h"
+
+class txInstruction;
+class txTemplateItem;
+class txVariableItem;
+class txStripSpaceItem;
+class txAttributeSetItem;
+class txDecimalFormat;
+class txStripSpaceTest;
+class txXSLKey;
+
+class txStylesheet final {
+ public:
+ class ImportFrame;
+ class GlobalVariable;
+ friend class txStylesheetCompilerState;
+ // To be able to do some cleaning up in destructor
+ friend class ImportFrame;
+
+ txStylesheet();
+ nsresult init();
+
+ NS_INLINE_DECL_REFCOUNTING(txStylesheet)
+
+ nsresult findTemplate(const txXPathNode& aNode, const txExpandedName& aMode,
+ txIMatchContext* aContext, ImportFrame* aImportedBy,
+ txInstruction** aTemplate, ImportFrame** aImportFrame);
+ txDecimalFormat* getDecimalFormat(const txExpandedName& aName);
+ txInstruction* getAttributeSet(const txExpandedName& aName);
+ txInstruction* getNamedTemplate(const txExpandedName& aName);
+ txOutputFormat* getOutputFormat();
+ GlobalVariable* getGlobalVariable(const txExpandedName& aName);
+ const txOwningExpandedNameMap<txXSLKey>& getKeyMap();
+ nsresult isStripSpaceAllowed(const txXPathNode& aNode,
+ txIMatchContext* aContext, bool& aAllowed);
+
+ /**
+ * Called by the stylesheet compiler once all stylesheets has been read.
+ */
+ nsresult doneCompiling();
+
+ /**
+ * Add a key to the stylesheet
+ */
+ nsresult addKey(const txExpandedName& aName,
+ mozilla::UniquePtr<txPattern> aMatch,
+ mozilla::UniquePtr<Expr> aUse);
+
+ /**
+ * Add a decimal-format to the stylesheet
+ */
+ nsresult addDecimalFormat(const txExpandedName& aName,
+ mozilla::UniquePtr<txDecimalFormat>&& aFormat);
+
+ struct MatchableTemplate {
+ txInstruction* mFirstInstruction;
+ mozilla::UniquePtr<txPattern> mMatch;
+ double mPriority;
+ };
+
+ /**
+ * Contain information that is import precedence dependant.
+ */
+ class ImportFrame {
+ public:
+ ImportFrame() : mFirstNotImported(nullptr) {}
+ ~ImportFrame();
+
+ // List of toplevel items
+ txList mToplevelItems;
+
+ // Map of template modes
+ txOwningExpandedNameMap<nsTArray<MatchableTemplate> > mMatchableTemplates;
+
+ // ImportFrame which is the first one *not* imported by this frame
+ ImportFrame* mFirstNotImported;
+ };
+
+ class GlobalVariable : public txObject {
+ public:
+ GlobalVariable(mozilla::UniquePtr<Expr>&& aExpr,
+ mozilla::UniquePtr<txInstruction>&& aFirstInstruction,
+ bool aIsParam);
+
+ mozilla::UniquePtr<Expr> mExpr;
+ mozilla::UniquePtr<txInstruction> mFirstInstruction;
+ bool mIsParam;
+ };
+
+ private:
+ // Private destructor, to discourage deletion outside of Release():
+ ~txStylesheet();
+
+ nsresult addTemplate(txTemplateItem* aTemplate, ImportFrame* aImportFrame);
+ nsresult addGlobalVariable(txVariableItem* aVariable);
+ nsresult addFrames(txListIterator& aInsertIter);
+ nsresult addStripSpace(txStripSpaceItem* aStripSpaceItem,
+ nsTArray<txStripSpaceTest*>& aFrameStripSpaceTests);
+ nsresult addAttributeSet(txAttributeSetItem* aAttributeSetItem);
+
+ // List of ImportFrames
+ txList mImportFrames;
+
+ // output format
+ txOutputFormat mOutputFormat;
+
+ // List of first instructions of templates. This is the owner of all
+ // instructions used in templates
+ txList mTemplateInstructions;
+
+ // Root importframe
+ ImportFrame* mRootFrame;
+
+ // Named templates
+ txExpandedNameMap<txInstruction> mNamedTemplates;
+
+ // Map with all decimal-formats
+ txOwningExpandedNameMap<txDecimalFormat> mDecimalFormats;
+
+ // Map with all named attribute sets
+ txExpandedNameMap<txInstruction> mAttributeSets;
+
+ // Map with all global variables and parameters
+ txOwningExpandedNameMap<GlobalVariable> mGlobalVariables;
+
+ // Map with all keys
+ txOwningExpandedNameMap<txXSLKey> mKeys;
+
+ // Array of all txStripSpaceTests, sorted in acending order
+ nsTArray<mozilla::UniquePtr<txStripSpaceTest> > mStripSpaceTests;
+
+ // Default templates
+ mozilla::UniquePtr<txInstruction> mContainerTemplate;
+ mozilla::UniquePtr<txInstruction> mCharactersTemplate;
+ mozilla::UniquePtr<txInstruction> mEmptyTemplate;
+};
+
+/**
+ * txStripSpaceTest holds both an txNameTest and a bool for use in
+ * whitespace stripping.
+ */
+class txStripSpaceTest {
+ public:
+ txStripSpaceTest(nsAtom* aPrefix, nsAtom* aLocalName, int32_t aNSID,
+ bool stripSpace)
+ : mNameTest(aPrefix, aLocalName, aNSID, txXPathNodeType::ELEMENT_NODE),
+ mStrips(stripSpace) {}
+
+ nsresult matches(const txXPathNode& aNode, txIMatchContext* aContext,
+ bool& aMatched) {
+ return mNameTest.matches(aNode, aContext, aMatched);
+ }
+
+ bool stripsSpace() { return mStrips; }
+
+ double getDefaultPriority() { return mNameTest.getDefaultPriority(); }
+
+ protected:
+ txNameTest mNameTest;
+ bool mStrips;
+};
+
+/**
+ * Value of a global parameter
+ */
+class txIGlobalParameter {
+ public:
+ MOZ_COUNTED_DEFAULT_CTOR(txIGlobalParameter)
+ MOZ_COUNTED_DTOR_VIRTUAL(txIGlobalParameter)
+ virtual nsresult getValue(txAExprResult** aValue) = 0;
+};
+
+#endif // TX_TXSTYLESHEET_H
diff --git a/dom/xslt/xslt/txStylesheetCompileHandlers.cpp b/dom/xslt/xslt/txStylesheetCompileHandlers.cpp
new file mode 100644
index 0000000000..ddc9f6fb38
--- /dev/null
+++ b/dom/xslt/xslt/txStylesheetCompileHandlers.cpp
@@ -0,0 +1,2352 @@
+/* -*- 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 "txStylesheetCompileHandlers.h"
+
+#include <utility>
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/FloatingPoint.h"
+#include "mozilla/UniquePtrExtensions.h"
+#include "nsGkAtoms.h"
+#include "nsWhitespaceTokenizer.h"
+#include "txCore.h"
+#include "txInstructions.h"
+#include "txNamespaceMap.h"
+#include "txPatternParser.h"
+#include "txStringUtils.h"
+#include "txStylesheet.h"
+#include "txStylesheetCompiler.h"
+#include "txToplevelItems.h"
+#include "txURIUtils.h"
+#include "txXSLTFunctions.h"
+#include "nsStringFlags.h"
+#include "nsStyleUtil.h"
+#include "nsStringIterator.h"
+
+using namespace mozilla;
+
+txHandlerTable* gTxIgnoreHandler = 0;
+txHandlerTable* gTxRootHandler = 0;
+txHandlerTable* gTxEmbedHandler = 0;
+txHandlerTable* gTxTopHandler = 0;
+txHandlerTable* gTxTemplateHandler = 0;
+txHandlerTable* gTxTextHandler = 0;
+txHandlerTable* gTxApplyTemplatesHandler = 0;
+txHandlerTable* gTxCallTemplateHandler = 0;
+txHandlerTable* gTxVariableHandler = 0;
+txHandlerTable* gTxForEachHandler = 0;
+txHandlerTable* gTxTopVariableHandler = 0;
+txHandlerTable* gTxChooseHandler = 0;
+txHandlerTable* gTxParamHandler = 0;
+txHandlerTable* gTxImportHandler = 0;
+txHandlerTable* gTxAttributeSetHandler = 0;
+txHandlerTable* gTxFallbackHandler = 0;
+
+static nsresult txFnStartLRE(int32_t aNamespaceID, nsAtom* aLocalName,
+ nsAtom* aPrefix, txStylesheetAttr* aAttributes,
+ int32_t aAttrCount,
+ txStylesheetCompilerState& aState);
+static void txFnEndLRE(txStylesheetCompilerState& aState);
+
+#define TX_RETURN_IF_WHITESPACE(_str, _state) \
+ do { \
+ if (!_state.mElementContext->mPreserveWhitespace && \
+ XMLUtils::isWhitespace(_str)) { \
+ return NS_OK; \
+ } \
+ } while (0)
+
+static nsresult getStyleAttr(txStylesheetAttr* aAttributes, int32_t aAttrCount,
+ int32_t aNamespace, nsAtom* aName, bool aRequired,
+ txStylesheetAttr** aAttr) {
+ int32_t i;
+ for (i = 0; i < aAttrCount; ++i) {
+ txStylesheetAttr* attr = aAttributes + i;
+ if (attr->mNamespaceID == aNamespace && attr->mLocalName == aName) {
+ attr->mLocalName = nullptr;
+ *aAttr = attr;
+
+ return NS_OK;
+ }
+ }
+ *aAttr = nullptr;
+
+ if (aRequired) {
+ // XXX ErrorReport: missing required attribute
+ return NS_ERROR_XSLT_PARSE_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+static nsresult parseUseAttrSets(txStylesheetAttr* aAttributes,
+ int32_t aAttrCount, bool aInXSLTNS,
+ txStylesheetCompilerState& aState) {
+ txStylesheetAttr* attr = nullptr;
+ nsresult rv = getStyleAttr(aAttributes, aAttrCount,
+ aInXSLTNS ? kNameSpaceID_XSLT : kNameSpaceID_None,
+ nsGkAtoms::useAttributeSets, false, &attr);
+ if (!attr) {
+ return rv;
+ }
+
+ nsWhitespaceTokenizer tok(attr->mValue);
+ while (tok.hasMoreTokens()) {
+ txExpandedName name;
+ rv = name.init(tok.nextToken(), aState.mElementContext->mMappings, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aState.addInstruction(MakeUnique<txInsertAttrSet>(name));
+ }
+ return NS_OK;
+}
+
+static nsresult parseExcludeResultPrefixes(txStylesheetAttr* aAttributes,
+ int32_t aAttrCount,
+ int32_t aNamespaceID) {
+ txStylesheetAttr* attr = nullptr;
+ nsresult rv = getStyleAttr(aAttributes, aAttrCount, aNamespaceID,
+ nsGkAtoms::excludeResultPrefixes, false, &attr);
+ if (!attr) {
+ return rv;
+ }
+
+ // XXX Needs to be implemented.
+
+ return NS_OK;
+}
+
+static nsresult getQNameAttr(txStylesheetAttr* aAttributes, int32_t aAttrCount,
+ nsAtom* aName, bool aRequired,
+ txStylesheetCompilerState& aState,
+ txExpandedName& aExpName) {
+ aExpName.reset();
+ txStylesheetAttr* attr = nullptr;
+ nsresult rv = getStyleAttr(aAttributes, aAttrCount, kNameSpaceID_None, aName,
+ aRequired, &attr);
+ if (!attr) {
+ return rv;
+ }
+
+ rv = aExpName.init(attr->mValue, aState.mElementContext->mMappings, false);
+ if (!aRequired && NS_FAILED(rv) && aState.fcp()) {
+ aExpName.reset();
+ rv = NS_OK;
+ }
+
+ return rv;
+}
+
+static nsresult getExprAttr(txStylesheetAttr* aAttributes, int32_t aAttrCount,
+ nsAtom* aName, bool aRequired,
+ txStylesheetCompilerState& aState,
+ UniquePtr<Expr>& aExpr) {
+ aExpr = nullptr;
+ txStylesheetAttr* attr = nullptr;
+ nsresult rv = getStyleAttr(aAttributes, aAttrCount, kNameSpaceID_None, aName,
+ aRequired, &attr);
+ if (!attr) {
+ return rv;
+ }
+
+ rv = txExprParser::createExpr(attr->mValue, &aState, getter_Transfers(aExpr));
+ if (NS_FAILED(rv) && aState.ignoreError(rv)) {
+ // use default value in fcp for not required exprs
+ if (aRequired) {
+ aExpr = MakeUnique<txErrorExpr>(
+#ifdef TX_TO_STRING
+ attr->mValue
+#endif
+ );
+ } else {
+ aExpr = nullptr;
+ }
+ return NS_OK;
+ }
+
+ return rv;
+}
+
+static nsresult getAVTAttr(txStylesheetAttr* aAttributes, int32_t aAttrCount,
+ nsAtom* aName, bool aRequired,
+ txStylesheetCompilerState& aState,
+ UniquePtr<Expr>& aAVT) {
+ aAVT = nullptr;
+ txStylesheetAttr* attr = nullptr;
+ nsresult rv = getStyleAttr(aAttributes, aAttrCount, kNameSpaceID_None, aName,
+ aRequired, &attr);
+ if (!attr) {
+ return rv;
+ }
+
+ rv = txExprParser::createAVT(attr->mValue, &aState, getter_Transfers(aAVT));
+ if (NS_FAILED(rv) && aState.fcp()) {
+ // use default value in fcp for not required exprs
+ if (aRequired) {
+ aAVT = MakeUnique<txErrorExpr>(
+#ifdef TX_TO_STRING
+ attr->mValue
+#endif
+ );
+ } else {
+ aAVT = nullptr;
+ }
+ return NS_OK;
+ }
+
+ return rv;
+}
+
+static nsresult getPatternAttr(txStylesheetAttr* aAttributes,
+ int32_t aAttrCount, nsAtom* aName,
+ bool aRequired,
+ txStylesheetCompilerState& aState,
+ UniquePtr<txPattern>& aPattern) {
+ aPattern = nullptr;
+ txStylesheetAttr* attr = nullptr;
+ nsresult rv = getStyleAttr(aAttributes, aAttrCount, kNameSpaceID_None, aName,
+ aRequired, &attr);
+ if (!attr) {
+ return rv;
+ }
+
+ rv = txPatternParser::createPattern(attr->mValue, &aState,
+ getter_Transfers(aPattern));
+ if (NS_FAILED(rv) && (aRequired || !aState.ignoreError(rv))) {
+ // XXX ErrorReport: XSLT-Pattern parse failure
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+static nsresult getNumberAttr(txStylesheetAttr* aAttributes, int32_t aAttrCount,
+ nsAtom* aName, bool aRequired,
+ txStylesheetCompilerState& aState,
+ double& aNumber) {
+ aNumber = UnspecifiedNaN<double>();
+ txStylesheetAttr* attr = nullptr;
+ nsresult rv = getStyleAttr(aAttributes, aAttrCount, kNameSpaceID_None, aName,
+ aRequired, &attr);
+ if (!attr) {
+ return rv;
+ }
+
+ aNumber = txDouble::toDouble(attr->mValue);
+ if (std::isnan(aNumber) && (aRequired || !aState.fcp())) {
+ // XXX ErrorReport: number parse failure
+ return NS_ERROR_XSLT_PARSE_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+static nsresult getAtomAttr(txStylesheetAttr* aAttributes, int32_t aAttrCount,
+ nsAtom* aName, bool aRequired,
+ txStylesheetCompilerState& aState, nsAtom** aAtom) {
+ *aAtom = nullptr;
+ txStylesheetAttr* attr = nullptr;
+ nsresult rv = getStyleAttr(aAttributes, aAttrCount, kNameSpaceID_None, aName,
+ aRequired, &attr);
+ if (!attr) {
+ return rv;
+ }
+
+ *aAtom = NS_Atomize(attr->mValue).take();
+ NS_ENSURE_TRUE(*aAtom, NS_ERROR_OUT_OF_MEMORY);
+
+ return NS_OK;
+}
+
+static nsresult getYesNoAttr(txStylesheetAttr* aAttributes, int32_t aAttrCount,
+ nsAtom* aName, bool aRequired,
+ txStylesheetCompilerState& aState,
+ txThreeState& aRes) {
+ aRes = eNotSet;
+ RefPtr<nsAtom> atom;
+ nsresult rv = getAtomAttr(aAttributes, aAttrCount, aName, aRequired, aState,
+ getter_AddRefs(atom));
+ if (!atom) {
+ return rv;
+ }
+
+ if (atom == nsGkAtoms::yes) {
+ aRes = eTrue;
+ } else if (atom == nsGkAtoms::no) {
+ aRes = eFalse;
+ } else if (aRequired || !aState.fcp()) {
+ // XXX ErrorReport: unknown values
+ return NS_ERROR_XSLT_PARSE_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+static nsresult getCharAttr(txStylesheetAttr* aAttributes, int32_t aAttrCount,
+ nsAtom* aName, bool aRequired,
+ txStylesheetCompilerState& aState,
+ char16_t& aChar) {
+ // Don't reset aChar since it contains the default value
+ txStylesheetAttr* attr = nullptr;
+ nsresult rv = getStyleAttr(aAttributes, aAttrCount, kNameSpaceID_None, aName,
+ aRequired, &attr);
+ if (!attr) {
+ return rv;
+ }
+
+ if (attr->mValue.Length() == 1) {
+ aChar = attr->mValue.CharAt(0);
+ } else if (aRequired || !aState.fcp()) {
+ // XXX ErrorReport: not a character
+ return NS_ERROR_XSLT_PARSE_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+static void pushInstruction(txStylesheetCompilerState& aState,
+ UniquePtr<txInstruction> aInstruction) {
+ aState.pushObject(aInstruction.release());
+}
+
+template <class T = txInstruction>
+static UniquePtr<T> popInstruction(txStylesheetCompilerState& aState) {
+ return UniquePtr<T>(static_cast<T*>(aState.popObject()));
+}
+
+/**
+ * Ignore and error handlers
+ */
+static nsresult txFnTextIgnore(const nsAString& aStr,
+ txStylesheetCompilerState& aState) {
+ return NS_OK;
+}
+
+static nsresult txFnTextError(const nsAString& aStr,
+ txStylesheetCompilerState& aState) {
+ TX_RETURN_IF_WHITESPACE(aStr, aState);
+
+ return NS_ERROR_XSLT_PARSE_FAILURE;
+}
+
+void clearAttributes(txStylesheetAttr* aAttributes, int32_t aAttrCount) {
+ int32_t i;
+ for (i = 0; i < aAttrCount; ++i) {
+ aAttributes[i].mLocalName = nullptr;
+ }
+}
+
+static nsresult txFnStartElementIgnore(int32_t aNamespaceID, nsAtom* aLocalName,
+ nsAtom* aPrefix,
+ txStylesheetAttr* aAttributes,
+ int32_t aAttrCount,
+ txStylesheetCompilerState& aState) {
+ if (!aState.fcp()) {
+ clearAttributes(aAttributes, aAttrCount);
+ }
+
+ return NS_OK;
+}
+
+static void txFnEndElementIgnore(txStylesheetCompilerState& aState) {}
+
+static nsresult txFnStartElementSetIgnore(int32_t aNamespaceID,
+ nsAtom* aLocalName, nsAtom* aPrefix,
+ txStylesheetAttr* aAttributes,
+ int32_t aAttrCount,
+ txStylesheetCompilerState& aState) {
+ if (!aState.fcp()) {
+ clearAttributes(aAttributes, aAttrCount);
+ }
+
+ aState.pushHandlerTable(gTxIgnoreHandler);
+
+ return NS_OK;
+}
+
+static void txFnEndElementSetIgnore(txStylesheetCompilerState& aState) {
+ aState.popHandlerTable();
+}
+
+static nsresult txFnStartElementError(int32_t aNamespaceID, nsAtom* aLocalName,
+ nsAtom* aPrefix,
+ txStylesheetAttr* aAttributes,
+ int32_t aAttrCount,
+ txStylesheetCompilerState& aState) {
+ return NS_ERROR_XSLT_PARSE_FAILURE;
+}
+
+static void txFnEndElementError(txStylesheetCompilerState& aState) {
+ MOZ_CRASH("txFnEndElementError shouldn't be called");
+}
+
+/**
+ * Root handlers
+ */
+static nsresult txFnStartStylesheet(int32_t aNamespaceID, nsAtom* aLocalName,
+ nsAtom* aPrefix,
+ txStylesheetAttr* aAttributes,
+ int32_t aAttrCount,
+ txStylesheetCompilerState& aState) {
+ // extension-element-prefixes is handled in
+ // txStylesheetCompiler::startElementInternal
+
+ txStylesheetAttr* attr;
+ nsresult rv = getStyleAttr(aAttributes, aAttrCount, kNameSpaceID_None,
+ nsGkAtoms::id, false, &attr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = parseExcludeResultPrefixes(aAttributes, aAttrCount, kNameSpaceID_None);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = getStyleAttr(aAttributes, aAttrCount, kNameSpaceID_None,
+ nsGkAtoms::version, true, &attr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aState.pushHandlerTable(gTxImportHandler);
+
+ return NS_OK;
+}
+
+static void txFnEndStylesheet(txStylesheetCompilerState& aState) {
+ aState.popHandlerTable();
+}
+
+static nsresult txFnStartElementContinueTopLevel(
+ int32_t aNamespaceID, nsAtom* aLocalName, nsAtom* aPrefix,
+ txStylesheetAttr* aAttributes, int32_t aAttrCount,
+ txStylesheetCompilerState& aState) {
+ aState.mHandlerTable = gTxTopHandler;
+
+ return NS_XSLT_GET_NEW_HANDLER;
+}
+
+static nsresult txFnStartLREStylesheet(int32_t aNamespaceID, nsAtom* aLocalName,
+ nsAtom* aPrefix,
+ txStylesheetAttr* aAttributes,
+ int32_t aAttrCount,
+ txStylesheetCompilerState& aState) {
+ txStylesheetAttr* attr;
+ nsresult rv = getStyleAttr(aAttributes, aAttrCount, kNameSpaceID_XSLT,
+ nsGkAtoms::version, true, &attr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ txExpandedName nullExpr;
+ double prio = UnspecifiedNaN<double>();
+
+ UniquePtr<txPattern> match(new txRootPattern());
+ UniquePtr<txTemplateItem> templ(
+ new txTemplateItem(std::move(match), nullExpr, nullExpr, prio));
+ aState.openInstructionContainer(templ.get());
+ aState.addToplevelItem(templ.release());
+
+ aState.pushHandlerTable(gTxTemplateHandler);
+
+ return txFnStartLRE(aNamespaceID, aLocalName, aPrefix, aAttributes,
+ aAttrCount, aState);
+}
+
+static void txFnEndLREStylesheet(txStylesheetCompilerState& aState) {
+ txFnEndLRE(aState);
+
+ aState.popHandlerTable();
+
+ aState.addInstruction(MakeUnique<txReturn>());
+
+ aState.closeInstructionContainer();
+}
+
+static nsresult txFnStartEmbed(int32_t aNamespaceID, nsAtom* aLocalName,
+ nsAtom* aPrefix, txStylesheetAttr* aAttributes,
+ int32_t aAttrCount,
+ txStylesheetCompilerState& aState) {
+ if (!aState.handleEmbeddedSheet()) {
+ return NS_OK;
+ }
+ if (aNamespaceID != kNameSpaceID_XSLT ||
+ (aLocalName != nsGkAtoms::stylesheet &&
+ aLocalName != nsGkAtoms::transform)) {
+ return NS_ERROR_XSLT_PARSE_FAILURE;
+ }
+ return txFnStartStylesheet(aNamespaceID, aLocalName, aPrefix, aAttributes,
+ aAttrCount, aState);
+}
+
+static void txFnEndEmbed(txStylesheetCompilerState& aState) {
+ if (!aState.handleEmbeddedSheet()) {
+ return;
+ }
+ txFnEndStylesheet(aState);
+ aState.doneEmbedding();
+}
+
+/**
+ * Top handlers
+ */
+static nsresult txFnStartOtherTop(int32_t aNamespaceID, nsAtom* aLocalName,
+ nsAtom* aPrefix,
+ txStylesheetAttr* aAttributes,
+ int32_t aAttrCount,
+ txStylesheetCompilerState& aState) {
+ if (aNamespaceID == kNameSpaceID_None ||
+ (aNamespaceID == kNameSpaceID_XSLT && !aState.fcp())) {
+ return NS_ERROR_XSLT_PARSE_FAILURE;
+ }
+
+ aState.pushHandlerTable(gTxIgnoreHandler);
+
+ return NS_OK;
+}
+
+static void txFnEndOtherTop(txStylesheetCompilerState& aState) {
+ aState.popHandlerTable();
+}
+
+// xsl:attribute-set
+static nsresult txFnStartAttributeSet(int32_t aNamespaceID, nsAtom* aLocalName,
+ nsAtom* aPrefix,
+ txStylesheetAttr* aAttributes,
+ int32_t aAttrCount,
+ txStylesheetCompilerState& aState) {
+ nsresult rv = NS_OK;
+ txExpandedName name;
+ rv = getQNameAttr(aAttributes, aAttrCount, nsGkAtoms::name, true, aState,
+ name);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ UniquePtr<txAttributeSetItem> attrSet(new txAttributeSetItem(name));
+ aState.openInstructionContainer(attrSet.get());
+
+ aState.addToplevelItem(attrSet.release());
+
+ rv = parseUseAttrSets(aAttributes, aAttrCount, false, aState);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aState.pushHandlerTable(gTxAttributeSetHandler);
+
+ return NS_OK;
+}
+
+static void txFnEndAttributeSet(txStylesheetCompilerState& aState) {
+ aState.popHandlerTable();
+
+ aState.addInstruction(MakeUnique<txReturn>());
+
+ aState.closeInstructionContainer();
+}
+
+// xsl:decimal-format
+static nsresult txFnStartDecimalFormat(int32_t aNamespaceID, nsAtom* aLocalName,
+ nsAtom* aPrefix,
+ txStylesheetAttr* aAttributes,
+ int32_t aAttrCount,
+ txStylesheetCompilerState& aState) {
+ nsresult rv = NS_OK;
+ txExpandedName name;
+ rv = getQNameAttr(aAttributes, aAttrCount, nsGkAtoms::name, false, aState,
+ name);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ UniquePtr<txDecimalFormat> format(new txDecimalFormat);
+ rv = getCharAttr(aAttributes, aAttrCount, nsGkAtoms::decimalSeparator, false,
+ aState, format->mDecimalSeparator);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = getCharAttr(aAttributes, aAttrCount, nsGkAtoms::groupingSeparator, false,
+ aState, format->mGroupingSeparator);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ txStylesheetAttr* attr = nullptr;
+ rv = getStyleAttr(aAttributes, aAttrCount, kNameSpaceID_None,
+ nsGkAtoms::infinity, false, &attr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (attr) {
+ format->mInfinity = attr->mValue;
+ }
+
+ rv = getCharAttr(aAttributes, aAttrCount, nsGkAtoms::minusSign, false, aState,
+ format->mMinusSign);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = getStyleAttr(aAttributes, aAttrCount, kNameSpaceID_None, nsGkAtoms::NaN,
+ false, &attr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (attr) {
+ format->mNaN = attr->mValue;
+ }
+
+ rv = getCharAttr(aAttributes, aAttrCount, nsGkAtoms::percent, false, aState,
+ format->mPercent);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = getCharAttr(aAttributes, aAttrCount, nsGkAtoms::perMille, false, aState,
+ format->mPerMille);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = getCharAttr(aAttributes, aAttrCount, nsGkAtoms::zeroDigit, false, aState,
+ format->mZeroDigit);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = getCharAttr(aAttributes, aAttrCount, nsGkAtoms::digit, false, aState,
+ format->mDigit);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = getCharAttr(aAttributes, aAttrCount, nsGkAtoms::patternSeparator, false,
+ aState, format->mPatternSeparator);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aState.mStylesheet->addDecimalFormat(name, std::move(format));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aState.pushHandlerTable(gTxIgnoreHandler);
+
+ return NS_OK;
+}
+
+static void txFnEndDecimalFormat(txStylesheetCompilerState& aState) {
+ aState.popHandlerTable();
+}
+
+// xsl:import
+static nsresult txFnStartImport(int32_t aNamespaceID, nsAtom* aLocalName,
+ nsAtom* aPrefix, txStylesheetAttr* aAttributes,
+ int32_t aAttrCount,
+ txStylesheetCompilerState& aState) {
+ UniquePtr<txImportItem> import(new txImportItem);
+ import->mFrame = MakeUnique<txStylesheet::ImportFrame>();
+ txStylesheet::ImportFrame* frame = import->mFrame.get();
+ aState.addToplevelItem(import.release());
+
+ txStylesheetAttr* attr = nullptr;
+ nsresult rv = getStyleAttr(aAttributes, aAttrCount, kNameSpaceID_None,
+ nsGkAtoms::href, true, &attr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString absUri;
+ URIUtils::resolveHref(attr->mValue, aState.mElementContext->mBaseURI, absUri);
+ rv = aState.loadImportedStylesheet(absUri, frame);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aState.pushHandlerTable(gTxIgnoreHandler);
+
+ return NS_OK;
+}
+
+static void txFnEndImport(txStylesheetCompilerState& aState) {
+ aState.popHandlerTable();
+}
+
+// xsl:include
+static nsresult txFnStartInclude(int32_t aNamespaceID, nsAtom* aLocalName,
+ nsAtom* aPrefix, txStylesheetAttr* aAttributes,
+ int32_t aAttrCount,
+ txStylesheetCompilerState& aState) {
+ txStylesheetAttr* attr = nullptr;
+ nsresult rv = getStyleAttr(aAttributes, aAttrCount, kNameSpaceID_None,
+ nsGkAtoms::href, true, &attr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString absUri;
+ URIUtils::resolveHref(attr->mValue, aState.mElementContext->mBaseURI, absUri);
+ rv = aState.loadIncludedStylesheet(absUri);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aState.pushHandlerTable(gTxIgnoreHandler);
+
+ return NS_OK;
+}
+
+static void txFnEndInclude(txStylesheetCompilerState& aState) {
+ aState.popHandlerTable();
+}
+
+// xsl:key
+static nsresult txFnStartKey(int32_t aNamespaceID, nsAtom* aLocalName,
+ nsAtom* aPrefix, txStylesheetAttr* aAttributes,
+ int32_t aAttrCount,
+ txStylesheetCompilerState& aState) {
+ nsresult rv = NS_OK;
+ txExpandedName name;
+ rv = getQNameAttr(aAttributes, aAttrCount, nsGkAtoms::name, true, aState,
+ name);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aState.mDisAllowed = txIParseContext::KEY_FUNCTION;
+
+ UniquePtr<txPattern> match;
+ rv = getPatternAttr(aAttributes, aAttrCount, nsGkAtoms::match, true, aState,
+ match);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ UniquePtr<Expr> use;
+ rv = getExprAttr(aAttributes, aAttrCount, nsGkAtoms::use, true, aState, use);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aState.mDisAllowed = 0;
+
+ rv = aState.mStylesheet->addKey(name, std::move(match), std::move(use));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aState.pushHandlerTable(gTxIgnoreHandler);
+
+ return NS_OK;
+}
+
+static void txFnEndKey(txStylesheetCompilerState& aState) {
+ aState.popHandlerTable();
+}
+
+// xsl:namespace-alias
+static nsresult txFnStartNamespaceAlias(int32_t aNamespaceID,
+ nsAtom* aLocalName, nsAtom* aPrefix,
+ txStylesheetAttr* aAttributes,
+ int32_t aAttrCount,
+ txStylesheetCompilerState& aState) {
+ txStylesheetAttr* attr = nullptr;
+ nsresult rv = getStyleAttr(aAttributes, aAttrCount, kNameSpaceID_None,
+ nsGkAtoms::stylesheetPrefix, true, &attr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = getStyleAttr(aAttributes, aAttrCount, kNameSpaceID_None,
+ nsGkAtoms::resultPrefix, true, &attr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // XXX Needs to be implemented.
+
+ aState.pushHandlerTable(gTxIgnoreHandler);
+
+ return NS_OK;
+}
+
+static void txFnEndNamespaceAlias(txStylesheetCompilerState& aState) {
+ aState.popHandlerTable();
+}
+
+// xsl:output
+static nsresult txFnStartOutput(int32_t aNamespaceID, nsAtom* aLocalName,
+ nsAtom* aPrefix, txStylesheetAttr* aAttributes,
+ int32_t aAttrCount,
+ txStylesheetCompilerState& aState) {
+ nsresult rv = NS_OK;
+
+ UniquePtr<txOutputItem> item(new txOutputItem);
+
+ txExpandedName methodExpName;
+ rv = getQNameAttr(aAttributes, aAttrCount, nsGkAtoms::method, false, aState,
+ methodExpName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!methodExpName.isNull()) {
+ if (methodExpName.mNamespaceID != kNameSpaceID_None) {
+ // The spec doesn't say what to do here so we'll just ignore the
+ // value. We could possibly warn.
+ } else if (methodExpName.mLocalName == nsGkAtoms::html) {
+ item->mFormat.mMethod = eHTMLOutput;
+ } else if (methodExpName.mLocalName == nsGkAtoms::text) {
+ item->mFormat.mMethod = eTextOutput;
+ } else if (methodExpName.mLocalName == nsGkAtoms::xml) {
+ item->mFormat.mMethod = eXMLOutput;
+ } else {
+ return NS_ERROR_XSLT_PARSE_FAILURE;
+ }
+ }
+
+ txStylesheetAttr* attr = nullptr;
+ getStyleAttr(aAttributes, aAttrCount, kNameSpaceID_None, nsGkAtoms::version,
+ false, &attr);
+ if (attr) {
+ item->mFormat.mVersion = attr->mValue;
+ }
+
+ getStyleAttr(aAttributes, aAttrCount, kNameSpaceID_None, nsGkAtoms::encoding,
+ false, &attr);
+ if (attr) {
+ item->mFormat.mEncoding = attr->mValue;
+ }
+
+ rv = getYesNoAttr(aAttributes, aAttrCount, nsGkAtoms::omitXmlDeclaration,
+ false, aState, item->mFormat.mOmitXMLDeclaration);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = getYesNoAttr(aAttributes, aAttrCount, nsGkAtoms::standalone, false,
+ aState, item->mFormat.mStandalone);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ getStyleAttr(aAttributes, aAttrCount, kNameSpaceID_None,
+ nsGkAtoms::doctypePublic, false, &attr);
+ if (attr) {
+ item->mFormat.mPublicId = attr->mValue;
+ }
+
+ getStyleAttr(aAttributes, aAttrCount, kNameSpaceID_None,
+ nsGkAtoms::doctypeSystem, false, &attr);
+ if (attr) {
+ item->mFormat.mSystemId = attr->mValue;
+ }
+
+ getStyleAttr(aAttributes, aAttrCount, kNameSpaceID_None,
+ nsGkAtoms::cdataSectionElements, false, &attr);
+ if (attr) {
+ nsWhitespaceTokenizer tokens(attr->mValue);
+ while (tokens.hasMoreTokens()) {
+ UniquePtr<txExpandedName> qname(new txExpandedName());
+ rv = qname->init(tokens.nextToken(), aState.mElementContext->mMappings,
+ false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ item->mFormat.mCDATASectionElements.add(qname.release());
+ }
+ }
+
+ rv = getYesNoAttr(aAttributes, aAttrCount, nsGkAtoms::indent, false, aState,
+ item->mFormat.mIndent);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ getStyleAttr(aAttributes, aAttrCount, kNameSpaceID_None, nsGkAtoms::mediaType,
+ false, &attr);
+ if (attr) {
+ item->mFormat.mMediaType = NS_ConvertUTF16toUTF8(attr->mValue);
+ }
+
+ aState.addToplevelItem(item.release());
+
+ aState.pushHandlerTable(gTxIgnoreHandler);
+
+ return NS_OK;
+}
+
+static void txFnEndOutput(txStylesheetCompilerState& aState) {
+ aState.popHandlerTable();
+}
+
+// xsl:strip-space/xsl:preserve-space
+static nsresult txFnStartStripSpace(int32_t aNamespaceID, nsAtom* aLocalName,
+ nsAtom* aPrefix,
+ txStylesheetAttr* aAttributes,
+ int32_t aAttrCount,
+ txStylesheetCompilerState& aState) {
+ txStylesheetAttr* attr = nullptr;
+ nsresult rv = getStyleAttr(aAttributes, aAttrCount, kNameSpaceID_None,
+ nsGkAtoms::elements, true, &attr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool strip = aLocalName == nsGkAtoms::stripSpace;
+
+ UniquePtr<txStripSpaceItem> stripItem(new txStripSpaceItem);
+ nsWhitespaceTokenizer tokenizer(attr->mValue);
+ while (tokenizer.hasMoreTokens()) {
+ const nsAString& name = tokenizer.nextToken();
+ int32_t ns = kNameSpaceID_None;
+ RefPtr<nsAtom> prefix, localName;
+ rv = XMLUtils::splitQName(name, getter_AddRefs(prefix),
+ getter_AddRefs(localName));
+ if (NS_FAILED(rv)) {
+ // check for "*" or "prefix:*"
+ uint32_t length = name.Length();
+ const char16_t* c;
+ name.BeginReading(c);
+ if (length == 2 || c[length - 1] != '*') {
+ // these can't work
+ return NS_ERROR_XSLT_PARSE_FAILURE;
+ }
+ if (length > 1) {
+ // Check for a valid prefix, that is, the returned prefix
+ // should be empty and the real prefix is returned in
+ // localName.
+ if (c[length - 2] != ':') {
+ return NS_ERROR_XSLT_PARSE_FAILURE;
+ }
+ rv = XMLUtils::splitQName(StringHead(name, length - 2),
+ getter_AddRefs(prefix),
+ getter_AddRefs(localName));
+ if (NS_FAILED(rv) || prefix) {
+ // bad chars or two ':'
+ return NS_ERROR_XSLT_PARSE_FAILURE;
+ }
+ prefix = localName;
+ }
+ localName = nsGkAtoms::_asterisk;
+ }
+ if (prefix) {
+ ns = aState.mElementContext->mMappings->lookupNamespace(prefix);
+ NS_ENSURE_TRUE(ns != kNameSpaceID_Unknown, NS_ERROR_FAILURE);
+ }
+ stripItem->addStripSpaceTest(
+ new txStripSpaceTest(prefix, localName, ns, strip));
+ }
+
+ aState.addToplevelItem(stripItem.release());
+
+ aState.pushHandlerTable(gTxIgnoreHandler);
+
+ return NS_OK;
+}
+
+static void txFnEndStripSpace(txStylesheetCompilerState& aState) {
+ aState.popHandlerTable();
+}
+
+// xsl:template
+static nsresult txFnStartTemplate(int32_t aNamespaceID, nsAtom* aLocalName,
+ nsAtom* aPrefix,
+ txStylesheetAttr* aAttributes,
+ int32_t aAttrCount,
+ txStylesheetCompilerState& aState) {
+ nsresult rv = NS_OK;
+ txExpandedName name;
+ rv = getQNameAttr(aAttributes, aAttrCount, nsGkAtoms::name, false, aState,
+ name);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ txExpandedName mode;
+ rv = getQNameAttr(aAttributes, aAttrCount, nsGkAtoms::mode, false, aState,
+ mode);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ double prio = UnspecifiedNaN<double>();
+ rv = getNumberAttr(aAttributes, aAttrCount, nsGkAtoms::priority, false,
+ aState, prio);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ UniquePtr<txPattern> match;
+ rv = getPatternAttr(aAttributes, aAttrCount, nsGkAtoms::match, name.isNull(),
+ aState, match);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ UniquePtr<txTemplateItem> templ(
+ new txTemplateItem(std::move(match), name, mode, prio));
+ aState.openInstructionContainer(templ.get());
+ aState.addToplevelItem(templ.release());
+
+ aState.pushHandlerTable(gTxParamHandler);
+
+ return NS_OK;
+}
+
+static void txFnEndTemplate(txStylesheetCompilerState& aState) {
+ aState.popHandlerTable();
+
+ aState.addInstruction(MakeUnique<txReturn>());
+
+ aState.closeInstructionContainer();
+}
+
+// xsl:variable, xsl:param
+static nsresult txFnStartTopVariable(int32_t aNamespaceID, nsAtom* aLocalName,
+ nsAtom* aPrefix,
+ txStylesheetAttr* aAttributes,
+ int32_t aAttrCount,
+ txStylesheetCompilerState& aState) {
+ nsresult rv = NS_OK;
+ txExpandedName name;
+ rv = getQNameAttr(aAttributes, aAttrCount, nsGkAtoms::name, true, aState,
+ name);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ UniquePtr<Expr> select;
+ rv = getExprAttr(aAttributes, aAttrCount, nsGkAtoms::select, false, aState,
+ select);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ UniquePtr<txVariableItem> var(new txVariableItem(
+ name, std::move(select), aLocalName == nsGkAtoms::param));
+ aState.openInstructionContainer(var.get());
+ aState.pushPtr(var.get(), aState.eVariableItem);
+
+ if (var->mValue) {
+ // XXX should be gTxErrorHandler?
+ aState.pushHandlerTable(gTxIgnoreHandler);
+ } else {
+ aState.pushHandlerTable(gTxTopVariableHandler);
+ }
+
+ aState.addToplevelItem(var.release());
+
+ return NS_OK;
+}
+
+static void txFnEndTopVariable(txStylesheetCompilerState& aState) {
+ txHandlerTable* prev = aState.mHandlerTable;
+ aState.popHandlerTable();
+ txVariableItem* var =
+ static_cast<txVariableItem*>(aState.popPtr(aState.eVariableItem));
+
+ if (prev == gTxTopVariableHandler) {
+ // No children were found.
+ NS_ASSERTION(!var->mValue, "There shouldn't be a select-expression here");
+ var->mValue = MakeUnique<txLiteralExpr>(u""_ns);
+ } else if (!var->mValue) {
+ // If we don't have a select-expression there mush be children.
+ aState.addInstruction(MakeUnique<txReturn>());
+ }
+
+ aState.closeInstructionContainer();
+}
+
+static nsresult txFnStartElementStartTopVar(int32_t aNamespaceID,
+ nsAtom* aLocalName, nsAtom* aPrefix,
+ txStylesheetAttr* aAttributes,
+ int32_t aAttrCount,
+ txStylesheetCompilerState& aState) {
+ aState.mHandlerTable = gTxTemplateHandler;
+
+ return NS_XSLT_GET_NEW_HANDLER;
+}
+
+static nsresult txFnTextStartTopVar(const nsAString& aStr,
+ txStylesheetCompilerState& aState) {
+ TX_RETURN_IF_WHITESPACE(aStr, aState);
+
+ aState.mHandlerTable = gTxTemplateHandler;
+
+ return NS_XSLT_GET_NEW_HANDLER;
+}
+
+/**
+ * Template Handlers
+ */
+
+/*
+ LRE
+
+ txStartLREElement
+ txInsertAttrSet one for each qname in xsl:use-attribute-sets
+ txLREAttribute one for each attribute
+ [children]
+ txEndElement
+*/
+static nsresult txFnStartLRE(int32_t aNamespaceID, nsAtom* aLocalName,
+ nsAtom* aPrefix, txStylesheetAttr* aAttributes,
+ int32_t aAttrCount,
+ txStylesheetCompilerState& aState) {
+ nsresult rv = NS_OK;
+
+ aState.addInstruction(
+ MakeUnique<txStartLREElement>(aNamespaceID, aLocalName, aPrefix));
+
+ rv = parseExcludeResultPrefixes(aAttributes, aAttrCount, kNameSpaceID_XSLT);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = parseUseAttrSets(aAttributes, aAttrCount, true, aState);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ txStylesheetAttr* attr = nullptr;
+ int32_t i;
+ for (i = 0; i < aAttrCount; ++i) {
+ attr = aAttributes + i;
+
+ if (attr->mNamespaceID == kNameSpaceID_XSLT) {
+ if (attr->mLocalName == nsGkAtoms::version) {
+ attr->mLocalName = nullptr;
+ }
+
+ continue;
+ }
+
+ UniquePtr<Expr> avt;
+ rv = txExprParser::createAVT(attr->mValue, &aState, getter_Transfers(avt));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aState.addInstruction(MakeUnique<txLREAttribute>(
+ attr->mNamespaceID, attr->mLocalName, attr->mPrefix, std::move(avt)));
+ }
+
+ return NS_OK;
+}
+
+static void txFnEndLRE(txStylesheetCompilerState& aState) {
+ aState.addInstruction(MakeUnique<txEndElement>());
+}
+
+/*
+ "LRE text"
+
+ txText
+*/
+static nsresult txFnText(const nsAString& aStr,
+ txStylesheetCompilerState& aState) {
+ TX_RETURN_IF_WHITESPACE(aStr, aState);
+
+ aState.addInstruction(MakeUnique<txText>(aStr, false));
+
+ return NS_OK;
+}
+
+/*
+ xsl:apply-imports
+
+ txApplyImportsStart
+ txApplyImportsEnd
+*/
+static nsresult txFnStartApplyImports(int32_t aNamespaceID, nsAtom* aLocalName,
+ nsAtom* aPrefix,
+ txStylesheetAttr* aAttributes,
+ int32_t aAttrCount,
+ txStylesheetCompilerState& aState) {
+ aState.addInstruction(MakeUnique<txApplyImportsStart>());
+ aState.addInstruction(MakeUnique<txApplyImportsEnd>());
+
+ aState.pushHandlerTable(gTxIgnoreHandler);
+
+ return NS_OK;
+}
+
+static void txFnEndApplyImports(txStylesheetCompilerState& aState) {
+ aState.popHandlerTable();
+}
+
+/*
+ xsl:apply-templates
+
+ txPushParams
+ [params]
+ txPushNewContext -+ (holds <xsl:sort>s)
+ txApplyTemplate <-+ |
+ txLoopNodeSet -+ |
+ txPopParams <-+
+*/
+static nsresult txFnStartApplyTemplates(int32_t aNamespaceID,
+ nsAtom* aLocalName, nsAtom* aPrefix,
+ txStylesheetAttr* aAttributes,
+ int32_t aAttrCount,
+ txStylesheetCompilerState& aState) {
+ nsresult rv = NS_OK;
+
+ aState.addInstruction(MakeUnique<txPushParams>());
+
+ txExpandedName mode;
+ rv = getQNameAttr(aAttributes, aAttrCount, nsGkAtoms::mode, false, aState,
+ mode);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ pushInstruction(aState, MakeUnique<txApplyTemplates>(mode));
+
+ UniquePtr<Expr> select;
+ rv = getExprAttr(aAttributes, aAttrCount, nsGkAtoms::select, false, aState,
+ select);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!select) {
+ UniquePtr<txNodeTest> nt(new txNodeTypeTest(txNodeTypeTest::NODE_TYPE));
+ select = MakeUnique<LocationStep>(nt.release(), LocationStep::CHILD_AXIS);
+ }
+
+ UniquePtr<txPushNewContext> pushcontext(
+ new txPushNewContext(std::move(select)));
+ aState.pushSorter(pushcontext.get());
+ pushInstruction(aState, std::move(pushcontext));
+
+ aState.pushHandlerTable(gTxApplyTemplatesHandler);
+
+ return NS_OK;
+}
+
+static void txFnEndApplyTemplates(txStylesheetCompilerState& aState) {
+ aState.popHandlerTable();
+
+ txPushNewContext* pushcontext =
+ aState.addInstruction(popInstruction<txPushNewContext>(aState));
+
+ aState.popSorter();
+
+ // txApplyTemplates
+ txInstruction* instr = aState.addInstruction(popInstruction(aState));
+ aState.addInstruction(MakeUnique<txLoopNodeSet>(instr));
+
+ pushcontext->mBailTarget = aState.addInstruction(MakeUnique<txPopParams>());
+}
+
+/*
+ xsl:attribute
+
+ txPushStringHandler
+ [children]
+ txAttribute
+*/
+static nsresult txFnStartAttribute(int32_t aNamespaceID, nsAtom* aLocalName,
+ nsAtom* aPrefix,
+ txStylesheetAttr* aAttributes,
+ int32_t aAttrCount,
+ txStylesheetCompilerState& aState) {
+ nsresult rv = NS_OK;
+
+ aState.addInstruction(MakeUnique<txPushStringHandler>(true));
+
+ UniquePtr<Expr> name;
+ rv = getAVTAttr(aAttributes, aAttrCount, nsGkAtoms::name, true, aState, name);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ UniquePtr<Expr> nspace;
+ rv = getAVTAttr(aAttributes, aAttrCount, nsGkAtoms::_namespace, false, aState,
+ nspace);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ pushInstruction(aState,
+ MakeUnique<txAttribute>(std::move(name), std::move(nspace),
+ aState.mElementContext->mMappings));
+
+ // We need to push the template-handler since the current might be
+ // the attributeset-handler
+ aState.pushHandlerTable(gTxTemplateHandler);
+
+ return NS_OK;
+}
+
+static void txFnEndAttribute(txStylesheetCompilerState& aState) {
+ aState.popHandlerTable();
+ aState.addInstruction(popInstruction(aState));
+}
+
+/*
+ xsl:call-template
+
+ txPushParams
+ [params]
+ txCallTemplate
+ txPopParams
+*/
+static nsresult txFnStartCallTemplate(int32_t aNamespaceID, nsAtom* aLocalName,
+ nsAtom* aPrefix,
+ txStylesheetAttr* aAttributes,
+ int32_t aAttrCount,
+ txStylesheetCompilerState& aState) {
+ nsresult rv = NS_OK;
+
+ aState.addInstruction(MakeUnique<txPushParams>());
+
+ txExpandedName name;
+ rv = getQNameAttr(aAttributes, aAttrCount, nsGkAtoms::name, true, aState,
+ name);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ pushInstruction(aState, MakeUnique<txCallTemplate>(name));
+
+ aState.pushHandlerTable(gTxCallTemplateHandler);
+
+ return NS_OK;
+}
+
+static void txFnEndCallTemplate(txStylesheetCompilerState& aState) {
+ aState.popHandlerTable();
+
+ // txCallTemplate
+ aState.addInstruction(popInstruction(aState));
+
+ aState.addInstruction(MakeUnique<txPopParams>());
+}
+
+/*
+ xsl:choose
+
+ txCondotionalGoto --+ \
+ [children] | | one for each xsl:when
+ txGoTo --+ | /
+ | |
+ txCondotionalGoto | <-+ --+
+ [children] | |
+ txGoTo --+ |
+ | |
+ [children] | <-+ for the xsl:otherwise, if there is one
+ <-+
+*/
+static nsresult txFnStartChoose(int32_t aNamespaceID, nsAtom* aLocalName,
+ nsAtom* aPrefix, txStylesheetAttr* aAttributes,
+ int32_t aAttrCount,
+ txStylesheetCompilerState& aState) {
+ aState.pushChooseGotoList();
+
+ aState.pushHandlerTable(gTxChooseHandler);
+
+ return NS_OK;
+}
+
+static void txFnEndChoose(txStylesheetCompilerState& aState) {
+ aState.popHandlerTable();
+ txListIterator iter(aState.mChooseGotoList.get());
+ txGoTo* gotoinstr;
+ while ((gotoinstr = static_cast<txGoTo*>(iter.next()))) {
+ aState.addGotoTarget(&gotoinstr->mTarget);
+ }
+
+ aState.popChooseGotoList();
+}
+
+/*
+ xsl:comment
+
+ txPushStringHandler
+ [children]
+ txComment
+*/
+static nsresult txFnStartComment(int32_t aNamespaceID, nsAtom* aLocalName,
+ nsAtom* aPrefix, txStylesheetAttr* aAttributes,
+ int32_t aAttrCount,
+ txStylesheetCompilerState& aState) {
+ aState.addInstruction(MakeUnique<txPushStringHandler>(true));
+
+ return NS_OK;
+}
+
+static void txFnEndComment(txStylesheetCompilerState& aState) {
+ aState.addInstruction(MakeUnique<txComment>());
+}
+
+/*
+ xsl:copy
+
+ txCopy -+
+ txInsertAttrSet | one for each qname in use-attribute-sets
+ [children] |
+ txEndElement |
+ <-+
+*/
+static nsresult txFnStartCopy(int32_t aNamespaceID, nsAtom* aLocalName,
+ nsAtom* aPrefix, txStylesheetAttr* aAttributes,
+ int32_t aAttrCount,
+ txStylesheetCompilerState& aState) {
+ aState.pushPtr(aState.addInstruction(MakeUnique<txCopy>()), aState.eCopy);
+
+ return parseUseAttrSets(aAttributes, aAttrCount, false, aState);
+}
+
+static void txFnEndCopy(txStylesheetCompilerState& aState) {
+ aState.addInstruction(MakeUnique<txEndElement>());
+
+ txCopy* copy = static_cast<txCopy*>(aState.popPtr(aState.eCopy));
+ aState.addGotoTarget(&copy->mBailTarget);
+}
+
+/*
+ xsl:copy-of
+
+ txCopyOf
+*/
+static nsresult txFnStartCopyOf(int32_t aNamespaceID, nsAtom* aLocalName,
+ nsAtom* aPrefix, txStylesheetAttr* aAttributes,
+ int32_t aAttrCount,
+ txStylesheetCompilerState& aState) {
+ nsresult rv = NS_OK;
+
+ UniquePtr<Expr> select;
+ rv = getExprAttr(aAttributes, aAttrCount, nsGkAtoms::select, true, aState,
+ select);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aState.addInstruction(MakeUnique<txCopyOf>(std::move(select)));
+
+ aState.pushHandlerTable(gTxIgnoreHandler);
+
+ return NS_OK;
+}
+
+static void txFnEndCopyOf(txStylesheetCompilerState& aState) {
+ aState.popHandlerTable();
+}
+
+/*
+ xsl:element
+
+ txStartElement
+ txInsertAttrSet one for each qname in use-attribute-sets
+ [children]
+ txEndElement
+*/
+static nsresult txFnStartElement(int32_t aNamespaceID, nsAtom* aLocalName,
+ nsAtom* aPrefix, txStylesheetAttr* aAttributes,
+ int32_t aAttrCount,
+ txStylesheetCompilerState& aState) {
+ nsresult rv = NS_OK;
+
+ UniquePtr<Expr> name;
+ rv = getAVTAttr(aAttributes, aAttrCount, nsGkAtoms::name, true, aState, name);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ UniquePtr<Expr> nspace;
+ rv = getAVTAttr(aAttributes, aAttrCount, nsGkAtoms::_namespace, false, aState,
+ nspace);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aState.addInstruction(MakeUnique<txStartElement>(
+ std::move(name), std::move(nspace), aState.mElementContext->mMappings));
+
+ rv = parseUseAttrSets(aAttributes, aAttrCount, false, aState);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+static void txFnEndElement(txStylesheetCompilerState& aState) {
+ aState.addInstruction(MakeUnique<txEndElement>());
+}
+
+/*
+ xsl:fallback
+
+ [children]
+*/
+static nsresult txFnStartFallback(int32_t aNamespaceID, nsAtom* aLocalName,
+ nsAtom* aPrefix,
+ txStylesheetAttr* aAttributes,
+ int32_t aAttrCount,
+ txStylesheetCompilerState& aState) {
+ aState.mSearchingForFallback = false;
+
+ aState.pushHandlerTable(gTxTemplateHandler);
+
+ return NS_OK;
+}
+
+static void txFnEndFallback(txStylesheetCompilerState& aState) {
+ aState.popHandlerTable();
+
+ NS_ASSERTION(!aState.mSearchingForFallback,
+ "bad nesting of unknown-instruction and fallback handlers");
+}
+
+/*
+ xsl:for-each
+
+ txPushNewContext -+ (holds <xsl:sort>s)
+ txPushNullTemplateRule <-+ |
+ [children] | |
+ txLoopNodeSet -+ |
+ <-+
+*/
+static nsresult txFnStartForEach(int32_t aNamespaceID, nsAtom* aLocalName,
+ nsAtom* aPrefix, txStylesheetAttr* aAttributes,
+ int32_t aAttrCount,
+ txStylesheetCompilerState& aState) {
+ nsresult rv = NS_OK;
+
+ UniquePtr<Expr> select;
+ rv = getExprAttr(aAttributes, aAttrCount, nsGkAtoms::select, true, aState,
+ select);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ txPushNewContext* pushcontext =
+ aState.addInstruction(MakeUnique<txPushNewContext>(std::move(select)));
+ aState.pushPtr(pushcontext, aState.ePushNewContext);
+ aState.pushSorter(pushcontext);
+
+ aState.pushPtr(aState.addInstruction(MakeUnique<txPushNullTemplateRule>()),
+ aState.ePushNullTemplateRule);
+
+ aState.pushHandlerTable(gTxForEachHandler);
+
+ return NS_OK;
+}
+
+static void txFnEndForEach(txStylesheetCompilerState& aState) {
+ aState.popHandlerTable();
+
+ // This is a txPushNullTemplateRule
+ txInstruction* pnullrule =
+ static_cast<txInstruction*>(aState.popPtr(aState.ePushNullTemplateRule));
+
+ aState.addInstruction(MakeUnique<txLoopNodeSet>(pnullrule));
+
+ aState.popSorter();
+ txPushNewContext* pushcontext =
+ static_cast<txPushNewContext*>(aState.popPtr(aState.ePushNewContext));
+ aState.addGotoTarget(&pushcontext->mBailTarget);
+}
+
+static nsresult txFnStartElementContinueTemplate(
+ int32_t aNamespaceID, nsAtom* aLocalName, nsAtom* aPrefix,
+ txStylesheetAttr* aAttributes, int32_t aAttrCount,
+ txStylesheetCompilerState& aState) {
+ aState.mHandlerTable = gTxTemplateHandler;
+
+ return NS_XSLT_GET_NEW_HANDLER;
+}
+
+static nsresult txFnTextContinueTemplate(const nsAString& aStr,
+ txStylesheetCompilerState& aState) {
+ TX_RETURN_IF_WHITESPACE(aStr, aState);
+
+ aState.mHandlerTable = gTxTemplateHandler;
+
+ return NS_XSLT_GET_NEW_HANDLER;
+}
+
+/*
+ xsl:if
+
+ txConditionalGoto -+
+ [children] |
+ <-+
+*/
+static nsresult txFnStartIf(int32_t aNamespaceID, nsAtom* aLocalName,
+ nsAtom* aPrefix, txStylesheetAttr* aAttributes,
+ int32_t aAttrCount,
+ txStylesheetCompilerState& aState) {
+ nsresult rv = NS_OK;
+
+ UniquePtr<Expr> test;
+ rv =
+ getExprAttr(aAttributes, aAttrCount, nsGkAtoms::test, true, aState, test);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aState.pushPtr(aState.addInstruction(
+ MakeUnique<txConditionalGoto>(std::move(test), nullptr)),
+ aState.eConditionalGoto);
+
+ return NS_OK;
+}
+
+static void txFnEndIf(txStylesheetCompilerState& aState) {
+ txConditionalGoto* condGoto =
+ static_cast<txConditionalGoto*>(aState.popPtr(aState.eConditionalGoto));
+ aState.addGotoTarget(&condGoto->mTarget);
+}
+
+/*
+ xsl:message
+
+ txPushStringHandler
+ [children]
+ txMessage
+*/
+static nsresult txFnStartMessage(int32_t aNamespaceID, nsAtom* aLocalName,
+ nsAtom* aPrefix, txStylesheetAttr* aAttributes,
+ int32_t aAttrCount,
+ txStylesheetCompilerState& aState) {
+ aState.addInstruction(MakeUnique<txPushStringHandler>(false));
+
+ txThreeState term;
+ nsresult rv = getYesNoAttr(aAttributes, aAttrCount, nsGkAtoms::terminate,
+ false, aState, term);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ pushInstruction(aState, MakeUnique<txMessage>(term == eTrue));
+
+ return NS_OK;
+}
+
+static void txFnEndMessage(txStylesheetCompilerState& aState) {
+ aState.addInstruction(popInstruction(aState));
+}
+
+/*
+ xsl:number
+
+ txNumber
+*/
+static nsresult txFnStartNumber(int32_t aNamespaceID, nsAtom* aLocalName,
+ nsAtom* aPrefix, txStylesheetAttr* aAttributes,
+ int32_t aAttrCount,
+ txStylesheetCompilerState& aState) {
+ nsresult rv = NS_OK;
+
+ RefPtr<nsAtom> levelAtom;
+ rv = getAtomAttr(aAttributes, aAttrCount, nsGkAtoms::level, false, aState,
+ getter_AddRefs(levelAtom));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ txXSLTNumber::LevelType level = txXSLTNumber::eLevelSingle;
+ if (levelAtom == nsGkAtoms::multiple) {
+ level = txXSLTNumber::eLevelMultiple;
+ } else if (levelAtom == nsGkAtoms::any) {
+ level = txXSLTNumber::eLevelAny;
+ } else if (levelAtom && levelAtom != nsGkAtoms::single && !aState.fcp()) {
+ return NS_ERROR_XSLT_PARSE_FAILURE;
+ }
+
+ UniquePtr<txPattern> count;
+ rv = getPatternAttr(aAttributes, aAttrCount, nsGkAtoms::count, false, aState,
+ count);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ UniquePtr<txPattern> from;
+ rv = getPatternAttr(aAttributes, aAttrCount, nsGkAtoms::from, false, aState,
+ from);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ UniquePtr<Expr> value;
+ rv = getExprAttr(aAttributes, aAttrCount, nsGkAtoms::value, false, aState,
+ value);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ UniquePtr<Expr> format;
+ rv = getAVTAttr(aAttributes, aAttrCount, nsGkAtoms::format, false, aState,
+ format);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ UniquePtr<Expr> lang;
+ rv =
+ getAVTAttr(aAttributes, aAttrCount, nsGkAtoms::lang, false, aState, lang);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ UniquePtr<Expr> letterValue;
+ rv = getAVTAttr(aAttributes, aAttrCount, nsGkAtoms::letterValue, false,
+ aState, letterValue);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ UniquePtr<Expr> groupingSeparator;
+ rv = getAVTAttr(aAttributes, aAttrCount, nsGkAtoms::groupingSeparator, false,
+ aState, groupingSeparator);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ UniquePtr<Expr> groupingSize;
+ rv = getAVTAttr(aAttributes, aAttrCount, nsGkAtoms::groupingSize, false,
+ aState, groupingSize);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aState.addInstruction(MakeUnique<txNumber>(
+ level, std::move(count), std::move(from), std::move(value),
+ std::move(format), std::move(groupingSeparator),
+ std::move(groupingSize)));
+
+ aState.pushHandlerTable(gTxIgnoreHandler);
+
+ return NS_OK;
+}
+
+static void txFnEndNumber(txStylesheetCompilerState& aState) {
+ aState.popHandlerTable();
+}
+
+/*
+ xsl:otherwise
+
+ (see xsl:choose)
+*/
+static nsresult txFnStartOtherwise(int32_t aNamespaceID, nsAtom* aLocalName,
+ nsAtom* aPrefix,
+ txStylesheetAttr* aAttributes,
+ int32_t aAttrCount,
+ txStylesheetCompilerState& aState) {
+ aState.pushHandlerTable(gTxTemplateHandler);
+
+ return NS_OK;
+}
+
+static void txFnEndOtherwise(txStylesheetCompilerState& aState) {
+ aState.popHandlerTable();
+ aState.mHandlerTable = gTxIgnoreHandler; // XXX should be gTxErrorHandler
+}
+
+/*
+ xsl:param
+
+ txCheckParam --+
+ txPushRTFHandler | --- (for RTF-parameters)
+ [children] | /
+ txSetVariable |
+ <-+
+*/
+static nsresult txFnStartParam(int32_t aNamespaceID, nsAtom* aLocalName,
+ nsAtom* aPrefix, txStylesheetAttr* aAttributes,
+ int32_t aAttrCount,
+ txStylesheetCompilerState& aState) {
+ nsresult rv = NS_OK;
+
+ txExpandedName name;
+ rv = getQNameAttr(aAttributes, aAttrCount, nsGkAtoms::name, true, aState,
+ name);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aState.pushPtr(aState.addInstruction(MakeUnique<txCheckParam>(name)),
+ aState.eCheckParam);
+
+ UniquePtr<Expr> select;
+ rv = getExprAttr(aAttributes, aAttrCount, nsGkAtoms::select, false, aState,
+ select);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ UniquePtr<txSetVariable> var =
+ MakeUnique<txSetVariable>(name, std::move(select));
+ if (var->mValue) {
+ // XXX should be gTxErrorHandler?
+ aState.pushHandlerTable(gTxIgnoreHandler);
+ } else {
+ aState.pushHandlerTable(gTxVariableHandler);
+ }
+
+ pushInstruction(aState, std::move(var));
+
+ return NS_OK;
+}
+
+static void txFnEndParam(txStylesheetCompilerState& aState) {
+ UniquePtr<txSetVariable> var = popInstruction<txSetVariable>(aState);
+ txHandlerTable* prev = aState.mHandlerTable;
+ aState.popHandlerTable();
+
+ if (prev == gTxVariableHandler) {
+ // No children were found.
+ NS_ASSERTION(!var->mValue, "There shouldn't be a select-expression here");
+ var->mValue = MakeUnique<txLiteralExpr>(u""_ns);
+ }
+
+ aState.addVariable(var->mName);
+
+ aState.addInstruction(std::move(var));
+
+ txCheckParam* checkParam =
+ static_cast<txCheckParam*>(aState.popPtr(aState.eCheckParam));
+ aState.addGotoTarget(&checkParam->mBailTarget);
+}
+
+/*
+ xsl:processing-instruction
+
+ txPushStringHandler
+ [children]
+ txProcessingInstruction
+*/
+static nsresult txFnStartPI(int32_t aNamespaceID, nsAtom* aLocalName,
+ nsAtom* aPrefix, txStylesheetAttr* aAttributes,
+ int32_t aAttrCount,
+ txStylesheetCompilerState& aState) {
+ aState.addInstruction(MakeUnique<txPushStringHandler>(true));
+
+ UniquePtr<Expr> name;
+ nsresult rv =
+ getAVTAttr(aAttributes, aAttrCount, nsGkAtoms::name, true, aState, name);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ pushInstruction(aState, MakeUnique<txProcessingInstruction>(std::move(name)));
+
+ return NS_OK;
+}
+
+static void txFnEndPI(txStylesheetCompilerState& aState) {
+ aState.addInstruction(popInstruction(aState));
+}
+
+/*
+ xsl:sort
+
+ (no instructions)
+*/
+static nsresult txFnStartSort(int32_t aNamespaceID, nsAtom* aLocalName,
+ nsAtom* aPrefix, txStylesheetAttr* aAttributes,
+ int32_t aAttrCount,
+ txStylesheetCompilerState& aState) {
+ nsresult rv = NS_OK;
+
+ UniquePtr<Expr> select;
+ rv = getExprAttr(aAttributes, aAttrCount, nsGkAtoms::select, false, aState,
+ select);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!select) {
+ UniquePtr<txNodeTest> nt(new txNodeTypeTest(txNodeTypeTest::NODE_TYPE));
+ select = MakeUnique<LocationStep>(nt.release(), LocationStep::SELF_AXIS);
+ }
+
+ UniquePtr<Expr> lang;
+ rv =
+ getAVTAttr(aAttributes, aAttrCount, nsGkAtoms::lang, false, aState, lang);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ UniquePtr<Expr> dataType;
+ rv = getAVTAttr(aAttributes, aAttrCount, nsGkAtoms::dataType, false, aState,
+ dataType);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ UniquePtr<Expr> order;
+ rv = getAVTAttr(aAttributes, aAttrCount, nsGkAtoms::order, false, aState,
+ order);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ UniquePtr<Expr> caseOrder;
+ rv = getAVTAttr(aAttributes, aAttrCount, nsGkAtoms::caseOrder, false, aState,
+ caseOrder);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aState.mSorter->addSort(std::move(select), std::move(lang),
+ std::move(dataType), std::move(order),
+ std::move(caseOrder));
+
+ aState.pushHandlerTable(gTxIgnoreHandler);
+
+ return NS_OK;
+}
+
+static void txFnEndSort(txStylesheetCompilerState& aState) {
+ aState.popHandlerTable();
+}
+
+/*
+ xsl:text
+
+ [children] (only txText)
+*/
+static nsresult txFnStartText(int32_t aNamespaceID, nsAtom* aLocalName,
+ nsAtom* aPrefix, txStylesheetAttr* aAttributes,
+ int32_t aAttrCount,
+ txStylesheetCompilerState& aState) {
+ NS_ASSERTION(!aState.mDOE, "nested d-o-e elements should not happen");
+
+ nsresult rv = NS_OK;
+ txThreeState doe;
+ rv = getYesNoAttr(aAttributes, aAttrCount, nsGkAtoms::disableOutputEscaping,
+ false, aState, doe);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aState.mDOE = doe == eTrue;
+
+ aState.pushHandlerTable(gTxTextHandler);
+
+ return NS_OK;
+}
+
+static void txFnEndText(txStylesheetCompilerState& aState) {
+ aState.mDOE = false;
+ aState.popHandlerTable();
+}
+
+static nsresult txFnTextText(const nsAString& aStr,
+ txStylesheetCompilerState& aState) {
+ aState.addInstruction(MakeUnique<txText>(aStr, aState.mDOE));
+
+ return NS_OK;
+}
+
+/*
+ xsl:value-of
+
+ txValueOf
+*/
+static nsresult txFnStartValueOf(int32_t aNamespaceID, nsAtom* aLocalName,
+ nsAtom* aPrefix, txStylesheetAttr* aAttributes,
+ int32_t aAttrCount,
+ txStylesheetCompilerState& aState) {
+ nsresult rv = NS_OK;
+
+ txThreeState doe;
+ rv = getYesNoAttr(aAttributes, aAttrCount, nsGkAtoms::disableOutputEscaping,
+ false, aState, doe);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ UniquePtr<Expr> select;
+ rv = getExprAttr(aAttributes, aAttrCount, nsGkAtoms::select, true, aState,
+ select);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aState.addInstruction(MakeUnique<txValueOf>(std::move(select), doe == eTrue));
+
+ aState.pushHandlerTable(gTxIgnoreHandler);
+
+ return NS_OK;
+}
+
+static void txFnEndValueOf(txStylesheetCompilerState& aState) {
+ aState.popHandlerTable();
+}
+
+/*
+ xsl:variable
+
+ txPushRTFHandler --- (for RTF-parameters)
+ [children] /
+ txSetVariable
+*/
+static nsresult txFnStartVariable(int32_t aNamespaceID, nsAtom* aLocalName,
+ nsAtom* aPrefix,
+ txStylesheetAttr* aAttributes,
+ int32_t aAttrCount,
+ txStylesheetCompilerState& aState) {
+ nsresult rv = NS_OK;
+
+ txExpandedName name;
+ rv = getQNameAttr(aAttributes, aAttrCount, nsGkAtoms::name, true, aState,
+ name);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ UniquePtr<Expr> select;
+ rv = getExprAttr(aAttributes, aAttrCount, nsGkAtoms::select, false, aState,
+ select);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ UniquePtr<txSetVariable> var =
+ MakeUnique<txSetVariable>(name, std::move(select));
+ if (var->mValue) {
+ // XXX should be gTxErrorHandler?
+ aState.pushHandlerTable(gTxIgnoreHandler);
+ } else {
+ aState.pushHandlerTable(gTxVariableHandler);
+ }
+
+ pushInstruction(aState, std::move(var));
+
+ return NS_OK;
+}
+
+static void txFnEndVariable(txStylesheetCompilerState& aState) {
+ UniquePtr<txSetVariable> var = popInstruction<txSetVariable>(aState);
+
+ txHandlerTable* prev = aState.mHandlerTable;
+ aState.popHandlerTable();
+
+ if (prev == gTxVariableHandler) {
+ // No children were found.
+ NS_ASSERTION(!var->mValue, "There shouldn't be a select-expression here");
+ var->mValue = MakeUnique<txLiteralExpr>(u""_ns);
+ }
+
+ aState.addVariable(var->mName);
+
+ aState.addInstruction(std::move(var));
+}
+
+static nsresult txFnStartElementStartRTF(int32_t aNamespaceID,
+ nsAtom* aLocalName, nsAtom* aPrefix,
+ txStylesheetAttr* aAttributes,
+ int32_t aAttrCount,
+ txStylesheetCompilerState& aState) {
+ aState.addInstruction(MakeUnique<txPushRTFHandler>());
+
+ aState.mHandlerTable = gTxTemplateHandler;
+
+ return NS_XSLT_GET_NEW_HANDLER;
+}
+
+static nsresult txFnTextStartRTF(const nsAString& aStr,
+ txStylesheetCompilerState& aState) {
+ TX_RETURN_IF_WHITESPACE(aStr, aState);
+
+ aState.addInstruction(MakeUnique<txPushRTFHandler>());
+
+ aState.mHandlerTable = gTxTemplateHandler;
+
+ return NS_XSLT_GET_NEW_HANDLER;
+}
+
+/*
+ xsl:when
+
+ (see xsl:choose)
+*/
+static nsresult txFnStartWhen(int32_t aNamespaceID, nsAtom* aLocalName,
+ nsAtom* aPrefix, txStylesheetAttr* aAttributes,
+ int32_t aAttrCount,
+ txStylesheetCompilerState& aState) {
+ nsresult rv = NS_OK;
+
+ UniquePtr<Expr> test;
+ rv =
+ getExprAttr(aAttributes, aAttrCount, nsGkAtoms::test, true, aState, test);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aState.pushPtr(aState.addInstruction(
+ MakeUnique<txConditionalGoto>(std::move(test), nullptr)),
+ aState.eConditionalGoto);
+
+ aState.pushHandlerTable(gTxTemplateHandler);
+
+ return NS_OK;
+}
+
+static void txFnEndWhen(txStylesheetCompilerState& aState) {
+ aState.popHandlerTable();
+ aState.mChooseGotoList->add(
+ aState.addInstruction(MakeUnique<txGoTo>(nullptr)));
+
+ txConditionalGoto* condGoto =
+ static_cast<txConditionalGoto*>(aState.popPtr(aState.eConditionalGoto));
+ aState.addGotoTarget(&condGoto->mTarget);
+}
+
+/*
+ xsl:with-param
+
+ txPushRTFHandler -- for RTF-parameters
+ [children] /
+ txSetParam
+*/
+static nsresult txFnStartWithParam(int32_t aNamespaceID, nsAtom* aLocalName,
+ nsAtom* aPrefix,
+ txStylesheetAttr* aAttributes,
+ int32_t aAttrCount,
+ txStylesheetCompilerState& aState) {
+ nsresult rv = NS_OK;
+
+ txExpandedName name;
+ rv = getQNameAttr(aAttributes, aAttrCount, nsGkAtoms::name, true, aState,
+ name);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ UniquePtr<Expr> select;
+ rv = getExprAttr(aAttributes, aAttrCount, nsGkAtoms::select, false, aState,
+ select);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ UniquePtr<txSetParam> var = MakeUnique<txSetParam>(name, std::move(select));
+ if (var->mValue) {
+ // XXX should be gTxErrorHandler?
+ aState.pushHandlerTable(gTxIgnoreHandler);
+ } else {
+ aState.pushHandlerTable(gTxVariableHandler);
+ }
+
+ pushInstruction(aState, std::move(var));
+
+ return NS_OK;
+}
+
+static void txFnEndWithParam(txStylesheetCompilerState& aState) {
+ UniquePtr<txSetParam> var = popInstruction<txSetParam>(aState);
+ txHandlerTable* prev = aState.mHandlerTable;
+ aState.popHandlerTable();
+
+ if (prev == gTxVariableHandler) {
+ // No children were found.
+ NS_ASSERTION(!var->mValue, "There shouldn't be a select-expression here");
+ var->mValue = MakeUnique<txLiteralExpr>(u""_ns);
+ }
+
+ aState.addInstruction(std::move(var));
+}
+
+/*
+ Unknown instruction
+
+ [fallbacks] if one or more xsl:fallbacks are found
+ or
+ txErrorInstruction otherwise
+*/
+static nsresult txFnStartUnknownInstruction(int32_t aNamespaceID,
+ nsAtom* aLocalName, nsAtom* aPrefix,
+ txStylesheetAttr* aAttributes,
+ int32_t aAttrCount,
+ txStylesheetCompilerState& aState) {
+ NS_ASSERTION(!aState.mSearchingForFallback,
+ "bad nesting of unknown-instruction and fallback handlers");
+
+ if (aNamespaceID == kNameSpaceID_XSLT && !aState.fcp()) {
+ return NS_ERROR_XSLT_PARSE_FAILURE;
+ }
+
+ aState.mSearchingForFallback = true;
+
+ aState.pushHandlerTable(gTxFallbackHandler);
+
+ return NS_OK;
+}
+
+static void txFnEndUnknownInstruction(txStylesheetCompilerState& aState) {
+ aState.popHandlerTable();
+
+ if (aState.mSearchingForFallback) {
+ aState.addInstruction(MakeUnique<txErrorInstruction>());
+ }
+
+ aState.mSearchingForFallback = false;
+}
+
+/**
+ * Table Datas
+ */
+
+struct txHandlerTableData {
+ txElementHandler mOtherHandler;
+ txElementHandler mLREHandler;
+ HandleTextFn mTextHandler;
+};
+
+const txHandlerTableData gTxIgnoreTableData = {
+ // Other
+ {0, 0, txFnStartElementIgnore, txFnEndElementIgnore},
+ // LRE
+ {0, 0, txFnStartElementIgnore, txFnEndElementIgnore},
+ // Text
+ txFnTextIgnore};
+
+const txElementHandler gTxRootElementHandlers[] = {
+ {kNameSpaceID_XSLT, "stylesheet", txFnStartStylesheet, txFnEndStylesheet},
+ {kNameSpaceID_XSLT, "transform", txFnStartStylesheet, txFnEndStylesheet}};
+
+const txHandlerTableData gTxRootTableData = {
+ // Other
+ {0, 0, txFnStartElementError, txFnEndElementError},
+ // LRE
+ {0, 0, txFnStartLREStylesheet, txFnEndLREStylesheet},
+ // Text
+ txFnTextError};
+
+const txHandlerTableData gTxEmbedTableData = {
+ // Other
+ {0, 0, txFnStartEmbed, txFnEndEmbed},
+ // LRE
+ {0, 0, txFnStartEmbed, txFnEndEmbed},
+ // Text
+ txFnTextIgnore};
+
+const txElementHandler gTxTopElementHandlers[] = {
+ {kNameSpaceID_XSLT, "attribute-set", txFnStartAttributeSet,
+ txFnEndAttributeSet},
+ {kNameSpaceID_XSLT, "decimal-format", txFnStartDecimalFormat,
+ txFnEndDecimalFormat},
+ {kNameSpaceID_XSLT, "include", txFnStartInclude, txFnEndInclude},
+ {kNameSpaceID_XSLT, "key", txFnStartKey, txFnEndKey},
+ {kNameSpaceID_XSLT, "namespace-alias", txFnStartNamespaceAlias,
+ txFnEndNamespaceAlias},
+ {kNameSpaceID_XSLT, "output", txFnStartOutput, txFnEndOutput},
+ {kNameSpaceID_XSLT, "param", txFnStartTopVariable, txFnEndTopVariable},
+ {kNameSpaceID_XSLT, "preserve-space", txFnStartStripSpace,
+ txFnEndStripSpace},
+ {kNameSpaceID_XSLT, "strip-space", txFnStartStripSpace, txFnEndStripSpace},
+ {kNameSpaceID_XSLT, "template", txFnStartTemplate, txFnEndTemplate},
+ {kNameSpaceID_XSLT, "variable", txFnStartTopVariable, txFnEndTopVariable}};
+
+const txHandlerTableData gTxTopTableData = {
+ // Other
+ {0, 0, txFnStartOtherTop, txFnEndOtherTop},
+ // LRE
+ {0, 0, txFnStartOtherTop, txFnEndOtherTop},
+ // Text
+ txFnTextIgnore};
+
+const txElementHandler gTxTemplateElementHandlers[] = {
+ {kNameSpaceID_XSLT, "apply-imports", txFnStartApplyImports,
+ txFnEndApplyImports},
+ {kNameSpaceID_XSLT, "apply-templates", txFnStartApplyTemplates,
+ txFnEndApplyTemplates},
+ {kNameSpaceID_XSLT, "attribute", txFnStartAttribute, txFnEndAttribute},
+ {kNameSpaceID_XSLT, "call-template", txFnStartCallTemplate,
+ txFnEndCallTemplate},
+ {kNameSpaceID_XSLT, "choose", txFnStartChoose, txFnEndChoose},
+ {kNameSpaceID_XSLT, "comment", txFnStartComment, txFnEndComment},
+ {kNameSpaceID_XSLT, "copy", txFnStartCopy, txFnEndCopy},
+ {kNameSpaceID_XSLT, "copy-of", txFnStartCopyOf, txFnEndCopyOf},
+ {kNameSpaceID_XSLT, "element", txFnStartElement, txFnEndElement},
+ {kNameSpaceID_XSLT, "fallback", txFnStartElementSetIgnore,
+ txFnEndElementSetIgnore},
+ {kNameSpaceID_XSLT, "for-each", txFnStartForEach, txFnEndForEach},
+ {kNameSpaceID_XSLT, "if", txFnStartIf, txFnEndIf},
+ {kNameSpaceID_XSLT, "message", txFnStartMessage, txFnEndMessage},
+ {kNameSpaceID_XSLT, "number", txFnStartNumber, txFnEndNumber},
+ {kNameSpaceID_XSLT, "processing-instruction", txFnStartPI, txFnEndPI},
+ {kNameSpaceID_XSLT, "text", txFnStartText, txFnEndText},
+ {kNameSpaceID_XSLT, "value-of", txFnStartValueOf, txFnEndValueOf},
+ {kNameSpaceID_XSLT, "variable", txFnStartVariable, txFnEndVariable}};
+
+const txHandlerTableData gTxTemplateTableData = {
+ // Other
+ {0, 0, txFnStartUnknownInstruction, txFnEndUnknownInstruction},
+ // LRE
+ {0, 0, txFnStartLRE, txFnEndLRE},
+ // Text
+ txFnText};
+
+const txHandlerTableData gTxTextTableData = {
+ // Other
+ {0, 0, txFnStartElementError, txFnEndElementError},
+ // LRE
+ {0, 0, txFnStartElementError, txFnEndElementError},
+ // Text
+ txFnTextText};
+
+const txElementHandler gTxApplyTemplatesElementHandlers[] = {
+ {kNameSpaceID_XSLT, "sort", txFnStartSort, txFnEndSort},
+ {kNameSpaceID_XSLT, "with-param", txFnStartWithParam, txFnEndWithParam}};
+
+const txHandlerTableData gTxApplyTemplatesTableData = {
+ // Other
+ {0, 0, txFnStartElementSetIgnore,
+ txFnEndElementSetIgnore}, // should this be error?
+ // LRE
+ {0, 0, txFnStartElementSetIgnore, txFnEndElementSetIgnore},
+ // Text
+ txFnTextIgnore};
+
+const txElementHandler gTxCallTemplateElementHandlers[] = {
+ {kNameSpaceID_XSLT, "with-param", txFnStartWithParam, txFnEndWithParam}};
+
+const txHandlerTableData gTxCallTemplateTableData = {
+ // Other
+ {0, 0, txFnStartElementSetIgnore,
+ txFnEndElementSetIgnore}, // should this be error?
+ // LRE
+ {0, 0, txFnStartElementSetIgnore, txFnEndElementSetIgnore},
+ // Text
+ txFnTextIgnore};
+
+const txHandlerTableData gTxVariableTableData = {
+ // Other
+ {0, 0, txFnStartElementStartRTF, 0},
+ // LRE
+ {0, 0, txFnStartElementStartRTF, 0},
+ // Text
+ txFnTextStartRTF};
+
+const txElementHandler gTxForEachElementHandlers[] = {
+ {kNameSpaceID_XSLT, "sort", txFnStartSort, txFnEndSort}};
+
+const txHandlerTableData gTxForEachTableData = {
+ // Other
+ {0, 0, txFnStartElementContinueTemplate, 0},
+ // LRE
+ {0, 0, txFnStartElementContinueTemplate, 0},
+ // Text
+ txFnTextContinueTemplate};
+
+const txHandlerTableData gTxTopVariableTableData = {
+ // Other
+ {0, 0, txFnStartElementStartTopVar, 0},
+ // LRE
+ {0, 0, txFnStartElementStartTopVar, 0},
+ // Text
+ txFnTextStartTopVar};
+
+const txElementHandler gTxChooseElementHandlers[] = {
+ {kNameSpaceID_XSLT, "otherwise", txFnStartOtherwise, txFnEndOtherwise},
+ {kNameSpaceID_XSLT, "when", txFnStartWhen, txFnEndWhen}};
+
+const txHandlerTableData gTxChooseTableData = {
+ // Other
+ {0, 0, txFnStartElementError, 0},
+ // LRE
+ {0, 0, txFnStartElementError, 0},
+ // Text
+ txFnTextError};
+
+const txElementHandler gTxParamElementHandlers[] = {
+ {kNameSpaceID_XSLT, "param", txFnStartParam, txFnEndParam}};
+
+const txHandlerTableData gTxParamTableData = {
+ // Other
+ {0, 0, txFnStartElementContinueTemplate, 0},
+ // LRE
+ {0, 0, txFnStartElementContinueTemplate, 0},
+ // Text
+ txFnTextContinueTemplate};
+
+const txElementHandler gTxImportElementHandlers[] = {
+ {kNameSpaceID_XSLT, "import", txFnStartImport, txFnEndImport}};
+
+const txHandlerTableData gTxImportTableData = {
+ // Other
+ {0, 0, txFnStartElementContinueTopLevel, 0},
+ // LRE
+ {0, 0, txFnStartOtherTop, txFnEndOtherTop}, // XXX what should we do here?
+ // Text
+ txFnTextIgnore // XXX what should we do here?
+};
+
+const txElementHandler gTxAttributeSetElementHandlers[] = {
+ {kNameSpaceID_XSLT, "attribute", txFnStartAttribute, txFnEndAttribute}};
+
+const txHandlerTableData gTxAttributeSetTableData = {
+ // Other
+ {0, 0, txFnStartElementError, 0},
+ // LRE
+ {0, 0, txFnStartElementError, 0},
+ // Text
+ txFnTextError};
+
+const txElementHandler gTxFallbackElementHandlers[] = {
+ {kNameSpaceID_XSLT, "fallback", txFnStartFallback, txFnEndFallback}};
+
+const txHandlerTableData gTxFallbackTableData = {
+ // Other
+ {0, 0, txFnStartElementSetIgnore, txFnEndElementSetIgnore},
+ // LRE
+ {0, 0, txFnStartElementSetIgnore, txFnEndElementSetIgnore},
+ // Text
+ txFnTextIgnore};
+
+/**
+ * txHandlerTable
+ */
+txHandlerTable::txHandlerTable(const HandleTextFn aTextHandler,
+ const txElementHandler* aLREHandler,
+ const txElementHandler* aOtherHandler)
+ : mTextHandler(aTextHandler),
+ mLREHandler(aLREHandler),
+ mOtherHandler(aOtherHandler) {}
+
+nsresult txHandlerTable::init(const txElementHandler* aHandlers,
+ uint32_t aCount) {
+ nsresult rv = NS_OK;
+
+ uint32_t i;
+ for (i = 0; i < aCount; ++i) {
+ RefPtr<nsAtom> nameAtom = NS_Atomize(aHandlers->mLocalName);
+ txExpandedName name(aHandlers->mNamespaceID, nameAtom);
+ rv = mHandlers.add(name, aHandlers);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ ++aHandlers;
+ }
+ return NS_OK;
+}
+
+const txElementHandler* txHandlerTable::find(int32_t aNamespaceID,
+ nsAtom* aLocalName) {
+ txExpandedName name(aNamespaceID, aLocalName);
+ const txElementHandler* handler = mHandlers.get(name);
+ if (!handler) {
+ handler = mOtherHandler;
+ }
+ return handler;
+}
+
+#define INIT_HANDLER(_name) \
+ gTx##_name##Handler = new txHandlerTable( \
+ gTx##_name##TableData.mTextHandler, &gTx##_name##TableData.mLREHandler, \
+ &gTx##_name##TableData.mOtherHandler); \
+ if (!gTx##_name##Handler) return false
+
+#define INIT_HANDLER_WITH_ELEMENT_HANDLERS(_name) \
+ INIT_HANDLER(_name); \
+ \
+ rv = gTx##_name##Handler->init(gTx##_name##ElementHandlers, \
+ ArrayLength(gTx##_name##ElementHandlers)); \
+ if (NS_FAILED(rv)) return false
+
+#define SHUTDOWN_HANDLER(_name) \
+ delete gTx##_name##Handler; \
+ gTx##_name##Handler = nullptr
+
+// static
+bool txHandlerTable::init() {
+ nsresult rv = NS_OK;
+
+ INIT_HANDLER_WITH_ELEMENT_HANDLERS(Root);
+ INIT_HANDLER(Embed);
+ INIT_HANDLER_WITH_ELEMENT_HANDLERS(Top);
+ INIT_HANDLER(Ignore);
+ INIT_HANDLER_WITH_ELEMENT_HANDLERS(Template);
+ INIT_HANDLER(Text);
+ INIT_HANDLER_WITH_ELEMENT_HANDLERS(ApplyTemplates);
+ INIT_HANDLER_WITH_ELEMENT_HANDLERS(CallTemplate);
+ INIT_HANDLER(Variable);
+ INIT_HANDLER_WITH_ELEMENT_HANDLERS(ForEach);
+ INIT_HANDLER(TopVariable);
+ INIT_HANDLER_WITH_ELEMENT_HANDLERS(Choose);
+ INIT_HANDLER_WITH_ELEMENT_HANDLERS(Param);
+ INIT_HANDLER_WITH_ELEMENT_HANDLERS(Import);
+ INIT_HANDLER_WITH_ELEMENT_HANDLERS(AttributeSet);
+ INIT_HANDLER_WITH_ELEMENT_HANDLERS(Fallback);
+
+ return true;
+}
+
+// static
+void txHandlerTable::shutdown() {
+ SHUTDOWN_HANDLER(Root);
+ SHUTDOWN_HANDLER(Embed);
+ SHUTDOWN_HANDLER(Top);
+ SHUTDOWN_HANDLER(Ignore);
+ SHUTDOWN_HANDLER(Template);
+ SHUTDOWN_HANDLER(Text);
+ SHUTDOWN_HANDLER(ApplyTemplates);
+ SHUTDOWN_HANDLER(CallTemplate);
+ SHUTDOWN_HANDLER(Variable);
+ SHUTDOWN_HANDLER(ForEach);
+ SHUTDOWN_HANDLER(TopVariable);
+ SHUTDOWN_HANDLER(Choose);
+ SHUTDOWN_HANDLER(Param);
+ SHUTDOWN_HANDLER(Import);
+ SHUTDOWN_HANDLER(AttributeSet);
+ SHUTDOWN_HANDLER(Fallback);
+}
diff --git a/dom/xslt/xslt/txStylesheetCompileHandlers.h b/dom/xslt/xslt/txStylesheetCompileHandlers.h
new file mode 100644
index 0000000000..a1913110ef
--- /dev/null
+++ b/dom/xslt/xslt/txStylesheetCompileHandlers.h
@@ -0,0 +1,54 @@
+/* -*- 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/. */
+
+#ifndef TRANSFRMX_TXSTYLESHEETCOMPILEHANDLERS_H
+#define TRANSFRMX_TXSTYLESHEETCOMPILEHANDLERS_H
+
+#include "nsError.h"
+#include "txNamespaceMap.h"
+#include "txExpandedNameMap.h"
+
+struct txStylesheetAttr;
+class txStylesheetCompilerState;
+
+using HandleStartFn = nsresult (*)(int32_t aNamespaceID, nsAtom* aLocalName,
+ nsAtom* aPrefix,
+ txStylesheetAttr* aAttributes,
+ int32_t aAttrCount,
+ txStylesheetCompilerState& aState);
+using HandleEndFn = void (*)(txStylesheetCompilerState& aState);
+using HandleTextFn = nsresult (*)(const nsAString& aStr,
+ txStylesheetCompilerState& aState);
+
+struct txElementHandler {
+ int32_t mNamespaceID;
+ const char* mLocalName;
+ HandleStartFn mStartFunction;
+ HandleEndFn mEndFunction;
+};
+
+class txHandlerTable {
+ public:
+ txHandlerTable(const HandleTextFn aTextHandler,
+ const txElementHandler* aLREHandler,
+ const txElementHandler* aOtherHandler);
+ nsresult init(const txElementHandler* aHandlers, uint32_t aCount);
+ const txElementHandler* find(int32_t aNamespaceID, nsAtom* aLocalName);
+
+ const HandleTextFn mTextHandler;
+ const txElementHandler* const mLREHandler;
+
+ static bool init();
+ static void shutdown();
+
+ private:
+ const txElementHandler* const mOtherHandler;
+ txExpandedNameMap<const txElementHandler> mHandlers;
+};
+
+extern txHandlerTable* gTxRootHandler;
+extern txHandlerTable* gTxEmbedHandler;
+
+#endif // TRANSFRMX_TXSTYLESHEETCOMPILEHANDLERS_H
diff --git a/dom/xslt/xslt/txStylesheetCompiler.cpp b/dom/xslt/xslt/txStylesheetCompiler.cpp
new file mode 100644
index 0000000000..82b6de4c79
--- /dev/null
+++ b/dom/xslt/xslt/txStylesheetCompiler.cpp
@@ -0,0 +1,839 @@
+/* -*- 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 "txStylesheetCompiler.h"
+
+#include <utility>
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/UniquePtrExtensions.h"
+#include "nsGkAtoms.h"
+#include "nsServiceManagerUtils.h"
+#include "nsTArray.h"
+#include "nsWhitespaceTokenizer.h"
+#include "txExprParser.h"
+#include "txInstructions.h"
+#include "txLog.h"
+#include "txPatternParser.h"
+#include "txStringUtils.h"
+#include "txStylesheet.h"
+#include "txStylesheetCompileHandlers.h"
+#include "txToplevelItems.h"
+#include "txURIUtils.h"
+#include "txXSLTFunctions.h"
+
+using namespace mozilla;
+using mozilla::dom::ReferrerPolicy;
+
+txStylesheetCompiler::txStylesheetCompiler(const nsAString& aStylesheetURI,
+ ReferrerPolicy aReferrerPolicy,
+ txACompileObserver* aObserver)
+ : txStylesheetCompilerState(aObserver) {
+ mStatus = init(aStylesheetURI, aReferrerPolicy, nullptr, nullptr);
+}
+
+txStylesheetCompiler::txStylesheetCompiler(const nsAString& aStylesheetURI,
+ txStylesheet* aStylesheet,
+ txListIterator* aInsertPosition,
+ ReferrerPolicy aReferrerPolicy,
+ txACompileObserver* aObserver)
+ : txStylesheetCompilerState(aObserver) {
+ mStatus = init(aStylesheetURI, aReferrerPolicy, aStylesheet, aInsertPosition);
+}
+
+void txStylesheetCompiler::setBaseURI(const nsString& aBaseURI) {
+ NS_ASSERTION(mObjectStack.size() == 1 && !mObjectStack.peek(),
+ "Execution already started");
+
+ if (NS_FAILED(mStatus)) {
+ return;
+ }
+
+ mElementContext->mBaseURI = aBaseURI;
+}
+
+nsresult txStylesheetCompiler::startElement(int32_t aNamespaceID,
+ nsAtom* aLocalName, nsAtom* aPrefix,
+ txStylesheetAttr* aAttributes,
+ int32_t aAttrCount) {
+ if (NS_FAILED(mStatus)) {
+ // ignore content after failure
+ // XXX reevaluate once expat stops on failure
+ return NS_OK;
+ }
+
+ nsresult rv = flushCharacters();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // look for new namespace mappings
+ bool hasOwnNamespaceMap = false;
+ int32_t i;
+ for (i = 0; i < aAttrCount; ++i) {
+ txStylesheetAttr* attr = aAttributes + i;
+ if (attr->mNamespaceID == kNameSpaceID_XMLNS) {
+ rv = ensureNewElementContext();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!hasOwnNamespaceMap) {
+ mElementContext->mMappings =
+ new txNamespaceMap(*mElementContext->mMappings);
+ hasOwnNamespaceMap = true;
+ }
+
+ if (attr->mLocalName == nsGkAtoms::xmlns) {
+ mElementContext->mMappings->mapNamespace(nullptr, attr->mValue);
+ } else {
+ mElementContext->mMappings->mapNamespace(attr->mLocalName,
+ attr->mValue);
+ }
+ }
+ }
+
+ return startElementInternal(aNamespaceID, aLocalName, aPrefix, aAttributes,
+ aAttrCount);
+}
+
+nsresult txStylesheetCompiler::startElement(const char16_t* aName,
+ const char16_t** aAttrs,
+ int32_t aAttrCount) {
+ if (NS_FAILED(mStatus)) {
+ // ignore content after failure
+ // XXX reevaluate once expat stops on failure
+ return NS_OK;
+ }
+
+ nsresult rv = flushCharacters();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ UniquePtr<txStylesheetAttr[]> atts;
+ if (aAttrCount > 0) {
+ atts = MakeUnique<txStylesheetAttr[]>(aAttrCount);
+ }
+
+ bool hasOwnNamespaceMap = false;
+ int32_t i;
+ for (i = 0; i < aAttrCount; ++i) {
+ rv = XMLUtils::splitExpatName(
+ aAttrs[i * 2], getter_AddRefs(atts[i].mPrefix),
+ getter_AddRefs(atts[i].mLocalName), &atts[i].mNamespaceID);
+ NS_ENSURE_SUCCESS(rv, rv);
+ atts[i].mValue.Append(aAttrs[i * 2 + 1]);
+
+ RefPtr<nsAtom> prefixToBind;
+ if (atts[i].mPrefix == nsGkAtoms::xmlns) {
+ prefixToBind = atts[i].mLocalName;
+ } else if (atts[i].mNamespaceID == kNameSpaceID_XMLNS) {
+ prefixToBind = nsGkAtoms::_empty;
+ }
+
+ if (prefixToBind) {
+ rv = ensureNewElementContext();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!hasOwnNamespaceMap) {
+ mElementContext->mMappings =
+ new txNamespaceMap(*mElementContext->mMappings);
+ hasOwnNamespaceMap = true;
+ }
+
+ rv = mElementContext->mMappings->mapNamespace(prefixToBind,
+ atts[i].mValue);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ RefPtr<nsAtom> prefix, localname;
+ int32_t namespaceID;
+ rv = XMLUtils::splitExpatName(aName, getter_AddRefs(prefix),
+ getter_AddRefs(localname), &namespaceID);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return startElementInternal(namespaceID, localname, prefix, atts.get(),
+ aAttrCount);
+}
+
+nsresult txStylesheetCompiler::startElementInternal(
+ int32_t aNamespaceID, nsAtom* aLocalName, nsAtom* aPrefix,
+ txStylesheetAttr* aAttributes, int32_t aAttrCount) {
+ nsresult rv = NS_OK;
+ int32_t i;
+ for (i = mInScopeVariables.Length() - 1; i >= 0; --i) {
+ ++mInScopeVariables[i].mLevel;
+ }
+
+ // Update the elementcontext if we have special attributes
+ for (i = 0; i < aAttrCount; ++i) {
+ txStylesheetAttr* attr = aAttributes + i;
+
+ // id
+ if (mEmbedStatus == eNeedEmbed && attr->mLocalName == nsGkAtoms::id &&
+ attr->mNamespaceID == kNameSpaceID_None &&
+ attr->mValue.Equals(mTarget)) {
+ // We found the right ID, signal to compile the
+ // embedded stylesheet.
+ mEmbedStatus = eInEmbed;
+ }
+
+ // xml:space
+ if (attr->mNamespaceID == kNameSpaceID_XML &&
+ attr->mLocalName == nsGkAtoms::space) {
+ rv = ensureNewElementContext();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (TX_StringEqualsAtom(attr->mValue, nsGkAtoms::preserve)) {
+ mElementContext->mPreserveWhitespace = true;
+ } else if (TX_StringEqualsAtom(attr->mValue, nsGkAtoms::_default)) {
+ mElementContext->mPreserveWhitespace = false;
+ } else {
+ return NS_ERROR_XSLT_PARSE_FAILURE;
+ }
+ }
+
+ // extension-element-prefixes
+ if ((attr->mNamespaceID == kNameSpaceID_XSLT &&
+ attr->mLocalName == nsGkAtoms::extensionElementPrefixes &&
+ aNamespaceID != kNameSpaceID_XSLT) ||
+ (attr->mNamespaceID == kNameSpaceID_None &&
+ attr->mLocalName == nsGkAtoms::extensionElementPrefixes &&
+ aNamespaceID == kNameSpaceID_XSLT &&
+ (aLocalName == nsGkAtoms::stylesheet ||
+ aLocalName == nsGkAtoms::transform))) {
+ rv = ensureNewElementContext();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsWhitespaceTokenizer tok(attr->mValue);
+ while (tok.hasMoreTokens()) {
+ int32_t namespaceID =
+ mElementContext->mMappings->lookupNamespaceWithDefault(
+ tok.nextToken());
+
+ if (namespaceID == kNameSpaceID_Unknown)
+ return NS_ERROR_XSLT_PARSE_FAILURE;
+
+ mElementContext->mInstructionNamespaces.AppendElement(namespaceID);
+ }
+
+ attr->mLocalName = nullptr;
+ }
+
+ // version
+ if ((attr->mNamespaceID == kNameSpaceID_XSLT &&
+ attr->mLocalName == nsGkAtoms::version &&
+ aNamespaceID != kNameSpaceID_XSLT) ||
+ (attr->mNamespaceID == kNameSpaceID_None &&
+ attr->mLocalName == nsGkAtoms::version &&
+ aNamespaceID == kNameSpaceID_XSLT &&
+ (aLocalName == nsGkAtoms::stylesheet ||
+ aLocalName == nsGkAtoms::transform))) {
+ rv = ensureNewElementContext();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (attr->mValue.EqualsLiteral("1.0")) {
+ mElementContext->mForwardsCompatibleParsing = false;
+ } else {
+ mElementContext->mForwardsCompatibleParsing = true;
+ }
+ }
+ }
+
+ // Find the right elementhandler and execute it
+ bool isInstruction = false;
+ int32_t count = mElementContext->mInstructionNamespaces.Length();
+ for (i = 0; i < count; ++i) {
+ if (mElementContext->mInstructionNamespaces[i] == aNamespaceID) {
+ isInstruction = true;
+ break;
+ }
+ }
+
+ const txElementHandler* handler;
+ do {
+ handler = isInstruction ? mHandlerTable->find(aNamespaceID, aLocalName)
+ : mHandlerTable->mLREHandler;
+
+ rv = (handler->mStartFunction)(aNamespaceID, aLocalName, aPrefix,
+ aAttributes, aAttrCount, *this);
+ } while (rv == NS_XSLT_GET_NEW_HANDLER);
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!fcp()) {
+ for (i = 0; i < aAttrCount; ++i) {
+ txStylesheetAttr& attr = aAttributes[i];
+ if (attr.mLocalName && (attr.mNamespaceID == kNameSpaceID_XSLT ||
+ (aNamespaceID == kNameSpaceID_XSLT &&
+ attr.mNamespaceID == kNameSpaceID_None))) {
+ // XXX ErrorReport: unknown attribute
+ return NS_ERROR_XSLT_PARSE_FAILURE;
+ }
+ }
+ }
+
+ pushPtr(const_cast<txElementHandler*>(handler), eElementHandler);
+
+ mElementContext->mDepth++;
+
+ return NS_OK;
+}
+
+nsresult txStylesheetCompiler::endElement() {
+ if (NS_FAILED(mStatus)) {
+ // ignore content after failure
+ // XXX reevaluate once expat stops on failure
+ return NS_OK;
+ }
+
+ nsresult rv = flushCharacters();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t i;
+ for (i = mInScopeVariables.Length() - 1; i >= 0; --i) {
+ txInScopeVariable& var = mInScopeVariables[i];
+ if (!--(var.mLevel)) {
+ addInstruction(MakeUnique<txRemoveVariable>(var.mName));
+
+ mInScopeVariables.RemoveElementAt(i);
+ }
+ }
+
+ const txElementHandler* handler = const_cast<const txElementHandler*>(
+ static_cast<txElementHandler*>(popPtr(eElementHandler)));
+ (handler->mEndFunction)(*this);
+
+ if (!--mElementContext->mDepth) {
+ // this will delete the old object
+ mElementContext = WrapUnique(static_cast<txElementContext*>(popObject()));
+ }
+
+ return NS_OK;
+}
+
+nsresult txStylesheetCompiler::characters(const nsAString& aStr) {
+ if (NS_FAILED(mStatus)) {
+ // ignore content after failure
+ // XXX reevaluate once expat stops on failure
+ return NS_OK;
+ }
+
+ mCharacters.Append(aStr);
+
+ return NS_OK;
+}
+
+nsresult txStylesheetCompiler::doneLoading() {
+ MOZ_LOG(txLog::xslt, LogLevel::Info,
+ ("Compiler::doneLoading: %s\n",
+ NS_LossyConvertUTF16toASCII(mStylesheetURI).get()));
+ if (NS_FAILED(mStatus)) {
+ return mStatus;
+ }
+
+ mDoneWithThisStylesheet = true;
+
+ return maybeDoneCompiling();
+}
+
+void txStylesheetCompiler::cancel(nsresult aError, const char16_t* aErrorText,
+ const char16_t* aParam) {
+ MOZ_LOG(txLog::xslt, LogLevel::Info,
+ ("Compiler::cancel: %s, module: %d, code %d\n",
+ NS_LossyConvertUTF16toASCII(mStylesheetURI).get(),
+ NS_ERROR_GET_MODULE(aError), NS_ERROR_GET_CODE(aError)));
+ if (NS_SUCCEEDED(mStatus)) {
+ mStatus = aError;
+ }
+
+ if (mObserver) {
+ mObserver->onDoneCompiling(this, mStatus, aErrorText, aParam);
+ // This will ensure that we don't call onDoneCompiling twice. Also
+ // ensures that we don't keep the observer alive longer then necessary.
+ mObserver = nullptr;
+ }
+}
+
+txStylesheet* txStylesheetCompiler::getStylesheet() { return mStylesheet; }
+
+nsresult txStylesheetCompiler::loadURI(const nsAString& aUri,
+ const nsAString& aReferrerUri,
+ ReferrerPolicy aReferrerPolicy,
+ txStylesheetCompiler* aCompiler) {
+ MOZ_LOG(txLog::xslt, LogLevel::Info,
+ ("Compiler::loadURI forwards %s thru %s\n",
+ NS_LossyConvertUTF16toASCII(aUri).get(),
+ NS_LossyConvertUTF16toASCII(mStylesheetURI).get()));
+ if (mStylesheetURI.Equals(aUri)) {
+ return NS_ERROR_XSLT_LOAD_RECURSION;
+ }
+ return mObserver ? mObserver->loadURI(aUri, aReferrerUri, aReferrerPolicy,
+ aCompiler)
+ : NS_ERROR_FAILURE;
+}
+
+void txStylesheetCompiler::onDoneCompiling(txStylesheetCompiler* aCompiler,
+ nsresult aResult,
+ const char16_t* aErrorText,
+ const char16_t* aParam) {
+ if (NS_FAILED(aResult)) {
+ cancel(aResult, aErrorText, aParam);
+ return;
+ }
+
+ mChildCompilerList.RemoveElement(aCompiler);
+
+ maybeDoneCompiling();
+}
+
+nsresult txStylesheetCompiler::flushCharacters() {
+ // Bail if we don't have any characters. The handler will detect
+ // ignoreable whitespace
+ if (mCharacters.IsEmpty()) {
+ return NS_OK;
+ }
+
+ nsresult rv = NS_OK;
+
+ do {
+ rv = (mHandlerTable->mTextHandler)(mCharacters, *this);
+ } while (rv == NS_XSLT_GET_NEW_HANDLER);
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mCharacters.Truncate();
+
+ return NS_OK;
+}
+
+nsresult txStylesheetCompiler::ensureNewElementContext() {
+ // Do we already have a new context?
+ if (!mElementContext->mDepth) {
+ return NS_OK;
+ }
+
+ UniquePtr<txElementContext> context(new txElementContext(*mElementContext));
+ pushObject(mElementContext.release());
+ mElementContext = std::move(context);
+
+ return NS_OK;
+}
+
+nsresult txStylesheetCompiler::maybeDoneCompiling() {
+ if (!mDoneWithThisStylesheet || !mChildCompilerList.IsEmpty()) {
+ return NS_OK;
+ }
+
+ if (mIsTopCompiler) {
+ nsresult rv = mStylesheet->doneCompiling();
+ if (NS_FAILED(rv)) {
+ cancel(rv);
+ return rv;
+ }
+ }
+
+ if (mObserver) {
+ mObserver->onDoneCompiling(this, mStatus);
+ // This will ensure that we don't call onDoneCompiling twice. Also
+ // ensures that we don't keep the observer alive longer then necessary.
+ mObserver = nullptr;
+ }
+
+ return NS_OK;
+}
+
+/**
+ * txStylesheetCompilerState
+ */
+
+txStylesheetCompilerState::txStylesheetCompilerState(
+ txACompileObserver* aObserver)
+ : mHandlerTable(nullptr),
+ mSorter(nullptr),
+ mDOE(false),
+ mSearchingForFallback(false),
+ mDisAllowed(0),
+ mObserver(aObserver),
+ mEmbedStatus(eNoEmbed),
+ mIsTopCompiler(false),
+ mDoneWithThisStylesheet(false),
+ mNextInstrPtr(nullptr),
+ mToplevelIterator(nullptr),
+ mReferrerPolicy(ReferrerPolicy::_empty) {
+ // Embedded stylesheets have another handler, which is set in
+ // txStylesheetCompiler::init if the baseURI has a fragment identifier.
+ mHandlerTable = gTxRootHandler;
+}
+
+nsresult txStylesheetCompilerState::init(const nsAString& aStylesheetURI,
+ ReferrerPolicy aReferrerPolicy,
+ txStylesheet* aStylesheet,
+ txListIterator* aInsertPosition) {
+ NS_ASSERTION(!aStylesheet || aInsertPosition,
+ "must provide insertposition if loading subsheet");
+ mStylesheetURI = aStylesheetURI;
+ mReferrerPolicy = aReferrerPolicy;
+ // Check for fragment identifier of an embedded stylesheet.
+ int32_t fragment = aStylesheetURI.FindChar('#') + 1;
+ if (fragment > 0) {
+ int32_t fragmentLength = aStylesheetURI.Length() - fragment;
+ if (fragmentLength > 0) {
+ // This is really an embedded stylesheet, not just a
+ // "url#". We may want to unescape the fragment.
+ mTarget = Substring(aStylesheetURI, (uint32_t)fragment, fragmentLength);
+ mEmbedStatus = eNeedEmbed;
+ mHandlerTable = gTxEmbedHandler;
+ }
+ }
+ nsresult rv = NS_OK;
+ if (aStylesheet) {
+ mStylesheet = aStylesheet;
+ mToplevelIterator = *aInsertPosition;
+ mIsTopCompiler = false;
+ } else {
+ mStylesheet = new txStylesheet;
+ rv = mStylesheet->init();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mToplevelIterator =
+ txListIterator(&mStylesheet->mRootFrame->mToplevelItems);
+ mToplevelIterator.next(); // go to the end of the list
+ mIsTopCompiler = true;
+ }
+
+ mElementContext = MakeUnique<txElementContext>(aStylesheetURI);
+
+ // Push the "old" txElementContext
+ pushObject(nullptr);
+
+ return NS_OK;
+}
+
+txStylesheetCompilerState::~txStylesheetCompilerState() {
+ while (!mObjectStack.isEmpty()) {
+ delete popObject();
+ }
+}
+
+void txStylesheetCompilerState::pushHandlerTable(txHandlerTable* aTable) {
+ pushPtr(mHandlerTable, eHandlerTable);
+ mHandlerTable = aTable;
+}
+
+void txStylesheetCompilerState::popHandlerTable() {
+ mHandlerTable = static_cast<txHandlerTable*>(popPtr(eHandlerTable));
+}
+
+void txStylesheetCompilerState::pushSorter(txPushNewContext* aSorter) {
+ pushPtr(mSorter, ePushNewContext);
+ mSorter = aSorter;
+}
+
+void txStylesheetCompilerState::popSorter() {
+ mSorter = static_cast<txPushNewContext*>(popPtr(ePushNewContext));
+}
+
+void txStylesheetCompilerState::pushChooseGotoList() {
+ pushObject(mChooseGotoList.release());
+ mChooseGotoList = MakeUnique<txList>();
+}
+
+void txStylesheetCompilerState::popChooseGotoList() {
+ // this will delete the old value
+ mChooseGotoList = WrapUnique(static_cast<txList*>(popObject()));
+}
+
+void txStylesheetCompilerState::pushObject(txObject* aObject) {
+ mObjectStack.push(aObject);
+}
+
+txObject* txStylesheetCompilerState::popObject() {
+ return static_cast<txObject*>(mObjectStack.pop());
+}
+
+void txStylesheetCompilerState::pushPtr(void* aPtr, enumStackType aType) {
+#ifdef TX_DEBUG_STACK
+ MOZ_LOG(txLog::xslt, LogLevel::Debug,
+ ("pushPtr: 0x%x type %u\n", aPtr, aType));
+#endif
+ mTypeStack.AppendElement(aType);
+ mOtherStack.push(aPtr);
+}
+
+void* txStylesheetCompilerState::popPtr(enumStackType aType) {
+ if (mTypeStack.IsEmpty()) {
+ MOZ_CRASH("Attempt to pop when type stack is empty");
+ }
+
+ enumStackType type = mTypeStack.PopLastElement();
+ void* value = mOtherStack.pop();
+
+#ifdef TX_DEBUG_STACK
+ MOZ_LOG(txLog::xslt, LogLevel::Debug,
+ ("popPtr: 0x%x type %u requested %u\n", value, type, aType));
+#endif
+
+ if (type != aType) {
+ MOZ_CRASH("Expected type does not match top element type");
+ }
+
+ return value;
+}
+
+void txStylesheetCompilerState::addToplevelItem(txToplevelItem* aItem) {
+ mToplevelIterator.addBefore(aItem);
+}
+
+nsresult txStylesheetCompilerState::openInstructionContainer(
+ txInstructionContainer* aContainer) {
+ MOZ_ASSERT(!mNextInstrPtr, "can't nest instruction-containers");
+
+ mNextInstrPtr = &aContainer->mFirstInstruction;
+ return NS_OK;
+}
+
+void txStylesheetCompilerState::closeInstructionContainer() {
+ NS_ASSERTION(mGotoTargetPointers.IsEmpty(),
+ "GotoTargets still exists, did you forget to add txReturn?");
+ mNextInstrPtr = 0;
+}
+
+txInstruction* txStylesheetCompilerState::addInstruction(
+ UniquePtr<txInstruction>&& aInstruction) {
+ MOZ_ASSERT(mNextInstrPtr, "adding instruction outside container");
+
+ txInstruction* newInstr = aInstruction.get();
+
+ *mNextInstrPtr = std::move(aInstruction);
+ mNextInstrPtr = &newInstr->mNext;
+
+ uint32_t i, count = mGotoTargetPointers.Length();
+ for (i = 0; i < count; ++i) {
+ *mGotoTargetPointers[i] = newInstr;
+ }
+ mGotoTargetPointers.Clear();
+
+ return newInstr;
+}
+
+nsresult txStylesheetCompilerState::loadIncludedStylesheet(
+ const nsAString& aURI) {
+ MOZ_LOG(txLog::xslt, LogLevel::Info,
+ ("CompilerState::loadIncludedStylesheet: %s\n",
+ NS_LossyConvertUTF16toASCII(aURI).get()));
+ if (mStylesheetURI.Equals(aURI)) {
+ return NS_ERROR_XSLT_LOAD_RECURSION;
+ }
+ NS_ENSURE_TRUE(mObserver, NS_ERROR_NOT_IMPLEMENTED);
+
+ UniquePtr<txToplevelItem> item(new txDummyItem);
+
+ mToplevelIterator.addBefore(item.release());
+
+ // step back to the dummy-item
+ mToplevelIterator.previous();
+
+ txACompileObserver* observer = static_cast<txStylesheetCompiler*>(this);
+
+ RefPtr<txStylesheetCompiler> compiler = new txStylesheetCompiler(
+ aURI, mStylesheet, &mToplevelIterator, mReferrerPolicy, observer);
+
+ // step forward before calling the observer in case of syncronous loading
+ mToplevelIterator.next();
+
+ mChildCompilerList.AppendElement(compiler);
+
+ nsresult rv =
+ mObserver->loadURI(aURI, mStylesheetURI, mReferrerPolicy, compiler);
+ if (NS_FAILED(rv)) {
+ mChildCompilerList.RemoveElement(compiler);
+ }
+
+ return rv;
+}
+
+nsresult txStylesheetCompilerState::loadImportedStylesheet(
+ const nsAString& aURI, txStylesheet::ImportFrame* aFrame) {
+ MOZ_LOG(txLog::xslt, LogLevel::Info,
+ ("CompilerState::loadImportedStylesheet: %s\n",
+ NS_LossyConvertUTF16toASCII(aURI).get()));
+ if (mStylesheetURI.Equals(aURI)) {
+ return NS_ERROR_XSLT_LOAD_RECURSION;
+ }
+ NS_ENSURE_TRUE(mObserver, NS_ERROR_NOT_IMPLEMENTED);
+
+ txListIterator iter(&aFrame->mToplevelItems);
+ iter.next(); // go to the end of the list
+
+ txACompileObserver* observer = static_cast<txStylesheetCompiler*>(this);
+
+ RefPtr<txStylesheetCompiler> compiler = new txStylesheetCompiler(
+ aURI, mStylesheet, &iter, mReferrerPolicy, observer);
+
+ mChildCompilerList.AppendElement(compiler);
+
+ nsresult rv =
+ mObserver->loadURI(aURI, mStylesheetURI, mReferrerPolicy, compiler);
+ if (NS_FAILED(rv)) {
+ mChildCompilerList.RemoveElement(compiler);
+ }
+
+ return rv;
+}
+
+void txStylesheetCompilerState::addGotoTarget(txInstruction** aTargetPointer) {
+ mGotoTargetPointers.AppendElement(aTargetPointer);
+}
+
+void txStylesheetCompilerState::addVariable(const txExpandedName& aName) {
+ mInScopeVariables.AppendElement(aName);
+}
+
+nsresult txStylesheetCompilerState::resolveNamespacePrefix(nsAtom* aPrefix,
+ int32_t& aID) {
+ NS_ASSERTION(aPrefix && aPrefix != nsGkAtoms::_empty,
+ "caller should handle default namespace ''");
+ aID = mElementContext->mMappings->lookupNamespace(aPrefix);
+ return (aID != kNameSpaceID_Unknown) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+/**
+ * Error Function to be used for unknown extension functions.
+ *
+ */
+class txErrorFunctionCall : public FunctionCall {
+ public:
+ explicit txErrorFunctionCall(nsAtom* aName) : mName(aName) {}
+
+ TX_DECL_FUNCTION
+
+ private:
+ RefPtr<nsAtom> mName;
+};
+
+nsresult txErrorFunctionCall::evaluate(txIEvalContext* aContext,
+ txAExprResult** aResult) {
+ *aResult = nullptr;
+
+ return NS_ERROR_XPATH_BAD_EXTENSION_FUNCTION;
+}
+
+Expr::ResultType txErrorFunctionCall::getReturnType() {
+ // It doesn't really matter what we return here, but it might
+ // be a good idea to try to keep this as unoptimizable as possible
+ return ANY_RESULT;
+}
+
+bool txErrorFunctionCall::isSensitiveTo(ContextSensitivity aContext) {
+ // It doesn't really matter what we return here, but it might
+ // be a good idea to try to keep this as unoptimizable as possible
+ return true;
+}
+
+#ifdef TX_TO_STRING
+void txErrorFunctionCall::appendName(nsAString& aDest) {
+ aDest.Append(mName->GetUTF16String());
+}
+#endif
+
+static nsresult TX_ConstructXSLTFunction(nsAtom* aName,
+ txStylesheetCompilerState* aState,
+ FunctionCall** aFunction) {
+ if (aName == nsGkAtoms::document) {
+ *aFunction = new DocumentFunctionCall(aState->mElementContext->mBaseURI);
+ } else if (aName == nsGkAtoms::key) {
+ if (!aState->allowed(txIParseContext::KEY_FUNCTION)) {
+ return NS_ERROR_XSLT_CALL_TO_KEY_NOT_ALLOWED;
+ }
+ *aFunction = new txKeyFunctionCall(aState->mElementContext->mMappings);
+ } else if (aName == nsGkAtoms::formatNumber) {
+ *aFunction = new txFormatNumberFunctionCall(
+ aState->mStylesheet, aState->mElementContext->mMappings);
+ } else if (aName == nsGkAtoms::current) {
+ *aFunction = new CurrentFunctionCall();
+ } else if (aName == nsGkAtoms::unparsedEntityUri) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ } else if (aName == nsGkAtoms::generateId) {
+ *aFunction = new GenerateIdFunctionCall();
+ } else if (aName == nsGkAtoms::systemProperty) {
+ *aFunction = new txXSLTEnvironmentFunctionCall(
+ txXSLTEnvironmentFunctionCall::SYSTEM_PROPERTY,
+ aState->mElementContext->mMappings);
+ } else if (aName == nsGkAtoms::elementAvailable) {
+ *aFunction = new txXSLTEnvironmentFunctionCall(
+ txXSLTEnvironmentFunctionCall::ELEMENT_AVAILABLE,
+ aState->mElementContext->mMappings);
+ } else if (aName == nsGkAtoms::functionAvailable) {
+ *aFunction = new txXSLTEnvironmentFunctionCall(
+ txXSLTEnvironmentFunctionCall::FUNCTION_AVAILABLE,
+ aState->mElementContext->mMappings);
+ } else {
+ return NS_ERROR_XPATH_UNKNOWN_FUNCTION;
+ }
+
+ MOZ_ASSERT(*aFunction);
+ return NS_OK;
+}
+
+extern nsresult TX_ConstructEXSLTFunction(nsAtom* aName, int32_t aNamespaceID,
+ txStylesheetCompilerState* aState,
+ FunctionCall** aResult);
+
+static nsresult findFunction(nsAtom* aName, int32_t aNamespaceID,
+ txStylesheetCompilerState* aState,
+ FunctionCall** aResult) {
+ if (aNamespaceID == kNameSpaceID_None) {
+ return TX_ConstructXSLTFunction(aName, aState, aResult);
+ }
+
+ return TX_ConstructEXSLTFunction(aName, aNamespaceID, aState, aResult);
+}
+
+extern bool TX_XSLTFunctionAvailable(nsAtom* aName, int32_t aNameSpaceID) {
+ RefPtr<txStylesheetCompiler> compiler =
+ new txStylesheetCompiler(u""_ns, ReferrerPolicy::_empty, nullptr);
+ NS_ENSURE_TRUE(compiler, false);
+
+ UniquePtr<FunctionCall> fnCall;
+
+ return NS_SUCCEEDED(
+ findFunction(aName, aNameSpaceID, compiler, getter_Transfers(fnCall)));
+}
+
+nsresult txStylesheetCompilerState::resolveFunctionCall(
+ nsAtom* aName, int32_t aID, FunctionCall** aFunction) {
+ *aFunction = nullptr;
+
+ nsresult rv = findFunction(aName, aID, this, aFunction);
+ if (rv == NS_ERROR_XPATH_UNKNOWN_FUNCTION &&
+ (aID != kNameSpaceID_None || fcp())) {
+ *aFunction = new txErrorFunctionCall(aName);
+ rv = NS_OK;
+ }
+
+ return rv;
+}
+
+bool txStylesheetCompilerState::caseInsensitiveNameTests() { return false; }
+
+void txStylesheetCompilerState::SetErrorOffset(uint32_t aOffset) {
+ // XXX implement me
+}
+
+txElementContext::txElementContext(const nsAString& aBaseURI)
+ : mPreserveWhitespace(false),
+ mForwardsCompatibleParsing(true),
+ mBaseURI(aBaseURI),
+ mMappings(new txNamespaceMap),
+ mDepth(0) {
+ mInstructionNamespaces.AppendElement(kNameSpaceID_XSLT);
+}
+
+txElementContext::txElementContext(const txElementContext& aOther)
+ : mPreserveWhitespace(aOther.mPreserveWhitespace),
+ mForwardsCompatibleParsing(aOther.mForwardsCompatibleParsing),
+ mBaseURI(aOther.mBaseURI),
+ mMappings(aOther.mMappings),
+ mDepth(0) {
+ mInstructionNamespaces = aOther.mInstructionNamespaces.Clone();
+}
diff --git a/dom/xslt/xslt/txStylesheetCompiler.h b/dom/xslt/xslt/txStylesheetCompiler.h
new file mode 100644
index 0000000000..759219ced6
--- /dev/null
+++ b/dom/xslt/xslt/txStylesheetCompiler.h
@@ -0,0 +1,234 @@
+/* -*- 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/. */
+
+#ifndef TRANSFRMX_TXSTYLESHEETCOMPILER_H
+#define TRANSFRMX_TXSTYLESHEETCOMPILER_H
+
+#include "mozilla/Attributes.h"
+#include "mozilla/UniquePtr.h"
+#include "txStack.h"
+#include "txXSLTPatterns.h"
+#include "txExpr.h"
+#include "txIXPathContext.h"
+#include "txStylesheet.h"
+#include "nsTArray.h"
+
+extern bool TX_XSLTFunctionAvailable(nsAtom* aName, int32_t aNameSpaceID);
+
+class txHandlerTable;
+class txElementContext;
+class txInstructionContainer;
+class txInstruction;
+class txNamespaceMap;
+class txToplevelItem;
+class txPushNewContext;
+class txStylesheetCompiler;
+
+class txElementContext : public txObject {
+ public:
+ explicit txElementContext(const nsAString& aBaseURI);
+ txElementContext(const txElementContext& aOther);
+
+ bool mPreserveWhitespace;
+ bool mForwardsCompatibleParsing;
+ nsString mBaseURI;
+ RefPtr<txNamespaceMap> mMappings;
+ nsTArray<int32_t> mInstructionNamespaces;
+ int32_t mDepth;
+};
+
+using mozilla::dom::ReferrerPolicy;
+
+class txACompileObserver {
+ public:
+ NS_INLINE_DECL_PURE_VIRTUAL_REFCOUNTING
+
+ virtual nsresult loadURI(const nsAString& aUri, const nsAString& aReferrerUri,
+ ReferrerPolicy aReferrerPolicy,
+ txStylesheetCompiler* aCompiler) = 0;
+ virtual void onDoneCompiling(txStylesheetCompiler* aCompiler,
+ nsresult aResult,
+ const char16_t* aErrorText = nullptr,
+ const char16_t* aParam = nullptr) = 0;
+};
+
+#define TX_DECL_ACOMPILEOBSERVER \
+ nsresult loadURI(const nsAString& aUri, const nsAString& aReferrerUri, \
+ ReferrerPolicy aReferrerPolicy, \
+ txStylesheetCompiler* aCompiler) override; \
+ void onDoneCompiling(txStylesheetCompiler* aCompiler, nsresult aResult, \
+ const char16_t* aErrorText = nullptr, \
+ const char16_t* aParam = nullptr) override;
+
+class txInScopeVariable {
+ public:
+ explicit txInScopeVariable(const txExpandedName& aName)
+ : mName(aName), mLevel(1) {}
+ txExpandedName mName;
+ int32_t mLevel;
+};
+
+class txStylesheetCompilerState : public txIParseContext {
+ public:
+ explicit txStylesheetCompilerState(txACompileObserver* aObserver);
+ ~txStylesheetCompilerState();
+
+ nsresult init(const nsAString& aStylesheetURI, ReferrerPolicy aReferrerPolicy,
+ txStylesheet* aStylesheet, txListIterator* aInsertPosition);
+
+ // Embedded stylesheets state
+ bool handleEmbeddedSheet() { return mEmbedStatus == eInEmbed; }
+ void doneEmbedding() { mEmbedStatus = eHasEmbed; }
+
+ // Stack functions
+ enum enumStackType {
+ eElementHandler,
+ eHandlerTable,
+ eVariableItem,
+ eCopy,
+ eInstruction,
+ ePushNewContext,
+ eConditionalGoto,
+ eCheckParam,
+ ePushNullTemplateRule
+ };
+ void pushHandlerTable(txHandlerTable* aTable);
+ void popHandlerTable();
+ void pushSorter(txPushNewContext* aSorter);
+ void popSorter();
+ void pushChooseGotoList();
+ void popChooseGotoList();
+ void pushObject(txObject* aObject);
+ txObject* popObject();
+ void pushPtr(void* aPtr, enumStackType aType);
+ void* popPtr(enumStackType aType);
+
+ // stylesheet functions
+ void addToplevelItem(txToplevelItem* aItem);
+ nsresult openInstructionContainer(txInstructionContainer* aContainer);
+ void closeInstructionContainer();
+ txInstruction* addInstruction(
+ mozilla::UniquePtr<txInstruction>&& aInstruction);
+ template <class T>
+ T* addInstruction(mozilla::UniquePtr<T> aInstruction) {
+ return static_cast<T*>(addInstruction(
+ mozilla::UniquePtr<txInstruction>(std::move(aInstruction))));
+ }
+ nsresult loadIncludedStylesheet(const nsAString& aURI);
+ nsresult loadImportedStylesheet(const nsAString& aURI,
+ txStylesheet::ImportFrame* aFrame);
+
+ // misc
+ void addGotoTarget(txInstruction** aTargetPointer);
+ void addVariable(const txExpandedName& aName);
+
+ // txIParseContext
+ nsresult resolveNamespacePrefix(nsAtom* aPrefix, int32_t& aID) override;
+ nsresult resolveFunctionCall(nsAtom* aName, int32_t aID,
+ FunctionCall** aFunction) override;
+ bool caseInsensitiveNameTests() override;
+
+ /**
+ * Should the stylesheet be parsed in forwards compatible parsing mode.
+ */
+ bool fcp() { return mElementContext->mForwardsCompatibleParsing; }
+
+ void SetErrorOffset(uint32_t aOffset) override;
+
+ bool allowed(Allowed aAllowed) override { return !(mDisAllowed & aAllowed); }
+
+ bool ignoreError(nsresult aResult) {
+ // Some errors shouldn't be ignored even in forwards compatible parsing
+ // mode.
+ return aResult != NS_ERROR_XSLT_CALL_TO_KEY_NOT_ALLOWED && fcp();
+ }
+
+ RefPtr<txStylesheet> mStylesheet;
+ txHandlerTable* mHandlerTable;
+ mozilla::UniquePtr<txElementContext> mElementContext;
+ txPushNewContext* mSorter;
+ mozilla::UniquePtr<txList> mChooseGotoList;
+ bool mDOE;
+ bool mSearchingForFallback;
+ uint16_t mDisAllowed;
+
+ protected:
+ RefPtr<txACompileObserver> mObserver;
+ nsTArray<txInScopeVariable> mInScopeVariables;
+ nsTArray<txStylesheetCompiler*> mChildCompilerList;
+ // embed info, target information is the ID
+ nsString mTarget;
+ enum { eNoEmbed, eNeedEmbed, eInEmbed, eHasEmbed } mEmbedStatus;
+ nsString mStylesheetURI;
+ bool mIsTopCompiler;
+ bool mDoneWithThisStylesheet;
+ txStack mObjectStack;
+ txStack mOtherStack;
+ nsTArray<enumStackType> mTypeStack;
+
+ private:
+ mozilla::UniquePtr<txInstruction>* mNextInstrPtr;
+ txListIterator mToplevelIterator;
+ nsTArray<txInstruction**> mGotoTargetPointers;
+ ReferrerPolicy mReferrerPolicy;
+};
+
+struct txStylesheetAttr {
+ int32_t mNamespaceID;
+ RefPtr<nsAtom> mLocalName;
+ RefPtr<nsAtom> mPrefix;
+ nsString mValue;
+};
+
+class txStylesheetCompiler final : private txStylesheetCompilerState,
+ public txACompileObserver {
+ public:
+ friend class txStylesheetCompilerState;
+ friend bool TX_XSLTFunctionAvailable(nsAtom* aName, int32_t aNameSpaceID);
+ txStylesheetCompiler(const nsAString& aStylesheetURI,
+ ReferrerPolicy aReferrerPolicy,
+ txACompileObserver* aObserver);
+ txStylesheetCompiler(const nsAString& aStylesheetURI,
+ txStylesheet* aStylesheet,
+ txListIterator* aInsertPosition,
+ ReferrerPolicy aReferrerPolicy,
+ txACompileObserver* aObserver);
+
+ void setBaseURI(const nsString& aBaseURI);
+
+ nsresult startElement(int32_t aNamespaceID, nsAtom* aLocalName,
+ nsAtom* aPrefix, txStylesheetAttr* aAttributes,
+ int32_t aAttrCount);
+ nsresult startElement(const char16_t* aName, const char16_t** aAtts,
+ int32_t aAttrCount);
+ nsresult endElement();
+ nsresult characters(const nsAString& aStr);
+ nsresult doneLoading();
+
+ void cancel(nsresult aError, const char16_t* aErrorText = nullptr,
+ const char16_t* aParam = nullptr);
+
+ txStylesheet* getStylesheet();
+
+ TX_DECL_ACOMPILEOBSERVER
+ NS_INLINE_DECL_REFCOUNTING(txStylesheetCompiler, override)
+
+ private:
+ // Private destructor, to discourage deletion outside of Release():
+ ~txStylesheetCompiler() = default;
+
+ nsresult startElementInternal(int32_t aNamespaceID, nsAtom* aLocalName,
+ nsAtom* aPrefix, txStylesheetAttr* aAttributes,
+ int32_t aAttrCount);
+
+ nsresult flushCharacters();
+ nsresult ensureNewElementContext();
+ nsresult maybeDoneCompiling();
+
+ nsString mCharacters;
+ nsresult mStatus;
+};
+
+#endif
diff --git a/dom/xslt/xslt/txTextHandler.cpp b/dom/xslt/xslt/txTextHandler.cpp
new file mode 100644
index 0000000000..0147c7a94d
--- /dev/null
+++ b/dom/xslt/xslt/txTextHandler.cpp
@@ -0,0 +1,60 @@
+/* -*- 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 "txTextHandler.h"
+#include "nsAString.h"
+
+txTextHandler::txTextHandler(bool aOnlyText)
+ : mLevel(0), mOnlyText(aOnlyText) {}
+
+nsresult txTextHandler::attribute(nsAtom* aPrefix, nsAtom* aLocalName,
+ nsAtom* aLowercaseLocalName, int32_t aNsID,
+ const nsString& aValue) {
+ return NS_OK;
+}
+
+nsresult txTextHandler::attribute(nsAtom* aPrefix, const nsAString& aLocalName,
+ const int32_t aNsID, const nsString& aValue) {
+ return NS_OK;
+}
+
+nsresult txTextHandler::characters(const nsAString& aData, bool aDOE) {
+ if (mLevel == 0) mValue.Append(aData);
+
+ return NS_OK;
+}
+
+nsresult txTextHandler::comment(const nsString& aData) { return NS_OK; }
+
+nsresult txTextHandler::endDocument(nsresult aResult) { return NS_OK; }
+
+nsresult txTextHandler::endElement() {
+ if (mOnlyText) --mLevel;
+
+ return NS_OK;
+}
+
+nsresult txTextHandler::processingInstruction(const nsString& aTarget,
+ const nsString& aData) {
+ return NS_OK;
+}
+
+nsresult txTextHandler::startDocument() { return NS_OK; }
+
+nsresult txTextHandler::startElement(nsAtom* aPrefix, nsAtom* aLocalName,
+ nsAtom* aLowercaseLocalName,
+ const int32_t aNsID) {
+ if (mOnlyText) ++mLevel;
+
+ return NS_OK;
+}
+
+nsresult txTextHandler::startElement(nsAtom* aPrefix,
+ const nsAString& aLocalName,
+ const int32_t aNsID) {
+ if (mOnlyText) ++mLevel;
+
+ return NS_OK;
+}
diff --git a/dom/xslt/xslt/txTextHandler.h b/dom/xslt/xslt/txTextHandler.h
new file mode 100644
index 0000000000..7c41e3ec7a
--- /dev/null
+++ b/dom/xslt/xslt/txTextHandler.h
@@ -0,0 +1,25 @@
+/* -*- 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/. */
+
+#ifndef TRANSFRMX_TEXT_HANDLER_H
+#define TRANSFRMX_TEXT_HANDLER_H
+
+#include "txXMLEventHandler.h"
+#include "nsString.h"
+
+class txTextHandler : public txAXMLEventHandler {
+ public:
+ explicit txTextHandler(bool aOnlyText);
+
+ TX_DECL_TXAXMLEVENTHANDLER
+
+ nsString mValue;
+
+ private:
+ uint32_t mLevel;
+ bool mOnlyText;
+};
+
+#endif
diff --git a/dom/xslt/xslt/txToplevelItems.cpp b/dom/xslt/xslt/txToplevelItems.cpp
new file mode 100644
index 0000000000..204111cc25
--- /dev/null
+++ b/dom/xslt/xslt/txToplevelItems.cpp
@@ -0,0 +1,45 @@
+/* -*- 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 "txToplevelItems.h"
+
+#include <utility>
+
+#include "txInstructions.h"
+#include "txStylesheet.h"
+#include "txXSLTPatterns.h"
+
+using mozilla::UniquePtr;
+
+TX_IMPL_GETTYPE(txAttributeSetItem, txToplevelItem::attributeSet)
+TX_IMPL_GETTYPE(txImportItem, txToplevelItem::import)
+TX_IMPL_GETTYPE(txOutputItem, txToplevelItem::output)
+TX_IMPL_GETTYPE(txDummyItem, txToplevelItem::dummy)
+
+TX_IMPL_GETTYPE(txStripSpaceItem, txToplevelItem::stripSpace)
+
+txStripSpaceItem::~txStripSpaceItem() {
+ int32_t i, count = mStripSpaceTests.Length();
+ for (i = 0; i < count; ++i) {
+ delete mStripSpaceTests[i];
+ }
+}
+
+void txStripSpaceItem::addStripSpaceTest(txStripSpaceTest* aStripSpaceTest) {
+ mStripSpaceTests.AppendElement(aStripSpaceTest);
+}
+
+TX_IMPL_GETTYPE(txTemplateItem, txToplevelItem::templ)
+
+txTemplateItem::txTemplateItem(UniquePtr<txPattern>&& aMatch,
+ const txExpandedName& aName,
+ const txExpandedName& aMode, double aPrio)
+ : mMatch(std::move(aMatch)), mName(aName), mMode(aMode), mPrio(aPrio) {}
+
+TX_IMPL_GETTYPE(txVariableItem, txToplevelItem::variable)
+
+txVariableItem::txVariableItem(const txExpandedName& aName,
+ UniquePtr<Expr>&& aValue, bool aIsParam)
+ : mName(aName), mValue(std::move(aValue)), mIsParam(aIsParam) {}
diff --git a/dom/xslt/xslt/txToplevelItems.h b/dom/xslt/xslt/txToplevelItems.h
new file mode 100644
index 0000000000..a8ae7d44ef
--- /dev/null
+++ b/dom/xslt/xslt/txToplevelItems.h
@@ -0,0 +1,118 @@
+/* -*- 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/. */
+
+#ifndef TRANSFRMX_TXTOPLEVELITEMS_H
+#define TRANSFRMX_TXTOPLEVELITEMS_H
+
+#include "nsError.h"
+#include "txOutputFormat.h"
+#include "txXMLUtils.h"
+#include "txStylesheet.h"
+#include "txInstructions.h"
+
+class txPattern;
+class Expr;
+
+class txToplevelItem {
+ public:
+ MOZ_COUNTED_DEFAULT_CTOR(txToplevelItem)
+ MOZ_COUNTED_DTOR_VIRTUAL(txToplevelItem)
+
+ enum type {
+ attributeSet,
+ dummy,
+ import,
+ // namespaceAlias,
+ output,
+ stripSpace, // also used for preserve-space
+ templ,
+ variable
+ };
+
+ virtual type getType() = 0;
+};
+
+#define TX_DECL_TOPLEVELITEM virtual type getType() override;
+#define TX_IMPL_GETTYPE(_class, _type) \
+ txToplevelItem::type _class::getType() { return _type; }
+
+class txInstructionContainer : public txToplevelItem {
+ public:
+ mozilla::UniquePtr<txInstruction> mFirstInstruction;
+};
+
+// xsl:attribute-set
+class txAttributeSetItem : public txInstructionContainer {
+ public:
+ explicit txAttributeSetItem(const txExpandedName aName) : mName(aName) {}
+
+ TX_DECL_TOPLEVELITEM
+
+ txExpandedName mName;
+};
+
+// xsl:import
+class txImportItem : public txToplevelItem {
+ public:
+ TX_DECL_TOPLEVELITEM
+
+ mozilla::UniquePtr<txStylesheet::ImportFrame> mFrame;
+};
+
+// xsl:output
+class txOutputItem : public txToplevelItem {
+ public:
+ TX_DECL_TOPLEVELITEM
+
+ txOutputFormat mFormat;
+};
+
+// insertionpoint for xsl:include
+class txDummyItem : public txToplevelItem {
+ public:
+ TX_DECL_TOPLEVELITEM
+};
+
+// xsl:strip-space and xsl:preserve-space
+class txStripSpaceItem : public txToplevelItem {
+ public:
+ ~txStripSpaceItem();
+
+ TX_DECL_TOPLEVELITEM
+
+ void addStripSpaceTest(txStripSpaceTest* aStripSpaceTest);
+
+ nsTArray<txStripSpaceTest*> mStripSpaceTests;
+};
+
+// xsl:template
+class txTemplateItem : public txInstructionContainer {
+ public:
+ txTemplateItem(mozilla::UniquePtr<txPattern>&& aMatch,
+ const txExpandedName& aName, const txExpandedName& aMode,
+ double aPrio);
+
+ TX_DECL_TOPLEVELITEM
+
+ mozilla::UniquePtr<txPattern> mMatch;
+ txExpandedName mName;
+ txExpandedName mMode;
+ double mPrio;
+};
+
+// xsl:variable at top level
+class txVariableItem : public txInstructionContainer {
+ public:
+ txVariableItem(const txExpandedName& aName, mozilla::UniquePtr<Expr>&& aValue,
+ bool aIsParam);
+
+ TX_DECL_TOPLEVELITEM
+
+ txExpandedName mName;
+ mozilla::UniquePtr<Expr> mValue;
+ bool mIsParam;
+};
+
+#endif // TRANSFRMX_TXTOPLEVELITEMS_H
diff --git a/dom/xslt/xslt/txUnknownHandler.cpp b/dom/xslt/xslt/txUnknownHandler.cpp
new file mode 100644
index 0000000000..7f13418941
--- /dev/null
+++ b/dom/xslt/xslt/txUnknownHandler.cpp
@@ -0,0 +1,177 @@
+/* -*- 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 "txUnknownHandler.h"
+
+#include <utility>
+
+#include "mozilla/UniquePtrExtensions.h"
+#include "nsGkAtoms.h"
+#include "txExecutionState.h"
+#include "txStringUtils.h"
+#include "txStylesheet.h"
+
+using mozilla::UniquePtr;
+using mozilla::WrapUnique;
+
+txUnknownHandler::txUnknownHandler(txExecutionState* aEs)
+ : mEs(aEs), mFlushed(false) {
+ MOZ_COUNT_CTOR_INHERITED(txUnknownHandler, txBufferingHandler);
+}
+
+txUnknownHandler::~txUnknownHandler() {
+ MOZ_COUNT_DTOR_INHERITED(txUnknownHandler, txBufferingHandler);
+}
+
+nsresult txUnknownHandler::attribute(nsAtom* aPrefix, nsAtom* aLocalName,
+ nsAtom* aLowercaseLocalName, int32_t aNsID,
+ const nsString& aValue) {
+ return mFlushed
+ ? mEs->mResultHandler->attribute(
+ aPrefix, aLocalName, aLowercaseLocalName, aNsID, aValue)
+ : txBufferingHandler::attribute(
+ aPrefix, aLocalName, aLowercaseLocalName, aNsID, aValue);
+}
+
+nsresult txUnknownHandler::attribute(nsAtom* aPrefix,
+ const nsAString& aLocalName,
+ const int32_t aNsID,
+ const nsString& aValue) {
+ return mFlushed ? mEs->mResultHandler->attribute(aPrefix, aLocalName, aNsID,
+ aValue)
+ : txBufferingHandler::attribute(aPrefix, aLocalName, aNsID,
+ aValue);
+}
+
+nsresult txUnknownHandler::characters(const nsAString& aData, bool aDOE) {
+ return mFlushed ? mEs->mResultHandler->characters(aData, aDOE)
+ : txBufferingHandler::characters(aData, aDOE);
+}
+
+nsresult txUnknownHandler::comment(const nsString& aData) {
+ return mFlushed ? mEs->mResultHandler->comment(aData)
+ : txBufferingHandler::comment(aData);
+}
+
+nsresult txUnknownHandler::endDocument(nsresult aResult) {
+ if (!mFlushed) {
+ if (NS_FAILED(aResult)) {
+ return NS_OK;
+ }
+
+ // This is an unusual case, no output method has been set and we
+ // didn't create a document element. Switching to XML output mode
+ // anyway.
+
+ // Make sure that mEs->mResultHandler == this is true, otherwise we'll
+ // leak mEs->mResultHandler in createHandlerAndFlush.
+ NS_ASSERTION(mEs->mResultHandler == this,
+ "We're leaking mEs->mResultHandler.");
+
+ nsresult rv = createHandlerAndFlush(false, u""_ns, kNameSpaceID_None);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return mEs->mResultHandler->endDocument(aResult);
+}
+
+nsresult txUnknownHandler::endElement() {
+ return mFlushed ? mEs->mResultHandler->endElement()
+ : txBufferingHandler::endElement();
+}
+
+nsresult txUnknownHandler::processingInstruction(const nsString& aTarget,
+ const nsString& aData) {
+ return mFlushed ? mEs->mResultHandler->processingInstruction(aTarget, aData)
+ : txBufferingHandler::processingInstruction(aTarget, aData);
+}
+
+nsresult txUnknownHandler::startDocument() {
+ return mFlushed ? mEs->mResultHandler->startDocument()
+ : txBufferingHandler::startDocument();
+}
+
+nsresult txUnknownHandler::startElement(nsAtom* aPrefix, nsAtom* aLocalName,
+ nsAtom* aLowercaseLocalName,
+ int32_t aNsID) {
+ if (!mFlushed) {
+ // Make sure that mEs->mResultHandler == this is true, otherwise we'll
+ // leak mEs->mResultHandler in createHandlerAndFlush.
+ NS_ASSERTION(mEs->mResultHandler == this,
+ "We're leaking mEs->mResultHandler.");
+
+ RefPtr<nsAtom> owner;
+ if (!aLowercaseLocalName) {
+ owner = TX_ToLowerCaseAtom(aLocalName);
+ NS_ENSURE_TRUE(owner, NS_ERROR_OUT_OF_MEMORY);
+
+ aLowercaseLocalName = owner;
+ }
+
+ bool htmlRoot = aNsID == kNameSpaceID_None && !aPrefix &&
+ aLowercaseLocalName == nsGkAtoms::html;
+
+ // Use aLocalName and not aLowercaseLocalName in case the output
+ // handler cares about case. For eHTMLOutput the handler will hardcode
+ // to 'html' anyway.
+ nsresult rv = createHandlerAndFlush(
+ htmlRoot, nsDependentAtomString(aLocalName), aNsID);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return mEs->mResultHandler->startElement(aPrefix, aLocalName,
+ aLowercaseLocalName, aNsID);
+}
+
+nsresult txUnknownHandler::startElement(nsAtom* aPrefix,
+ const nsAString& aLocalName,
+ const int32_t aNsID) {
+ if (!mFlushed) {
+ // Make sure that mEs->mResultHandler == this is true, otherwise we'll
+ // leak mEs->mResultHandler in createHandlerAndFlush.
+ NS_ASSERTION(mEs->mResultHandler == this,
+ "We're leaking mEs->mResultHandler.");
+
+ bool htmlRoot =
+ aNsID == kNameSpaceID_None && !aPrefix &&
+ aLocalName.Equals(u"html"_ns, nsCaseInsensitiveStringComparator);
+ nsresult rv = createHandlerAndFlush(htmlRoot, aLocalName, aNsID);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return mEs->mResultHandler->startElement(aPrefix, aLocalName, aNsID);
+}
+
+nsresult txUnknownHandler::createHandlerAndFlush(bool aHTMLRoot,
+ const nsAString& aName,
+ const int32_t aNsID) {
+ NS_ENSURE_TRUE(mBuffer, NS_ERROR_NOT_INITIALIZED);
+
+ txOutputFormat format;
+ format.merge(*(mEs->mStylesheet->getOutputFormat()));
+ if (format.mMethod == eMethodNotSet) {
+ format.mMethod = aHTMLRoot ? eHTMLOutput : eXMLOutput;
+ }
+
+ UniquePtr<txAXMLEventHandler> handler;
+ nsresult rv = mEs->mOutputHandlerFactory->createHandlerWith(
+ &format, aName, aNsID, getter_Transfers(handler));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mEs->mOutputHandler = handler.get();
+ mEs->mResultHandler = handler.release();
+ // Let the executionstate delete us. We need to stay alive because we might
+ // need to forward hooks to mEs->mResultHandler if someone is currently
+ // flushing a buffer to mEs->mResultHandler.
+ mEs->mObsoleteHandler = WrapUnique(this);
+
+ mFlushed = true;
+
+ // Let go of out buffer as soon as we're done flushing it, we're not going
+ // to need it anymore from this point on (all hooks get forwarded to
+ // mEs->mResultHandler.
+ UniquePtr<txResultBuffer> buffer(std::move(mBuffer));
+ return buffer->flushToHandler(mEs->mResultHandler);
+}
diff --git a/dom/xslt/xslt/txUnknownHandler.h b/dom/xslt/xslt/txUnknownHandler.h
new file mode 100644
index 0000000000..3b13edcfc9
--- /dev/null
+++ b/dom/xslt/xslt/txUnknownHandler.h
@@ -0,0 +1,37 @@
+/* -*- 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/. */
+
+#ifndef txUnknownHandler_h___
+#define txUnknownHandler_h___
+
+#include "txBufferingHandler.h"
+#include "txOutputFormat.h"
+
+class txExecutionState;
+
+class txUnknownHandler : public txBufferingHandler {
+ public:
+ explicit txUnknownHandler(txExecutionState* aEs);
+ virtual ~txUnknownHandler();
+
+ TX_DECL_TXAXMLEVENTHANDLER
+
+ private:
+ nsresult createHandlerAndFlush(bool aHTMLRoot, const nsAString& aName,
+ const int32_t aNsID);
+
+ /*
+ * XXX we shouldn't hold to the txExecutionState, as we're supposed
+ * to live without it. But as a standalone handler, we don't.
+ * The right fix may need a txOutputFormat here.
+ */
+ txExecutionState* mEs;
+
+ // If mFlushed is true then we've replaced mEs->mResultHandler with a
+ // different handler and we should forward to that handler.
+ bool mFlushed;
+};
+
+#endif /* txUnknownHandler_h___ */
diff --git a/dom/xslt/xslt/txVariableMap.h b/dom/xslt/xslt/txVariableMap.h
new file mode 100644
index 0000000000..20c1405524
--- /dev/null
+++ b/dom/xslt/xslt/txVariableMap.h
@@ -0,0 +1,88 @@
+/* -*- 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/. */
+
+#ifndef TRANSFRMX_VARIABLEMAP_H
+#define TRANSFRMX_VARIABLEMAP_H
+
+#include "nsError.h"
+#include "txXMLUtils.h"
+#include "txExprResult.h"
+#include "txExpandedNameMap.h"
+
+/**
+ * Map that maps from expanded name to an expression result value. This is just
+ * a base class, use txVariableMap or txParameterMap instead.
+ */
+class txVariableMapBase {
+ public:
+ nsresult bindVariable(const txExpandedName& aName, txAExprResult* aValue);
+
+ void getVariable(const txExpandedName& aName, txAExprResult** aResult);
+
+ void removeVariable(const txExpandedName& aName);
+
+ protected:
+ txVariableMapBase() = default;
+ ~txVariableMapBase();
+
+ txExpandedNameMap<txAExprResult> mMap;
+};
+
+/**
+ * Map for mapping from expanded name to variable values. This is not
+ * refcounted, so owners need to be careful to clean this up.
+ */
+class txVariableMap : public txVariableMapBase {
+ public:
+ txVariableMap() { MOZ_COUNT_CTOR(txVariableMap); }
+ MOZ_COUNTED_DTOR(txVariableMap)
+};
+
+/**
+ * Map for mapping from expanded name to parameter values. This is refcounted,
+ * so multiple owners can hold a reference.
+ */
+class txParameterMap : public txVariableMapBase {
+ public:
+ NS_INLINE_DECL_REFCOUNTING(txParameterMap)
+
+ private:
+ ~txParameterMap() = default;
+};
+
+inline txVariableMapBase::~txVariableMapBase() {
+ txExpandedNameMap<txAExprResult>::iterator iter(mMap);
+ while (iter.next()) {
+ txAExprResult* res = iter.value();
+ NS_RELEASE(res);
+ }
+}
+
+inline nsresult txVariableMapBase::bindVariable(const txExpandedName& aName,
+ txAExprResult* aValue) {
+ NS_ASSERTION(aValue, "can't add null-variables to a txVariableMap");
+ nsresult rv = mMap.add(aName, aValue);
+ if (NS_SUCCEEDED(rv)) {
+ NS_ADDREF(aValue);
+ } else if (rv == NS_ERROR_XSLT_ALREADY_SET) {
+ rv = NS_ERROR_XSLT_VAR_ALREADY_SET;
+ }
+ return rv;
+}
+
+inline void txVariableMapBase::getVariable(const txExpandedName& aName,
+ txAExprResult** aResult) {
+ *aResult = mMap.get(aName);
+ if (*aResult) {
+ NS_ADDREF(*aResult);
+ }
+}
+
+inline void txVariableMapBase::removeVariable(const txExpandedName& aName) {
+ txAExprResult* var = mMap.remove(aName);
+ NS_IF_RELEASE(var);
+}
+
+#endif // TRANSFRMX_VARIABLEMAP_H
diff --git a/dom/xslt/xslt/txXMLEventHandler.h b/dom/xslt/xslt/txXMLEventHandler.h
new file mode 100644
index 0000000000..7e173bdab6
--- /dev/null
+++ b/dom/xslt/xslt/txXMLEventHandler.h
@@ -0,0 +1,184 @@
+/* -*- 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/. */
+
+#ifndef TRANSFRMX_XML_EVENT_HANDLER_H
+#define TRANSFRMX_XML_EVENT_HANDLER_H
+
+#include "txCore.h"
+#include "nsAtom.h"
+
+#define kTXNameSpaceURI u"http://www.mozilla.org/TransforMiix"
+#define kTXWrapper "transformiix:result"
+
+class txOutputFormat;
+namespace mozilla::dom {
+class Document;
+} // namespace mozilla::dom
+
+/**
+ * An interface for handling XML documents, loosely modeled
+ * after Dave Megginson's SAX 1.0 API.
+ */
+
+class txAXMLEventHandler {
+ public:
+ virtual ~txAXMLEventHandler() = default;
+
+ /**
+ * Signals to receive the start of an attribute.
+ *
+ * @param aPrefix the prefix of the attribute
+ * @param aLocalName the localname of the attribute
+ * @param aLowercaseName the localname of the attribute in lower case
+ * @param aNsID the namespace ID of the attribute
+ * @param aValue the value of the attribute
+ */
+ virtual nsresult attribute(nsAtom* aPrefix, nsAtom* aLocalName,
+ nsAtom* aLowercaseLocalName, int32_t aNsID,
+ const nsString& aValue) = 0;
+
+ /**
+ * Signals to receive the start of an attribute.
+ *
+ * @param aPrefix the prefix of the attribute
+ * @param aLocalName the localname of the attribute
+ * @param aNsID the namespace ID of the attribute
+ * @param aValue the value of the attribute
+ */
+ virtual nsresult attribute(nsAtom* aPrefix, const nsAString& aLocalName,
+ const int32_t aNsID, const nsString& aValue) = 0;
+
+ /**
+ * Signals to receive characters.
+ *
+ * @param aData the characters to receive
+ * @param aDOE disable output escaping for these characters
+ */
+ virtual nsresult characters(const nsAString& aData, bool aDOE) = 0;
+
+ /**
+ * Signals to receive data that should be treated as a comment.
+ *
+ * @param data the comment data to receive
+ */
+ virtual nsresult comment(const nsString& aData) = 0;
+
+ /**
+ * Signals the end of a document. It is an error to call
+ * this method more than once.
+ */
+ virtual nsresult endDocument(nsresult aResult) = 0;
+
+ /**
+ * Signals to receive the end of an element.
+ */
+ virtual nsresult endElement() = 0;
+
+ /**
+ * Signals to receive a processing instruction.
+ *
+ * @param aTarget the target of the processing instruction
+ * @param aData the data of the processing instruction
+ */
+ virtual nsresult processingInstruction(const nsString& aTarget,
+ const nsString& aData) = 0;
+
+ /**
+ * Signals the start of a document.
+ */
+ virtual nsresult startDocument() = 0;
+
+ /**
+ * Signals to receive the start of an element.
+ *
+ * @param aPrefix the prefix of the element
+ * @param aLocalName the localname of the element
+ * @param aLowercaseName the localname of the element in lower case
+ * @param aNsID the namespace ID of the element
+ */
+ virtual nsresult startElement(nsAtom* aPrefix, nsAtom* aLocalName,
+ nsAtom* aLowercaseLocalName, int32_t aNsID) = 0;
+
+ /**
+ * Signals to receive the start of an element. Can throw
+ * NS_ERROR_XSLT_BAD_NODE_NAME if the name is invalid
+ *
+ * @param aPrefix the prefix of the element
+ * @param aLocalName the localname of the element
+ * @param aNsID the namespace ID of the element
+ */
+ virtual nsresult startElement(nsAtom* aPrefix, const nsAString& aLocalName,
+ const int32_t aNsID) = 0;
+};
+
+#define TX_DECL_TXAXMLEVENTHANDLER \
+ virtual nsresult attribute(nsAtom* aPrefix, nsAtom* aLocalName, \
+ nsAtom* aLowercaseLocalName, int32_t aNsID, \
+ const nsString& aValue) override; \
+ virtual nsresult attribute(nsAtom* aPrefix, const nsAString& aLocalName, \
+ const int32_t aNsID, const nsString& aValue) \
+ override; \
+ virtual nsresult characters(const nsAString& aData, bool aDOE) override; \
+ virtual nsresult comment(const nsString& aData) override; \
+ virtual nsresult endDocument(nsresult aResult = NS_OK) override; \
+ virtual nsresult endElement() override; \
+ virtual nsresult processingInstruction(const nsString& aTarget, \
+ const nsString& aData) override; \
+ virtual nsresult startDocument() override; \
+ virtual nsresult startElement(nsAtom* aPrefix, nsAtom* aLocalName, \
+ nsAtom* aLowercaseLocalName, int32_t aNsID) \
+ override; \
+ virtual nsresult startElement(nsAtom* aPrefix, const nsAString& aName, \
+ const int32_t aNsID) override;
+
+class txAOutputXMLEventHandler : public txAXMLEventHandler {
+ public:
+ /**
+ * Gets the Mozilla output document
+ *
+ * @param aDocument the Mozilla output document
+ */
+ virtual void getOutputDocument(mozilla::dom::Document** aDocument) = 0;
+};
+
+#define TX_DECL_TXAOUTPUTXMLEVENTHANDLER \
+ virtual void getOutputDocument(mozilla::dom::Document** aDocument) override;
+
+/**
+ * Interface used to create the appropriate outputhandler
+ */
+class txAOutputHandlerFactory {
+ public:
+ virtual ~txAOutputHandlerFactory() = default;
+
+ /**
+ * Creates an outputhandler for the specified format.
+ * @param aFromat format to get handler for
+ * @param aHandler outparam. The created handler
+ */
+ virtual nsresult createHandlerWith(txOutputFormat* aFormat,
+ txAXMLEventHandler** aHandler) = 0;
+
+ /**
+ * Creates an outputhandler for the specified format, with the specified
+ * name and namespace for the root element.
+ * @param aFromat format to get handler for
+ * @param aName name of the root element
+ * @param aNsID namespace-id of the root element
+ * @param aHandler outparam. The created handler
+ */
+ virtual nsresult createHandlerWith(txOutputFormat* aFormat,
+ const nsAString& aName, int32_t aNsID,
+ txAXMLEventHandler** aHandler) = 0;
+};
+
+#define TX_DECL_TXAOUTPUTHANDLERFACTORY \
+ nsresult createHandlerWith(txOutputFormat* aFormat, \
+ txAXMLEventHandler** aHandler) override; \
+ nsresult createHandlerWith(txOutputFormat* aFormat, const nsAString& aName, \
+ int32_t aNsID, txAXMLEventHandler** aHandler) \
+ override;
+
+#endif
diff --git a/dom/xslt/xslt/txXPathResultComparator.cpp b/dom/xslt/xslt/txXPathResultComparator.cpp
new file mode 100644
index 0000000000..470a55dc50
--- /dev/null
+++ b/dom/xslt/xslt/txXPathResultComparator.cpp
@@ -0,0 +1,120 @@
+/* -*- 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 "mozilla/FloatingPoint.h"
+#include "mozilla/intl/Collator.h"
+#include "mozilla/intl/LocaleService.h"
+
+#include "txXPathResultComparator.h"
+#include "txExpr.h"
+#include "nsComponentManagerUtils.h"
+#include "txCore.h"
+
+using namespace mozilla;
+using Collator = mozilla::intl::Collator;
+
+#define kAscending (1 << 0)
+#define kUpperFirst (1 << 1)
+
+txResultStringComparator::txResultStringComparator(bool aAscending,
+ bool aUpperFirst,
+ const nsString& aLanguage) {
+ mSorting = 0;
+ if (aAscending) mSorting |= kAscending;
+ if (aUpperFirst) mSorting |= kUpperFirst;
+ nsresult rv = init(aLanguage);
+ if (NS_FAILED(rv)) NS_ERROR("Failed to initialize txResultStringComparator");
+}
+
+nsresult txResultStringComparator::init(const nsString& aLanguage) {
+ auto result =
+ aLanguage.IsEmpty()
+ ? mozilla::intl::LocaleService::TryCreateComponent<Collator>()
+ : mozilla::intl::LocaleService::TryCreateComponentWithLocale<
+ Collator>(NS_ConvertUTF16toUTF8(aLanguage).get());
+
+ NS_ENSURE_TRUE(result.isOk(), NS_ERROR_FAILURE);
+ auto collator = result.unwrap();
+
+ // Sort in a case-insensitive way, where "base" letters are considered
+ // equal, e.g: a = á, a = A, a ≠ b.
+ Collator::Options options{};
+ options.sensitivity = Collator::Sensitivity::Base;
+ auto optResult = collator->SetOptions(options);
+ NS_ENSURE_TRUE(optResult.isOk(), NS_ERROR_FAILURE);
+
+ mCollator = UniquePtr<const Collator>(collator.release());
+ return NS_OK;
+}
+
+nsresult txResultStringComparator::createSortableValue(Expr* aExpr,
+ txIEvalContext* aContext,
+ txObject*& aResult) {
+ UniquePtr<StringValue> val(new StringValue);
+
+ if (!mCollator) {
+ return NS_ERROR_FAILURE;
+ }
+
+ val->mString = MakeUnique<nsString>();
+ nsString& string = *val->mString;
+ nsresult rv = aExpr->evaluateToString(aContext, string);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aResult = val.release();
+
+ return NS_OK;
+}
+
+int txResultStringComparator::compareValues(txObject* aVal1, txObject* aVal2) {
+ nsString& dval1 = *((StringValue*)aVal1)->mString;
+ nsString& dval2 = *((StringValue*)aVal2)->mString;
+
+ if (!mCollator) {
+ MOZ_ASSERT_UNREACHABLE("No mCollator");
+ return -1;
+ }
+
+ int32_t result = mCollator->CompareStrings(dval1, dval2);
+
+ return (mSorting & kAscending) ? result : -result;
+}
+
+txResultStringComparator::StringValue::StringValue() = default;
+
+txResultStringComparator::StringValue::~StringValue() = default;
+
+txResultNumberComparator::txResultNumberComparator(bool aAscending) {
+ mAscending = aAscending ? 1 : -1;
+}
+
+nsresult txResultNumberComparator::createSortableValue(Expr* aExpr,
+ txIEvalContext* aContext,
+ txObject*& aResult) {
+ UniquePtr<NumberValue> numval(new NumberValue);
+
+ RefPtr<txAExprResult> exprRes;
+ nsresult rv = aExpr->evaluate(aContext, getter_AddRefs(exprRes));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ numval->mVal = exprRes->numberValue();
+
+ aResult = numval.release();
+
+ return NS_OK;
+}
+
+int txResultNumberComparator::compareValues(txObject* aVal1, txObject* aVal2) {
+ double dval1 = ((NumberValue*)aVal1)->mVal;
+ double dval2 = ((NumberValue*)aVal2)->mVal;
+
+ if (std::isnan(dval1)) return std::isnan(dval2) ? 0 : -mAscending;
+
+ if (std::isnan(dval2)) return mAscending;
+
+ if (dval1 == dval2) return 0;
+
+ return (dval1 < dval2) ? -mAscending : mAscending;
+}
diff --git a/dom/xslt/xslt/txXPathResultComparator.h b/dom/xslt/xslt/txXPathResultComparator.h
new file mode 100644
index 0000000000..165cc5f2ed
--- /dev/null
+++ b/dom/xslt/xslt/txXPathResultComparator.h
@@ -0,0 +1,86 @@
+/* -*- 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/. */
+
+#ifndef TRANSFRMX_XPATHRESULTCOMPARATOR_H
+#define TRANSFRMX_XPATHRESULTCOMPARATOR_H
+
+#include "mozilla/Attributes.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/intl/Collator.h"
+#include "mozilla/UniquePtr.h"
+#include "txCore.h"
+#include "nsCOMPtr.h"
+#include "nsString.h"
+
+class Expr;
+class txIEvalContext;
+
+/*
+ * Result comparators
+ */
+class txXPathResultComparator {
+ public:
+ virtual ~txXPathResultComparator() = default;
+
+ /*
+ * Compares two XPath results. Returns -1 if val1 < val2,
+ * 1 if val1 > val2 and 0 if val1 == val2.
+ */
+ virtual int compareValues(txObject* val1, txObject* val2) = 0;
+
+ /*
+ * Create a sortable value.
+ */
+ virtual nsresult createSortableValue(Expr* aExpr, txIEvalContext* aContext,
+ txObject*& aResult) = 0;
+};
+
+/*
+ * Compare results as strings (data-type="text")
+ */
+class txResultStringComparator : public txXPathResultComparator {
+ public:
+ txResultStringComparator(bool aAscending, bool aUpperFirst,
+ const nsString& aLanguage);
+
+ int compareValues(txObject* aVal1, txObject* aVal2) override;
+ nsresult createSortableValue(Expr* aExpr, txIEvalContext* aContext,
+ txObject*& aResult) override;
+
+ private:
+ mozilla::UniquePtr<const mozilla::intl::Collator> mCollator;
+ nsresult init(const nsString& aLanguage);
+ int mSorting;
+
+ class StringValue : public txObject {
+ public:
+ StringValue();
+ ~StringValue();
+
+ mozilla::UniquePtr<nsString> mString;
+ };
+};
+
+/*
+ * Compare results as numbers (data-type="number")
+ */
+class txResultNumberComparator : public txXPathResultComparator {
+ public:
+ explicit txResultNumberComparator(bool aAscending);
+
+ int compareValues(txObject* aVal1, txObject* aVal2) override;
+ nsresult createSortableValue(Expr* aExpr, txIEvalContext* aContext,
+ txObject*& aResult) override;
+
+ private:
+ int mAscending;
+
+ class NumberValue : public txObject {
+ public:
+ double mVal;
+ };
+};
+
+#endif
diff --git a/dom/xslt/xslt/txXSLTEnvironmentFunctionCall.cpp b/dom/xslt/xslt/txXSLTEnvironmentFunctionCall.cpp
new file mode 100644
index 0000000000..02068f2b35
--- /dev/null
+++ b/dom/xslt/xslt/txXSLTEnvironmentFunctionCall.cpp
@@ -0,0 +1,124 @@
+/* -*- 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 "txIXPathContext.h"
+#include "nsGkAtoms.h"
+#include "nsError.h"
+#include "txXMLUtils.h"
+#include "txXSLTFunctions.h"
+#include "txExpandedName.h"
+#include "txNamespaceMap.h"
+
+nsresult txXSLTEnvironmentFunctionCall::evaluate(txIEvalContext* aContext,
+ txAExprResult** aResult) {
+ *aResult = nullptr;
+
+ if (!requireParams(1, 1, aContext)) {
+ return NS_ERROR_XPATH_BAD_ARGUMENT_COUNT;
+ }
+
+ nsAutoString property;
+ nsresult rv = mParams[0]->evaluateToString(aContext, property);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ txExpandedName qname;
+ rv = qname.init(property, mMappings, mType != FUNCTION_AVAILABLE);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ switch (mType) {
+ case SYSTEM_PROPERTY: {
+ if (qname.mNamespaceID == kNameSpaceID_XSLT) {
+ if (qname.mLocalName == nsGkAtoms::version) {
+ return aContext->recycler()->getNumberResult(1.0, aResult);
+ }
+ if (qname.mLocalName == nsGkAtoms::vendor) {
+ return aContext->recycler()->getStringResult(u"Transformiix"_ns,
+ aResult);
+ }
+ if (qname.mLocalName == nsGkAtoms::vendorUrl) {
+ return aContext->recycler()->getStringResult(
+ u"http://www.mozilla.org/projects/xslt/"_ns, aResult);
+ }
+ }
+ aContext->recycler()->getEmptyStringResult(aResult);
+ break;
+ }
+ case ELEMENT_AVAILABLE: {
+ bool val = qname.mNamespaceID == kNameSpaceID_XSLT &&
+ (qname.mLocalName == nsGkAtoms::applyImports ||
+ qname.mLocalName == nsGkAtoms::applyTemplates ||
+ qname.mLocalName == nsGkAtoms::attribute ||
+ qname.mLocalName == nsGkAtoms::attributeSet ||
+ qname.mLocalName == nsGkAtoms::callTemplate ||
+ qname.mLocalName == nsGkAtoms::choose ||
+ qname.mLocalName == nsGkAtoms::comment ||
+ qname.mLocalName == nsGkAtoms::copy ||
+ qname.mLocalName == nsGkAtoms::copyOf ||
+ qname.mLocalName == nsGkAtoms::decimalFormat ||
+ qname.mLocalName == nsGkAtoms::element ||
+ qname.mLocalName == nsGkAtoms::fallback ||
+ qname.mLocalName == nsGkAtoms::forEach ||
+ qname.mLocalName == nsGkAtoms::_if ||
+ qname.mLocalName == nsGkAtoms::import ||
+ qname.mLocalName == nsGkAtoms::include ||
+ qname.mLocalName == nsGkAtoms::key ||
+ qname.mLocalName == nsGkAtoms::message ||
+ // qname.mLocalName == nsGkAtoms::namespaceAlias ||
+ qname.mLocalName == nsGkAtoms::number ||
+ qname.mLocalName == nsGkAtoms::otherwise ||
+ qname.mLocalName == nsGkAtoms::output ||
+ qname.mLocalName == nsGkAtoms::param ||
+ qname.mLocalName == nsGkAtoms::preserveSpace ||
+ qname.mLocalName == nsGkAtoms::processingInstruction ||
+ qname.mLocalName == nsGkAtoms::sort ||
+ qname.mLocalName == nsGkAtoms::stripSpace ||
+ qname.mLocalName == nsGkAtoms::stylesheet ||
+ qname.mLocalName == nsGkAtoms::_template ||
+ qname.mLocalName == nsGkAtoms::text ||
+ qname.mLocalName == nsGkAtoms::transform ||
+ qname.mLocalName == nsGkAtoms::valueOf ||
+ qname.mLocalName == nsGkAtoms::variable ||
+ qname.mLocalName == nsGkAtoms::when ||
+ qname.mLocalName == nsGkAtoms::withParam);
+
+ aContext->recycler()->getBoolResult(val, aResult);
+ break;
+ }
+ case FUNCTION_AVAILABLE: {
+ extern bool TX_XSLTFunctionAvailable(nsAtom * aName,
+ int32_t aNameSpaceID);
+
+ txCoreFunctionCall::eType type;
+ bool val =
+ (qname.mNamespaceID == kNameSpaceID_None &&
+ txCoreFunctionCall::getTypeFromAtom(qname.mLocalName, type)) ||
+ TX_XSLTFunctionAvailable(qname.mLocalName, qname.mNamespaceID);
+
+ aContext->recycler()->getBoolResult(val, aResult);
+ break;
+ }
+ }
+
+ return NS_OK;
+}
+
+Expr::ResultType txXSLTEnvironmentFunctionCall::getReturnType() {
+ return mType == SYSTEM_PROPERTY ? (STRING_RESULT | NUMBER_RESULT)
+ : BOOLEAN_RESULT;
+}
+
+bool txXSLTEnvironmentFunctionCall::isSensitiveTo(ContextSensitivity aContext) {
+ return argsSensitiveTo(aContext);
+}
+
+#ifdef TX_TO_STRING
+void txXSLTEnvironmentFunctionCall::appendName(nsAString& aDest) {
+ nsStaticAtom* atom = mType == SYSTEM_PROPERTY ? nsGkAtoms::systemProperty
+ : mType == ELEMENT_AVAILABLE
+ ? nsGkAtoms::elementAvailable
+ : nsGkAtoms::functionAvailable;
+ aDest.Append(atom->GetUTF16String());
+}
+#endif
diff --git a/dom/xslt/xslt/txXSLTFunctions.h b/dom/xslt/xslt/txXSLTFunctions.h
new file mode 100644
index 0000000000..4b7401eb83
--- /dev/null
+++ b/dom/xslt/xslt/txXSLTFunctions.h
@@ -0,0 +1,149 @@
+/* -*- 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/. */
+
+#ifndef TRANSFRMX_XSLT_FUNCTIONS_H
+#define TRANSFRMX_XSLT_FUNCTIONS_H
+
+#include "mozilla/UniquePtr.h"
+#include "txExpr.h"
+#include "txXMLUtils.h"
+#include "txNamespaceMap.h"
+
+class txStylesheet;
+
+/**
+ * The definition for the XSLT document() function
+ **/
+class DocumentFunctionCall : public FunctionCall {
+ public:
+ /**
+ * Creates a new document() function call
+ **/
+ explicit DocumentFunctionCall(const nsAString& aBaseURI);
+
+ TX_DECL_FUNCTION
+
+ private:
+ nsString mBaseURI;
+};
+
+/*
+ * The definition for the XSLT key() function
+ */
+class txKeyFunctionCall : public FunctionCall {
+ public:
+ /*
+ * Creates a new key() function call
+ */
+ explicit txKeyFunctionCall(txNamespaceMap* aMappings);
+
+ TX_DECL_FUNCTION
+
+ private:
+ RefPtr<txNamespaceMap> mMappings;
+};
+
+/**
+ * The definition for the XSLT format-number() function
+ **/
+class txFormatNumberFunctionCall : public FunctionCall {
+ public:
+ /**
+ * Creates a new format-number() function call
+ **/
+ txFormatNumberFunctionCall(txStylesheet* aStylesheet,
+ txNamespaceMap* aMappings);
+
+ TX_DECL_FUNCTION
+
+ private:
+ static const char16_t FORMAT_QUOTE;
+
+ enum FormatParseState {
+ Prefix,
+ IntDigit,
+ IntZero,
+ FracZero,
+ FracDigit,
+ Suffix,
+ Finished
+ };
+
+ // Helper that reports and invalid arg to the provided context.
+ void ReportInvalidArg(txIEvalContext* aContext);
+
+ txStylesheet* mStylesheet;
+ RefPtr<txNamespaceMap> mMappings;
+};
+
+/**
+ * DecimalFormat
+ * A representation of the XSLT element <xsl:decimal-format>
+ */
+class txDecimalFormat {
+ public:
+ /*
+ * Creates a new decimal format and initilizes all properties with
+ * default values
+ */
+ txDecimalFormat();
+ bool isEqual(txDecimalFormat* other);
+
+ char16_t mDecimalSeparator;
+ char16_t mGroupingSeparator;
+ nsString mInfinity;
+ char16_t mMinusSign;
+ nsString mNaN;
+ char16_t mPercent;
+ char16_t mPerMille;
+ char16_t mZeroDigit;
+ char16_t mDigit;
+ char16_t mPatternSeparator;
+};
+
+/**
+ * The definition for the XSLT current() function
+ **/
+class CurrentFunctionCall : public FunctionCall {
+ public:
+ /**
+ * Creates a new current() function call
+ **/
+ CurrentFunctionCall();
+
+ TX_DECL_FUNCTION
+};
+
+/**
+ * The definition for the XSLT generate-id() function
+ **/
+class GenerateIdFunctionCall : public FunctionCall {
+ public:
+ /**
+ * Creates a new generate-id() function call
+ **/
+ GenerateIdFunctionCall();
+
+ TX_DECL_FUNCTION
+};
+
+/**
+ * A system-property(), element-available() or function-available() function.
+ */
+class txXSLTEnvironmentFunctionCall : public FunctionCall {
+ public:
+ enum eType { SYSTEM_PROPERTY, ELEMENT_AVAILABLE, FUNCTION_AVAILABLE };
+
+ txXSLTEnvironmentFunctionCall(eType aType, txNamespaceMap* aMappings)
+ : mType(aType), mMappings(aMappings) {}
+
+ TX_DECL_FUNCTION
+
+ private:
+ eType mType;
+ RefPtr<txNamespaceMap> mMappings; // Used to resolve prefixes
+};
+
+#endif
diff --git a/dom/xslt/xslt/txXSLTMsgsURL.h b/dom/xslt/xslt/txXSLTMsgsURL.h
new file mode 100644
index 0000000000..321b59548a
--- /dev/null
+++ b/dom/xslt/xslt/txXSLTMsgsURL.h
@@ -0,0 +1,11 @@
+/* -*- 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/. */
+
+#ifndef DOM_XSLT_XSLT_TXXSLTMSGSURL_H_
+#define DOM_XSLT_XSLT_TXXSLTMSGSURL_H_
+
+#define XSLT_MSGS_URL "chrome://global/locale/xslt/xslt.properties"
+
+#endif // DOM_XSLT_XSLT_TXXSLTMSGSURL_H_
diff --git a/dom/xslt/xslt/txXSLTNumber.cpp b/dom/xslt/xslt/txXSLTNumber.cpp
new file mode 100644
index 0000000000..9ae449f27f
--- /dev/null
+++ b/dom/xslt/xslt/txXSLTNumber.cpp
@@ -0,0 +1,741 @@
+/* -*- 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 "mozilla/ArrayUtils.h"
+#include "mozilla/FloatingPoint.h"
+
+#include "txXSLTNumber.h"
+#include "nsGkAtoms.h"
+#include "txCore.h"
+#include <math.h>
+#include "txExpr.h"
+#include "txXSLTPatterns.h"
+#include "txIXPathContext.h"
+#include "txXPathTreeWalker.h"
+
+#include <algorithm>
+
+using mozilla::MakeUnique;
+using mozilla::UniquePtr;
+
+nsresult txXSLTNumber::createNumber(Expr* aValueExpr, txPattern* aCountPattern,
+ txPattern* aFromPattern, LevelType aLevel,
+ Expr* aGroupSize, Expr* aGroupSeparator,
+ Expr* aFormat, txIEvalContext* aContext,
+ nsAString& aResult) {
+ aResult.Truncate();
+ nsresult rv = NS_OK;
+
+ // Parse format
+ txList counters;
+ nsAutoString head, tail;
+ rv = getCounters(aGroupSize, aGroupSeparator, aFormat, aContext, counters,
+ head, tail);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Create list of values to format
+ txList values;
+ nsAutoString valueString;
+ rv = getValueList(aValueExpr, aCountPattern, aFromPattern, aLevel, aContext,
+ values, valueString);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!valueString.IsEmpty()) {
+ aResult = valueString;
+
+ return NS_OK;
+ }
+
+ // Create resulting string
+ aResult = head;
+ bool first = true;
+ txListIterator valueIter(&values);
+ txListIterator counterIter(&counters);
+ valueIter.resetToEnd();
+ int32_t value;
+ txFormattedCounter* counter = 0;
+ while ((value = NS_PTR_TO_INT32(valueIter.previous()))) {
+ if (counterIter.hasNext()) {
+ counter = (txFormattedCounter*)counterIter.next();
+ }
+
+ if (!first) {
+ aResult.Append(counter->mSeparator);
+ }
+
+ counter->appendNumber(value, aResult);
+ first = false;
+ }
+
+ aResult.Append(tail);
+
+ txListIterator iter(&counters);
+ while (iter.hasNext()) {
+ delete (txFormattedCounter*)iter.next();
+ }
+
+ return NS_OK;
+}
+
+nsresult txXSLTNumber::getValueList(Expr* aValueExpr, txPattern* aCountPattern,
+ txPattern* aFromPattern, LevelType aLevel,
+ txIEvalContext* aContext, txList& aValues,
+ nsAString& aValueString) {
+ aValueString.Truncate();
+ nsresult rv = NS_OK;
+
+ // If the value attribute exists then use that
+ if (aValueExpr) {
+ RefPtr<txAExprResult> result;
+ rv = aValueExpr->evaluate(aContext, getter_AddRefs(result));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ double value = result->numberValue();
+
+ if (std::isinf(value) || std::isnan(value) || value < 0.5) {
+ txDouble::toString(value, aValueString);
+ return NS_OK;
+ }
+
+ aValues.add(NS_INT32_TO_PTR((int32_t)floor(value + 0.5)));
+ return NS_OK;
+ }
+
+ // Otherwise use count/from/level
+
+ txPattern* countPattern = aCountPattern;
+ UniquePtr<txPattern> newCountPattern;
+ const txXPathNode& currNode = aContext->getContextNode();
+
+ // Parse count- and from-attributes
+
+ if (!aCountPattern) {
+ txNodeTest* nodeTest;
+ uint16_t nodeType = txXPathNodeUtils::getNodeType(currNode);
+ switch (nodeType) {
+ case txXPathNodeType::ELEMENT_NODE: {
+ RefPtr<nsAtom> localName = txXPathNodeUtils::getLocalName(currNode);
+ int32_t namespaceID = txXPathNodeUtils::getNamespaceID(currNode);
+ nodeTest = new txNameTest(0, localName, namespaceID,
+ txXPathNodeType::ELEMENT_NODE);
+ break;
+ }
+ case txXPathNodeType::TEXT_NODE:
+ case txXPathNodeType::CDATA_SECTION_NODE: {
+ nodeTest = new txNodeTypeTest(txNodeTypeTest::TEXT_TYPE);
+ break;
+ }
+ case txXPathNodeType::PROCESSING_INSTRUCTION_NODE: {
+ txNodeTypeTest* typeTest;
+ typeTest = new txNodeTypeTest(txNodeTypeTest::PI_TYPE);
+ nsAutoString nodeName;
+ txXPathNodeUtils::getNodeName(currNode, nodeName);
+ typeTest->setNodeName(nodeName);
+ nodeTest = typeTest;
+ break;
+ }
+ case txXPathNodeType::COMMENT_NODE: {
+ nodeTest = new txNodeTypeTest(txNodeTypeTest::COMMENT_TYPE);
+ break;
+ }
+ case txXPathNodeType::DOCUMENT_NODE:
+ case txXPathNodeType::ATTRIBUTE_NODE:
+ default: {
+ // this won't match anything as we walk up the tree
+ // but it's what the spec says to do
+ nodeTest = new txNameTest(0, nsGkAtoms::_asterisk, 0, nodeType);
+ break;
+ }
+ }
+ MOZ_ASSERT(nodeTest);
+ newCountPattern = MakeUnique<txStepPattern>(nodeTest, false);
+ countPattern = newCountPattern.get();
+ }
+
+ // Generate list of values depending on the value of the level-attribute
+
+ // level = "single"
+ if (aLevel == eLevelSingle) {
+ txXPathTreeWalker walker(currNode);
+ do {
+ if (aFromPattern && !walker.isOnNode(currNode)) {
+ bool matched;
+ rv = aFromPattern->matches(walker.getCurrentPosition(), aContext,
+ matched);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (matched) {
+ break;
+ }
+ }
+
+ bool matched;
+ rv =
+ countPattern->matches(walker.getCurrentPosition(), aContext, matched);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (matched) {
+ int32_t count;
+ rv = getSiblingCount(walker, countPattern, aContext, &count);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aValues.add(NS_INT32_TO_PTR(count));
+ break;
+ }
+
+ } while (walker.moveToParent());
+
+ // Spec says to only match ancestors that are decendants of the
+ // ancestor that matches the from-pattern, so keep going to make
+ // sure that there is an ancestor that does.
+ if (aFromPattern && aValues.getLength()) {
+ bool hasParent;
+ while ((hasParent = walker.moveToParent())) {
+ bool matched;
+ rv = aFromPattern->matches(walker.getCurrentPosition(), aContext,
+ matched);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (matched) {
+ break;
+ }
+ }
+
+ if (!hasParent) {
+ aValues.clear();
+ }
+ }
+ }
+ // level = "multiple"
+ else if (aLevel == eLevelMultiple) {
+ // find all ancestor-or-selfs that matches count until...
+ txXPathTreeWalker walker(currNode);
+ bool matchedFrom = false;
+ do {
+ if (aFromPattern && !walker.isOnNode(currNode)) {
+ rv = aFromPattern->matches(walker.getCurrentPosition(), aContext,
+ matchedFrom);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (matchedFrom) {
+ //... we find one that matches from
+ break;
+ }
+ }
+
+ bool matched;
+ rv =
+ countPattern->matches(walker.getCurrentPosition(), aContext, matched);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (matched) {
+ int32_t count;
+ rv = getSiblingCount(walker, countPattern, aContext, &count);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aValues.add(NS_INT32_TO_PTR(count));
+ }
+ } while (walker.moveToParent());
+
+ // Spec says to only match ancestors that are decendants of the
+ // ancestor that matches the from-pattern, so if none did then
+ // we shouldn't search anything
+ if (aFromPattern && !matchedFrom) {
+ aValues.clear();
+ }
+ }
+ // level = "any"
+ else if (aLevel == eLevelAny) {
+ int32_t value = 0;
+ bool matchedFrom = false;
+
+ txXPathTreeWalker walker(currNode);
+ do {
+ if (aFromPattern && !walker.isOnNode(currNode)) {
+ rv = aFromPattern->matches(walker.getCurrentPosition(), aContext,
+ matchedFrom);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (matchedFrom) {
+ break;
+ }
+ }
+
+ bool matched;
+ rv =
+ countPattern->matches(walker.getCurrentPosition(), aContext, matched);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (matched) {
+ ++value;
+ }
+
+ } while (getPrevInDocumentOrder(walker));
+
+ // Spec says to only count nodes that follows the first node that
+ // matches the from pattern. So so if none did then we shouldn't
+ // count any
+ if (aFromPattern && !matchedFrom) {
+ value = 0;
+ }
+
+ if (value) {
+ aValues.add(NS_INT32_TO_PTR(value));
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult txXSLTNumber::getCounters(Expr* aGroupSize, Expr* aGroupSeparator,
+ Expr* aFormat, txIEvalContext* aContext,
+ txList& aCounters, nsAString& aHead,
+ nsAString& aTail) {
+ aHead.Truncate();
+ aTail.Truncate();
+
+ nsresult rv = NS_OK;
+
+ nsAutoString groupSeparator;
+ int32_t groupSize = 0;
+ if (aGroupSize && aGroupSeparator) {
+ nsAutoString sizeStr;
+ rv = aGroupSize->evaluateToString(aContext, sizeStr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ double size = txDouble::toDouble(sizeStr);
+ groupSize = (int32_t)size;
+ if ((double)groupSize != size) {
+ groupSize = 0;
+ }
+
+ rv = aGroupSeparator->evaluateToString(aContext, groupSeparator);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ nsAutoString format;
+ if (aFormat) {
+ rv = aFormat->evaluateToString(aContext, format);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ uint32_t formatLen = format.Length();
+ uint32_t formatPos = 0;
+ char16_t ch = 0;
+
+ // start with header
+ while (formatPos < formatLen &&
+ !isAlphaNumeric(ch = format.CharAt(formatPos))) {
+ aHead.Append(ch);
+ ++formatPos;
+ }
+
+ // If there are no formatting tokens we need to create a default one.
+ if (formatPos == formatLen) {
+ txFormattedCounter* defaultCounter;
+ rv = txFormattedCounter::getCounterFor(u"1"_ns, groupSize, groupSeparator,
+ defaultCounter);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ defaultCounter->mSeparator.Assign('.');
+ aCounters.add(defaultCounter);
+
+ return NS_OK;
+ }
+
+ while (formatPos < formatLen) {
+ nsAutoString sepToken;
+ // parse separator token
+ if (!aCounters.getLength()) {
+ // Set the first counters separator to default value so that if
+ // there is only one formatting token and we're formatting a
+ // value-list longer then one we use the default separator. This
+ // won't be used when formatting the first value anyway.
+ sepToken.Assign('.');
+ } else {
+ while (formatPos < formatLen &&
+ !isAlphaNumeric(ch = format.CharAt(formatPos))) {
+ sepToken.Append(ch);
+ ++formatPos;
+ }
+ }
+
+ // if we're at the end of the string then the previous token was the tail
+ if (formatPos == formatLen) {
+ aTail = sepToken;
+ return NS_OK;
+ }
+
+ // parse formatting token
+ nsAutoString numToken;
+ while (formatPos < formatLen &&
+ isAlphaNumeric(ch = format.CharAt(formatPos))) {
+ numToken.Append(ch);
+ ++formatPos;
+ }
+
+ txFormattedCounter* counter = 0;
+ rv = txFormattedCounter::getCounterFor(numToken, groupSize, groupSeparator,
+ counter);
+ if (NS_FAILED(rv)) {
+ txListIterator iter(&aCounters);
+ while (iter.hasNext()) {
+ delete (txFormattedCounter*)iter.next();
+ }
+ aCounters.clear();
+ return rv;
+ }
+
+ // Add to list of counters
+ counter->mSeparator = sepToken;
+ aCounters.add(counter);
+ }
+
+ return NS_OK;
+}
+
+nsresult txXSLTNumber::getSiblingCount(txXPathTreeWalker& aWalker,
+ txPattern* aCountPattern,
+ txIMatchContext* aContext,
+ int32_t* aCount) {
+ int32_t value = 1;
+ while (aWalker.moveToPreviousSibling()) {
+ bool matched;
+ nsresult rv =
+ aCountPattern->matches(aWalker.getCurrentPosition(), aContext, matched);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (matched) {
+ ++value;
+ }
+ }
+
+ *aCount = value;
+
+ return NS_OK;
+}
+
+bool txXSLTNumber::getPrevInDocumentOrder(txXPathTreeWalker& aWalker) {
+ if (aWalker.moveToPreviousSibling()) {
+ while (aWalker.moveToLastChild()) {
+ // do nothing
+ }
+ return true;
+ }
+ return aWalker.moveToParent();
+}
+
+struct CharRange {
+ char16_t lower; // inclusive
+ char16_t upper; // inclusive
+
+ bool operator<(const CharRange& other) const { return upper < other.lower; }
+};
+
+bool txXSLTNumber::isAlphaNumeric(char16_t ch) {
+ static const CharRange alphanumericRanges[] = {
+ // clang-format off
+ { 0x0030, 0x0039 },
+ { 0x0041, 0x005A },
+ { 0x0061, 0x007A },
+ { 0x00AA, 0x00AA },
+ { 0x00B2, 0x00B3 },
+ { 0x00B5, 0x00B5 },
+ { 0x00B9, 0x00BA },
+ { 0x00BC, 0x00BE },
+ { 0x00C0, 0x00D6 },
+ { 0x00D8, 0x00F6 },
+ { 0x00F8, 0x021F },
+ { 0x0222, 0x0233 },
+ { 0x0250, 0x02AD },
+ { 0x02B0, 0x02B8 },
+ { 0x02BB, 0x02C1 },
+ { 0x02D0, 0x02D1 },
+ { 0x02E0, 0x02E4 },
+ { 0x02EE, 0x02EE },
+ { 0x037A, 0x037A },
+ { 0x0386, 0x0386 },
+ { 0x0388, 0x038A },
+ { 0x038C, 0x038C },
+ { 0x038E, 0x03A1 },
+ { 0x03A3, 0x03CE },
+ { 0x03D0, 0x03D7 },
+ { 0x03DA, 0x03F3 },
+ { 0x0400, 0x0481 },
+ { 0x048C, 0x04C4 },
+ { 0x04C7, 0x04C8 },
+ { 0x04CB, 0x04CC },
+ { 0x04D0, 0x04F5 },
+ { 0x04F8, 0x04F9 },
+ { 0x0531, 0x0556 },
+ { 0x0559, 0x0559 },
+ { 0x0561, 0x0587 },
+ { 0x05D0, 0x05EA },
+ { 0x05F0, 0x05F2 },
+ { 0x0621, 0x063A },
+ { 0x0640, 0x064A },
+ { 0x0660, 0x0669 },
+ { 0x0671, 0x06D3 },
+ { 0x06D5, 0x06D5 },
+ { 0x06E5, 0x06E6 },
+ { 0x06F0, 0x06FC },
+ { 0x0710, 0x0710 },
+ { 0x0712, 0x072C },
+ { 0x0780, 0x07A5 },
+ { 0x0905, 0x0939 },
+ { 0x093D, 0x093D },
+ { 0x0950, 0x0950 },
+ { 0x0958, 0x0961 },
+ { 0x0966, 0x096F },
+ { 0x0985, 0x098C },
+ { 0x098F, 0x0990 },
+ { 0x0993, 0x09A8 },
+ { 0x09AA, 0x09B0 },
+ { 0x09B2, 0x09B2 },
+ { 0x09B6, 0x09B9 },
+ { 0x09DC, 0x09DD },
+ { 0x09DF, 0x09E1 },
+ { 0x09E6, 0x09F1 },
+ { 0x09F4, 0x09F9 },
+ { 0x0A05, 0x0A0A },
+ { 0x0A0F, 0x0A10 },
+ { 0x0A13, 0x0A28 },
+ { 0x0A2A, 0x0A30 },
+ { 0x0A32, 0x0A33 },
+ { 0x0A35, 0x0A36 },
+ { 0x0A38, 0x0A39 },
+ { 0x0A59, 0x0A5C },
+ { 0x0A5E, 0x0A5E },
+ { 0x0A66, 0x0A6F },
+ { 0x0A72, 0x0A74 },
+ { 0x0A85, 0x0A8B },
+ { 0x0A8D, 0x0A8D },
+ { 0x0A8F, 0x0A91 },
+ { 0x0A93, 0x0AA8 },
+ { 0x0AAA, 0x0AB0 },
+ { 0x0AB2, 0x0AB3 },
+ { 0x0AB5, 0x0AB9 },
+ { 0x0ABD, 0x0ABD },
+ { 0x0AD0, 0x0AD0 },
+ { 0x0AE0, 0x0AE0 },
+ { 0x0AE6, 0x0AEF },
+ { 0x0B05, 0x0B0C },
+ { 0x0B0F, 0x0B10 },
+ { 0x0B13, 0x0B28 },
+ { 0x0B2A, 0x0B30 },
+ { 0x0B32, 0x0B33 },
+ { 0x0B36, 0x0B39 },
+ { 0x0B3D, 0x0B3D },
+ { 0x0B5C, 0x0B5D },
+ { 0x0B5F, 0x0B61 },
+ { 0x0B66, 0x0B6F },
+ { 0x0B85, 0x0B8A },
+ { 0x0B8E, 0x0B90 },
+ { 0x0B92, 0x0B95 },
+ { 0x0B99, 0x0B9A },
+ { 0x0B9C, 0x0B9C },
+ { 0x0B9E, 0x0B9F },
+ { 0x0BA3, 0x0BA4 },
+ { 0x0BA8, 0x0BAA },
+ { 0x0BAE, 0x0BB5 },
+ { 0x0BB7, 0x0BB9 },
+ { 0x0BE7, 0x0BF2 },
+ { 0x0C05, 0x0C0C },
+ { 0x0C0E, 0x0C10 },
+ { 0x0C12, 0x0C28 },
+ { 0x0C2A, 0x0C33 },
+ { 0x0C35, 0x0C39 },
+ { 0x0C60, 0x0C61 },
+ { 0x0C66, 0x0C6F },
+ { 0x0C85, 0x0C8C },
+ { 0x0C8E, 0x0C90 },
+ { 0x0C92, 0x0CA8 },
+ { 0x0CAA, 0x0CB3 },
+ { 0x0CB5, 0x0CB9 },
+ { 0x0CDE, 0x0CDE },
+ { 0x0CE0, 0x0CE1 },
+ { 0x0CE6, 0x0CEF },
+ { 0x0D05, 0x0D0C },
+ { 0x0D0E, 0x0D10 },
+ { 0x0D12, 0x0D28 },
+ { 0x0D2A, 0x0D39 },
+ { 0x0D60, 0x0D61 },
+ { 0x0D66, 0x0D6F },
+ { 0x0D85, 0x0D96 },
+ { 0x0D9A, 0x0DB1 },
+ { 0x0DB3, 0x0DBB },
+ { 0x0DBD, 0x0DBD },
+ { 0x0DC0, 0x0DC6 },
+ { 0x0E01, 0x0E30 },
+ { 0x0E32, 0x0E33 },
+ { 0x0E40, 0x0E46 },
+ { 0x0E50, 0x0E59 },
+ { 0x0E81, 0x0E82 },
+ { 0x0E84, 0x0E84 },
+ { 0x0E87, 0x0E88 },
+ { 0x0E8A, 0x0E8A },
+ { 0x0E8D, 0x0E8D },
+ { 0x0E94, 0x0E97 },
+ { 0x0E99, 0x0E9F },
+ { 0x0EA1, 0x0EA3 },
+ { 0x0EA5, 0x0EA5 },
+ { 0x0EA7, 0x0EA7 },
+ { 0x0EAA, 0x0EAB },
+ { 0x0EAD, 0x0EB0 },
+ { 0x0EB2, 0x0EB3 },
+ { 0x0EBD, 0x0EBD },
+ { 0x0EC0, 0x0EC4 },
+ { 0x0EC6, 0x0EC6 },
+ { 0x0ED0, 0x0ED9 },
+ { 0x0EDC, 0x0EDD },
+ { 0x0F00, 0x0F00 },
+ { 0x0F20, 0x0F33 },
+ { 0x0F40, 0x0F47 },
+ { 0x0F49, 0x0F6A },
+ { 0x0F88, 0x0F8B },
+ { 0x1000, 0x1021 },
+ { 0x1023, 0x1027 },
+ { 0x1029, 0x102A },
+ { 0x1040, 0x1049 },
+ { 0x1050, 0x1055 },
+ { 0x10A0, 0x10C5 },
+ { 0x10D0, 0x10F6 },
+ { 0x1100, 0x1159 },
+ { 0x115F, 0x11A2 },
+ { 0x11A8, 0x11F9 },
+ { 0x1200, 0x1206 },
+ { 0x1208, 0x1246 },
+ { 0x1248, 0x1248 },
+ { 0x124A, 0x124D },
+ { 0x1250, 0x1256 },
+ { 0x1258, 0x1258 },
+ { 0x125A, 0x125D },
+ { 0x1260, 0x1286 },
+ { 0x1288, 0x1288 },
+ { 0x128A, 0x128D },
+ { 0x1290, 0x12AE },
+ { 0x12B0, 0x12B0 },
+ { 0x12B2, 0x12B5 },
+ { 0x12B8, 0x12BE },
+ { 0x12C0, 0x12C0 },
+ { 0x12C2, 0x12C5 },
+ { 0x12C8, 0x12CE },
+ { 0x12D0, 0x12D6 },
+ { 0x12D8, 0x12EE },
+ { 0x12F0, 0x130E },
+ { 0x1310, 0x1310 },
+ { 0x1312, 0x1315 },
+ { 0x1318, 0x131E },
+ { 0x1320, 0x1346 },
+ { 0x1348, 0x135A },
+ { 0x1369, 0x137C },
+ { 0x13A0, 0x13F4 },
+ { 0x1401, 0x166C },
+ { 0x166F, 0x1676 },
+ { 0x1681, 0x169A },
+ { 0x16A0, 0x16EA },
+ { 0x16EE, 0x16F0 },
+ { 0x1780, 0x17B3 },
+ { 0x17E0, 0x17E9 },
+ { 0x1810, 0x1819 },
+ { 0x1820, 0x1877 },
+ { 0x1880, 0x18A8 },
+ { 0x1E00, 0x1E9B },
+ { 0x1EA0, 0x1EF9 },
+ { 0x1F00, 0x1F15 },
+ { 0x1F18, 0x1F1D },
+ { 0x1F20, 0x1F45 },
+ { 0x1F48, 0x1F4D },
+ { 0x1F50, 0x1F57 },
+ { 0x1F59, 0x1F59 },
+ { 0x1F5B, 0x1F5B },
+ { 0x1F5D, 0x1F5D },
+ { 0x1F5F, 0x1F7D },
+ { 0x1F80, 0x1FB4 },
+ { 0x1FB6, 0x1FBC },
+ { 0x1FBE, 0x1FBE },
+ { 0x1FC2, 0x1FC4 },
+ { 0x1FC6, 0x1FCC },
+ { 0x1FD0, 0x1FD3 },
+ { 0x1FD6, 0x1FDB },
+ { 0x1FE0, 0x1FEC },
+ { 0x1FF2, 0x1FF4 },
+ { 0x1FF6, 0x1FFC },
+ { 0x2070, 0x2070 },
+ { 0x2074, 0x2079 },
+ { 0x207F, 0x2089 },
+ { 0x2102, 0x2102 },
+ { 0x2107, 0x2107 },
+ { 0x210A, 0x2113 },
+ { 0x2115, 0x2115 },
+ { 0x2119, 0x211D },
+ { 0x2124, 0x2124 },
+ { 0x2126, 0x2126 },
+ { 0x2128, 0x2128 },
+ { 0x212A, 0x212D },
+ { 0x212F, 0x2131 },
+ { 0x2133, 0x2139 },
+ { 0x2153, 0x2183 },
+ { 0x2460, 0x249B },
+ { 0x24EA, 0x24EA },
+ { 0x2776, 0x2793 },
+ { 0x3005, 0x3007 },
+ { 0x3021, 0x3029 },
+ { 0x3031, 0x3035 },
+ { 0x3038, 0x303A },
+ { 0x3041, 0x3094 },
+ { 0x309D, 0x309E },
+ { 0x30A1, 0x30FA },
+ { 0x30FC, 0x30FE },
+ { 0x3105, 0x312C },
+ { 0x3131, 0x318E },
+ { 0x3192, 0x3195 },
+ { 0x31A0, 0x31B7 },
+ { 0x3220, 0x3229 },
+ { 0x3280, 0x3289 },
+ { 0x3400, 0x3400 },
+ { 0x4DB5, 0x4DB5 },
+ { 0x4E00, 0x4E00 },
+ { 0x9FA5, 0x9FA5 },
+ { 0xA000, 0xA48C },
+ { 0xAC00, 0xAC00 },
+ { 0xD7A3, 0xD7A3 },
+ { 0xF900, 0xFA2D },
+ { 0xFB00, 0xFB06 },
+ { 0xFB13, 0xFB17 },
+ { 0xFB1D, 0xFB1D },
+ { 0xFB1F, 0xFB28 },
+ { 0xFB2A, 0xFB36 },
+ { 0xFB38, 0xFB3C },
+ { 0xFB3E, 0xFB3E },
+ { 0xFB40, 0xFB41 },
+ { 0xFB43, 0xFB44 },
+ { 0xFB46, 0xFBB1 },
+ { 0xFBD3, 0xFD3D },
+ { 0xFD50, 0xFD8F },
+ { 0xFD92, 0xFDC7 },
+ { 0xFDF0, 0xFDFB },
+ { 0xFE70, 0xFE72 },
+ { 0xFE74, 0xFE74 },
+ { 0xFE76, 0xFEFC },
+ { 0xFF10, 0xFF19 },
+ { 0xFF21, 0xFF3A },
+ { 0xFF41, 0xFF5A },
+ { 0xFF66, 0xFFBE },
+ { 0xFFC2, 0xFFC7 },
+ { 0xFFCA, 0xFFCF },
+ { 0xFFD2, 0xFFD7 }
+ // clang-format on
+ };
+
+ CharRange search = {ch, ch};
+ const CharRange* end = mozilla::ArrayEnd(alphanumericRanges);
+ const CharRange* element =
+ std::lower_bound(&alphanumericRanges[0], end, search);
+ if (element == end) {
+ return false;
+ }
+ return element->lower <= ch && ch <= element->upper;
+}
diff --git a/dom/xslt/xslt/txXSLTNumber.h b/dom/xslt/xslt/txXSLTNumber.h
new file mode 100644
index 0000000000..ea4092e178
--- /dev/null
+++ b/dom/xslt/xslt/txXSLTNumber.h
@@ -0,0 +1,67 @@
+/* -*- 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/. */
+
+#ifndef TRANSFRMX_TXXSLTNUMBER_H
+#define TRANSFRMX_TXXSLTNUMBER_H
+
+#include "nsError.h"
+#include "txList.h"
+#include "nsString.h"
+
+class Expr;
+class txPattern;
+class txIEvalContext;
+class txIMatchContext;
+class txXPathTreeWalker;
+
+class txXSLTNumber {
+ public:
+ enum LevelType { eLevelSingle, eLevelMultiple, eLevelAny };
+
+ static nsresult createNumber(Expr* aValueExpr, txPattern* aCountPattern,
+ txPattern* aFromPattern, LevelType aLevel,
+ Expr* aGroupSize, Expr* aGroupSeparator,
+ Expr* aFormat, txIEvalContext* aContext,
+ nsAString& aResult);
+
+ private:
+ static nsresult getValueList(Expr* aValueExpr, txPattern* aCountPattern,
+ txPattern* aFromPattern, LevelType aLevel,
+ txIEvalContext* aContext, txList& aValues,
+ nsAString& aValueString);
+
+ static nsresult getCounters(Expr* aGroupSize, Expr* aGroupSeparator,
+ Expr* aFormat, txIEvalContext* aContext,
+ txList& aCounters, nsAString& aHead,
+ nsAString& aTail);
+
+ /**
+ * getSiblingCount uses aWalker to walk the siblings of aWalker's current
+ * position.
+ *
+ */
+ static nsresult getSiblingCount(txXPathTreeWalker& aWalker,
+ txPattern* aCountPattern,
+ txIMatchContext* aContext, int32_t* aCount);
+
+ static bool getPrevInDocumentOrder(txXPathTreeWalker& aWalker);
+
+ static bool isAlphaNumeric(char16_t ch);
+};
+
+class txFormattedCounter {
+ public:
+ virtual ~txFormattedCounter() = default;
+
+ virtual void appendNumber(int32_t aNumber, nsAString& aDest) = 0;
+
+ static nsresult getCounterFor(const nsString& aToken, int aGroupSize,
+ const nsAString& aGroupSeparator,
+ txFormattedCounter*& aCounter);
+
+ nsString mSeparator;
+};
+
+#endif // TRANSFRMX_TXXSLTNUMBER_H
diff --git a/dom/xslt/xslt/txXSLTNumberCounters.cpp b/dom/xslt/xslt/txXSLTNumberCounters.cpp
new file mode 100644
index 0000000000..de1de46556
--- /dev/null
+++ b/dom/xslt/xslt/txXSLTNumberCounters.cpp
@@ -0,0 +1,199 @@
+/* -*- 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 "txXSLTNumber.h"
+#include "nsReadableUtils.h"
+#include "txCore.h"
+
+class txDecimalCounter : public txFormattedCounter {
+ public:
+ txDecimalCounter() : mMinLength(1), mGroupSize(50) {}
+
+ txDecimalCounter(int32_t aMinLength, int32_t aGroupSize,
+ const nsAString& mGroupSeparator);
+
+ virtual void appendNumber(int32_t aNumber, nsAString& aDest) override;
+
+ private:
+ int32_t mMinLength;
+ int32_t mGroupSize;
+ nsString mGroupSeparator;
+};
+
+class txAlphaCounter : public txFormattedCounter {
+ public:
+ explicit txAlphaCounter(char16_t aOffset) : mOffset(aOffset) {}
+
+ virtual void appendNumber(int32_t aNumber, nsAString& aDest) override;
+
+ private:
+ char16_t mOffset;
+};
+
+class txRomanCounter : public txFormattedCounter {
+ public:
+ explicit txRomanCounter(bool aUpper) : mTableOffset(aUpper ? 30 : 0) {}
+
+ void appendNumber(int32_t aNumber, nsAString& aDest) override;
+
+ private:
+ int32_t mTableOffset;
+};
+
+nsresult txFormattedCounter::getCounterFor(const nsString& aToken,
+ int32_t aGroupSize,
+ const nsAString& aGroupSeparator,
+ txFormattedCounter*& aCounter) {
+ int32_t length = aToken.Length();
+ NS_ASSERTION(length, "getting counter for empty token");
+ aCounter = 0;
+
+ if (length == 1) {
+ char16_t ch = aToken.CharAt(0);
+ switch (ch) {
+ case 'i':
+ case 'I':
+ aCounter = new txRomanCounter(ch == 'I');
+ break;
+
+ case 'a':
+ case 'A':
+ aCounter = new txAlphaCounter(ch);
+ break;
+
+ case '1':
+ default:
+ // if we don't recognize the token then use "1"
+ aCounter = new txDecimalCounter(1, aGroupSize, aGroupSeparator);
+ break;
+ }
+ MOZ_ASSERT(aCounter);
+ return NS_OK;
+ }
+
+ // for now, the only multi-char token we support are decimals
+ int32_t i;
+ for (i = 0; i < length - 1; ++i) {
+ if (aToken.CharAt(i) != '0') break;
+ }
+ if (i == length - 1 && aToken.CharAt(i) == '1') {
+ aCounter = new txDecimalCounter(length, aGroupSize, aGroupSeparator);
+ } else {
+ // if we don't recognize the token then use '1'
+ aCounter = new txDecimalCounter(1, aGroupSize, aGroupSeparator);
+ }
+ MOZ_ASSERT(aCounter);
+ return NS_OK;
+}
+
+txDecimalCounter::txDecimalCounter(int32_t aMinLength, int32_t aGroupSize,
+ const nsAString& aGroupSeparator)
+ : mMinLength(aMinLength),
+ mGroupSize(aGroupSize),
+ mGroupSeparator(aGroupSeparator) {
+ if (mGroupSize <= 0) {
+ mGroupSize = aMinLength + 10;
+ }
+}
+
+void txDecimalCounter::appendNumber(int32_t aNumber, nsAString& aDest) {
+ const int32_t bufsize = 10; // must be able to fit an int32_t
+ char16_t buf[bufsize];
+ int32_t pos = bufsize;
+ while (aNumber > 0) {
+ int32_t ch = aNumber % 10;
+ aNumber /= 10;
+ buf[--pos] = ch + '0';
+ }
+
+ // in case we didn't get a long enough string
+ int32_t end = (bufsize > mMinLength) ? bufsize - mMinLength : 0;
+ while (pos > end) {
+ buf[--pos] = '0';
+ }
+
+ // in case we *still* didn't get a long enough string.
+ // this should be very rare since it only happens if mMinLength is bigger
+ // then the length of any int32_t.
+ // pos will always be zero
+ int32_t extraPos = mMinLength;
+ while (extraPos > bufsize) {
+ aDest.Append(char16_t('0'));
+ --extraPos;
+ if (extraPos % mGroupSize == 0) {
+ aDest.Append(mGroupSeparator);
+ }
+ }
+
+ // copy string to buffer
+ if (mGroupSize >= bufsize - pos) {
+ // no grouping will occur
+ aDest.Append(buf + pos, (uint32_t)(bufsize - pos));
+ } else {
+ // append chars up to first grouping separator
+ int32_t len = ((bufsize - pos - 1) % mGroupSize) + 1;
+ aDest.Append(buf + pos, len);
+ pos += len;
+ while (bufsize - pos > 0) {
+ aDest.Append(mGroupSeparator);
+ aDest.Append(buf + pos, mGroupSize);
+ pos += mGroupSize;
+ }
+ NS_ASSERTION(bufsize == pos, "error while grouping");
+ }
+}
+
+void txAlphaCounter::appendNumber(int32_t aNumber, nsAString& aDest) {
+ char16_t buf[12];
+ buf[11] = 0;
+ int32_t pos = 11;
+ while (aNumber > 0) {
+ --aNumber;
+ int32_t ch = aNumber % 26;
+ aNumber /= 26;
+ buf[--pos] = ch + mOffset;
+ }
+
+ aDest.Append(buf + pos, (uint32_t)(11 - pos));
+}
+
+const char* const kTxRomanNumbers[] = {
+ "", "c", "cc", "ccc", "cd", "d", "dc", "dcc", "dccc", "cm",
+ "", "x", "xx", "xxx", "xl", "l", "lx", "lxx", "lxxx", "xc",
+ "", "i", "ii", "iii", "iv", "v", "vi", "vii", "viii", "ix",
+ "", "C", "CC", "CCC", "CD", "D", "DC", "DCC", "DCCC", "CM",
+ "", "X", "XX", "XXX", "XL", "L", "LX", "LXX", "LXXX", "XC",
+ "", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX"};
+
+void txRomanCounter::appendNumber(int32_t aNumber, nsAString& aDest) {
+ // Numbers bigger then 3999 and negative numbers can't be done in roman
+ if (uint32_t(aNumber) >= 4000) {
+ txDecimalCounter().appendNumber(aNumber, aDest);
+ return;
+ }
+
+ while (aNumber >= 1000) {
+ aDest.Append(!mTableOffset ? char16_t('m') : char16_t('M'));
+ aNumber -= 1000;
+ }
+
+ int32_t posValue;
+
+ // Hundreds
+ posValue = aNumber / 100;
+ aNumber %= 100;
+ AppendASCIItoUTF16(
+ mozilla::MakeStringSpan(kTxRomanNumbers[posValue + mTableOffset]), aDest);
+ // Tens
+ posValue = aNumber / 10;
+ aNumber %= 10;
+ AppendASCIItoUTF16(
+ mozilla::MakeStringSpan(kTxRomanNumbers[10 + posValue + mTableOffset]),
+ aDest);
+ // Ones
+ AppendASCIItoUTF16(
+ mozilla::MakeStringSpan(kTxRomanNumbers[20 + aNumber + mTableOffset]),
+ aDest);
+}
diff --git a/dom/xslt/xslt/txXSLTPatterns.cpp b/dom/xslt/xslt/txXSLTPatterns.cpp
new file mode 100644
index 0000000000..dc1b81e210
--- /dev/null
+++ b/dom/xslt/xslt/txXSLTPatterns.cpp
@@ -0,0 +1,530 @@
+/* -*- 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 "mozilla/FloatingPoint.h"
+
+#include "nsReadableUtils.h"
+#include "txExecutionState.h"
+#include "txXSLTPatterns.h"
+#include "txNodeSetContext.h"
+#include "txForwardContext.h"
+#include "txXMLUtils.h"
+#include "txXSLTFunctions.h"
+#include "nsWhitespaceTokenizer.h"
+#include "nsIContent.h"
+
+using mozilla::UniquePtr;
+using mozilla::Unused;
+using mozilla::WrapUnique;
+
+/*
+ * Returns the default priority of this Pattern.
+ * UnionPatterns don't like this.
+ * This should be called on the simple patterns.
+ */
+double txUnionPattern::getDefaultPriority() {
+ NS_ERROR("Don't call getDefaultPriority on txUnionPattern");
+ return mozilla::UnspecifiedNaN<double>();
+}
+
+/*
+ * Determines whether this Pattern matches the given node within
+ * the given context
+ * This should be called on the simple patterns for xsl:template,
+ * but is fine for xsl:key and xsl:number
+ */
+nsresult txUnionPattern::matches(const txXPathNode& aNode,
+ txIMatchContext* aContext, bool& aMatched) {
+ uint32_t i, len = mLocPathPatterns.Length();
+ for (i = 0; i < len; ++i) {
+ nsresult rv = mLocPathPatterns[i]->matches(aNode, aContext, aMatched);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (aMatched) {
+ aMatched = true;
+
+ return NS_OK;
+ }
+ }
+
+ aMatched = false;
+
+ return NS_OK;
+}
+
+txPattern::Type txUnionPattern::getType() { return UNION_PATTERN; }
+
+TX_IMPL_PATTERN_STUBS_NO_SUB_EXPR(txUnionPattern)
+txPattern* txUnionPattern::getSubPatternAt(uint32_t aPos) {
+ return mLocPathPatterns.SafeElementAt(aPos);
+}
+
+void txUnionPattern::setSubPatternAt(uint32_t aPos, txPattern* aPattern) {
+ NS_ASSERTION(aPos < mLocPathPatterns.Length(),
+ "setting bad subexpression index");
+ mLocPathPatterns[aPos] = aPattern;
+}
+
+#ifdef TX_TO_STRING
+void txUnionPattern::toString(nsAString& aDest) {
+# ifdef DEBUG
+ aDest.AppendLiteral("txUnionPattern{");
+# endif
+ StringJoinAppend(
+ aDest, u" | "_ns, mLocPathPatterns,
+ [](nsAString& dest, txPattern* pattern) { pattern->toString(dest); });
+# ifdef DEBUG
+ aDest.Append(char16_t('}'));
+# endif
+}
+#endif
+
+/*
+ * LocationPathPattern
+ *
+ * a list of step patterns, can start with id or key
+ * (dealt with by the parser)
+ */
+
+void txLocPathPattern::addStep(txPattern* aPattern, bool isChild) {
+ Step* step = mSteps.AppendElement();
+ step->pattern = WrapUnique(aPattern);
+ step->isChild = isChild;
+}
+
+nsresult txLocPathPattern::matches(const txXPathNode& aNode,
+ txIMatchContext* aContext, bool& aMatched) {
+ NS_ASSERTION(mSteps.Length() > 1, "Internal error");
+
+ /*
+ * The idea is to split up a path into blocks separated by descendant
+ * operators. For example "foo/bar//baz/bop//ying/yang" is split up into
+ * three blocks. The "ying/yang" block is handled by the first while-loop
+ * and the "foo/bar" and "baz/bop" blocks are handled by the second
+ * while-loop.
+ * A block is considered matched when we find a list of ancestors that
+ * match the block. If there are more than one list of ancestors that
+ * match a block we only need to find the one furthermost down in the
+ * tree.
+ */
+
+ uint32_t pos = mSteps.Length();
+ Step* step = &mSteps[--pos];
+ nsresult rv = step->pattern->matches(aNode, aContext, aMatched);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!aMatched) {
+ return NS_OK;
+ }
+
+ txXPathTreeWalker walker(aNode);
+ bool hasParent = walker.moveToParent();
+
+ while (step->isChild) {
+ if (!pos) {
+ aMatched = true;
+
+ return NS_OK; // all steps matched
+ }
+
+ if (!hasParent) {
+ // no more ancestors
+ aMatched = false;
+
+ return NS_OK;
+ }
+
+ step = &mSteps[--pos];
+ rv =
+ step->pattern->matches(walker.getCurrentPosition(), aContext, aMatched);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!aMatched) {
+ // no match
+ return NS_OK;
+ }
+
+ hasParent = walker.moveToParent();
+ }
+
+ // We have at least one // path separator
+ txXPathTreeWalker blockWalker(walker);
+ uint32_t blockPos = pos;
+
+ while (pos) {
+ if (!hasParent) {
+ aMatched = false; // There are more steps in the current block
+ // than ancestors of the tested node
+ return NS_OK;
+ }
+
+ step = &mSteps[--pos];
+ bool matched;
+ rv = step->pattern->matches(walker.getCurrentPosition(), aContext, matched);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!matched) {
+ // Didn't match. We restart at beginning of block using a new
+ // start node
+ pos = blockPos;
+ hasParent = blockWalker.moveToParent();
+ walker.moveTo(blockWalker);
+ } else {
+ hasParent = walker.moveToParent();
+ if (!step->isChild) {
+ // We've matched an entire block. Set new start pos and start node
+ blockPos = pos;
+ blockWalker.moveTo(walker);
+ }
+ }
+ }
+
+ aMatched = true;
+
+ return NS_OK;
+} // txLocPathPattern::matches
+
+double txLocPathPattern::getDefaultPriority() {
+ NS_ASSERTION(mSteps.Length() > 1, "Internal error");
+
+ return 0.5;
+}
+
+TX_IMPL_PATTERN_STUBS_NO_SUB_EXPR(txLocPathPattern)
+txPattern* txLocPathPattern::getSubPatternAt(uint32_t aPos) {
+ return aPos < mSteps.Length() ? mSteps[aPos].pattern.get() : nullptr;
+}
+
+void txLocPathPattern::setSubPatternAt(uint32_t aPos, txPattern* aPattern) {
+ NS_ASSERTION(aPos < mSteps.Length(), "setting bad subexpression index");
+ Step* step = &mSteps[aPos];
+ Unused << step->pattern.release();
+ step->pattern = WrapUnique(aPattern);
+}
+
+#ifdef TX_TO_STRING
+void txLocPathPattern::toString(nsAString& aDest) {
+# ifdef DEBUG
+ aDest.AppendLiteral("txLocPathPattern{");
+# endif
+ for (uint32_t i = 0; i < mSteps.Length(); ++i) {
+ if (i != 0) {
+ if (mSteps[i].isChild)
+ aDest.Append(char16_t('/'));
+ else
+ aDest.AppendLiteral("//");
+ }
+ mSteps[i].pattern->toString(aDest);
+ }
+# ifdef DEBUG
+ aDest.Append(char16_t('}'));
+# endif
+}
+#endif
+
+/*
+ * txRootPattern
+ *
+ * a txPattern matching the document node, or '/'
+ */
+
+nsresult txRootPattern::matches(const txXPathNode& aNode,
+ txIMatchContext* aContext, bool& aMatched) {
+ aMatched = txXPathNodeUtils::isRoot(aNode);
+
+ return NS_OK;
+}
+
+double txRootPattern::getDefaultPriority() { return 0.5; }
+
+TX_IMPL_PATTERN_STUBS_NO_SUB_EXPR(txRootPattern)
+TX_IMPL_PATTERN_STUBS_NO_SUB_PATTERN(txRootPattern)
+
+#ifdef TX_TO_STRING
+void txRootPattern::toString(nsAString& aDest) {
+# ifdef DEBUG
+ aDest.AppendLiteral("txRootPattern{");
+# endif
+ if (mSerialize) aDest.Append(char16_t('/'));
+# ifdef DEBUG
+ aDest.Append(char16_t('}'));
+# endif
+}
+#endif
+
+/*
+ * txIdPattern
+ *
+ * txIdPattern matches if the given node has a ID attribute with one
+ * of the space delimited values.
+ * This looks like the id() function, but may only have LITERALs as
+ * argument.
+ */
+txIdPattern::txIdPattern(const nsAString& aString) {
+ nsWhitespaceTokenizer tokenizer(aString);
+ while (tokenizer.hasMoreTokens()) {
+ // this can fail, XXX move to a Init(aString) method
+ RefPtr<nsAtom> atom = NS_Atomize(tokenizer.nextToken());
+ mIds.AppendElement(atom);
+ }
+}
+
+nsresult txIdPattern::matches(const txXPathNode& aNode,
+ txIMatchContext* aContext, bool& aMatched) {
+ if (!txXPathNodeUtils::isElement(aNode)) {
+ aMatched = false;
+
+ return NS_OK;
+ }
+
+ // Get a ID attribute, if there is
+ nsIContent* content = txXPathNativeNode::getContent(aNode);
+ NS_ASSERTION(content, "a Element without nsIContent");
+
+ nsAtom* id = content->GetID();
+ aMatched = id && mIds.IndexOf(id) != mIds.NoIndex;
+
+ return NS_OK;
+}
+
+double txIdPattern::getDefaultPriority() { return 0.5; }
+
+TX_IMPL_PATTERN_STUBS_NO_SUB_EXPR(txIdPattern)
+TX_IMPL_PATTERN_STUBS_NO_SUB_PATTERN(txIdPattern)
+
+#ifdef TX_TO_STRING
+void txIdPattern::toString(nsAString& aDest) {
+# ifdef DEBUG
+ aDest.AppendLiteral("txIdPattern{");
+# endif
+ aDest.AppendLiteral("id('");
+ uint32_t k, count = mIds.Length() - 1;
+ for (k = 0; k < count; ++k) {
+ nsAutoString str;
+ mIds[k]->ToString(str);
+ aDest.Append(str);
+ aDest.Append(char16_t(' '));
+ }
+ nsAutoString str;
+ mIds[count]->ToString(str);
+ aDest.Append(str);
+ aDest.AppendLiteral("')");
+# ifdef DEBUG
+ aDest.Append(char16_t('}'));
+# endif
+}
+#endif
+
+/*
+ * txKeyPattern
+ *
+ * txKeyPattern matches if the given node is in the evalation of
+ * the key() function
+ * This resembles the key() function, but may only have LITERALs as
+ * argument.
+ */
+
+nsresult txKeyPattern::matches(const txXPathNode& aNode,
+ txIMatchContext* aContext, bool& aMatched) {
+ txExecutionState* es = (txExecutionState*)aContext->getPrivateContext();
+ UniquePtr<txXPathNode> contextDoc(txXPathNodeUtils::getOwnerDocument(aNode));
+ NS_ENSURE_TRUE(contextDoc, NS_ERROR_FAILURE);
+
+ RefPtr<txNodeSet> nodes;
+ nsresult rv =
+ es->getKeyNodes(mName, *contextDoc, mValue, true, getter_AddRefs(nodes));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aMatched = nodes->contains(aNode);
+
+ return NS_OK;
+}
+
+double txKeyPattern::getDefaultPriority() { return 0.5; }
+
+TX_IMPL_PATTERN_STUBS_NO_SUB_EXPR(txKeyPattern)
+TX_IMPL_PATTERN_STUBS_NO_SUB_PATTERN(txKeyPattern)
+
+#ifdef TX_TO_STRING
+void txKeyPattern::toString(nsAString& aDest) {
+# ifdef DEBUG
+ aDest.AppendLiteral("txKeyPattern{");
+# endif
+ aDest.AppendLiteral("key('");
+ nsAutoString tmp;
+ if (mPrefix) {
+ mPrefix->ToString(tmp);
+ aDest.Append(tmp);
+ aDest.Append(char16_t(':'));
+ }
+ mName.mLocalName->ToString(tmp);
+ aDest.Append(tmp);
+ aDest.AppendLiteral(", ");
+ aDest.Append(mValue);
+ aDest.AppendLiteral("')");
+# ifdef DEBUG
+ aDest.Append(char16_t('}'));
+# endif
+}
+#endif
+
+/*
+ * txStepPattern
+ *
+ * a txPattern to hold the NodeTest and the Predicates of a StepPattern
+ */
+
+nsresult txStepPattern::matches(const txXPathNode& aNode,
+ txIMatchContext* aContext, bool& aMatched) {
+ NS_ASSERTION(mNodeTest, "Internal error");
+
+ nsresult rv = mNodeTest->matches(aNode, aContext, aMatched);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!aMatched) {
+ return NS_OK;
+ }
+
+ txXPathTreeWalker walker(aNode);
+ if ((!mIsAttr &&
+ txXPathNodeUtils::isAttribute(walker.getCurrentPosition())) ||
+ !walker.moveToParent()) {
+ aMatched = false;
+
+ return NS_OK;
+ }
+
+ if (isEmpty()) {
+ aMatched = true;
+
+ return NS_OK;
+ }
+
+ /*
+ * Evaluate Predicates
+ *
+ * Copy all siblings/attributes matching mNodeTest to nodes
+ * Up to the last Predicate do
+ * Foreach node in nodes
+ * evaluate Predicate with node as context node
+ * if the result is a number, check the context position,
+ * otherwise convert to bool
+ * if result is true, copy node to newNodes
+ * if aNode is not member of newNodes, return false
+ * nodes = newNodes
+ *
+ * For the last Predicate, evaluate Predicate with aNode as
+ * context node, if the result is a number, check the position,
+ * otherwise return the result converted to boolean
+ */
+
+ // Create the context node set for evaluating the predicates
+ RefPtr<txNodeSet> nodes;
+ rv = aContext->recycler()->getNodeSet(getter_AddRefs(nodes));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool hasNext =
+ mIsAttr ? walker.moveToFirstAttribute() : walker.moveToFirstChild();
+ while (hasNext) {
+ bool matched;
+ rv = mNodeTest->matches(walker.getCurrentPosition(), aContext, matched);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (matched) {
+ nodes->append(walker.getCurrentPosition());
+ }
+ hasNext =
+ mIsAttr ? walker.moveToNextAttribute() : walker.moveToNextSibling();
+ }
+
+ Expr* predicate = mPredicates[0];
+ RefPtr<txNodeSet> newNodes;
+ rv = aContext->recycler()->getNodeSet(getter_AddRefs(newNodes));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t i, predLen = mPredicates.Length();
+ for (i = 1; i < predLen; ++i) {
+ newNodes->clear();
+ bool contextIsInPredicate = false;
+ txNodeSetContext predContext(nodes, aContext);
+ while (predContext.hasNext()) {
+ predContext.next();
+ RefPtr<txAExprResult> exprResult;
+ rv = predicate->evaluate(&predContext, getter_AddRefs(exprResult));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ switch (exprResult->getResultType()) {
+ case txAExprResult::NUMBER:
+ // handle default, [position() == numberValue()]
+ if ((double)predContext.position() == exprResult->numberValue()) {
+ const txXPathNode& tmp = predContext.getContextNode();
+ if (tmp == aNode) contextIsInPredicate = true;
+ newNodes->append(tmp);
+ }
+ break;
+ default:
+ if (exprResult->booleanValue()) {
+ const txXPathNode& tmp = predContext.getContextNode();
+ if (tmp == aNode) contextIsInPredicate = true;
+ newNodes->append(tmp);
+ }
+ break;
+ }
+ }
+ // Move new NodeSet to the current one
+ nodes->clear();
+ nodes->append(*newNodes);
+ if (!contextIsInPredicate) {
+ aMatched = false;
+
+ return NS_OK;
+ }
+ predicate = mPredicates[i];
+ }
+ txForwardContext evalContext(aContext, aNode, nodes);
+ RefPtr<txAExprResult> exprResult;
+ rv = predicate->evaluate(&evalContext, getter_AddRefs(exprResult));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (exprResult->getResultType() == txAExprResult::NUMBER) {
+ // handle default, [position() == numberValue()]
+ aMatched = ((double)evalContext.position() == exprResult->numberValue());
+ } else {
+ aMatched = exprResult->booleanValue();
+ }
+
+ return NS_OK;
+} // matches
+
+double txStepPattern::getDefaultPriority() {
+ if (isEmpty()) return mNodeTest->getDefaultPriority();
+ return 0.5;
+}
+
+txPattern::Type txStepPattern::getType() { return STEP_PATTERN; }
+
+TX_IMPL_PATTERN_STUBS_NO_SUB_PATTERN(txStepPattern)
+Expr* txStepPattern::getSubExprAt(uint32_t aPos) {
+ return PredicateList::getSubExprAt(aPos);
+}
+
+void txStepPattern::setSubExprAt(uint32_t aPos, Expr* aExpr) {
+ PredicateList::setSubExprAt(aPos, aExpr);
+}
+
+#ifdef TX_TO_STRING
+void txStepPattern::toString(nsAString& aDest) {
+# ifdef DEBUG
+ aDest.AppendLiteral("txStepPattern{");
+# endif
+ if (mIsAttr) aDest.Append(char16_t('@'));
+ if (mNodeTest) mNodeTest->toString(aDest);
+
+ PredicateList::toString(aDest);
+# ifdef DEBUG
+ aDest.Append(char16_t('}'));
+# endif
+}
+#endif
diff --git a/dom/xslt/xslt/txXSLTPatterns.h b/dom/xslt/xslt/txXSLTPatterns.h
new file mode 100644
index 0000000000..6d6a5d8723
--- /dev/null
+++ b/dom/xslt/xslt/txXSLTPatterns.h
@@ -0,0 +1,200 @@
+/* -*- 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/. */
+
+#ifndef TX_XSLT_PATTERNS_H
+#define TX_XSLT_PATTERNS_H
+
+#include "mozilla/Attributes.h"
+#include "txExpandedName.h"
+#include "txExpr.h"
+#include "txXMLUtils.h"
+
+class txPattern {
+ public:
+ MOZ_COUNTED_DEFAULT_CTOR(txPattern)
+ MOZ_COUNTED_DTOR_VIRTUAL(txPattern)
+
+ /*
+ * Determines whether this Pattern matches the given node.
+ */
+ virtual nsresult matches(const txXPathNode& aNode, txIMatchContext* aContext,
+ bool& aMatched) = 0;
+
+ /*
+ * Returns the default priority of this Pattern.
+ *
+ * Simple Patterns return the values as specified in XPath 5.5.
+ * Returns -Inf for union patterns, as it shouldn't be called on them.
+ */
+ virtual double getDefaultPriority() = 0;
+
+ /**
+ * Returns the type of this pattern.
+ */
+ enum Type { STEP_PATTERN, UNION_PATTERN, OTHER_PATTERN };
+ virtual Type getType() { return OTHER_PATTERN; }
+
+ /**
+ * Returns sub-expression at given position
+ */
+ virtual Expr* getSubExprAt(uint32_t aPos) = 0;
+
+ /**
+ * Replace sub-expression at given position. Does not delete the old
+ * expression, that is the responsibility of the caller.
+ */
+ virtual void setSubExprAt(uint32_t aPos, Expr* aExpr) = 0;
+
+ /**
+ * Returns sub-pattern at given position
+ */
+ virtual txPattern* getSubPatternAt(uint32_t aPos) = 0;
+
+ /**
+ * Replace sub-pattern at given position. Does not delete the old
+ * pattern, that is the responsibility of the caller.
+ */
+ virtual void setSubPatternAt(uint32_t aPos, txPattern* aPattern) = 0;
+
+#ifdef TX_TO_STRING
+ /*
+ * Returns the String representation of this Pattern.
+ * @param dest the String to use when creating the String
+ * representation. The String representation will be appended to
+ * any data in the destination String, to allow cascading calls to
+ * other #toString() methods for Patterns.
+ * @return the String representation of this Pattern.
+ */
+ virtual void toString(nsAString& aDest) = 0;
+#endif
+};
+
+#define TX_DECL_PATTERN_BASE \
+ nsresult matches(const txXPathNode& aNode, txIMatchContext* aContext, \
+ bool& aMatched) override; \
+ double getDefaultPriority() override; \
+ virtual Expr* getSubExprAt(uint32_t aPos) override; \
+ virtual void setSubExprAt(uint32_t aPos, Expr* aExpr) override; \
+ virtual txPattern* getSubPatternAt(uint32_t aPos) override; \
+ virtual void setSubPatternAt(uint32_t aPos, txPattern* aPattern) override
+
+#ifndef TX_TO_STRING
+# define TX_DECL_PATTERN TX_DECL_PATTERN_BASE
+#else
+# define TX_DECL_PATTERN \
+ TX_DECL_PATTERN_BASE; \
+ void toString(nsAString& aDest) override
+#endif
+
+#define TX_IMPL_PATTERN_STUBS_NO_SUB_EXPR(_class) \
+ Expr* _class::getSubExprAt(uint32_t aPos) { return nullptr; } \
+ void _class::setSubExprAt(uint32_t aPos, Expr* aExpr) { \
+ MOZ_ASSERT_UNREACHABLE("setting bad subexpression index"); \
+ }
+
+#define TX_IMPL_PATTERN_STUBS_NO_SUB_PATTERN(_class) \
+ txPattern* _class::getSubPatternAt(uint32_t aPos) { return nullptr; } \
+ void _class::setSubPatternAt(uint32_t aPos, txPattern* aPattern) { \
+ MOZ_ASSERT_UNREACHABLE("setting bad subexpression index"); \
+ }
+
+class txUnionPattern : public txPattern {
+ public:
+ void addPattern(txPattern* aPattern) {
+ mLocPathPatterns.AppendElement(aPattern);
+ }
+
+ TX_DECL_PATTERN;
+ Type getType() override;
+
+ private:
+ txOwningArray<txPattern> mLocPathPatterns;
+};
+
+class txLocPathPattern : public txPattern {
+ public:
+ void addStep(txPattern* aPattern, bool isChild);
+
+ TX_DECL_PATTERN;
+
+ private:
+ class Step {
+ public:
+ mozilla::UniquePtr<txPattern> pattern;
+ bool isChild;
+ };
+
+ nsTArray<Step> mSteps;
+};
+
+class txRootPattern : public txPattern {
+ public:
+#ifdef TX_TO_STRING
+ txRootPattern() : mSerialize(true) {}
+#endif
+
+ TX_DECL_PATTERN;
+
+#ifdef TX_TO_STRING
+ public:
+ void setSerialize(bool aSerialize) { mSerialize = aSerialize; }
+
+ private:
+ // Don't serialize txRootPattern if it's used in a txLocPathPattern
+ bool mSerialize;
+#endif
+};
+
+class txIdPattern : public txPattern {
+ public:
+ explicit txIdPattern(const nsAString& aString);
+
+ TX_DECL_PATTERN;
+
+ private:
+ nsTArray<RefPtr<nsAtom>> mIds;
+};
+
+class txKeyPattern : public txPattern {
+ public:
+ txKeyPattern(nsAtom* aPrefix, nsAtom* aLocalName, int32_t aNSID,
+ const nsAString& aValue)
+ : mName(aNSID, aLocalName),
+#ifdef TX_TO_STRING
+ mPrefix(aPrefix),
+#endif
+ mValue(aValue) {
+ }
+
+ TX_DECL_PATTERN;
+
+ private:
+ txExpandedName mName;
+#ifdef TX_TO_STRING
+ RefPtr<nsAtom> mPrefix;
+#endif
+ nsString mValue;
+};
+
+class txStepPattern : public txPattern, public PredicateList {
+ public:
+ txStepPattern(txNodeTest* aNodeTest, bool isAttr)
+ : mNodeTest(aNodeTest), mIsAttr(isAttr) {}
+
+ TX_DECL_PATTERN;
+ Type getType() override;
+
+ txNodeTest* getNodeTest() { return mNodeTest.get(); }
+ void setNodeTest(txNodeTest* aNodeTest) {
+ mozilla::Unused << mNodeTest.release();
+ mNodeTest = mozilla::WrapUnique(aNodeTest);
+ }
+
+ private:
+ mozilla::UniquePtr<txNodeTest> mNodeTest;
+ bool mIsAttr;
+};
+
+#endif // TX_XSLT_PATTERNS_H
diff --git a/dom/xslt/xslt/txXSLTProcessor.cpp b/dom/xslt/xslt/txXSLTProcessor.cpp
new file mode 100644
index 0000000000..bcf1a61108
--- /dev/null
+++ b/dom/xslt/xslt/txXSLTProcessor.cpp
@@ -0,0 +1,50 @@
+/* -*- 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 "txXSLTProcessor.h"
+#include "txInstructions.h"
+#include "nsGkAtoms.h"
+#include "txLog.h"
+#include "txStylesheetCompileHandlers.h"
+#include "txStylesheetCompiler.h"
+#include "txExecutionState.h"
+#include "txExprResult.h"
+
+TX_LG_IMPL
+
+/* static */
+bool txXSLTProcessor::init() {
+ TX_LG_CREATE;
+
+ if (!txHandlerTable::init()) return false;
+
+ extern bool TX_InitEXSLTFunction();
+ if (!TX_InitEXSLTFunction()) return false;
+
+ return true;
+}
+
+/* static */
+void txXSLTProcessor::shutdown() { txHandlerTable::shutdown(); }
+
+/* static */
+nsresult txXSLTProcessor::execute(txExecutionState& aEs) {
+ nsresult rv;
+ do {
+ mozilla::Result<txInstruction*, nsresult> result = aEs.getNextInstruction();
+ if (result.isErr()) {
+ return result.unwrapErr();
+ }
+
+ txInstruction* instr = result.unwrap();
+ if (!instr) {
+ return NS_OK;
+ }
+
+ rv = instr->execute(aEs);
+ } while (NS_SUCCEEDED(rv));
+
+ return rv;
+}
diff --git a/dom/xslt/xslt/txXSLTProcessor.h b/dom/xslt/xslt/txXSLTProcessor.h
new file mode 100644
index 0000000000..f06a885219
--- /dev/null
+++ b/dom/xslt/xslt/txXSLTProcessor.h
@@ -0,0 +1,26 @@
+/* -*- 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/. */
+
+#ifndef TRANSFRMX_TXXSLTPROCESSOR_H
+#define TRANSFRMX_TXXSLTPROCESSOR_H
+
+#include "txExecutionState.h"
+
+class txXSLTProcessor {
+ public:
+ /**
+ * Initialisation and shutdown routines. Initilizes and cleansup all
+ * dependant classes
+ */
+ static bool init();
+ static void shutdown();
+
+ static nsresult execute(txExecutionState& aEs);
+
+ // once we want to have interuption we should probably have functions for
+ // running X number of steps or running until a condition is true.
+};
+
+#endif