diff options
Diffstat (limited to 'dom/base/test/unit')
52 files changed, 4413 insertions, 0 deletions
diff --git a/dom/base/test/unit/1_original.xml b/dom/base/test/unit/1_original.xml new file mode 100644 index 0000000000..4b7915159d --- /dev/null +++ b/dom/base/test/unit/1_original.xml @@ -0,0 +1,3 @@ +<?xml version="1.0"?> + +<foo /> <!-- é -->
\ No newline at end of file diff --git a/dom/base/test/unit/1_result.xml b/dom/base/test/unit/1_result.xml new file mode 100644 index 0000000000..61d4458be9 --- /dev/null +++ b/dom/base/test/unit/1_result.xml @@ -0,0 +1,3 @@ +<?xml version="1.0" encoding="UTF-8"?> +<foo/> +<!-- é -->
\ No newline at end of file diff --git a/dom/base/test/unit/2_original.xml b/dom/base/test/unit/2_original.xml new file mode 100644 index 0000000000..16eeb817ff --- /dev/null +++ b/dom/base/test/unit/2_original.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE whatever PUBLIC "-//MOZ//WHATEVER//EN" "http://mozilla.org/ns/foo"> +<foo xmlns="htp://mozilla.org/ns"> + <baz/><!-- a comment --> <bar> <robots> & <aliens> +<mozilla> a a a a a éèàùûî</mozilla> + <firefox>Lorem ip<!-- aaa -->sum dolor sit amet, consectetuer adipiscing elit. Nam eu sapien. Sed viverra lacus. Donec quis ipsum. Nunc cursus aliquet lectus. Nunc vitae eros. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos hymenaeos. Nam tellus massa, fringilla aliquam, fermentum sit amet, posuere ac, est. Duis tristique egestas ligula. Mauris quis felis. Fusce a ipsum non lacus posuere aliquet. Sed fermentum posuere nulla. Donec tempor. Donec sollicitudin tortor lacinia libero ullamcorper laoreet. Cras quis nisi at odio consectetuer molestie.</firefox> + <?xml-foo "hey" ?> +</bar> + <!-- a comment + on several lines--> + <?xml-foo "another pi on two lines" + example="hello"?> +</foo>
\ No newline at end of file diff --git a/dom/base/test/unit/2_result_1.xml b/dom/base/test/unit/2_result_1.xml new file mode 100644 index 0000000000..16eeb817ff --- /dev/null +++ b/dom/base/test/unit/2_result_1.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE whatever PUBLIC "-//MOZ//WHATEVER//EN" "http://mozilla.org/ns/foo"> +<foo xmlns="htp://mozilla.org/ns"> + <baz/><!-- a comment --> <bar> <robots> & <aliens> +<mozilla> a a a a a éèàùûî</mozilla> + <firefox>Lorem ip<!-- aaa -->sum dolor sit amet, consectetuer adipiscing elit. Nam eu sapien. Sed viverra lacus. Donec quis ipsum. Nunc cursus aliquet lectus. Nunc vitae eros. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos hymenaeos. Nam tellus massa, fringilla aliquam, fermentum sit amet, posuere ac, est. Duis tristique egestas ligula. Mauris quis felis. Fusce a ipsum non lacus posuere aliquet. Sed fermentum posuere nulla. Donec tempor. Donec sollicitudin tortor lacinia libero ullamcorper laoreet. Cras quis nisi at odio consectetuer molestie.</firefox> + <?xml-foo "hey" ?> +</bar> + <!-- a comment + on several lines--> + <?xml-foo "another pi on two lines" + example="hello"?> +</foo>
\ No newline at end of file diff --git a/dom/base/test/unit/2_result_2.xml b/dom/base/test/unit/2_result_2.xml new file mode 100644 index 0000000000..c3eeadb58f --- /dev/null +++ b/dom/base/test/unit/2_result_2.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE whatever PUBLIC "-//MOZ//WHATEVER//EN" "http://mozilla.org/ns/foo"> +<foo xmlns="htp://mozilla.org/ns"> + <baz/><!-- a comment --> + <bar> <robots> & <aliens> + <mozilla> a a a a a éèàùûî</mozilla> + <firefox>Lorem ip<!-- aaa -->sum dolor sit amet, consectetuer adipiscing elit. Nam eu sapien. Sed viverra lacus. Donec quis ipsum. Nunc cursus aliquet lectus. Nunc vitae eros. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos hymenaeos. Nam tellus massa, fringilla aliquam, fermentum sit amet, posuere ac, est. Duis tristique egestas ligula. Mauris quis felis. Fusce a ipsum non lacus posuere aliquet. Sed fermentum posuere nulla. Donec tempor. Donec sollicitudin tortor lacinia libero ullamcorper laoreet. Cras quis nisi at odio consectetuer molestie.</firefox> + <?xml-foo "hey" ?> + </bar> + <!-- a comment + on several lines--> + <?xml-foo "another pi on two lines" + example="hello"?> +</foo>
\ No newline at end of file diff --git a/dom/base/test/unit/2_result_3.xml b/dom/base/test/unit/2_result_3.xml new file mode 100644 index 0000000000..906ac89ee5 --- /dev/null +++ b/dom/base/test/unit/2_result_3.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE whatever PUBLIC "-//MOZ//WHATEVER//EN" "http://mozilla.org/ns/foo"> +<foo xmlns="htp://mozilla.org/ns"> + <baz/><!-- a comment --> + <bar> <robots> & <aliens> + <mozilla> a a a a a éèàùûî</mozilla> + <firefox>Lorem ip<!-- aaa -->sum dolor sit amet, consectetuer + adipiscing elit. Nam eu sapien. Sed viverra lacus. Donec quis + ipsum. Nunc cursus aliquet lectus. Nunc vitae eros. Class aptent + taciti sociosqu ad litora torquent per conubia nostra, per + inceptos hymenaeos. Nam tellus massa, fringilla aliquam, fermentum + sit amet, posuere ac, est. Duis tristique egestas ligula. Mauris + quis felis. Fusce a ipsum non lacus posuere aliquet. Sed fermentum + posuere nulla. Donec tempor. Donec sollicitudin tortor lacinia + libero ullamcorper laoreet. Cras quis nisi at odio consectetuer + molestie.</firefox> + <?xml-foo "hey" ?> + </bar> + <!-- a comment + on several lines--> + <?xml-foo "another pi on two lines" + example="hello"?> +</foo>
\ No newline at end of file diff --git a/dom/base/test/unit/2_result_4.xml b/dom/base/test/unit/2_result_4.xml new file mode 100644 index 0000000000..27ed219211 --- /dev/null +++ b/dom/base/test/unit/2_result_4.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE whatever PUBLIC "-//MOZ//WHATEVER//EN" "http://mozilla.org/ns/foo"> +<foo xmlns="htp://mozilla.org/ns"> + <baz/><!-- a comment --> <bar> <robots> & <aliens> +<mozilla> a a a a a éèàùûî</mozilla> + <firefox>Lorem ip<!-- aaa -->sum dolor sit amet, consectetuer +adipiscing elit. Nam eu sapien. Sed viverra lacus. Donec quis ipsum. +Nunc cursus aliquet lectus. Nunc vitae eros. Class aptent taciti +sociosqu ad litora torquent per conubia nostra, per inceptos hymenaeos. +Nam tellus massa, fringilla aliquam, fermentum sit amet, posuere ac, +est. Duis tristique egestas ligula. Mauris quis felis. Fusce a ipsum non + lacus posuere aliquet. Sed fermentum posuere nulla. Donec tempor. Donec + sollicitudin tortor lacinia libero ullamcorper laoreet. Cras quis nisi +at odio consectetuer molestie.</firefox> + <?xml-foo "hey" ?> +</bar> + <!-- a comment + on several lines--> + <?xml-foo "another pi on two lines" + example="hello"?> +</foo>
\ No newline at end of file diff --git a/dom/base/test/unit/3_original.xml b/dom/base/test/unit/3_original.xml new file mode 100644 index 0000000000..eb9c1bd656 --- /dev/null +++ b/dom/base/test/unit/3_original.xml @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="UTF-8"?> +<foo> +Lorem ip<!-- aaa -->sum dolorsitamet, consectetuer adipiscing elit. Nameusapien. Sed viverralacus. this_is_a_very_long_long_word_which_has_a_length_higher_than_the_max_column Donecquisipsum. Nunc cursus aliquet lectus. Nunc vitae eros. +</foo>
\ No newline at end of file diff --git a/dom/base/test/unit/3_result.xml b/dom/base/test/unit/3_result.xml new file mode 100644 index 0000000000..e556c61e58 --- /dev/null +++ b/dom/base/test/unit/3_result.xml @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="UTF-8"?> +<foo> + Lorem ip<!-- aaa -->sum dolorsitamet, consectetuer adipiscing elit. + Nameusapien. Sed viverralacus. + this_is_a_very_long_long_word_which_has_a_length_higher_than_the_max_column + Donecquisipsum. Nunc cursus aliquet lectus. Nunc vitae eros. +</foo>
\ No newline at end of file diff --git a/dom/base/test/unit/3_result_2.xml b/dom/base/test/unit/3_result_2.xml new file mode 100644 index 0000000000..2df257ca75 --- /dev/null +++ b/dom/base/test/unit/3_result_2.xml @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="UTF-8"?> +<foo> +Lorem ip<!-- aaa -->sum dolorsitamet, consectetuer adipiscing elit. +Nameusapien. Sed viverralacus. +this_is_a_very_long_long_word_which_has_a_length_higher_than_the_max_column + Donecquisipsum. Nunc cursus aliquet lectus. Nunc vitae eros. +</foo>
\ No newline at end of file diff --git a/dom/base/test/unit/4_original.xml b/dom/base/test/unit/4_original.xml new file mode 100644 index 0000000000..b985da960e --- /dev/null +++ b/dom/base/test/unit/4_original.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE whatever PUBLIC "-//MOZ//WHATEVER//EN" "http://mozilla.org/ns/foo"> +<foo xmlns="http://mozilla.org/ns" xmlns:falsexul="http://mozilla.org/ns3"> + <!-- document to test namespaces--> + <baz/> + <bar> <robots> & <aliens> + <mozilla xmlns="http://mozilla.org/ns2"> a a a <moz>a a</moz> éèàùûî</mozilla> + <firefox>Lorem ipsum dolor sit amet, consectetuer adipiscing elit.</firefox> + </bar> + + <xul xmlns="http://mozilla.org/ns3" xmlns:other="http://mozilla.org/ns/other"> + <box> + <other:what>lorem ipsum</other:what> + <description other:yes="no">xul fake</description> + </box> + </xul> + + <falsexul:xul xmlns:other="http://mozilla.org/ns/other"> + <box> + <other:what>lorem ipsum</other:what> + <description>xul fake</description> + <what xmlns="http://mozilla.org/ns/other">lorem ipsum <falsexul:label value="hello"/> the return</what> + </box> + </falsexul:xul> + + <ho:xul xmlns="http://mozilla.org/ns4" xmlns:ho="http://mozilla.org/ns4" xmlns:other="http://mozilla.org/ns/other"> + <box> + <other:what>lorem ipsum</other:what> + <description ho:foo="bar" bla="hello" other:yes="no" ho:foo2="bar2">xul fake</description> + </box> + </ho:xul> +</foo>
\ No newline at end of file diff --git a/dom/base/test/unit/4_result_1.xml b/dom/base/test/unit/4_result_1.xml new file mode 100644 index 0000000000..b985da960e --- /dev/null +++ b/dom/base/test/unit/4_result_1.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE whatever PUBLIC "-//MOZ//WHATEVER//EN" "http://mozilla.org/ns/foo"> +<foo xmlns="http://mozilla.org/ns" xmlns:falsexul="http://mozilla.org/ns3"> + <!-- document to test namespaces--> + <baz/> + <bar> <robots> & <aliens> + <mozilla xmlns="http://mozilla.org/ns2"> a a a <moz>a a</moz> éèàùûî</mozilla> + <firefox>Lorem ipsum dolor sit amet, consectetuer adipiscing elit.</firefox> + </bar> + + <xul xmlns="http://mozilla.org/ns3" xmlns:other="http://mozilla.org/ns/other"> + <box> + <other:what>lorem ipsum</other:what> + <description other:yes="no">xul fake</description> + </box> + </xul> + + <falsexul:xul xmlns:other="http://mozilla.org/ns/other"> + <box> + <other:what>lorem ipsum</other:what> + <description>xul fake</description> + <what xmlns="http://mozilla.org/ns/other">lorem ipsum <falsexul:label value="hello"/> the return</what> + </box> + </falsexul:xul> + + <ho:xul xmlns="http://mozilla.org/ns4" xmlns:ho="http://mozilla.org/ns4" xmlns:other="http://mozilla.org/ns/other"> + <box> + <other:what>lorem ipsum</other:what> + <description ho:foo="bar" bla="hello" other:yes="no" ho:foo2="bar2">xul fake</description> + </box> + </ho:xul> +</foo>
\ No newline at end of file diff --git a/dom/base/test/unit/4_result_2.xml b/dom/base/test/unit/4_result_2.xml new file mode 100644 index 0000000000..bc408b431c --- /dev/null +++ b/dom/base/test/unit/4_result_2.xml @@ -0,0 +1,7 @@ +<falsexul:xul xmlns:falsexul="http://mozilla.org/ns3" xmlns:other="http://mozilla.org/ns/other"> + <box xmlns="http://mozilla.org/ns"> + <other:what>lorem ipsum</other:what> + <description>xul fake</description> + <what xmlns="http://mozilla.org/ns/other">lorem ipsum <falsexul:label value="hello"/> the return</what> + </box> + </falsexul:xul>
\ No newline at end of file diff --git a/dom/base/test/unit/4_result_3.xml b/dom/base/test/unit/4_result_3.xml new file mode 100644 index 0000000000..30c8b47de7 --- /dev/null +++ b/dom/base/test/unit/4_result_3.xml @@ -0,0 +1,4 @@ +<box xmlns="http://mozilla.org/ns3"> + <other:what xmlns:other="http://mozilla.org/ns/other">lorem ipsum</other:what> + <description other:yes="no" xmlns:other="http://mozilla.org/ns/other">xul fake</description> + </box>
\ No newline at end of file diff --git a/dom/base/test/unit/4_result_4.xml b/dom/base/test/unit/4_result_4.xml new file mode 100644 index 0000000000..9346d5d170 --- /dev/null +++ b/dom/base/test/unit/4_result_4.xml @@ -0,0 +1,4 @@ +<box xmlns="http://mozilla.org/ns4"> + <other:what xmlns:other="http://mozilla.org/ns/other">lorem ipsum</other:what> + <description ho:foo="bar" xmlns:ho="http://mozilla.org/ns4" bla="hello" other:yes="no" xmlns:other="http://mozilla.org/ns/other" ho:foo2="bar2">xul fake</description> + </box>
\ No newline at end of file diff --git a/dom/base/test/unit/4_result_5.xml b/dom/base/test/unit/4_result_5.xml new file mode 100644 index 0000000000..4bb152576a --- /dev/null +++ b/dom/base/test/unit/4_result_5.xml @@ -0,0 +1,46 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE whatever PUBLIC "-//MOZ//WHATEVER//EN" "http://mozilla.org/ns/foo"> +<foo xmlns="http://mozilla.org/ns" +xmlns:falsexul="http://mozilla.org/ns3"> + <!-- document to test namespaces--> + <baz/> + <bar> <robots> & + <aliens> + <mozilla + xmlns="http://mozilla.org/ns2"> a + a a + <moz>a a</moz> éèàùûî</mozilla> + <firefox>Lorem ipsum dolor sit amet, + consectetuer adipiscing elit.</firefox> + </bar> + <xul xmlns="http://mozilla.org/ns3" +xmlns:other="http://mozilla.org/ns/other"> + <box> + <other:what>lorem ipsum</other:what> + <description other:yes="no">xul + fake</description> + </box> + </xul> + <falsexul:xul +xmlns:other="http://mozilla.org/ns/other"> + <box> + <other:what>lorem ipsum</other:what> + <description>xul fake</description> + <what +xmlns="http://mozilla.org/ns/other">lorem + ipsum + <falsexul:label value="hello"/> + the return</what> + </box> + </falsexul:xul> + <ho:xul xmlns="http://mozilla.org/ns4" + xmlns:ho="http://mozilla.org/ns4" +xmlns:other="http://mozilla.org/ns/other"> + <box> + <other:what>lorem ipsum</other:what> + <description ho:foo="bar" + bla="hello" other:yes="no" + ho:foo2="bar2">xul fake</description> + </box> + </ho:xul> +</foo>
\ No newline at end of file diff --git a/dom/base/test/unit/4_result_6.xml b/dom/base/test/unit/4_result_6.xml new file mode 100644 index 0000000000..ce18f72a81 --- /dev/null +++ b/dom/base/test/unit/4_result_6.xml @@ -0,0 +1,48 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE whatever PUBLIC "-//MOZ//WHATEVER//EN" "http://mozilla.org/ns/foo"> +<foo xmlns="http://mozilla.org/ns" +xmlns:falsexul="http://mozilla.org/ns3"> + <!-- document to test namespaces--> + <baz/> + <bar> <robots> & +<aliens> + <mozilla +xmlns="http://mozilla.org/ns2"> a a a <moz>a + a</moz> éèàùûî</mozilla> + <firefox>Lorem ipsum dolor sit +amet, consectetuer adipiscing elit.</firefox> + </bar> + + <xul xmlns="http://mozilla.org/ns3" +xmlns:other="http://mozilla.org/ns/other"> + <box> + <other:what>lorem ipsum</other:what> + <description other:yes="no">xul +fake</description> + </box> + </xul> + + <falsexul:xul +xmlns:other="http://mozilla.org/ns/other"> + <box> + <other:what>lorem ipsum</other:what> + <description>xul fake</description> + <what +xmlns="http://mozilla.org/ns/other">lorem + ipsum <falsexul:label value="hello"/> +the return</what> + </box> + </falsexul:xul> + + <ho:xul +xmlns="http://mozilla.org/ns4" +xmlns:ho="http://mozilla.org/ns4" +xmlns:other="http://mozilla.org/ns/other"> + <box> + <other:what>lorem ipsum</other:what> + <description ho:foo="bar" +bla="hello" other:yes="no" +ho:foo2="bar2">xul fake</description> + </box> + </ho:xul> +</foo>
\ No newline at end of file diff --git a/dom/base/test/unit/empty_document.xml b/dom/base/test/unit/empty_document.xml new file mode 100644 index 0000000000..ebd60b08c7 --- /dev/null +++ b/dom/base/test/unit/empty_document.xml @@ -0,0 +1,3 @@ +<?xml version="1.0" ?> +<!-- empty document for use in tests that don't need any particular DOM --> +<root/> diff --git a/dom/base/test/unit/head_utilities.js b/dom/base/test/unit/head_utilities.js new file mode 100644 index 0000000000..0de7868642 --- /dev/null +++ b/dom/base/test/unit/head_utilities.js @@ -0,0 +1,71 @@ +/* -*- 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/. */ + +var { + HTTP_400, + HTTP_401, + HTTP_402, + HTTP_403, + HTTP_404, + HTTP_405, + HTTP_406, + HTTP_407, + HTTP_408, + HTTP_409, + HTTP_410, + HTTP_411, + HTTP_412, + HTTP_413, + HTTP_414, + HTTP_415, + HTTP_417, + HTTP_500, + HTTP_501, + HTTP_502, + HTTP_503, + HTTP_504, + HTTP_505, + HttpError, + HttpServer, +} = ChromeUtils.importESModule("resource://testing-common/httpd.sys.mjs"); +var { NetUtil } = ChromeUtils.importESModule( + "resource://gre/modules/NetUtil.sys.mjs" +); + +const nsIDocumentEncoder = Ci.nsIDocumentEncoder; +const replacementChar = + Ci.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER; + +function loadContentFile(aFile, aCharset) { + // if(aAsIso == undefined) aAsIso = false; + if (aCharset == undefined) { + aCharset = "UTF-8"; + } + + var file = do_get_file(aFile); + + var chann = NetUtil.newChannel({ + uri: Services.io.newFileURI(file), + loadUsingSystemPrincipal: true, + }); + chann.contentCharset = aCharset; + + /* var inputStream = Components.classes["@mozilla.org/scriptableinputstream;1"] + .createInstance(Components.interfaces.nsIScriptableInputStream); + inputStream.init(chann.open()); + return inputStream.read(file.fileSize); + */ + + var inputStream = Cc[ + "@mozilla.org/intl/converter-input-stream;1" + ].createInstance(Ci.nsIConverterInputStream); + inputStream.init(chann.open(), aCharset, 1024, replacementChar); + var str = {}, + content = ""; + while (inputStream.readString(4096, str) != 0) { + content += str.value; + } + return content; +} diff --git a/dom/base/test/unit/head_xml.js b/dom/base/test/unit/head_xml.js new file mode 100644 index 0000000000..ed96926c80 --- /dev/null +++ b/dom/base/test/unit/head_xml.js @@ -0,0 +1,152 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et: */ +/* 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/. */ + +const I = Ci; +const C = Cc; + +const nsIFile = I.nsIFile; +const nsIProperties = I.nsIProperties; +const nsIFileInputStream = I.nsIFileInputStream; +const nsIInputStream = I.nsIInputStream; + +function getParser() { + var parser = new DOMParser(); + parser.forceEnableXULXBL(); + return parser; +} + +var __testsDirectory = null; + +function ParseFile(file) { + if (typeof file == "string") { + if (!__testsDirectory) { + __testsDirectory = do_get_cwd(); + } + var fileObj = __testsDirectory.clone(); + fileObj.append(file); + file = fileObj; + } + + Assert.equal(file instanceof nsIFile, true); + + var fileStr = + C["@mozilla.org/network/file-input-stream;1"].createInstance( + nsIFileInputStream + ); + // Init for readonly reading + fileStr.init(file, 0x01, 0o400, nsIFileInputStream.CLOSE_ON_EOF); + return ParseXML(fileStr); +} + +function ParseXML(data) { + if (typeof data == "string") { + return getParser().parseFromString(data, "application/xml"); + } + + Assert.equal(data instanceof nsIInputStream, true); + + return getParser().parseFromStream( + data, + "UTF-8", + data.available(), + "application/xml" + ); +} + +function DOMSerializer() { + return new XMLSerializer(); +} + +function SerializeXML(node) { + return DOMSerializer().serializeToString(node); +} + +function roundtrip(obj) { + if (typeof obj == "string") { + return SerializeXML(ParseXML(obj)); + } + + Assert.equal(Node.isInstance(obj), true); + return ParseXML(SerializeXML(obj)); +} + +function do_compare_attrs(e1, e2) { + const xmlns = "http://www.w3.org/2000/xmlns/"; + + var a1 = e1.attributes; + var a2 = e2.attributes; + for (var i = 0; i < a1.length; ++i) { + var att = a1.item(i); + // Don't test for namespace decls, since those can just sorta be + // scattered about + if (att.namespaceURI != xmlns) { + var att2 = a2.getNamedItemNS(att.namespaceURI, att.localName); + if (!att2) { + do_throw( + "Missing attribute with namespaceURI '" + + att.namespaceURI + + "' and localName '" + + att.localName + + "'" + ); + } + Assert.equal(att.value, att2.value); + } + } +} + +function do_check_equiv(dom1, dom2) { + Assert.equal(dom1.nodeType, dom2.nodeType); + switch (dom1.nodeType) { + case Node.PROCESSING_INSTRUCTION_NODE: + Assert.equal(dom1.target, dom2.target); + Assert.equal(dom1.data, dom2.data); + // fall through + case Node.TEXT_NODE: + case Node.CDATA_SECTION_NODE: + case Node.COMMENT_NODE: + Assert.equal(dom1.data, dom2.data); + break; + case Node.ELEMENT_NODE: + Assert.equal(dom1.namespaceURI, dom2.namespaceURI); + Assert.equal(dom1.localName, dom2.localName); + // Compare attrs in both directions -- do_compare_attrs does a + // subset check. + do_compare_attrs(dom1, dom2); + do_compare_attrs(dom2, dom1); + // Fall through + case Node.DOCUMENT_NODE: + Assert.equal(dom1.childNodes.length, dom2.childNodes.length); + for (var i = 0; i < dom1.childNodes.length; ++i) { + do_check_equiv(dom1.childNodes.item(i), dom2.childNodes.item(i)); + } + break; + } +} + +function do_check_serialize(dom) { + do_check_equiv(dom, roundtrip(dom)); +} + +function Pipe() { + var p = C["@mozilla.org/pipe;1"].createInstance(I.nsIPipe); + p.init(false, false, 0, 0xffffffff, null); + return p; +} + +function ScriptableInput(arg) { + if (arg instanceof I.nsIPipe) { + arg = arg.inputStream; + } + + var str = C["@mozilla.org/scriptableinputstream;1"].createInstance( + I.nsIScriptableInputStream + ); + + str.init(arg); + + return str; +} diff --git a/dom/base/test/unit/isequalnode_data.xml b/dom/base/test/unit/isequalnode_data.xml new file mode 100644 index 0000000000..4b72f5d502 --- /dev/null +++ b/dom/base/test/unit/isequalnode_data.xml @@ -0,0 +1,150 @@ +<?xml version="1.0" ?> +<!DOCTYPE Test [ + <!ATTLIST test id ID #REQUIRED> +]> + +<root> + + <test id="test_setAttribute"> + <foo/> + <foo/> + </test> + + <test id="test_normalization"> + <bar/> + <bar/> + </test> + + <test id="test_whitespace"> + + <!-- + Tests here consist of isEqualNode comparisons of the first and third + (zero-indexed) child nodes of each test. + + In the typical case this means that the zeroth, second, and fourth + children are whitespace and the first and third are the nodes being + compared for equality or inequality. + + In some cases, however, where either node is a Text node, this pattern + must of necessity be violated. Examples of such tests include the + test_text# tests. + + As a result of this, BE CAREFUL NOT TO INTRODUCE STRAY WHITESPACE WHEN + EDITING THIS FILE. You have been warned. + --> + + <test id="test_pi1"> + <?pi data?> + <?pi data?> + </test> + <test id="test_pi2"> + <?pi data?> + <?pi data?> + </test> + <test id="test_pi3"> + <?pi data?> + <?pi data ?> + </test> + <test id="test_pi4"> + <?pi ?> + <?pi ?> + </test> + <test id="test_pi5"> + <?pi?> + <?pi ?> + </test> + + <test id="test_elt1"> + <foo></foo> + <foo> </foo> + </test> + <test id="test_elt2"> + <foo></foo> + <foo> +</foo> + </test> + <test id="test_elt3"> + <foo ></foo> + <foo></foo> + </test> + <test id="test_elt4"> + <bar xmlns="http://example.com/"/> + <bar/> + </test> + <test id="test_elt5"> + <bar xmlns="http://example.com/"/> + <bar xmlns="http://example.com"/> + </test> + + <test id="test_comment1"> + <!--foo--> + <!--foo--> + </test> + <test id="test_comment2"> + <!--foo--> + <!--foo --> + </test> + <test id="test_comment3"> + <!--foo--> + <!--foo +--> + </test> + <test id="test_comment4"> + <!-- +foo--> + <!-- +foo--> + </test> + + <test id="test_text1"><placeholder-dont-move/> +<placeholder-dont-move/> +<placeholder-dont-move/> + </test> + <test id="test_text2"><placeholder-dont-move/> +<placeholder-dont-move/> <placeholder-dont-move/> + </test> + <test id="test_text3"><placeholder-dont-move/> +<placeholder-dont-move/><![CDATA[ +]]> + </test> + + <test id="test_cdata1"> + <![CDATA[ ]]><placeholder-dont-move/> <placeholder-dont-move/> + </test> + <test id="test_cdata2"> + <![CDATA[ ]]> + <![CDATA[ ]]> + </test> + <test id="test_cdata3"> + <![CDATA[ ]]> + <![CDATA[ ]]> + </test> + <test id="test_cdata4"> + <![CDATA[]]> + <![CDATA[ +]]> + </test> + <test id="test_cdata5"> + <![CDATA[ ]]> + <![CDATA[ +]]> + </test> + + </test> + + <test id="test_namespaces"> + <test id="test_ns1"> + <foo xmlns:quiz="http://example.com/" + quiz:q="fun"/> + <foo xmlns:f="http://example.com/" + f:q="fun"/> + </test> + <test id="test_ns2"> + <quiz:foo xmlns:quiz="http://example.com/" + q="fun"/> + <f:foo xmlns:f="http://example.com/" + q="fun"/> + </test> + </test> + +</root> diff --git a/dom/base/test/unit/nodelist_data_1.xml b/dom/base/test/unit/nodelist_data_1.xml new file mode 100644 index 0000000000..ddde596a27 --- /dev/null +++ b/dom/base/test/unit/nodelist_data_1.xml @@ -0,0 +1,58 @@ +<?xml version="1.0" ?> +<!DOCTYPE Test [ + <!ATTLIST test id ID #REQUIRED> + <!ATTLIST foo:test id ID #REQUIRED> + <!ATTLIST foo2:test id ID #REQUIRED> + <!ATTLIST bar:test id ID #REQUIRED> +]> + +<!-- Comment --> + +<?This-is-a-PI ?> + +<root xmlns:foo="foo" + xmlns:bar="bar" + xmlns:foo2="foo"> + + <test id="test1"> + </test> + + <test id="test2"> + <!-- Another comment --> + <test id="test3"> + </test> + + <test id="test4" xmlns="foo"> + <test id="test5"> + </test> + + <bar:test id="test6" /> + </test> + + <foo:test id="test7"> + </foo:test> + + <foo2:test id="test8"> + <?Another-PI ?> + <baz /> + </foo2:test> + + <bar:test id="test9"> + </bar:test> + </test> + + <foo:test id="test10"> + <foo2:test id="test11"> + <bar:test id="test12"> + </bar:test> + </foo2:test> + </foo:test> + + <foo2:test id="test13"> + </foo2:test> + + <bar:test id="test14"> + </bar:test> + +</root> + diff --git a/dom/base/test/unit/nodelist_data_2.xhtml b/dom/base/test/unit/nodelist_data_2.xhtml new file mode 100644 index 0000000000..247ef0353c --- /dev/null +++ b/dom/base/test/unit/nodelist_data_2.xhtml @@ -0,0 +1,45 @@ +<?xml version="1.0"?> +<!DOCTYPE window [ + <!ENTITY fooSet ' + <addOrRemove foo:foo="foo"/> + <addOrRemove foo:foo="bar"/> + <addOrRemove foo:bar="foo"/> + <addOrRemove foo:bar="bar"/> + <addOrRemove foo:foo="foo" foo:bar="bar"/> + <addOrRemove foo2:foo="foo"/> + <addOrRemove foo="foo"/> + <addOrRemove foo="bar"/> + <addOrRemove bar="bar" foo="foo"/> +'> +]> +<window + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + xmlns:foo="foo" + xmlns:foo2="foo" + xmlns:bar="bar" + > + <vbox id="boxes"> + <groupbox id="master1" foo:foo="bar"> + &fooSet; + <groupbox foo:foo="foo"> + &fooSet; + </groupbox> + </groupbox> + <groupbox id="master2" foo:foo="foo"> + &fooSet; + <groupbox foo:foo="bar"> + &fooSet; + </groupbox> + </groupbox> + <groupbox id="master3"> + &fooSet; + <groupbox foo2:foo="foo"> + &fooSet; + </groupbox> + </groupbox> + <groupbox id="external"> + &fooSet; + </groupbox> + </vbox> +</window> diff --git a/dom/base/test/unit/test_blockParsing.js b/dom/base/test/unit/test_blockParsing.js new file mode 100644 index 0000000000..e5d35f6eef --- /dev/null +++ b/dom/base/test/unit/test_blockParsing.js @@ -0,0 +1,121 @@ +"use strict"; + +const { TestUtils } = ChromeUtils.importESModule( + "resource://testing-common/TestUtils.sys.mjs" +); +const { XPCShellContentUtils } = ChromeUtils.importESModule( + "resource://testing-common/XPCShellContentUtils.sys.mjs" +); +const { setTimeout } = ChromeUtils.importESModule( + "resource://gre/modules/Timer.sys.mjs" +); + +XPCShellContentUtils.init(this); + +function delay() { + return new Promise(resolve => { + setTimeout(resolve, 0); + }); +} + +const server = XPCShellContentUtils.createHttpServer({ + hosts: ["example.com"], +}); + +// XML document with only a <script> tag as the document element. +const PAGE_URL = "http://example.com/"; +server.registerPathHandler("/", (request, response) => { + response.setHeader("Content-Type", "application/xhtml+xml"); + response.write(String.raw`<!DOCTYPE html> + <script xmlns="http://www.w3.org/1999/xhtml" src="slow.js"/> + `); +}); + +let resolveResumeScriptPromise; +let resumeScriptPromise = new Promise(resolve => { + resolveResumeScriptPromise = resolve; +}); + +let resolveScriptRequestPromise; +let scriptRequestPromise = new Promise(resolve => { + resolveScriptRequestPromise = resolve; +}); + +// An empty script which waits to complete until `resumeScriptPromise` +// resolves. +server.registerPathHandler("/slow.js", async (request, response) => { + response.processAsync(); + resolveScriptRequestPromise(); + + await resumeScriptPromise; + + response.setHeader("Content-type", "text/javascript"); + response.write(""); + response.finish(); +}); + +add_setup(function () { + Services.prefs.setBoolPref("security.allow_unsafe_parent_loads", true); +}); + +registerCleanupFunction(function () { + Services.prefs.clearUserPref("security.allow_unsafe_parent_loads"); +}); + +// Tests that attempting to block parsing for a <script> tag while the +// parser is already blocked is handled correctly, and does not cause +// crashes or hangs. +add_task(async function test_nested_blockParser() { + // Wait for the document element of the page to be inserted, and block + // the parser when it is. + let resolveBlockerPromise; + let blockerPromise; + let docElementPromise = TestUtils.topicObserved( + "document-element-inserted", + doc => { + if (doc.location.href === PAGE_URL) { + blockerPromise = new Promise(resolve => { + resolveBlockerPromise = resolve; + }); + + doc.blockParsing(blockerPromise); + return true; + } + return false; + } + ); + + // Begin loading the page. + let pagePromise = XPCShellContentUtils.loadContentPage(PAGE_URL, { + remote: false, + }); + + // Wait for the document element to be inserted. + await docElementPromise; + // Wait for the /slow.js script request to initiate. + await scriptRequestPromise; + + // Make some trips through the event loop to be safe. + await delay(); + await delay(); + + // Allow the /slow.js script request to complete. + resolveResumeScriptPromise(); + + // Make some trips through the event loop so that the <script> request + // unblocks the parser. + await delay(); + await delay(); + + // Release the parser blocker added in the observer above. + resolveBlockerPromise(); + + // Make some trips through the event loop to allow the parser to + // unblock. + await delay(); + await delay(); + + // Wait for the document to finish loading, and then close it. + let page = await pagePromise; + await page.close(); +}); diff --git a/dom/base/test/unit/test_bug553888.js b/dom/base/test/unit/test_bug553888.js new file mode 100644 index 0000000000..3747bf7902 --- /dev/null +++ b/dom/base/test/unit/test_bug553888.js @@ -0,0 +1,59 @@ +/* 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/. */ + +var { HttpServer } = ChromeUtils.importESModule( + "resource://testing-common/httpd.sys.mjs" +); + +var server = new HttpServer(); +server.start(-1); + +const SERVER_PORT = server.identity.primaryPort; +const HTTP_BASE = "http://localhost:" + SERVER_PORT; +const redirectPath = "/redirect"; +const headerCheckPath = "/headerCheck"; +const redirectURL = HTTP_BASE + redirectPath; +const headerCheckURL = HTTP_BASE + headerCheckPath; + +function redirectHandler(metadata, response) { + response.setStatusLine(metadata.httpVersion, 302, "Found"); + response.setHeader("Location", headerCheckURL, false); +} + +function headerCheckHandler(metadata, response) { + try { + let headerValue = metadata.getHeader("X-Custom-Header"); + Assert.equal(headerValue, "present"); + } catch (e) { + do_throw("No header present after redirect"); + } + try { + metadata.getHeader("X-Unwanted-Header"); + do_throw("Unwanted header present after redirect"); + } catch (x) {} + response.setStatusLine(metadata.httpVersion, 200, "OK"); + response.setHeader("Content-Type", "text/plain"); + response.write(""); +} + +function run_test() { + server.registerPathHandler(redirectPath, redirectHandler); + server.registerPathHandler(headerCheckPath, headerCheckHandler); + + do_test_pending(); + var request = new XMLHttpRequest(); + request.open("GET", redirectURL, true); + request.setRequestHeader("X-Custom-Header", "present"); + request.addEventListener("readystatechange", function () { + if (request.readyState == 4) { + Assert.equal(request.status, 200); + server.stop(do_test_finished); + } + }); + request.send(); + try { + request.setRequestHeader("X-Unwanted-Header", "present"); + do_throw("Shouldn't be able to set a header after send"); + } catch (x) {} +} diff --git a/dom/base/test/unit/test_bug737966.js b/dom/base/test/unit/test_bug737966.js new file mode 100644 index 0000000000..a5b8f48425 --- /dev/null +++ b/dom/base/test/unit/test_bug737966.js @@ -0,0 +1,16 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +/* If charset parameter is invalid, the encoding should be detected as UTF-8 */ + +function run_test() { + let body = '<?xml version="1.0"><html>%c3%80</html>'; + let result = '<?xml version="1.0"><html>\u00c0</html>'; + + let xhr = new XMLHttpRequest(); + xhr.open("GET", "data:text/xml;charset=abc," + body, false); + xhr.send(null); + + Assert.equal(xhr.responseText, result); +} diff --git a/dom/base/test/unit/test_cancelPrefetch.js b/dom/base/test/unit/test_cancelPrefetch.js new file mode 100644 index 0000000000..6663d60fbb --- /dev/null +++ b/dom/base/test/unit/test_cancelPrefetch.js @@ -0,0 +1,149 @@ +var prefetch = Cc["@mozilla.org/prefetch-service;1"].getService( + Ci.nsIPrefetchService +); + +var ReferrerInfo = Components.Constructor( + "@mozilla.org/referrer-info;1", + "nsIReferrerInfo", + "init" +); + +var ios = Services.io; +var prefs = Services.prefs; + +var parser = new DOMParser(); + +var doc; + +var docbody = + '<html xmlns="http://www.w3.org/1999/xhtml"><head></head><body>' + + '<link id="node1"/><link id="node2"/>' + + "</body></html>"; + +var node1; +var node2; + +function run_test() { + prefs.setBoolPref("network.prefetch-next", true); + + doc = parser.parseFromString(docbody, "text/html"); + + node1 = doc.getElementById("node1"); + node2 = doc.getElementById("node2"); + + run_next_test(); +} + +add_test(function test_cancel1() { + var uri = ios.newURI("http://localhost/1"); + var referrerInfo = new ReferrerInfo(Ci.nsIReferrerInfo.EMPTY, true, uri); + + prefetch.prefetchURI(uri, referrerInfo, node1, true); + + Assert.ok(prefetch.hasMoreElements(), "There is a request in the queue"); + + // Trying to prefetch again the same uri with the same node will fail. + var didFail = 0; + + try { + prefetch.prefetchURI(uri, referrerInfo, node1, true); + } catch (e) { + didFail = 1; + } + + Assert.ok( + didFail == 1, + "Prefetching the same request with the same node fails." + ); + + Assert.ok(prefetch.hasMoreElements(), "There is still request in the queue"); + + prefetch.cancelPrefetchPreloadURI(uri, node1); + + Assert.ok(!prefetch.hasMoreElements(), "There is no request in the queue"); + run_next_test(); +}); + +add_test(function test_cancel2() { + // Prefetch a uri with 2 different nodes. There should be 2 request + // in the queue and canceling one will not cancel the other. + + var uri = ios.newURI("http://localhost/1"); + var referrerInfo = new ReferrerInfo(Ci.nsIReferrerInfo.EMPTY, true, uri); + + prefetch.prefetchURI(uri, referrerInfo, node1, true); + prefetch.prefetchURI(uri, referrerInfo, node2, true); + + Assert.ok(prefetch.hasMoreElements(), "There are requests in the queue"); + + prefetch.cancelPrefetchPreloadURI(uri, node1); + + Assert.ok( + prefetch.hasMoreElements(), + "There is still one more request in the queue" + ); + + prefetch.cancelPrefetchPreloadURI(uri, node2); + + Assert.ok(!prefetch.hasMoreElements(), "There is no request in the queue"); + run_next_test(); +}); + +add_test(function test_cancel3() { + // Request a prefetch of a uri. Trying to cancel a prefetch for the same uri + // with a different node will fail. + var uri = ios.newURI("http://localhost/1"); + var referrerInfo = new ReferrerInfo(Ci.nsIReferrerInfo.EMPTY, true, uri); + + prefetch.prefetchURI(uri, referrerInfo, node1, true); + + Assert.ok(prefetch.hasMoreElements(), "There is a request in the queue"); + + var didFail = 0; + + try { + prefetch.cancelPrefetchPreloadURI(uri, node2, true); + } catch (e) { + didFail = 1; + } + Assert.ok(didFail == 1, "Canceling the request failed"); + + Assert.ok( + prefetch.hasMoreElements(), + "There is still a request in the queue" + ); + + prefetch.cancelPrefetchPreloadURI(uri, node1); + Assert.ok(!prefetch.hasMoreElements(), "There is no request in the queue"); + run_next_test(); +}); + +add_test(function test_cancel4() { + // Request a prefetch of a uri. Trying to cancel a prefetch for a different uri + // with the same node will fail. + var uri1 = ios.newURI("http://localhost/1"); + var uri2 = ios.newURI("http://localhost/2"); + var referrerInfo = new ReferrerInfo(Ci.nsIReferrerInfo.EMPTY, true, uri1); + + prefetch.prefetchURI(uri1, referrerInfo, node1, true); + + Assert.ok(prefetch.hasMoreElements(), "There is a request in the queue"); + + var didFail = 0; + + try { + prefetch.cancelPrefetchPreloadURI(uri2, node1); + } catch (e) { + didFail = 1; + } + Assert.ok(didFail == 1, "Canceling the request failed"); + + Assert.ok( + prefetch.hasMoreElements(), + "There is still a request in the queue" + ); + + prefetch.cancelPrefetchPreloadURI(uri1, node1); + Assert.ok(!prefetch.hasMoreElements(), "There is no request in the queue"); + run_next_test(); +}); diff --git a/dom/base/test/unit/test_chromeutils_base64.js b/dom/base/test/unit/test_chromeutils_base64.js new file mode 100644 index 0000000000..ca2722016b --- /dev/null +++ b/dom/base/test/unit/test_chromeutils_base64.js @@ -0,0 +1,140 @@ +"use strict"; + +function run_test() { + test_base64URLEncode(); + test_base64URLDecode(); +} + +// Test vectors from RFC 4648, section 10. +let textTests = { + "": "", + f: "Zg", + fo: "Zm8", + foo: "Zm9v", + foob: "Zm9vYg", + fooba: "Zm9vYmE", + foobar: "Zm9vYmFy", +}; + +// Examples from RFC 4648, section 9. +let binaryTests = [ + { + decoded: new Uint8Array([0x14, 0xfb, 0x9c, 0x03, 0xd9, 0x7e]), + encoded: "FPucA9l-", + }, + { + decoded: new Uint8Array([0x14, 0xfb, 0x9c, 0x03, 0xd9]), + encoded: "FPucA9k", + }, + { + decoded: new Uint8Array([0x14, 0xfb, 0x9c, 0x03]), + encoded: "FPucAw", + }, +]; + +function padEncodedValue(value) { + switch (value.length % 4) { + case 0: + return value; + case 2: + return value + "=="; + case 3: + return value + "="; + default: + throw new TypeError("Invalid encoded value"); + } +} + +function testEncode(input, encoded) { + equal( + ChromeUtils.base64URLEncode(input, { pad: false }), + encoded, + encoded + " without padding" + ); + equal( + ChromeUtils.base64URLEncode(input, { pad: true }), + padEncodedValue(encoded), + encoded + " with padding" + ); +} + +function test_base64URLEncode() { + throws( + _ => ChromeUtils.base64URLEncode(new Uint8Array(0)), + /TypeError/, + "Should require encoding options" + ); + throws( + _ => ChromeUtils.base64URLEncode(new Uint8Array(0), {}), + /TypeError/, + "Encoding should require the padding option" + ); + + for (let { decoded, encoded } of binaryTests) { + testEncode(decoded, encoded); + } + + let textEncoder = new TextEncoder(); + for (let decoded of Object.keys(textTests)) { + let input = textEncoder.encode(decoded); + testEncode(input, textTests[decoded]); + } +} + +function testDecode(input, decoded) { + let buffer = ChromeUtils.base64URLDecode(input, { padding: "reject" }); + deepEqual(new Uint8Array(buffer), decoded, input + " with padding rejected"); + + let paddedValue = padEncodedValue(input); + buffer = ChromeUtils.base64URLDecode(paddedValue, { padding: "ignore" }); + deepEqual(new Uint8Array(buffer), decoded, input + " with padding ignored"); + + if (paddedValue.length > input.length) { + throws( + _ => ChromeUtils.base64URLDecode(paddedValue, { padding: "reject" }), + /NS_ERROR_ILLEGAL_VALUE/, + paddedValue + " with padding rejected should throw" + ); + + throws( + _ => ChromeUtils.base64URLDecode(input, { padding: "require" }), + /NS_ERROR_ILLEGAL_VALUE/, + input + " with padding required should throw" + ); + + buffer = ChromeUtils.base64URLDecode(paddedValue, { padding: "require" }); + deepEqual( + new Uint8Array(buffer), + decoded, + paddedValue + " with padding required" + ); + } +} + +function test_base64URLDecode() { + throws( + _ => ChromeUtils.base64URLDecode(""), + /TypeError/, + "Should require decoding options" + ); + throws( + _ => ChromeUtils.base64URLDecode("", {}), + /TypeError/, + "Decoding should require the padding option" + ); + throws( + _ => ChromeUtils.base64URLDecode("", { padding: "chocolate" }), + /TypeError/, + "Decoding should throw for invalid padding policy" + ); + + for (let { decoded, encoded } of binaryTests) { + testDecode(encoded, decoded); + } + + let textEncoder = new TextEncoder(); + for (let decoded of Object.keys(textTests)) { + let expectedBuffer = textEncoder.encode(decoded); + testDecode(textTests[decoded], expectedBuffer); + } +} diff --git a/dom/base/test/unit/test_chromeutils_defineLazyGetter.js b/dom/base/test/unit/test_chromeutils_defineLazyGetter.js new file mode 100644 index 0000000000..a3dae487cf --- /dev/null +++ b/dom/base/test/unit/test_chromeutils_defineLazyGetter.js @@ -0,0 +1,25 @@ +"use strict"; + +add_task(function test_defineLazyGetter() { + let accessCount = 0; + let obj = { + inScope: false, + }; + const TEST_VALUE = "test value"; + ChromeUtils.defineLazyGetter(obj, "foo", function () { + accessCount++; + this.inScope = true; + return TEST_VALUE; + }); + Assert.equal(accessCount, 0); + + // Get the property, making sure the access count has increased. + Assert.equal(obj.foo, TEST_VALUE); + Assert.equal(accessCount, 1); + Assert.ok(obj.inScope); + + // Get the property once more, making sure the access count has not + // increased. + Assert.equal(obj.foo, TEST_VALUE); + Assert.equal(accessCount, 1); +}); diff --git a/dom/base/test/unit/test_chromeutils_getXPCOMErrorName.js b/dom/base/test/unit/test_chromeutils_getXPCOMErrorName.js new file mode 100644 index 0000000000..f7d6a89e71 --- /dev/null +++ b/dom/base/test/unit/test_chromeutils_getXPCOMErrorName.js @@ -0,0 +1,40 @@ +"use strict"; + +// Test ChromeUtils.getXPCOMErrorName + +add_task(function test_getXPCOMErrorName() { + info("Force the initialization of NSS to get the error names right"); + Cc["@mozilla.org/psm;1"].getService(Ci.nsISupports); + + Assert.equal( + ChromeUtils.getXPCOMErrorName(Cr.NS_OK), + "NS_OK", + "getXPCOMErrorName works for NS_OK" + ); + + Assert.equal( + ChromeUtils.getXPCOMErrorName(Cr.NS_ERROR_FAILURE), + "NS_ERROR_FAILURE", + "getXPCOMErrorName works for NS_ERROR_FAILURE" + ); + + const nssErrors = Cc["@mozilla.org/nss_errors_service;1"].getService( + Ci.nsINSSErrorsService + ); + Assert.equal( + ChromeUtils.getXPCOMErrorName( + nssErrors.getXPCOMFromNSSError(Ci.nsINSSErrorsService.NSS_SEC_ERROR_BASE) + ), + "NS_ERROR_GENERATE_FAILURE(NS_ERROR_MODULE_SECURITY, SEC_ERROR_IO)", + "getXPCOMErrorName works for NSS_SEC_ERROR_BASE" + ); + // See https://searchfox.org/mozilla-central/rev/a48e21143960b383004afa9ff9411c5cf6d5a958/security/nss/lib/util/secerr.h#20 + const SEC_ERROR_BAD_DATA = Ci.nsINSSErrorsService.NSS_SEC_ERROR_BASE + 2; + Assert.equal( + ChromeUtils.getXPCOMErrorName( + nssErrors.getXPCOMFromNSSError(SEC_ERROR_BAD_DATA) + ), + "NS_ERROR_GENERATE_FAILURE(NS_ERROR_MODULE_SECURITY, SEC_ERROR_BAD_DATA)", + "getXPCOMErrorName works for NSS's SEC_ERROR_BAD_DATA" + ); +}); diff --git a/dom/base/test/unit/test_chromeutils_shallowclone.js b/dom/base/test/unit/test_chromeutils_shallowclone.js new file mode 100644 index 0000000000..03e0d443c9 --- /dev/null +++ b/dom/base/test/unit/test_chromeutils_shallowclone.js @@ -0,0 +1,60 @@ +"use strict"; + +add_task(function test_shallowclone() { + // Check that shallow cloning an object with regular properties, + // results into a new object with all properties from the source object. + const fullyCloneableObject = { + numProp: 123, + strProp: "str", + boolProp: true, + arrayProp: [{ item1: "1", item2: "2" }], + fnProp() { + return "fn result"; + }, + promise: Promise.resolve("promised-value"), + weakmap: new WeakMap(), + proxy: new Proxy({}, {}), + }; + + let clonedObject = ChromeUtils.shallowClone(fullyCloneableObject); + + Assert.deepEqual( + clonedObject, + fullyCloneableObject, + "Got the expected cloned object for an object with regular properties" + ); + + // Check that shallow cloning an object with getters and setters properties, + // results into a new object without all the properties from the source object excluded + // its getters and setters. + const objectWithGetterAndSetter = { + get myGetter() { + return "getter result"; + }, + set mySetter(v) {}, + myFunction() { + return "myFunction result"; + }, + }; + + clonedObject = ChromeUtils.shallowClone(objectWithGetterAndSetter); + + Assert.deepEqual( + clonedObject, + { + myFunction: objectWithGetterAndSetter.myFunction, + }, + "Got the expected cloned object for an object with getters and setters" + ); + + // Check that shallow cloning a proxy object raises the expected exception.. + const proxyObject = new Proxy(fullyCloneableObject, {}); + + Assert.throws( + () => { + ChromeUtils.shallowClone(proxyObject); + }, + /Shallow cloning a proxy object is not allowed/, + "Got the expected error on ChromeUtils.shallowClone called on a proxy object" + ); +}); diff --git a/dom/base/test/unit/test_delete_range.xml b/dom/base/test/unit/test_delete_range.xml new file mode 100644 index 0000000000..c8d50bd32a --- /dev/null +++ b/dom/base/test/unit/test_delete_range.xml @@ -0,0 +1,125 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +This file holds serialized tests for DOM Range tests on extractContents. +The <test/> elements designate individual tests. Each one has the following: + +* A <source/> element, designating the start conditions of the test, +* A <result/> element, designating what the source element should look like + after the extraction, +* An <extract/> element, designating what the extracted content should look like. + +The <split/> element designates a split between two DOM nodes. This element will +be removed before the actual test, and the two nodes on either side will not be +merged. + +The <empty-cdata/> element designates an empty character data section. Before +the test executes, this element is replaced with an actual CDATASection node. + +For the <source/> element, there are four attributes: + +* startContainer: A XPath to the startContainer of the range. +* endContainer: A XPath to the endContainer of the range. +* startOffset: The startOffset of the range. +* endOffset: The endOffset of the range. + +Note this test may need updating with a fix for bug 401276. The spec states +adjacent nodes after an extraction should be merged if possible, but using the +normalize() method, which could have unintended side effects... For now, we're +not permitting that, pending clarification. + +Please make sure the first test in this document always tests a range where the +start container and end container are the same text node, and where the start +offset and end offsets are valid and inequal. Some of the additional range +tests (after the bulk of the delete/extract tests) depend on it. + --> +<root> + <!-- Extracting from a text node. --> + <test> + <source startContainer="text()[1]" + endContainer="text()[1]" + startOffset="4" + endOffset="10">The quick fox</source> + <result>The fox</result> + <extract>quick </extract> + </test> + + <!-- Extracting from a CDATA section. --> + <test> + <source startContainer="text()[1]" + endContainer="text()[1]" + startOffset="4" + endOffset="10"><![CDATA[The quick fox]]></source> + <result><![CDATA[The fox]]></result> + <extract><![CDATA[quick ]]></extract> + </test> + + <!-- Snipping the start of a text node. --> + <test> + <source startContainer="text()[1]" + endContainer="text()[1]" + startOffset="0" + endOffset="4"><![CDATA[The quick fox]]></source> + <result><![CDATA[quick fox]]></result> + <extract><![CDATA[The ]]></extract> + </test> + + <!-- Extracting from a comment. --> + <test> + <source startContainer="comment()[1]" + endContainer="comment()[1]" + startOffset="4" + endOffset="10"><!--The quick fox--></source> + <result><!--The fox--></result> + <extract><!--quick --></extract> + </test> + + <!-- Snipping whole nodes --> + <test> + <source startContainer="." + endContainer="." + startOffset="0" + endOffset="2">Fox<fox/>Fox<bear/><!--comment--></source> + <result>Fox<bear/><!--comment--></result> + <extract>Fox<fox/></extract> + </test> + + <!-- Snipping whole nodes --> + <test> + <source startContainer="." + endContainer="." + startOffset="1" + endOffset="3">Fox<fox/>Fox<bear/><!--comment--></source> + <result>Fox<bear/><!--comment--></result> + <extract><fox/>Fox</extract> + </test> + + <!-- Snipping a mixture of nodes and portions of text --> + <test> + <source startContainer="text()[2]" + startOffset="1" + endContainer="comment()[1]" + endOffset="3">Fox<fox/>Fox<bear><?cow ?></bear><!--comment--></source> + <result>Fox<fox/>F<!--ment--></result> + <extract>ox<bear><?cow ?></bear><!--com--></extract> + </test> + + <!-- Extracting with a collapsed range from a text node. --> + <test> + <source startContainer="text()[1]" + endContainer="text()[1]" + startOffset="4" + endOffset="4">The quick fox</source> + <result>The quick fox</result> + <extract/> + </test> + + <!-- Extracting with a collapsed range from a non-text node. --> + <test> + <source startContainer="." + endContainer="." + startOffset="0" + endOffset="0">Fox<fox/>Fox<bear/><!--comment--></source> + <result>Fox<fox/>Fox<bear/><!--comment--></result> + <extract/> + </test> +</root> diff --git a/dom/base/test/unit/test_error_codes.js b/dom/base/test/unit/test_error_codes.js new file mode 100644 index 0000000000..73c893c512 --- /dev/null +++ b/dom/base/test/unit/test_error_codes.js @@ -0,0 +1,56 @@ +/* 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/. + */ + +var prefs = Services.prefs; + +function asyncXHR(expectedStatus, nextTestFunc) { + var xhr = new XMLHttpRequest(); + xhr.open("GET", "http://localhost:4444/test_error_code.xml", true); + + var sawError = false; + xhr.addEventListener("loadend", function doAsyncRequest_onLoad(event) { + Assert.ok(sawError, "Should have received an error"); + nextTestFunc(); + }); + xhr.addEventListener("error", function doAsyncRequest_onError(event) { + var request = event.target.channel.QueryInterface(Ci.nsIRequest); + Assert.equal(request.status, expectedStatus); + sawError = true; + }); + xhr.send(null); +} + +function run_test() { + do_test_pending(); + do_timeout(0, run_test_pt1); +} + +// network offline +function run_test_pt1() { + try { + Services.io.manageOfflineStatus = false; + } catch (e) {} + Services.io.offline = true; + prefs.setBoolPref("network.dns.offline-localhost", false); + // We always resolve localhost as it's hardcoded without the following pref: + prefs.setBoolPref("network.proxy.allow_hijacking_localhost", true); + + dump("Testing error returned by async XHR when the network is offline\n"); + asyncXHR(Cr.NS_ERROR_OFFLINE, run_test_pt2); +} + +// connection refused +function run_test_pt2() { + Services.io.offline = false; + prefs.clearUserPref("network.dns.offline-localhost"); + prefs.clearUserPref("network.proxy.allow_hijacking_localhost"); + + dump("Testing error returned by aync XHR when the connection is refused\n"); + asyncXHR(Cr.NS_ERROR_CONNECTION_REFUSED, end_test); +} + +function end_test() { + do_test_finished(); +} diff --git a/dom/base/test/unit/test_generate_xpath.js b/dom/base/test/unit/test_generate_xpath.js new file mode 100644 index 0000000000..e8c7043690 --- /dev/null +++ b/dom/base/test/unit/test_generate_xpath.js @@ -0,0 +1,85 @@ +function run_test() { + test_generate_xpath(); +} + +// TEST CODE + +function test_generate_xpath() { + let docString = ` + <html> + <body> + <label><input type="checkbox" id="input1" />Input 1</label> + <label><input type="checkbox" id="input2'" />Input 2</label> + <label><input type="checkbox" id='"input3"' />Input 3</label> + <label><input type="checkbox"/>Input 4</label> + <label><input type="checkbox" />Input 5</label> + </body> + </html> + `; + let doc = getParser().parseFromString(docString, "text/html"); + + // Test generate xpath for body. + info("Test generate xpath for body node"); + let body = doc.getElementsByTagName("body")[0]; + let bodyXPath = body.generateXPath(); + let bodyExpXPath = "/xhtml:html/xhtml:body"; + equal(bodyExpXPath, bodyXPath, " xpath generated for body"); + + // Test generate xpath for input with id. + info("Test generate xpath for input with id"); + let inputWithId = doc.getElementById("input1"); + let inputWithIdXPath = inputWithId.generateXPath(); + let inputWithIdExpXPath = "//xhtml:input[@id='input1']"; + equal( + inputWithIdExpXPath, + inputWithIdXPath, + " xpath generated for input with id" + ); + + // Test generate xpath for input with id has single quote. + info("Test generate xpath for input with id has single quote"); + let inputWithIdSingleQuote = doc.getElementsByTagName("input")[1]; + let inputWithIdXPathSingleQuote = inputWithIdSingleQuote.generateXPath(); + let inputWithIdExpXPathSingleQuote = '//xhtml:input[@id="input2\'"]'; + equal( + inputWithIdExpXPathSingleQuote, + inputWithIdXPathSingleQuote, + " xpath generated for input with id" + ); + + // Test generate xpath for input with id has double quote. + info("Test generate xpath for input with id has double quote"); + let inputWithIdDoubleQuote = doc.getElementsByTagName("input")[2]; + let inputWithIdXPathDoubleQuote = inputWithIdDoubleQuote.generateXPath(); + let inputWithIdExpXPathDoubleQuote = "//xhtml:input[@id='\"input3\"']"; + equal( + inputWithIdExpXPathDoubleQuote, + inputWithIdXPathDoubleQuote, + " xpath generated for input with id" + ); + + // Test generate xpath for input with id has both single and double quote. + info("Test generate xpath for input with id has single and double quote"); + let inputWithIdSingleDoubleQuote = doc.getElementsByTagName("input")[3]; + inputWithIdSingleDoubleQuote.setAttribute("id", "\"input'4"); + let inputWithIdXPathSingleDoubleQuote = + inputWithIdSingleDoubleQuote.generateXPath(); + let inputWithIdExpXPathSingleDoubleQuote = + "//xhtml:input[@id=concat('\"input',\"'\",'4')]"; + equal( + inputWithIdExpXPathSingleDoubleQuote, + inputWithIdXPathSingleDoubleQuote, + " xpath generated for input with id" + ); + + // Test generate xpath for input without id. + info("Test generate xpath for input without id"); + let inputNoId = doc.getElementsByTagName("input")[4]; + let inputNoIdXPath = inputNoId.generateXPath(); + let inputNoIdExpXPath = "/xhtml:html/xhtml:body/xhtml:label[5]/xhtml:input"; + equal( + inputNoIdExpXPath, + inputNoIdXPath, + " xpath generated for input without id" + ); +} diff --git a/dom/base/test/unit/test_htmlserializer.js b/dom/base/test/unit/test_htmlserializer.js new file mode 100644 index 0000000000..17995dddde --- /dev/null +++ b/dom/base/test/unit/test_htmlserializer.js @@ -0,0 +1,70 @@ +/* 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/. */ + +add_task(async function testAttrsOutsideBodyWithRawBodyOnly() { + // Create a simple HTML document in which the header contains a tag with set + // attributes + const htmlString = `<html><head><link rel="stylesheet" href="foo"/></head><body>some content</body></html>`; + + const parser = new DOMParser(); + const doc = parser.parseFromString(htmlString, "text/html"); + + // Sanity check + const linkElems = doc.head.getElementsByTagName("link"); + Assert.equal( + linkElems.length, + 1, + "document header should contain one link element" + ); + Assert.equal( + linkElems[0].rel, + "stylesheet", + "link element should have rel attribute set" + ); + + // Verify that the combination of raw output and body-only does not allow + // attributes from header elements to creep into the output string + const encoder = Cu.createDocumentEncoder("text/html"); + encoder.init( + doc, + "text/html", + Ci.nsIDocumentEncoder.OutputRaw | Ci.nsIDocumentEncoder.OutputBodyOnly + ); + + const result = encoder.encodeToString(); + Assert.equal( + result, + "<body>some content</body>", + "output should not contain attributes from head elements" + ); +}); + +add_task(async function testAttrsInsideBodyWithRawBodyOnly() { + // Create a simple HTML document in which the body contains a tag with set + // attributes + const htmlString = `<html><head><link rel="stylesheet" href="foo"/></head><body><span id="foo">some content</span></body></html>`; + + const parser = new DOMParser(); + const doc = parser.parseFromString(htmlString, "text/html"); + + // Sanity check + const spanElem = doc.getElementById("foo"); + Assert.ok(spanElem, "should be able to get span element by ID"); + + // Verify that the combination of raw output and body-only does not strip + // tag attributes inside the body + const encoder = Cu.createDocumentEncoder("text/html"); + encoder.init( + doc, + "text/html", + Ci.nsIDocumentEncoder.OutputRaw | Ci.nsIDocumentEncoder.OutputBodyOnly + ); + + const result = encoder.encodeToString(); + Assert.equal( + result, + `<body><span id="foo">some content</span></body>`, + "output should not contain attributes from head elements" + ); +}); diff --git a/dom/base/test/unit/test_isequalnode.js b/dom/base/test/unit/test_isequalnode.js new file mode 100644 index 0000000000..1b9491fcdc --- /dev/null +++ b/dom/base/test/unit/test_isequalnode.js @@ -0,0 +1,390 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 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/. */ + +// TEST CODE + +var doc; // cache for use in all tests + +add_setup(function init() { + doc = ParseFile("isequalnode_data.xml"); +}); + +add_task(function test_isEqualNode_setAttribute() { + // NOTE: 0, 2 are whitespace + var test1 = doc.getElementById("test_setAttribute"); + var node1 = test1.childNodes.item(1); + var node2 = test1.childNodes.item(3); + + check_eq_nodes(node1, node2); + + node1.setAttribute("bar", "baz"); + check_neq_nodes(node1, node2); + + node2.setAttribute("bar", "baz"); + check_eq_nodes(node1, node2); + + // the null namespace is equivalent to no namespace -- section 1.3.3 + // (XML Namespaces) of DOM 3 Core + node1.setAttributeNS(null, "quux", "17"); + check_neq_nodes(node1, node2); + + node2.setAttribute("quux", "17"); + check_eq_nodes(node1, node2); + + node2.setAttributeNS("http://mozilla.org/", "seamonkey", "rheet"); + check_neq_nodes(node1, node2); + + node1.setAttribute("seamonkey", "rheet"); + check_neq_nodes(node1, node2); + + node1.setAttributeNS("http://mozilla.org/", "seamonkey", "rheet"); + check_neq_nodes(node1, node2); + + // this overwrites the namespaced "seamonkey" attribute added to node2 + // earlier, because this simply sets whatever attribute has the fully + // qualified name "seamonkey" (the setAttributeNS attribute string wasn't + // prefixed) -- consequently, node1 and node2 are still unequal + node2.setAttribute("seamonkey", "rheet"); + check_neq_nodes(node1, node2); +}); + +add_task(function test_isEqualNode_clones() { + // tests all elements and attributes in the document + var all_elts = doc.getElementsByTagName("*"); + for (var i = 0; i < all_elts.length; i++) { + var elt = all_elts.item(i); + check_eq_nodes(elt, elt.cloneNode(true)); + + var attrs = elt.attributes; + for (var j = 0; j < attrs.length; j++) { + var attr = attrs.item(j); + check_eq_nodes(attr, attr.cloneNode(true)); + } + } + + var elm = doc.createElement("foo"); + check_eq_nodes(elm, elm.cloneNode(true)); + check_eq_nodes(elm, elm.cloneNode(false)); + + elm.setAttribute("fiz", "eit"); + check_eq_nodes(elm, elm.cloneNode(true)); + check_eq_nodes(elm, elm.cloneNode(false)); + + elm.setAttributeNS("http://example.com/", "trendoid", "arthroscope"); + check_eq_nodes(elm, elm.cloneNode(true)); + check_eq_nodes(elm, elm.cloneNode(false)); + + var elm2 = elm.cloneNode(true); + check_eq_nodes(elm, elm2); + + const TEXT = "fetishist"; + + elm.textContent = TEXT; + check_neq_nodes(elm, elm2); + + check_neq_nodes(elm, elm.cloneNode(false)); + check_eq_nodes(elm, elm.cloneNode(true)); + + elm2.appendChild(doc.createTextNode(TEXT)); + check_eq_nodes(elm, elm2); + + var att = doc.createAttribute("bar"); + check_eq_nodes(att, att.cloneNode(true)); + check_eq_nodes(att, att.cloneNode(false)); +}); + +add_task(function test_isEqualNode_variety() { + const nodes = [ + doc.createElement("foo"), + doc.createElementNS("http://example.com/", "foo"), + doc.createElementNS("http://example.org/", "foo"), + doc.createElementNS("http://example.com/", "FOO"), + doc.createAttribute("foo", "href='biz'"), + doc.createAttributeNS("http://example.com/", "foo", "href='biz'"), + doc.createTextNode("foo"), + doc.createTextNode(" "), + doc.createTextNode(" "), + doc.createComment("foo"), + doc.createProcessingInstruction("foo", "href='biz'"), + doc.implementation.createDocumentType("foo", "href='biz'", ""), + doc.implementation.createDocument("http://example.com/", "foo", null), + doc.createDocumentFragment(), + ]; + + for (var i = 0; i < nodes.length; i++) { + for (var j = i; j < nodes.length; j++) { + if (i == j) { + check_eq_nodes(nodes[i], nodes[j]); + } else { + check_neq_nodes(nodes[i], nodes[j]); + } + } + } +}); + +add_task(function test_isEqualNode_normalization() { + var norm = doc.getElementById("test_normalization"); + var node1 = norm.childNodes.item(1); + var node2 = norm.childNodes.item(3); + + check_eq_nodes(node1, node2); + + node1.appendChild(doc.createTextNode("")); + check_neq_nodes(node1, node2); + + node1.normalize(); + check_eq_nodes(node1, node2); + + node2.appendChild(doc.createTextNode("fun")); + node2.appendChild(doc.createTextNode("ctor")); + node1.appendChild(doc.createTextNode("functor")); + check_neq_nodes(node1, node2); + + node1.normalize(); + check_neq_nodes(node1, node2); + + node2.normalize(); + check_eq_nodes(node1, node2); + + // reset + while (node1.hasChildNodes()) { + node1.removeChild(node1.childNodes.item(0)); + } + while (node2.hasChildNodes()) { + node2.removeChild(node2.childNodes.item(0)); + } + + // attribute normalization testing + + var at1 = doc.createAttribute("foo"); + var at2 = doc.createAttribute("foo"); + check_eq_nodes(at1, at2); + + // Attr.appendChild isn't implemented yet (bug 56758), so don't run this yet + if (false) { + at1.appendChild(doc.createTextNode("rasp")); + at2.appendChild(doc.createTextNode("rasp")); + check_eq_nodes(at1, at2); + + at1.appendChild(doc.createTextNode("")); + check_neq_nodes(at1, at2); + + at1.normalize(); + check_eq_nodes(at1, at2); + + at1.appendChild(doc.createTextNode("berry")); + check_neq_nodes(at1, at2); + + at2.appendChild(doc.createTextNode("ber")); + check_neq_nodes(at1, at2); + + at2.appendChild(doc.createTextNode("ry")); + check_neq_nodes(at1, at2); + + at1.normalize(); + check_neq_nodes(at1, at2); + + at2.normalize(); + check_eq_nodes(at1, at2); + } + + node1.setAttributeNode(at1); + check_neq_nodes(node1, node2); + + node2.setAttributeNode(at2); + check_eq_nodes(node1, node2); + + var n1text1 = doc.createTextNode("ratfink"); + var n1elt = doc.createElement("fruitcake"); + var n1text2 = doc.createTextNode("hydrospanner"); + + node1.appendChild(n1text1); + node1.appendChild(n1elt); + node1.appendChild(n1text2); + + check_neq_nodes(node1, node2); + + var n2text1a = doc.createTextNode("rat"); + var n2text1b = doc.createTextNode("fink"); + var n2elt = doc.createElement("fruitcake"); + var n2text2 = doc.createTextNode("hydrospanner"); + + node2.appendChild(n2text1b); + node2.appendChild(n2elt); + node2.appendChild(n2text2); + check_neq_nodes(node1, node2); + + node2.insertBefore(n2text1a, n2text1b); + check_neq_nodes(node1, node2); + + var tmp_node1 = node1.cloneNode(true); + tmp_node1.normalize(); + var tmp_node2 = node2.cloneNode(true); + tmp_node2.normalize(); + check_eq_nodes(tmp_node1, tmp_node2); + + n2elt.appendChild(doc.createTextNode("")); + check_neq_nodes(node1, node2); + + tmp_node1 = node1.cloneNode(true); + tmp_node1.normalize(); + tmp_node2 = node2.cloneNode(true); + tmp_node2.normalize(); + check_eq_nodes(tmp_node1, tmp_node2); + + var typeText1 = doc.createTextNode("type"); + n2elt.appendChild(typeText1); + tmp_node1 = node1.cloneNode(true); + tmp_node1.normalize(); + tmp_node2 = node2.cloneNode(true); + tmp_node2.normalize(); + check_neq_nodes(tmp_node1, tmp_node2); + + n1elt.appendChild(doc.createTextNode("typedef")); + tmp_node1 = node1.cloneNode(true); + tmp_node1.normalize(); + tmp_node2 = node2.cloneNode(true); + tmp_node2.normalize(); + check_neq_nodes(tmp_node1, tmp_node2); + check_neq_nodes(n1elt, n2elt); + + var typeText2 = doc.createTextNode("def"); + n2elt.appendChild(typeText2); + tmp_node1 = node1.cloneNode(true); + tmp_node1.normalize(); + tmp_node2 = node2.cloneNode(true); + tmp_node2.normalize(); + check_eq_nodes(tmp_node1, tmp_node2); + check_neq_nodes(node1, node2); + + n2elt.insertBefore(doc.createTextNode(""), typeText2); + check_neq_nodes(node1, node2); + + n2elt.insertBefore(doc.createTextNode(""), typeText2); + check_neq_nodes(node1, node2); + + n2elt.insertBefore(doc.createTextNode(""), typeText1); + check_neq_nodes(node1, node2); + + node1.normalize(); + node2.normalize(); + check_eq_nodes(node1, node2); +}); + +add_task(function test_isEqualNode_whitespace() { + equality_check_kids("test_pi1", true); + equality_check_kids("test_pi2", true); + equality_check_kids("test_pi3", false); + equality_check_kids("test_pi4", true); + equality_check_kids("test_pi5", true); + + equality_check_kids("test_elt1", false); + equality_check_kids("test_elt2", false); + equality_check_kids("test_elt3", true); + equality_check_kids("test_elt4", false); + equality_check_kids("test_elt5", false); + + equality_check_kids("test_comment1", true); + equality_check_kids("test_comment2", false); + equality_check_kids("test_comment3", false); + equality_check_kids("test_comment4", true); + + equality_check_kids("test_text1", true); + equality_check_kids("test_text2", false); + equality_check_kids("test_text3", false); + + equality_check_kids("test_cdata1", false); + equality_check_kids("test_cdata2", true); + equality_check_kids("test_cdata3", false); + equality_check_kids("test_cdata4", false); + equality_check_kids("test_cdata5", false); +}); + +add_task(function test_isEqualNode_namespaces() { + equality_check_kids("test_ns1", false); + equality_check_kids("test_ns2", false); + + // XXX want more tests here! +}); + +// XXX This test is skipped: +// should Node.isEqualNode(null) throw or return false? +add_task(function test_isEqualNode_null() { + check_neq_nodes(doc, null); + + var elts = doc.getElementsByTagName("*"); + for (var i = 0; i < elts.length; i++) { + var elt = elts.item(i); + check_neq_nodes(elt, null); + + var attrs = elt.attributes; + for (var j = 0; j < attrs.length; j++) { + var att = attrs.item(j); + check_neq_nodes(att, null); + + for (var k = 0; k < att.childNodes.length; k++) { + check_neq_nodes(att.childNodes.item(k), null); + } + } + } +}).skip(); + +add_task(function test_isEqualNode_wholeDoc() { + doc = ParseFile("isequalnode_data.xml"); + var doc2 = ParseFile("isequalnode_data.xml"); + var tw1 = doc.createTreeWalker(doc, NodeFilter.SHOW_ALL, null); + var tw2 = doc2.createTreeWalker(doc2, NodeFilter.SHOW_ALL, null); + do { + check_eq_nodes(tw1.currentNode, tw2.currentNode); + tw1.nextNode(); + } while (tw2.nextNode()); +}); + +// TESTING FUNCTIONS + +/** + * Compares the first and third (zero-indexed) child nodes of the element + * (typically to allow whitespace) referenced by parentId for isEqualNode + * equality or inequality based on the value of areEqual. + * + * Note that this means that the contents of the element referenced by parentId + * are whitespace-sensitive, and a stray space introduced during an edit to the + * file could result in a correct but unexpected (in)equality failure. + */ +function equality_check_kids(parentId, areEqual) { + var parent = doc.getElementById(parentId); + var kid1 = parent.childNodes.item(1); + var kid2 = parent.childNodes.item(3); + + if (areEqual) { + check_eq_nodes(kid1, kid2); + } else { + check_neq_nodes(kid1, kid2); + } +} + +function check_eq_nodes(n1, n2) { + if (n1 && !n1.isEqualNode(n2)) { + do_throw(n1 + " should be equal to " + n2); + } + if (n2 && !n2.isEqualNode(n1)) { + do_throw(n2 + " should be equal to " + n1); + } + if (!n1 && !n2) { + do_throw("nodes both null!"); + } +} + +function check_neq_nodes(n1, n2) { + if (n1 && n1.isEqualNode(n2)) { + do_throw(n1 + " should not be equal to " + n2); + } + if (n2 && n2.isEqualNode(n1)) { + do_throw(n2 + " should not be equal to " + n1); + } + if (!n1 && !n2) { + do_throw("n1 and n2 both null!"); + } +} diff --git a/dom/base/test/unit/test_js_dev_error_interceptor.js b/dom/base/test/unit/test_js_dev_error_interceptor.js new file mode 100644 index 0000000000..8ccc0dcaf8 --- /dev/null +++ b/dom/base/test/unit/test_js_dev_error_interceptor.js @@ -0,0 +1,53 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 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/. */ + +"use strict"; + +function errors() { + return [ + // The following two errors MUST NOT be captured. + new Error("This is an error: " + Math.random()), + new RangeError("This is a RangeError: " + Math.random()), + new TypeError("This is a TypeError: " + Math.random()), + "This is a string: " + Math.random(), + null, + undefined, + Math.random(), + {}, + + // The following errors MUST be captured. + new SyntaxError("This is a SyntaxError: " + Math.random()), + new ReferenceError("This is a ReferenceError: " + Math.random()), + ]; +} + +function isDeveloperError(e) { + if (e == null || typeof e != "object") { + return false; + } + + return e.constructor == SyntaxError || e.constructor == ReferenceError; +} + +function run_test() { + ChromeUtils.clearRecentJSDevError(); + Assert.equal(ChromeUtils.recentJSDevError, undefined); + + for (let exn of errors()) { + ChromeUtils.clearRecentJSDevError(); + try { + throw exn; + } catch (e) { + // Discard error. + } + if (isDeveloperError(exn)) { + Assert.equal(ChromeUtils.recentJSDevError.message, "" + exn); + } else { + Assert.equal(ChromeUtils.recentJSDevError, undefined); + } + ChromeUtils.clearRecentJSDevError(); + Assert.equal(ChromeUtils.recentJSDevError, undefined); + } +} diff --git a/dom/base/test/unit/test_nodelist.js b/dom/base/test/unit/test_nodelist.js new file mode 100644 index 0000000000..0ad24b9bd5 --- /dev/null +++ b/dom/base/test/unit/test_nodelist.js @@ -0,0 +1,345 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +function run_test() { + test_getElementsByTagName(); + test_getElementsByTagNameNS(); + test_getElementsByAttribute(); + test_getElementsByAttributeNS(); + + // What else should we test? + // XXXbz we need more tests here to test liveness! +} + +function test_getElementsByTagName() { + var doc = ParseFile("nodelist_data_1.xml"); + var root = doc.documentElement; + + // Check that getElementsByTagName returns an HTMLCollection. + Assert.equal( + ChromeUtils.getClassName(doc.getElementsByTagName("*")), + "HTMLCollection" + ); + Assert.ok( + ChromeUtils.getClassName(root.getElementsByTagName("*")), + "HTMLCollection" + ); + + // Check that getElementsByTagName excludes the element it's called on. + Assert.equal( + doc.getElementsByTagName("*").length, + root.getElementsByTagName("*").length + 1 + ); + Assert.equal(doc.getElementById("test2").getElementsByTagName("*").length, 8); + Assert.equal( + doc.getElementById("test2").getElementsByTagName("test").length, + 3 + ); + + // Check that the first element of getElementsByTagName on the document is + // the right thing. + Assert.equal(doc.getElementsByTagName("*").item(0), root); + + // Check that we get the right things in the right order + var numTests = doc.getElementsByTagName("test").length; + Assert.equal(numTests, 5); + + for (var i = 1; i <= numTests; ++i) { + Assert.ok(Element.isInstance(doc.getElementById("test" + i))); + Assert.equal( + doc.getElementById("test" + i), + doc.getElementsByTagName("test").item(i - 1) + ); + } + + // Check that we handle tagnames containing ':' correctly + Assert.equal( + ChromeUtils.getClassName(doc.getElementsByTagName("foo:test")), + "HTMLCollection" + ); + Assert.equal(doc.getElementsByTagName("foo:test").length, 2); + + Assert.equal( + ChromeUtils.getClassName(doc.getElementsByTagName("foo2:test")), + "HTMLCollection" + ); + Assert.equal(doc.getElementsByTagName("foo2:test").length, 3); + + Assert.equal( + ChromeUtils.getClassName(doc.getElementsByTagName("bar:test")), + "HTMLCollection" + ); + Assert.equal(doc.getElementsByTagName("bar:test").length, 4); +} + +function test_getElementsByTagNameNS() { + var doc = ParseFile("nodelist_data_1.xml"); + var root = doc.documentElement; + + // Check that getElementsByTagNameNS returns an HTMLCollection. + Assert.equal( + ChromeUtils.getClassName(doc.getElementsByTagNameNS("*", "*")), + "HTMLCollection" + ); + Assert.equal( + ChromeUtils.getClassName(root.getElementsByTagNameNS("*", "*")), + "HTMLCollection" + ); + + // Check that passing "" and null for the namespace URI gives the same result + var list1 = doc.getElementsByTagNameNS("", "test"); + var list2 = doc.getElementsByTagNameNS(null, "test"); + Assert.equal(list1.length, list2.length); + for (var i = 0; i < list1.length; ++i) { + Assert.equal(list1.item(i), list2.item(i)); + } + + // Check that getElementsByTagNameNS excludes the element it's called on. + Assert.equal( + doc.getElementsByTagNameNS("*", "*").length, + root.getElementsByTagNameNS("*", "*").length + 1 + ); + Assert.equal( + doc.getElementById("test2").getElementsByTagNameNS("*", "*").length, + 8 + ); + Assert.equal( + doc.getElementById("test2").getElementsByTagNameNS("", "test").length, + 1 + ); + Assert.equal( + doc.getElementById("test2").getElementsByTagNameNS("*", "test").length, + 7 + ); + + // Check that the first element of getElementsByTagNameNS on the document is + // the right thing. + Assert.equal(doc.getElementsByTagNameNS("*", "*").item(0), root); + Assert.equal(doc.getElementsByTagNameNS(null, "*").item(0), root); + + // Check that we get the right things in the right order + + var numTests = doc.getElementsByTagNameNS("*", "test").length; + Assert.equal(numTests, 14); + + for (i = 1; i <= numTests; ++i) { + Assert.ok(Element.isInstance(doc.getElementById("test" + i))); + Assert.equal( + doc.getElementById("test" + i), + doc.getElementsByTagNameNS("*", "test").item(i - 1) + ); + } + + // Check general proper functioning of having a non-wildcard namespace. + var test2 = doc.getElementById("test2"); + Assert.equal(doc.getElementsByTagNameNS("", "test").length, 3); + Assert.equal(test2.getElementsByTagNameNS("", "test").length, 1); + Assert.equal(doc.getElementsByTagNameNS("foo", "test").length, 7); + Assert.equal(test2.getElementsByTagNameNS("foo", "test").length, 4); + Assert.equal(doc.getElementsByTagNameNS("foo2", "test").length, 0); + Assert.equal(test2.getElementsByTagNameNS("foo2", "test").length, 0); + Assert.equal(doc.getElementsByTagNameNS("bar", "test").length, 4); + Assert.equal(test2.getElementsByTagNameNS("bar", "test").length, 2); + + // Check that we handle tagnames containing ':' correctly + Assert.equal( + ChromeUtils.getClassName(doc.getElementsByTagNameNS(null, "foo:test")), + "HTMLCollection" + ); + Assert.equal(doc.getElementsByTagNameNS(null, "foo:test").length, 0); + Assert.equal(doc.getElementsByTagNameNS("foo", "foo:test").length, 0); + Assert.equal(doc.getElementsByTagNameNS("bar", "foo:test").length, 0); + Assert.equal(doc.getElementsByTagNameNS("*", "foo:test").length, 0); + + Assert.equal( + ChromeUtils.getClassName(doc.getElementsByTagNameNS(null, "foo2:test")), + "HTMLCollection" + ); + Assert.equal(doc.getElementsByTagNameNS(null, "foo2:test").length, 0); + Assert.equal(doc.getElementsByTagNameNS("foo2", "foo2:test").length, 0); + Assert.equal(doc.getElementsByTagNameNS("bar", "foo2:test").length, 0); + Assert.equal(doc.getElementsByTagNameNS("*", "foo2:test").length, 0); + + Assert.equal( + ChromeUtils.getClassName(doc.getElementsByTagNameNS(null, "bar:test")), + "HTMLCollection" + ); + Assert.equal(doc.getElementsByTagNameNS(null, "bar:test").length, 0); + Assert.equal(doc.getElementsByTagNameNS("bar", "bar:test").length, 0); + Assert.equal(doc.getElementsByTagNameNS("*", "bar:test").length, 0); + + // Check that previously-unknown namespaces are handled right. Note that we + // can just hardcode the strings, since we're running only once in XPCshell. + // If someone wants to run these in a browser, some use of Math.random() may + // be in order. + list1 = doc.getElementsByTagNameNS("random-bogus-namespace", "foo"); + list2 = doc.documentElement.getElementsByTagNameNS( + "random-bogus-namespace2", + "foo" + ); + Assert.notEqual(list1, list2); + Assert.equal(list1.length, 0); + Assert.equal(list2.length, 0); + var newNode = doc.createElementNS("random-bogus-namespace", "foo"); + doc.documentElement.appendChild(newNode); + newNode = doc.createElementNS("random-bogus-namespace2", "foo"); + doc.documentElement.appendChild(newNode); + Assert.equal(list1.length, 1); + Assert.equal(list2.length, 1); +} + +function test_getElementsByAttribute() { + var doc = ParseFile("nodelist_data_2.xhtml"); + var root = doc.documentElement; + + Assert.equal(ChromeUtils.getClassName(root), "XULElement"); + + Assert.equal( + ChromeUtils.getClassName(root.getElementsByAttribute("foo", "foo")), + "HTMLCollection" + ); + + var master1 = doc.getElementById("master1"); + var master2 = doc.getElementById("master2"); + var master3 = doc.getElementById("master3"); + var external = doc.getElementById("external"); + + Assert.equal(ChromeUtils.getClassName(master1), "XULElement"); + Assert.equal(ChromeUtils.getClassName(master2), "XULElement"); + Assert.equal(ChromeUtils.getClassName(master3), "XULElement"); + Assert.equal(ChromeUtils.getClassName(external), "XULElement"); + + // Basic tests + Assert.equal(root.getElementsByAttribute("foo", "foo").length, 14); + Assert.equal(master1.getElementsByAttribute("foo", "foo").length, 4); + + Assert.equal(root.getElementsByAttribute("foo", "bar").length, 7); + Assert.equal(master1.getElementsByAttribute("foo", "bar").length, 2); + + Assert.equal(root.getElementsByAttribute("bar", "bar").length, 7); + Assert.equal(master1.getElementsByAttribute("bar", "bar").length, 2); + + Assert.equal(root.getElementsByAttribute("foo", "*").length, 21); + Assert.equal(master1.getElementsByAttribute("foo", "*").length, 6); + + // Test the various combinations of attributes with colons in the name + Assert.equal(root.getElementsByAttribute("foo:foo", "foo").length, 16); + Assert.equal(master1.getElementsByAttribute("foo:foo", "foo").length, 5); + Assert.equal(master2.getElementsByAttribute("foo:foo", "foo").length, 4); + Assert.equal(master3.getElementsByAttribute("foo:foo", "foo").length, 4); + Assert.equal(external.getElementsByAttribute("foo:foo", "foo").length, 2); + + Assert.equal(root.getElementsByAttribute("foo:foo", "bar").length, 9); + Assert.equal(master1.getElementsByAttribute("foo:foo", "bar").length, 2); + Assert.equal(master2.getElementsByAttribute("foo:foo", "bar").length, 3); + Assert.equal(master3.getElementsByAttribute("foo:foo", "bar").length, 2); + Assert.equal(external.getElementsByAttribute("foo:foo", "bar").length, 1); + + Assert.equal(root.getElementsByAttribute("foo:bar", "foo").length, 7); + Assert.equal(master1.getElementsByAttribute("foo:bar", "foo").length, 2); + Assert.equal(master2.getElementsByAttribute("foo:bar", "foo").length, 2); + Assert.equal(master3.getElementsByAttribute("foo:bar", "foo").length, 2); + Assert.equal(external.getElementsByAttribute("foo:bar", "foo").length, 1); + + Assert.equal(root.getElementsByAttribute("foo:bar", "bar").length, 14); + Assert.equal(master1.getElementsByAttribute("foo:bar", "bar").length, 4); + Assert.equal(master2.getElementsByAttribute("foo:bar", "bar").length, 4); + Assert.equal(master3.getElementsByAttribute("foo:bar", "bar").length, 4); + Assert.equal(external.getElementsByAttribute("foo:bar", "bar").length, 2); + + Assert.equal(root.getElementsByAttribute("foo2:foo", "foo").length, 8); + Assert.equal(master1.getElementsByAttribute("foo2:foo", "foo").length, 2); + Assert.equal(master2.getElementsByAttribute("foo2:foo", "foo").length, 2); + Assert.equal(master3.getElementsByAttribute("foo2:foo", "foo").length, 3); + Assert.equal(external.getElementsByAttribute("foo2:foo", "foo").length, 1); + + Assert.equal(root.getElementsByAttribute("foo:foo", "*").length, 25); + Assert.equal(master1.getElementsByAttribute("foo:foo", "*").length, 7); + Assert.equal(master2.getElementsByAttribute("foo:foo", "*").length, 7); + Assert.equal(master3.getElementsByAttribute("foo:foo", "*").length, 6); + Assert.equal(external.getElementsByAttribute("foo:foo", "*").length, 3); + + Assert.equal(root.getElementsByAttribute("foo2:foo", "bar").length, 0); + Assert.equal(root.getElementsByAttribute("foo:foo", "baz").length, 0); +} + +function test_getElementsByAttributeNS() { + var doc = ParseFile("nodelist_data_2.xhtml"); + var root = doc.documentElement; + + Assert.equal(ChromeUtils.getClassName(root), "XULElement"); + + // Check that getElementsByAttributeNS returns an HTMLCollection. + Assert.equal( + ChromeUtils.getClassName(root.getElementsByAttributeNS("*", "*", "*")), + "HTMLCollection" + ); + + var master1 = doc.getElementById("master1"); + var master2 = doc.getElementById("master2"); + var master3 = doc.getElementById("master3"); + var external = doc.getElementById("external"); + + Assert.equal(ChromeUtils.getClassName(master1), "XULElement"); + Assert.equal(ChromeUtils.getClassName(master2), "XULElement"); + Assert.equal(ChromeUtils.getClassName(master3), "XULElement"); + Assert.equal(ChromeUtils.getClassName(external), "XULElement"); + + // Test wildcard namespace + Assert.equal(root.getElementsByAttributeNS("*", "foo", "foo").length, 38); + Assert.equal(master1.getElementsByAttributeNS("*", "foo", "foo").length, 11); + Assert.equal(master2.getElementsByAttributeNS("*", "foo", "foo").length, 10); + Assert.equal(master3.getElementsByAttributeNS("*", "foo", "foo").length, 11); + + Assert.equal(root.getElementsByAttributeNS("*", "foo", "bar").length, 16); + Assert.equal(master1.getElementsByAttributeNS("*", "foo", "bar").length, 4); + Assert.equal(master2.getElementsByAttributeNS("*", "foo", "bar").length, 5); + Assert.equal(master3.getElementsByAttributeNS("*", "foo", "bar").length, 4); + + Assert.equal(root.getElementsByAttributeNS("*", "bar", "bar").length, 21); + Assert.equal(master1.getElementsByAttributeNS("*", "bar", "bar").length, 6); + Assert.equal(master2.getElementsByAttributeNS("*", "bar", "bar").length, 6); + Assert.equal(master3.getElementsByAttributeNS("*", "bar", "bar").length, 6); + + Assert.equal(root.getElementsByAttributeNS("*", "foo", "*").length, 54); + Assert.equal(master1.getElementsByAttributeNS("*", "foo", "*").length, 15); + Assert.equal(master2.getElementsByAttributeNS("*", "foo", "*").length, 15); + Assert.equal(master3.getElementsByAttributeNS("*", "foo", "*").length, 15); + + // Test null namespace. This should be the same as getElementsByAttribute. + Assert.equal( + root.getElementsByAttributeNS("", "foo", "foo").length, + root.getElementsByAttribute("foo", "foo").length + ); + Assert.equal( + master1.getElementsByAttributeNS("", "foo", "foo").length, + master1.getElementsByAttribute("foo", "foo").length + ); + Assert.equal( + master2.getElementsByAttributeNS("", "foo", "foo").length, + master2.getElementsByAttribute("foo", "foo").length + ); + Assert.equal( + master3.getElementsByAttributeNS("", "foo", "foo").length, + master3.getElementsByAttribute("foo", "foo").length + ); + + // Test namespace "foo" + Assert.equal(root.getElementsByAttributeNS("foo", "foo", "foo").length, 24); + Assert.equal(master1.getElementsByAttributeNS("foo", "foo", "foo").length, 7); + Assert.equal(master2.getElementsByAttributeNS("foo", "foo", "foo").length, 6); + Assert.equal(master3.getElementsByAttributeNS("foo", "foo", "foo").length, 7); + + Assert.equal(root.getElementsByAttributeNS("foo", "foo", "bar").length, 9); + Assert.equal(master1.getElementsByAttributeNS("foo", "foo", "bar").length, 2); + Assert.equal(master2.getElementsByAttributeNS("foo", "foo", "bar").length, 3); + Assert.equal(master3.getElementsByAttributeNS("foo", "foo", "bar").length, 2); + + Assert.equal(root.getElementsByAttributeNS("foo", "bar", "foo").length, 7); + Assert.equal(master1.getElementsByAttributeNS("foo", "bar", "foo").length, 2); + Assert.equal(master2.getElementsByAttributeNS("foo", "bar", "foo").length, 2); + Assert.equal(master3.getElementsByAttributeNS("foo", "bar", "foo").length, 2); + + Assert.equal(root.getElementsByAttributeNS("foo", "bar", "bar").length, 14); + Assert.equal(master1.getElementsByAttributeNS("foo", "bar", "bar").length, 4); + Assert.equal(master2.getElementsByAttributeNS("foo", "bar", "bar").length, 4); + Assert.equal(master3.getElementsByAttributeNS("foo", "bar", "bar").length, 4); +} diff --git a/dom/base/test/unit/test_normalize.js b/dom/base/test/unit/test_normalize.js new file mode 100644 index 0000000000..b7d89f14f8 --- /dev/null +++ b/dom/base/test/unit/test_normalize.js @@ -0,0 +1,100 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 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/. */ + +function run_test() { + /* + * NOTE: [i] is not allowed in this test, since it's done via classinfo and + * we don't have that in xpcshell; the workaround is item(i). Suck. + */ + init(); + + test_element(); + + // more tests would be nice here (such as for documents), but the primary + // uses of Node.normalize() are in test_element; stuff beyond this is either + // unimplemented or is unlikely to be used all that much within a browser + // DOM implementation +} + +// TEST CODE + +var doc; // cache for use in all tests + +function init() { + doc = ParseFile("empty_document.xml"); +} + +function test_element() { + var x = doc.createElement("funk"); + + // one empty Text node + x.appendChild(doc.createTextNode("")); + Assert.equal(x.childNodes.length, 1); + + x.normalize(); + Assert.equal(x.childNodes.length, 0); + + // multiple empty Text nodes + x.appendChild(doc.createTextNode("")); + x.appendChild(doc.createTextNode("")); + Assert.equal(x.childNodes.length, 2); + + x.normalize(); + Assert.equal(x.childNodes.length, 0); + + // empty Text node followed by other Text node + x.appendChild(doc.createTextNode("")); + x.appendChild(doc.createTextNode("Guaraldi")); + Assert.equal(x.childNodes.length, 2); + + x.normalize(); + Assert.equal(x.childNodes.length, 1); + Assert.equal(x.childNodes.item(0).nodeValue, "Guaraldi"); + + // Text node followed by empty Text node + clearKids(x); + x.appendChild(doc.createTextNode("Guaraldi")); + x.appendChild(doc.createTextNode("")); + Assert.equal(x.childNodes.length, 2); + + x.normalize(); + Assert.equal(x.childNodes.item(0).nodeValue, "Guaraldi"); + + // Text node followed by empty Text node followed by other Node + clearKids(x); + x.appendChild(doc.createTextNode("Guaraldi")); + x.appendChild(doc.createTextNode("")); + x.appendChild(doc.createElement("jazzy")); + Assert.equal(x.childNodes.length, 3); + + x.normalize(); + Assert.equal(x.childNodes.length, 2); + Assert.equal(x.childNodes.item(0).nodeValue, "Guaraldi"); + Assert.equal(x.childNodes.item(1).nodeName, "jazzy"); + + // Nodes are recursively normalized + clearKids(x); + var kid = doc.createElement("eit"); + kid.appendChild(doc.createTextNode("")); + + x.appendChild(doc.createTextNode("Guaraldi")); + x.appendChild(doc.createTextNode("")); + x.appendChild(kid); + Assert.equal(x.childNodes.length, 3); + Assert.equal(x.childNodes.item(2).childNodes.length, 1); + + x.normalize(); + Assert.equal(x.childNodes.length, 2); + Assert.equal(x.childNodes.item(0).nodeValue, "Guaraldi"); + Assert.equal(x.childNodes.item(1).childNodes.length, 0); +} + +// UTILITY FUNCTIONS + +function clearKids(node) { + while (node.hasChildNodes()) { + node.removeChild(node.childNodes.item(0)); + } +} diff --git a/dom/base/test/unit/test_range.js b/dom/base/test/unit/test_range.js new file mode 100644 index 0000000000..8b9f5c0b8b --- /dev/null +++ b/dom/base/test/unit/test_range.js @@ -0,0 +1,465 @@ +/* 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/. */ + +const UNORDERED_TYPE = 8; // XPathResult.ANY_UNORDERED_NODE_TYPE + +/** + * Determine if the data node has only ignorable white-space. + * + * @return NodeFilter.FILTER_SKIP if it does. + * @return NodeFilter.FILTER_ACCEPT otherwise. + */ +function isWhitespace(aNode) { + return /\S/.test(aNode.nodeValue) + ? NodeFilter.FILTER_SKIP + : NodeFilter.FILTER_ACCEPT; +} + +/** + * Create a DocumentFragment with cloned children equaling a node's children. + * + * @param aNode The node to copy from. + * + * @return DocumentFragment node. + */ +function getFragment(aNode) { + var frag = aNode.ownerDocument.createDocumentFragment(); + for (var i = 0; i < aNode.childNodes.length; i++) { + frag.appendChild(aNode.childNodes.item(i).cloneNode(true)); + } + return frag; +} + +// Goodies from head_content.js +const parser = getParser(); + +/** + * Translate an XPath to a DOM node. This method uses a document + * fragment as context node. + * + * @param aContextNode The context node to apply the XPath to. + * @param aPath The XPath to use. + * + * @return Node The target node retrieved from the XPath. + */ +function evalXPathInDocumentFragment(aContextNode, aPath) { + Assert.equal(ChromeUtils.getClassName(aContextNode), "DocumentFragment"); + Assert.ok(aContextNode.childNodes.length); + if (aPath == ".") { + return aContextNode; + } + + // Separate the fragment's xpath lookup from the rest. + var firstSlash = aPath.indexOf("/"); + if (firstSlash == -1) { + firstSlash = aPath.length; + } + var prefix = aPath.substr(0, firstSlash); + var realPath = aPath.substr(firstSlash + 1); + if (!realPath) { + realPath = "."; + } + + // Set up a special node filter to look among the fragment's child nodes. + var childIndex = 1; + var bracketIndex = prefix.indexOf("["); + if (bracketIndex != -1) { + childIndex = Number( + prefix.substring(bracketIndex + 1, prefix.indexOf("]")) + ); + Assert.ok(childIndex > 0); + prefix = prefix.substr(0, bracketIndex); + } + + var targetType = NodeFilter.SHOW_ELEMENT; + var targetNodeName = prefix; + if (prefix.indexOf("processing-instruction(") == 0) { + targetType = NodeFilter.SHOW_PROCESSING_INSTRUCTION; + targetNodeName = prefix.substring( + prefix.indexOf("(") + 2, + prefix.indexOf(")") - 1 + ); + } + switch (prefix) { + case "text()": + targetType = NodeFilter.SHOW_TEXT | NodeFilter.SHOW_CDATA_SECTION; + targetNodeName = null; + break; + case "comment()": + targetType = NodeFilter.SHOW_COMMENT; + targetNodeName = null; + break; + case "node()": + targetType = NodeFilter.SHOW_ALL; + targetNodeName = null; + } + + var filter = { + count: 0, + + // NodeFilter + acceptNode: function acceptNode(aNode) { + if (aNode.parentNode != aContextNode) { + // Don't bother looking at kids either. + return NodeFilter.FILTER_REJECT; + } + + if (targetNodeName && targetNodeName != aNode.nodeName) { + return NodeFilter.FILTER_SKIP; + } + + this.count++; + if (this.count != childIndex) { + return NodeFilter.FILTER_SKIP; + } + + return NodeFilter.FILTER_ACCEPT; + }, + }; + + // Look for the node matching the step from the document fragment. + var walker = aContextNode.ownerDocument.createTreeWalker( + aContextNode, + targetType, + filter + ); + var targetNode = walker.nextNode(); + Assert.notEqual(targetNode, null); + + // Apply our remaining xpath to the found node. + var expr = aContextNode.ownerDocument.createExpression(realPath, null); + var result = expr.evaluate(targetNode, UNORDERED_TYPE, null); + return result.singleNodeValue; +} + +/** + * Get a DOM range corresponding to the test's source node. + * + * @param aSourceNode <source/> element with range information. + * @param aFragment DocumentFragment generated with getFragment(). + * + * @return Range object. + */ +function getRange(aSourceNode, aFragment) { + Assert.ok(Element.isInstance(aSourceNode)); + Assert.equal(ChromeUtils.getClassName(aFragment), "DocumentFragment"); + var doc = aSourceNode.ownerDocument; + + var containerPath = aSourceNode.getAttribute("startContainer"); + var startContainer = evalXPathInDocumentFragment(aFragment, containerPath); + var startOffset = Number(aSourceNode.getAttribute("startOffset")); + + containerPath = aSourceNode.getAttribute("endContainer"); + var endContainer = evalXPathInDocumentFragment(aFragment, containerPath); + var endOffset = Number(aSourceNode.getAttribute("endOffset")); + + var range = doc.createRange(); + range.setStart(startContainer, startOffset); + range.setEnd(endContainer, endOffset); + return range; +} + +/** + * Get the document for a given path, and clean it up for our tests. + * + * @param aPath The path to the local document. + */ +function getParsedDocument(aPath) { + return do_parse_document(aPath, "application/xml").then( + processParsedDocument + ); +} + +function processParsedDocument(doc) { + Assert.ok(doc.documentElement.localName != "parsererror"); + Assert.equal(ChromeUtils.getClassName(doc), "XMLDocument"); + + // Clean out whitespace. + var walker = doc.createTreeWalker( + doc, + NodeFilter.SHOW_TEXT | NodeFilter.SHOW_CDATA_SECTION, + isWhitespace + ); + while (walker.nextNode()) { + var parent = walker.currentNode.parentNode; + parent.removeChild(walker.currentNode); + walker.currentNode = parent; + } + + // Clean out mandatory splits between nodes. + var splits = doc.getElementsByTagName("split"); + var i; + for (i = splits.length - 1; i >= 0; i--) { + let node = splits.item(i); + node.remove(); + } + splits = null; + + // Replace empty CDATA sections. + var emptyData = doc.getElementsByTagName("empty-cdata"); + for (i = emptyData.length - 1; i >= 0; i--) { + let node = emptyData.item(i); + var cdata = doc.createCDATASection(""); + node.parentNode.replaceChild(cdata, node); + } + + return doc; +} + +/** + * Run the extraction tests. + */ +function run_extract_test() { + var filePath = "test_delete_range.xml"; + getParsedDocument(filePath).then(do_extract_test); +} + +function do_extract_test(doc) { + var tests = doc.getElementsByTagName("test"); + + // Run our deletion, extraction tests. + for (var i = 0; i < tests.length; i++) { + dump("Configuring for test " + i + "\n"); + var currentTest = tests.item(i); + + // Validate the test is properly formatted for what this harness expects. + var baseSource = currentTest.firstChild; + Assert.equal(baseSource.nodeName, "source"); + var baseResult = baseSource.nextSibling; + Assert.equal(baseResult.nodeName, "result"); + var baseExtract = baseResult.nextSibling; + Assert.equal(baseExtract.nodeName, "extract"); + Assert.equal(baseExtract.nextSibling, null); + + /* We do all our tests on DOM document fragments, derived from the test + element's children. This lets us rip the various fragments to shreds, + while preserving the original elements so we can make more copies of + them. + + After the range's extraction or deletion is done, we use + Node.isEqualNode() between the altered source fragment and the + result fragment. We also run isEqualNode() between the extracted + fragment and the fragment from the baseExtract node. If they are not + equal, we have failed a test. + + We also have to ensure the original nodes on the end points of the + range are still in the source fragment. This is bug 332148. The nodes + may not be replaced with equal but separate nodes. The range extraction + may alter these nodes - in the case of text containers, they will - but + the nodes must stay there, to preserve references such as user data, + event listeners, etc. + + First, an extraction test. + */ + + var resultFrag = getFragment(baseResult); + var extractFrag = getFragment(baseExtract); + + dump("Extract contents test " + i + "\n\n"); + var baseFrag = getFragment(baseSource); + var baseRange = getRange(baseSource, baseFrag); + var startContainer = baseRange.startContainer; + var endContainer = baseRange.endContainer; + + var cutFragment = baseRange.extractContents(); + dump("cutFragment: " + cutFragment + "\n"); + if (cutFragment) { + Assert.ok(extractFrag.isEqualNode(cutFragment)); + } else { + Assert.equal(extractFrag.firstChild, null); + } + Assert.ok(baseFrag.isEqualNode(resultFrag)); + + dump("Ensure the original nodes weren't extracted - test " + i + "\n\n"); + var walker = doc.createTreeWalker(baseFrag, NodeFilter.SHOW_ALL, null); + var foundStart = false; + var foundEnd = false; + do { + if (walker.currentNode == startContainer) { + foundStart = true; + } + + if (walker.currentNode == endContainer) { + // An end container node should not come before the start container node. + Assert.ok(foundStart); + foundEnd = true; + break; + } + } while (walker.nextNode()); + Assert.ok(foundEnd); + + /* Now, we reset our test for the deleteContents case. This one differs + from the extractContents case only in that there is no extracted document + fragment to compare against. So we merely compare the starting fragment, + minus the extracted content, against the result fragment. + */ + dump("Delete contents test " + i + "\n\n"); + baseFrag = getFragment(baseSource); + baseRange = getRange(baseSource, baseFrag); + startContainer = baseRange.startContainer; + endContainer = baseRange.endContainer; + baseRange.deleteContents(); + Assert.ok(baseFrag.isEqualNode(resultFrag)); + + dump("Ensure the original nodes weren't deleted - test " + i + "\n\n"); + walker = doc.createTreeWalker(baseFrag, NodeFilter.SHOW_ALL, null); + foundStart = false; + foundEnd = false; + do { + if (walker.currentNode == startContainer) { + foundStart = true; + } + + if (walker.currentNode == endContainer) { + // An end container node should not come before the start container node. + Assert.ok(foundStart); + foundEnd = true; + break; + } + } while (walker.nextNode()); + Assert.ok(foundEnd); + + // Clean up after ourselves. + walker = null; + } +} + +/** + * Miscellaneous tests not covered above. + */ +function run_miscellaneous_tests() { + var filePath = "test_delete_range.xml"; + getParsedDocument(filePath).then(do_miscellaneous_tests); +} + +function isText(node) { + return ( + node.nodeType == node.TEXT_NODE || node.nodeType == node.CDATA_SECTION_NODE + ); +} + +function do_miscellaneous_tests(doc) { + var tests = doc.getElementsByTagName("test"); + + // Let's try some invalid inputs to our DOM range and see what happens. + var currentTest = tests.item(0); + var baseSource = currentTest.firstChild; + + var baseFrag = getFragment(baseSource); + + var baseRange = getRange(baseSource, baseFrag); + var startContainer = baseRange.startContainer; + var endContainer = baseRange.endContainer; + var startOffset = baseRange.startOffset; + var endOffset = baseRange.endOffset; + + // Text range manipulation. + if ( + endOffset > startOffset && + startContainer == endContainer && + isText(startContainer) + ) { + // Invalid start node + try { + baseRange.setStart(null, 0); + do_throw("Should have thrown NOT_OBJECT_ERR!"); + } catch (e) { + Assert.equal(e.constructor.name, "TypeError"); + } + + // Invalid start node + try { + baseRange.setStart({}, 0); + do_throw("Should have thrown SecurityError!"); + } catch (e) { + Assert.equal(e.constructor.name, "TypeError"); + } + + // Invalid index + try { + baseRange.setStart(startContainer, -1); + do_throw("Should have thrown IndexSizeError!"); + } catch (e) { + Assert.equal(e.name, "IndexSizeError"); + } + + // Invalid index + var newOffset = isText(startContainer) + ? startContainer.nodeValue.length + 1 + : startContainer.childNodes.length + 1; + try { + baseRange.setStart(startContainer, newOffset); + do_throw("Should have thrown IndexSizeError!"); + } catch (e) { + Assert.equal(e.name, "IndexSizeError"); + } + + newOffset--; + // Valid index + baseRange.setStart(startContainer, newOffset); + Assert.equal(baseRange.startContainer, baseRange.endContainer); + Assert.equal(baseRange.startOffset, newOffset); + Assert.ok(baseRange.collapsed); + + // Valid index + baseRange.setEnd(startContainer, 0); + Assert.equal(baseRange.startContainer, baseRange.endContainer); + Assert.equal(baseRange.startOffset, 0); + Assert.ok(baseRange.collapsed); + } else { + do_throw( + "The first test should be a text-only range test. Test is invalid." + ); + } + + /* See what happens when a range has a startContainer in one fragment, and an + endContainer in another. According to the DOM spec, section 2.4, the range + should collapse to the new container and offset. */ + baseRange = getRange(baseSource, baseFrag); + startContainer = baseRange.startContainer; + startOffset = baseRange.startOffset; + endContainer = baseRange.endContainer; + endOffset = baseRange.endOffset; + + dump("External fragment test\n\n"); + + var externalTest = tests.item(1); + var externalSource = externalTest.firstChild; + var externalFrag = getFragment(externalSource); + var externalRange = getRange(externalSource, externalFrag); + + baseRange.setEnd(externalRange.endContainer, 0); + Assert.equal(baseRange.startContainer, externalRange.endContainer); + Assert.equal(baseRange.startOffset, 0); + Assert.ok(baseRange.collapsed); + + /* + // XXX ajvincent if rv == WRONG_DOCUMENT_ERR, return false? + do_check_false(baseRange.isPointInRange(startContainer, startOffset)); + do_check_false(baseRange.isPointInRange(startContainer, startOffset + 1)); + do_check_false(baseRange.isPointInRange(endContainer, endOffset)); + */ + + // Requested by smaug: A range involving a comment as a document child. + doc = parser.parseFromString("<!-- foo --><foo/>", "application/xml"); + Assert.equal(ChromeUtils.getClassName(doc), "XMLDocument"); + Assert.equal(doc.childNodes.length, 2); + baseRange = doc.createRange(); + baseRange.setStart(doc.firstChild, 1); + baseRange.setEnd(doc.firstChild, 2); + var frag = baseRange.extractContents(); + Assert.equal(frag.childNodes.length, 1); + Assert.ok(ChromeUtils.getClassName(frag.firstChild) == "Comment"); + Assert.equal(frag.firstChild.nodeType, frag.COMMENT_NODE); + Assert.equal(frag.firstChild.nodeValue, "f"); + + /* smaug also requested attribute tests. Sadly, those are not yet supported + in ranges - see https://bugzilla.mozilla.org/show_bug.cgi?id=302775. + */ +} + +function run_test() { + run_extract_test(); + run_miscellaneous_tests(); +} diff --git a/dom/base/test/unit/test_serializers_entities.js b/dom/base/test/unit/test_serializers_entities.js new file mode 100644 index 0000000000..55778ce3d6 --- /dev/null +++ b/dom/base/test/unit/test_serializers_entities.js @@ -0,0 +1,99 @@ +const encoders = { + xml: doc => { + let enc = Cu.createDocumentEncoder("text/xml"); + enc.init(doc, "text/xml", Ci.nsIDocumentEncoder.OutputLFLineBreak); + return enc; + }, + html: doc => { + let enc = Cu.createDocumentEncoder("text/html"); + enc.init(doc, "text/html", Ci.nsIDocumentEncoder.OutputLFLineBreak); + return enc; + }, + htmlBasic: doc => { + let enc = Cu.createDocumentEncoder("text/html"); + enc.init( + doc, + "text/html", + Ci.nsIDocumentEncoder.OutputEncodeBasicEntities | + Ci.nsIDocumentEncoder.OutputLFLineBreak + ); + return enc; + }, + xhtml: doc => { + let enc = Cu.createDocumentEncoder("application/xhtml+xml"); + enc.init( + doc, + "application/xhtml+xml", + Ci.nsIDocumentEncoder.OutputLFLineBreak + ); + return enc; + }, +}; + +// Which characters should we encode as entities? It depends on the serializer. +const encodeAll = { html: true, htmlBasic: true, xhtml: true, xml: true }; +const encodeHTMLBasic = { + html: false, + htmlBasic: true, + xhtml: false, + xml: false, +}; +const encodeXML = { html: false, htmlBasic: false, xhtml: true, xml: true }; +const encodeNone = { html: false, htmlBasic: false, xhtml: false, xml: false }; +const encodingInfoMap = new Map([ + // Basic sanity chars '<', '>', '"', '&' get encoded in all cases. + ["<", encodeAll], + [">", encodeAll], + ["&", encodeAll], + // nbsp is only encoded with the HTML encoder when encoding basic entities. + ["\xA0", encodeHTMLBasic], +]); + +const encodingMap = new Map([ + ["<", "<"], + [">", ">"], + ["&", "&"], + // nbsp is only encoded with the HTML encoder when encoding basic entities. + ["\xA0", " "], +]); + +function encodingInfoForChar(c) { + var info = encodingInfoMap.get(c); + if (info) { + return info; + } + return encodeNone; +} + +function encodingForChar(c, type) { + var info = encodingInfoForChar(c); + if (!info[type]) { + return c; + } + return encodingMap.get(c); +} + +const doc = new DOMParser().parseFromString("<root></root>", "text/xml"); +const root = doc.documentElement; +for (let i = 0; i < 255; ++i) { + let el = doc.createElement("span"); + el.textContent = String.fromCharCode(i); + root.appendChild(el); +} +for (let type of ["xml", "xhtml", "htmlBasic", "html"]) { + let str = encoders[type](doc).encodeToString(); + const prefix = "<root><span>"; + const suffix = "</span></root>"; + Assert.ok(str.startsWith(prefix), `${type} serialization starts correctly`); + Assert.ok(str.endsWith(suffix), `${type} serialization ends correctly`); + str = str.substring(prefix.length, str.length - suffix.length); + let encodings = str.split("</span><span>"); + for (let i = 0; i < 255; ++i) { + let c = String.fromCharCode(i); + Assert.equal( + encodingForChar(c, type), + encodings[i], + `${type} encoding of char ${i} is correct` + ); + } +} diff --git a/dom/base/test/unit/test_serializers_entities_in_attr.js b/dom/base/test/unit/test_serializers_entities_in_attr.js new file mode 100644 index 0000000000..2497a480a7 --- /dev/null +++ b/dom/base/test/unit/test_serializers_entities_in_attr.js @@ -0,0 +1,108 @@ +const encoders = { + xml: doc => { + let enc = Cu.createDocumentEncoder("text/xml"); + enc.init(doc, "text/xml", Ci.nsIDocumentEncoder.OutputLFLineBreak); + return enc; + }, + html: doc => { + let enc = Cu.createDocumentEncoder("text/html"); + enc.init(doc, "text/html", Ci.nsIDocumentEncoder.OutputLFLineBreak); + return enc; + }, + htmlBasic: doc => { + let enc = Cu.createDocumentEncoder("text/html"); + enc.init( + doc, + "text/html", + Ci.nsIDocumentEncoder.OutputEncodeBasicEntities | + Ci.nsIDocumentEncoder.OutputLFLineBreak + ); + return enc; + }, + xhtml: doc => { + let enc = Cu.createDocumentEncoder("application/xhtml+xml"); + enc.init( + doc, + "application/xhtml+xml", + Ci.nsIDocumentEncoder.OutputLFLineBreak + ); + return enc; + }, +}; + +// Which characters should we encode as entities? It depends on the serializer. +const encodeAll = { html: true, htmlBasic: true, xhtml: true, xml: true }; +const encodeHTMLBasic = { + html: false, + htmlBasic: true, + xhtml: false, + xml: false, +}; +const encodeXML = { html: false, htmlBasic: false, xhtml: true, xml: true }; +const encodeNone = { html: false, htmlBasic: false, xhtml: false, xml: false }; +const encodingInfoMap = new Map([ + // Basic sanity chars '<', '>', '"', '&' get encoded in all cases. + ["<", encodeAll], + [">", encodeAll], + ['"', encodeAll], + ["&", encodeAll], + // nbsp is only encoded with the HTML encoder when encoding basic entities. + ["\xA0", encodeHTMLBasic], + // Whitespace bits are only encoded in XML. + ["\n", encodeXML], + ["\r", encodeXML], + ["\t", encodeXML], +]); + +const encodingMap = new Map([ + ["<", "<"], + [">", ">"], + ['"', """], + ["&", "&"], + ["\xA0", " "], + ["\n", "
"], + ["\r", "
"], + ["\t", "	"], +]); + +function encodingInfoForChar(c) { + var info = encodingInfoMap.get(c); + if (info) { + return info; + } + return encodeNone; +} + +function encodingForChar(c, type) { + var info = encodingInfoForChar(c); + if (!info[type]) { + return c; + } + return encodingMap.get(c); +} + +const doc = new DOMParser().parseFromString("<root></root>", "text/xml"); +const root = doc.documentElement; +for (let i = 0; i < 255; ++i) { + let el = doc.createElement("span"); + el.setAttribute("x", String.fromCharCode(i)); + el.textContent = " "; + root.appendChild(el); +} +for (let type of ["xml", "xhtml", "htmlBasic", "html"]) { + let str = encoders[type](doc).encodeToString(); + const prefix = '<root><span x="'; + const suffix = '"> </span></root>'; + Assert.ok(str.startsWith(prefix), `${type} serialization starts correctly`); + Assert.ok(str.endsWith(suffix), `${type} serialization ends correctly`); + str = str.substring(prefix.length, str.length - suffix.length); + let encodings = str.split('"> </span><span x="'); + for (let i = 0; i < 255; ++i) { + let c = String.fromCharCode(i); + Assert.equal( + encodingForChar(c, type), + encodings[i], + `${type} encoding of char ${i} is correct` + ); + } +} diff --git a/dom/base/test/unit/test_structuredcloneholder.js b/dom/base/test/unit/test_structuredcloneholder.js new file mode 100644 index 0000000000..fd9afc7f03 --- /dev/null +++ b/dom/base/test/unit/test_structuredcloneholder.js @@ -0,0 +1,159 @@ +"use strict"; + +const global = this; + +add_task(async function test_structuredCloneHolder() { + let principal = Services.scriptSecurityManager.createContentPrincipal( + Services.io.newURI("http://example.com/"), + {} + ); + + let sandbox = Cu.Sandbox(principal); + + const obj = { foo: [{ bar: "baz" }] }; + + let holder = new StructuredCloneHolder("", "", obj); + + // Test same-compartment deserialization + + let res = holder.deserialize(global, true); + + notEqual( + res, + obj, + "Deserialized result is a different object from the original" + ); + + deepEqual( + res, + obj, + "Deserialized result is deeply equivalent to the original" + ); + + equal( + Cu.getObjectPrincipal(res), + Cu.getObjectPrincipal(global), + "Deserialized result has the correct principal" + ); + + // Test non-object-value round-trip. + + equal( + new StructuredCloneHolder("", "", "foo").deserialize(global), + "foo", + "Round-tripping non-object values works as expected" + ); + + // Test cross-compartment deserialization + + res = holder.deserialize(sandbox, true); + + notEqual( + res, + obj, + "Cross-compartment-deserialized result is a different object from the original" + ); + + deepEqual( + res, + obj, + "Cross-compartment-deserialized result is deeply equivalent to the original" + ); + + equal( + Cu.getObjectPrincipal(res), + principal, + "Cross-compartment-deserialized result has the correct principal" + ); + + // Test message manager transportability + + const MSG = "StructuredCloneHolder"; + + let resultPromise = new Promise(resolve => { + Services.ppmm.addMessageListener(MSG, resolve); + }); + + Services.cpmm.sendAsyncMessage(MSG, holder); + + res = await resultPromise; + + ok( + StructuredCloneHolder.isInstance(res.data), + "Sending structured clone holders through message managers works as expected" + ); + + deepEqual( + res.data.deserialize(global, true), + obj, + "Sending structured clone holders through message managers works as expected" + ); + + // Test that attempting to deserialize a neutered holder throws. + + deepEqual( + holder.deserialize(global), + obj, + "Deserialized result is correct when discarding data" + ); + + Assert.throws( + () => holder.deserialize(global), + err => err.result == Cr.NS_ERROR_NOT_INITIALIZED, + "Attempting to deserialize neutered holder throws" + ); + + Assert.throws( + () => holder.deserialize(global, true), + err => err.result == Cr.NS_ERROR_NOT_INITIALIZED, + "Attempting to deserialize neutered holder throws" + ); +}); + +// Test that X-rays passed to an exported function are serialized +// through their exported wrappers. +add_task(async function test_structuredCloneHolder_xray() { + let principal = Services.scriptSecurityManager.createContentPrincipal( + Services.io.newURI("http://example.com/"), + {} + ); + + let sandbox1 = Cu.Sandbox(principal, { wantXrays: true }); + + let sandbox2 = Cu.Sandbox(principal, { wantXrays: true }); + Cu.evalInSandbox(`this.x = {y: "z", get z() { return "q" }}`, sandbox2); + + sandbox1.x = sandbox2.x; + + let holder; + Cu.exportFunction( + function serialize(val) { + holder = new StructuredCloneHolder("", "", val, sandbox1); + }, + sandbox1, + { defineAs: "serialize" } + ); + + Cu.evalInSandbox(`serialize(x)`, sandbox1); + + const obj = { y: "z" }; + + let res = holder.deserialize(global); + + deepEqual( + res, + obj, + "Deserialized result is deeply equivalent to the expected object" + ); + deepEqual( + res, + sandbox2.x, + "Deserialized result is deeply equivalent to the X-ray-wrapped object" + ); + + equal( + Cu.getObjectPrincipal(res), + Cu.getObjectPrincipal(global), + "Deserialized result has the correct principal" + ); +}); diff --git a/dom/base/test/unit/test_thirdpartyutil.js b/dom/base/test/unit/test_thirdpartyutil.js new file mode 100644 index 0000000000..777dc8dc62 --- /dev/null +++ b/dom/base/test/unit/test_thirdpartyutil.js @@ -0,0 +1,99 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +// Test ThirdPartyUtil methods. See mozIThirdPartyUtil. + +var prefs = Services.prefs; + +// Since this test creates a TYPE_DOCUMENT channel via javascript, it will +// end up using the wrong LoadInfo constructor. Setting this pref will disable +// the ContentPolicyType assertion in the constructor. +prefs.setBoolPref("network.loadinfo.skip_type_assertion", true); + +var NS_ERROR_INVALID_ARG = Cr.NS_ERROR_INVALID_ARG; + +function do_check_throws(f, result, stack) { + if (!stack) { + try { + // We might not have a 'Components' object. + stack = Components.stack.caller; + } catch (e) {} + } + + try { + f(); + } catch (exc) { + Assert.equal(exc.result, result); + return; + } + do_throw("expected " + result + " exception, none thrown", stack); +} + +function run_test() { + let util = Cc["@mozilla.org/thirdpartyutil;1"].getService( + Ci.mozIThirdPartyUtil + ); + + // Create URIs and channels pointing to foo.com and bar.com. + // We will use these to put foo.com into first and third party contexts. + let spec1 = "http://foo.com/foo.html"; + let spec2 = "http://bar.com/bar.html"; + let uri1 = NetUtil.newURI(spec1); + let uri2 = NetUtil.newURI(spec2); + const contentPolicyType = Ci.nsIContentPolicy.TYPE_DOCUMENT; + let channel1 = NetUtil.newChannel({ + uri: uri1, + loadUsingSystemPrincipal: true, + contentPolicyType, + }); + NetUtil.newChannel({ + uri: uri2, + loadUsingSystemPrincipal: true, + contentPolicyType, + }); + + // Create some file:// URIs. + let filespec1 = "file://foo.txt"; + let filespec2 = "file://bar.txt"; + let fileuri1 = NetUtil.newURI(filespec1); + let fileuri2 = NetUtil.newURI(filespec2); + NetUtil.newChannel({ uri: fileuri1, loadUsingSystemPrincipal: true }); + NetUtil.newChannel({ uri: fileuri2, loadUsingSystemPrincipal: true }); + + // Test isThirdPartyURI. + Assert.ok(!util.isThirdPartyURI(uri1, uri1)); + Assert.ok(util.isThirdPartyURI(uri1, uri2)); + Assert.ok(util.isThirdPartyURI(uri2, uri1)); + Assert.ok(!util.isThirdPartyURI(fileuri1, fileuri1)); + Assert.ok(!util.isThirdPartyURI(fileuri1, fileuri2)); + Assert.ok(util.isThirdPartyURI(uri1, fileuri1)); + do_check_throws(function () { + util.isThirdPartyURI(uri1, null); + }, NS_ERROR_INVALID_ARG); + do_check_throws(function () { + util.isThirdPartyURI(null, uri1); + }, NS_ERROR_INVALID_ARG); + do_check_throws(function () { + util.isThirdPartyURI(null, null); + }, NS_ERROR_INVALID_ARG); + + // We can't test isThirdPartyWindow since we can't really set up a window + // hierarchy. We leave that to mochitests. + + // Test isThirdPartyChannel. As above, we can't test the bits that require + // a load context or window heirarchy. Because of bug 1259873, we assume + // that these are not third-party. + do_check_throws(function () { + util.isThirdPartyChannel(null); + }, NS_ERROR_INVALID_ARG); + Assert.ok(!util.isThirdPartyChannel(channel1)); + Assert.ok(!util.isThirdPartyChannel(channel1, uri1)); + Assert.ok(util.isThirdPartyChannel(channel1, uri2)); + + let httpchannel1 = channel1.QueryInterface(Ci.nsIHttpChannelInternal); + httpchannel1.forceAllowThirdPartyCookie = true; + Assert.ok(!util.isThirdPartyChannel(channel1)); + Assert.ok(!util.isThirdPartyChannel(channel1, uri1)); + Assert.ok(util.isThirdPartyChannel(channel1, uri2)); +} diff --git a/dom/base/test/unit/test_treewalker.js b/dom/base/test/unit/test_treewalker.js new file mode 100644 index 0000000000..a932965370 --- /dev/null +++ b/dom/base/test/unit/test_treewalker.js @@ -0,0 +1,23 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 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/. */ + +function run_test() { + test_treeWalker_currentNode(); +} + +// TEST CODE + +function test_treeWalker_currentNode() { + var XHTMLDocString = '<html xmlns="http://www.w3.org/1999/xhtml">'; + XHTMLDocString += "<body><input/>input</body></html>"; + + var doc = ParseXML(XHTMLDocString); + + var body = doc.getElementsByTagName("body")[0]; + var filter = NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT; + var walker = doc.createTreeWalker(body, filter, null); + walker.currentNode = body.firstChild; + walker.nextNode(); +} diff --git a/dom/base/test/unit/test_xhr_document.js b/dom/base/test/unit/test_xhr_document.js new file mode 100644 index 0000000000..7c8c1eda69 --- /dev/null +++ b/dom/base/test/unit/test_xhr_document.js @@ -0,0 +1,49 @@ +/* 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/. */ + +var { HttpServer } = ChromeUtils.importESModule( + "resource://testing-common/httpd.sys.mjs" +); + +var server = new HttpServer(); +server.start(-1); + +var docbody = + '<html xmlns="http://www.w3.org/1999/xhtml"><head></head><body></body></html>'; + +function handler(metadata, response) { + var { NetUtil } = ChromeUtils.importESModule( + "resource://gre/modules/NetUtil.sys.mjs" + ); + + let body = NetUtil.readInputStreamToString( + metadata.bodyInputStream, + metadata.bodyInputStream.available() + ); + response.setStatusLine(metadata.httpVersion, 200, "OK"); + response.write(body, body.length); +} + +function run_test() { + do_test_pending(); + server.registerPathHandler("/foo", handler); + + var parser = new DOMParser(); + let doc = parser.parseFromString(docbody, "text/html"); + let xhr = new XMLHttpRequest(); + xhr.onload = function () { + Assert.equal(xhr.responseText, docbody); + server.stop(do_test_finished); + }; + xhr.onerror = function () { + Assert.equal(false, false); + server.stop(do_test_finished); + }; + xhr.open( + "POST", + "http://localhost:" + server.identity.primaryPort + "/foo", + true + ); + xhr.send(doc); +} diff --git a/dom/base/test/unit/test_xhr_origin_attributes.js b/dom/base/test/unit/test_xhr_origin_attributes.js new file mode 100644 index 0000000000..26848af479 --- /dev/null +++ b/dom/base/test/unit/test_xhr_origin_attributes.js @@ -0,0 +1,53 @@ +let server = new HttpServer(); +server.start(-1); + +let body = + "<!DOCTYPE HTML><html><head><meta charset='utf-8'></head><body></body></html>"; + +function handler(request, response) { + response.setStatusLine(request.httpVersion, 200, "Ok"); + response.setHeader("Content-Type", "text/html", false); + + if (!request.hasHeader("Cookie")) { + response.setHeader("Set-Cookie", "test", false); + ok(true); + } else { + ok(false); + } + + response.bodyOutputStream.write(body, body.length); +} + +function run_test() { + do_test_pending(); + server.registerPathHandler("/foo", handler); + + let xhr = new XMLHttpRequest(); + xhr.open( + "GET", + "http://localhost:" + server.identity.primaryPort + "/foo", + true + ); + xhr.send(null); + + xhr.onload = function () { + // We create another XHR to connect to the same site, but this time we + // specify with different origin attributes, which will make the XHR use a + // different cookie-jar than the previous one. + let xhr2 = new XMLHttpRequest(); + xhr2.open( + "GET", + "http://localhost:" + server.identity.primaryPort + "/foo", + true + ); + xhr2.setOriginAttributes({ userContextId: 1 }); + xhr2.send(null); + + let loadInfo = xhr2.channel.loadInfo; + Assert.equal(loadInfo.originAttributes.userContextId, 1); + + xhr2.onload = function () { + server.stop(do_test_finished); + }; + }; +} diff --git a/dom/base/test/unit/test_xhr_standalone.js b/dom/base/test/unit/test_xhr_standalone.js new file mode 100644 index 0000000000..94f2d7d642 --- /dev/null +++ b/dom/base/test/unit/test_xhr_standalone.js @@ -0,0 +1,19 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +// Test setting .responseType and .withCredentials is allowed +// in non-window non-Worker context + +function run_test() { + var xhr = new XMLHttpRequest(); + xhr.open("GET", "data:,", false); + var exceptionThrown = false; + try { + xhr.responseType = ""; + xhr.withCredentials = false; + } catch (e) { + exceptionThrown = true; + } + Assert.equal(false, exceptionThrown); +} diff --git a/dom/base/test/unit/test_xml_parser.js b/dom/base/test/unit/test_xml_parser.js new file mode 100644 index 0000000000..0d6675fdab --- /dev/null +++ b/dom/base/test/unit/test_xml_parser.js @@ -0,0 +1,48 @@ +function run_test() { + for (var i = 0; i < tests.length && tests[i][0]; ++i) { + if (!tests[i][0].call()) { + do_throw(tests[i][1]); + } + } +} + +var tests = [ + [test1, "Unable to parse basic XML document"], + [test2, "ParseXML doesn't return Document"], + [test3, "ParseXML return value's documentElement is not Element"], + [test4, ""], + [test5, ""], + [test6, ""], + [null], +]; + +function test1() { + return ParseXML("<root/>"); +} + +function test2() { + return ChromeUtils.getClassName(ParseXML("<root/>")) === "XMLDocument"; +} + +function test3() { + return Element.isInstance(ParseXML("<root/>").documentElement); +} + +function test4() { + var doc = ParseXML("<root/>"); + Assert.equal(doc.documentElement.namespaceURI, null); + return true; +} + +function test5() { + var doc = ParseXML("<root xmlns=''/>"); + Assert.equal(doc.documentElement.namespaceURI, null); + return true; +} + +function test6() { + var doc = ParseXML("<root xmlns='ns1'/>"); + Assert.notEqual(doc.documentElement.namespaceURI, null); + Assert.equal(doc.documentElement.namespaceURI, "ns1"); + return true; +} diff --git a/dom/base/test/unit/test_xml_serializer.js b/dom/base/test/unit/test_xml_serializer.js new file mode 100644 index 0000000000..11fbb02f6f --- /dev/null +++ b/dom/base/test/unit/test_xml_serializer.js @@ -0,0 +1,421 @@ +// The xml serializer uses the default line break of the plateform. +// So we need to know the value of this default line break, in order +// to build correctly the reference strings for tests. +// This variable will contain this value. +var LB; + +function run_test() { + if (mozinfo.os == "win") { + LB = "\r\n"; + } else { + LB = "\n"; + } + + for (var i = 0; i < tests.length && tests[i]; ++i) { + tests[i].call(); + } +} + +var tests = [ + test1, + test2, + test3, + test4, + test5, + test6, + test7, + test8, + test9, + test10, + null, +]; + +function testString(str) { + Assert.equal(roundtrip(str), str); +} + +function test1() { + // Basic round-tripping which we expect to hand back the same text + // as we passed in (not strictly required for correctness in some of + // those cases, but best for readability of serializer output) + testString("<root/>"); + testString("<root><child/></root>"); + testString('<root xmlns=""/>'); + testString('<root xml:lang="en"/>'); + testString('<root xmlns="ns1"><child xmlns="ns2"/></root>'); + testString('<root xmlns="ns1"><child xmlns=""/></root>'); + testString('<a:root xmlns:a="ns1"><child/></a:root>'); + testString('<a:root xmlns:a="ns1"><a:child/></a:root>'); + testString('<a:root xmlns:a="ns1"><b:child xmlns:b="ns1"/></a:root>'); + testString('<a:root xmlns:a="ns1"><a:child xmlns:a="ns2"/></a:root>'); + testString( + '<a:root xmlns:a="ns1"><b:child xmlns:b="ns1" b:attr=""/></a:root>' + ); +} + +function test2() { + // Test setting of "xmlns" attribute in the null namespace + + // XXXbz are these tests needed? What should happen here? These + // may be bogus. + + // Setting random "xmlns" attribute + var doc = ParseXML('<root xmlns="ns1"/>'); + doc.documentElement.setAttribute("xmlns", "ns2"); + do_check_serialize(doc); +} + +function test3() { + // Test basic appending of kids. Again, we're making assumptions + // about how our serializer will serialize simple DOMs. + var doc = ParseXML('<root xmlns="ns1"/>'); + var root = doc.documentElement; + var child = doc.createElementNS("ns2", "child"); + root.appendChild(child); + do_check_serialize(doc); + Assert.equal( + SerializeXML(doc), + '<root xmlns="ns1"><child xmlns="ns2"/></root>' + ); + + doc = ParseXML('<root xmlns="ns1"/>'); + root = doc.documentElement; + child = doc.createElementNS("ns2", "prefix:child"); + root.appendChild(child); + do_check_serialize(doc); + Assert.equal( + SerializeXML(doc), + '<root xmlns="ns1"><prefix:child xmlns:prefix="ns2"/></root>' + ); + + doc = ParseXML('<prefix:root xmlns:prefix="ns1"/>'); + root = doc.documentElement; + child = doc.createElementNS("ns2", "prefix:child"); + root.appendChild(child); + do_check_serialize(doc); + Assert.equal( + SerializeXML(doc), + '<prefix:root xmlns:prefix="ns1"><a0:child xmlns:a0="ns2"/>' + + "</prefix:root>" + ); +} + +function test4() { + // setAttributeNS tests + + var doc = ParseXML('<root xmlns="ns1"/>'); + var root = doc.documentElement; + root.setAttributeNS("ns1", "prefix:local", "val"); + do_check_serialize(doc); + Assert.equal( + SerializeXML(doc), + '<root xmlns="ns1" prefix:local="val" xmlns:prefix="ns1"/>' + ); + + doc = ParseXML('<prefix:root xmlns:prefix="ns1"/>'); + root = doc.documentElement; + root.setAttributeNS("ns1", "local", "val"); + do_check_serialize(doc); + Assert.equal( + SerializeXML(doc), + '<prefix:root xmlns:prefix="ns1" prefix:local="val"/>' + ); + + doc = ParseXML('<root xmlns="ns1"/>'); + root = doc.documentElement; + root.setAttributeNS("ns2", "local", "val"); + do_check_serialize(doc); + Assert.equal( + SerializeXML(doc), + '<root xmlns="ns1" a0:local="val" xmlns:a0="ns2"/>' + ); + + // Handling of prefix-generation for non-null-namespace attributes + // which have the same namespace as the current default namespace + // (bug 301260). + doc = ParseXML('<root xmlns="ns1"/>'); + root = doc.documentElement; + root.setAttributeNS("ns1", "local", "val"); + do_check_serialize(doc); + Assert.equal( + SerializeXML(doc), + '<root xmlns="ns1" a0:local="val" xmlns:a0="ns1"/>' + ); + + // Tree-walking test + doc = ParseXML( + '<root xmlns="ns1" xmlns:a="ns2">' + + '<child xmlns:b="ns2" xmlns:a="ns3">' + + "<child2/></child></root>" + ); + root = doc.documentElement; + var node = root.firstChild.firstChild; + node.setAttributeNS("ns4", "l1", "v1"); + node.setAttributeNS("ns4", "p2:l2", "v2"); + node.setAttributeNS("", "l3", "v3"); + node.setAttributeNS("ns3", "l4", "v4"); + node.setAttributeNS("ns3", "p5:l5", "v5"); + node.setAttributeNS("ns3", "a:l6", "v6"); + node.setAttributeNS("ns2", "l7", "v7"); + node.setAttributeNS("ns2", "p8:l8", "v8"); + node.setAttributeNS("ns2", "b:l9", "v9"); + node.setAttributeNS("ns2", "a:l10", "v10"); + node.setAttributeNS("ns1", "a:l11", "v11"); + node.setAttributeNS("ns1", "b:l12", "v12"); + node.setAttributeNS("ns1", "l13", "v13"); + do_check_serialize(doc); + // Note: we end up with "a2" as the prefix on "l11" and "l12" because we use + // "a1" earlier, and discard it in favor of something we get off the + // namespace stack, apparently + Assert.equal( + SerializeXML(doc), + '<root xmlns="ns1" xmlns:a="ns2">' + + '<child xmlns:b="ns2" xmlns:a="ns3">' + + '<child2 a0:l1="v1" xmlns:a0="ns4"' + + ' a0:l2="v2"' + + ' l3="v3"' + + ' a:l4="v4"' + + ' a:l5="v5"' + + ' a:l6="v6"' + + ' b:l7="v7"' + + ' b:l8="v8"' + + ' b:l9="v9"' + + ' b:l10="v10"' + + ' a2:l11="v11" xmlns:a2="ns1"' + + ' a2:l12="v12"' + + ' a2:l13="v13"/></child></root>' + ); +} + +function test5() { + // Handling of kids in the null namespace when the default is a + // different namespace (bug 301260). + var doc = ParseXML('<root xmlns="ns1"/>'); + var child = doc.createElement("child"); + doc.documentElement.appendChild(child); + do_check_serialize(doc); + Assert.equal(SerializeXML(doc), '<root xmlns="ns1"><child xmlns=""/></root>'); +} + +function test6() { + // Handling of not using a namespace prefix (or default namespace!) + // that's not bound to our namespace in our scope (bug 301260). + var doc = ParseXML('<prefix:root xmlns:prefix="ns1"/>'); + var root = doc.documentElement; + var child1 = doc.createElementNS("ns2", "prefix:child1"); + var child2 = doc.createElementNS("ns1", "prefix:child2"); + child1.appendChild(child2); + root.appendChild(child1); + do_check_serialize(doc); + Assert.equal( + SerializeXML(doc), + '<prefix:root xmlns:prefix="ns1"><a0:child1 xmlns:a0="ns2">' + + "<prefix:child2/></a0:child1></prefix:root>" + ); + + doc = ParseXML( + '<root xmlns="ns1"><prefix:child1 xmlns:prefix="ns2"/></root>' + ); + root = doc.documentElement; + child1 = root.firstChild; + child2 = doc.createElementNS("ns1", "prefix:child2"); + child1.appendChild(child2); + do_check_serialize(doc); + Assert.equal( + SerializeXML(doc), + '<root xmlns="ns1"><prefix:child1 xmlns:prefix="ns2">' + + "<child2/></prefix:child1></root>" + ); + + doc = ParseXML( + '<prefix:root xmlns:prefix="ns1">' + + '<prefix:child1 xmlns:prefix="ns2"/></prefix:root>' + ); + root = doc.documentElement; + child1 = root.firstChild; + child2 = doc.createElementNS("ns1", "prefix:child2"); + child1.appendChild(child2); + do_check_serialize(doc); + Assert.equal( + SerializeXML(doc), + '<prefix:root xmlns:prefix="ns1"><prefix:child1 xmlns:prefix="ns2">' + + '<a0:child2 xmlns:a0="ns1"/></prefix:child1></prefix:root>' + ); + + doc = ParseXML('<root xmlns="ns1"/>'); + root = doc.documentElement; + child1 = doc.createElementNS("ns2", "child1"); + child2 = doc.createElementNS("ns1", "child2"); + child1.appendChild(child2); + root.appendChild(child1); + do_check_serialize(doc); + Assert.equal( + SerializeXML(doc), + '<root xmlns="ns1"><child1 xmlns="ns2"><child2 xmlns="ns1"/>' + + "</child1></root>" + ); +} + +function test7() { + // Handle xmlns attribute declaring a default namespace on a non-namespaced + // element (bug 326994). + var doc = ParseXML('<root xmlns=""/>'); + var root = doc.documentElement; + root.setAttributeNS( + "http://www.w3.org/2000/xmlns/", + "xmlns", + "http://www.w3.org/1999/xhtml" + ); + do_check_serialize(doc); + Assert.equal(SerializeXML(doc), "<root/>"); + + doc = ParseXML('<root xmlns=""><child1/></root>'); + root = doc.documentElement; + root.setAttributeNS( + "http://www.w3.org/2000/xmlns/", + "xmlns", + "http://www.w3.org/1999/xhtml" + ); + do_check_serialize(doc); + Assert.equal(SerializeXML(doc), "<root><child1/></root>"); + + doc = ParseXML( + '<root xmlns="http://www.w3.org/1999/xhtml">' + + '<child1 xmlns=""><child2/></child1></root>' + ); + root = doc.documentElement; + + var child1 = root.firstChild; + child1.setAttributeNS( + "http://www.w3.org/2000/xmlns/", + "xmlns", + "http://www.w3.org/1999/xhtml" + ); + do_check_serialize(doc); + Assert.equal( + SerializeXML(doc), + '<root xmlns="http://www.w3.org/1999/xhtml"><child1 xmlns="">' + + "<child2/></child1></root>" + ); + + doc = ParseXML( + '<root xmlns="http://www.w3.org/1999/xhtml">' + + '<child1 xmlns="">' + + '<child2 xmlns="http://www.w3.org/1999/xhtml"></child2>' + + "</child1></root>" + ); + root = doc.documentElement; + child1 = root.firstChild; + var child2 = child1.firstChild; + child1.setAttributeNS( + "http://www.w3.org/2000/xmlns/", + "xmlns", + "http://www.w3.org/1999/xhtml" + ); + child2.setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns", ""); + do_check_serialize(doc); + Assert.equal( + SerializeXML(doc), + '<root xmlns="http://www.w3.org/1999/xhtml"><child1 xmlns="">' + + '<a0:child2 xmlns:a0="http://www.w3.org/1999/xhtml" xmlns=""></a0:child2></child1></root>' + ); +} + +function test8() { + // Test behavior of serializing with a given charset. + var str1 = '<?xml version="1.0" encoding="windows-1252"?>' + LB + "<root/>"; + var str2 = '<?xml version="1.0" encoding="UTF-8"?>' + LB + "<root/>"; + var doc1 = ParseXML(str1); + var doc2 = ParseXML(str2); + + var p = Pipe(); + DOMSerializer().serializeToStream(doc1, p.outputStream, "windows-1252"); + p.outputStream.close(); + Assert.equal(ScriptableInput(p).read(-1), str1); + + p = Pipe(); + DOMSerializer().serializeToStream(doc2, p.outputStream, "windows-1252"); + p.outputStream.close(); + Assert.equal(ScriptableInput(p).read(-1), str1); + + p = Pipe(); + DOMSerializer().serializeToStream(doc1, p.outputStream, "UTF-8"); + p.outputStream.close(); + Assert.equal(ScriptableInput(p).read(-1), str2); + + p = Pipe(); + DOMSerializer().serializeToStream(doc2, p.outputStream, "UTF-8"); + p.outputStream.close(); + Assert.equal(ScriptableInput(p).read(-1), str2); +} + +function test9() { + // Test behavior of serializing between given charsets, using + // windows-1252-representable text. + var contents = + // eslint-disable-next-line no-useless-concat + "<root>" + "\u00BD + \u00BE == \u00BD\u00B2 + \u00BC + \u00BE" + "</root>"; + var str1 = '<?xml version="1.0" encoding="windows-1252"?>' + LB + contents; + var str2 = '<?xml version="1.0" encoding="UTF-8"?>' + LB + contents; + var str3 = '<?xml version="1.0" encoding="UTF-16"?>' + LB + contents; + var doc1 = ParseXML(str1); + var doc2 = ParseXML(str2); + var doc3 = ParseXML(str3); + + checkSerialization(doc1, "windows-1252", str1); + checkSerialization(doc2, "windows-1252", str1); + checkSerialization(doc3, "windows-1252", str1); + + checkSerialization(doc1, "UTF-8", str2); + checkSerialization(doc2, "UTF-8", str2); + checkSerialization(doc3, "UTF-8", str2); + + checkSerialization(doc1, "UTF-16", str2); + checkSerialization(doc2, "UTF-16", str2); + checkSerialization(doc3, "UTF-16", str2); +} + +function test10() { + // Test behavior of serializing between given charsets, using + // Unicode characters (XXX but only BMP ones because I don't know + // how to create one with non-BMP characters, either with JS strings + // or using DOM APIs). + var contents = + "<root>" + + "AZaz09 \u007F " + // U+000000 to U+00007F + "\u0080 \u0398 \u03BB \u0725 " + // U+000080 to U+0007FF + "\u0964 \u0F5F \u20AC \uFFFB" + // U+000800 to U+00FFFF + "</root>"; + var str1 = '<?xml version="1.0" encoding="UTF-8"?>' + LB + contents; + var str2 = '<?xml version="1.0" encoding="UTF-16"?>' + LB + contents; + var doc1 = ParseXML(str1); + var doc2 = ParseXML(str2); + + checkSerialization(doc1, "UTF8", str1); + checkSerialization(doc2, "UTF8", str1); + + checkSerialization(doc1, "UTF-16", str1); + checkSerialization(doc2, "UTF-16", str1); +} + +function checkSerialization(doc, toCharset, expectedString) { + var p = Pipe(); + DOMSerializer().serializeToStream(doc, p.outputStream, toCharset); + p.outputStream.close(); + + var inCharset = toCharset == "UTF-16" ? "UTF-8" : toCharset; + var cin = C["@mozilla.org/intl/converter-input-stream;1"].createInstance( + I.nsIConverterInputStream + ); + cin.init(p.inputStream, inCharset, 1024, 0x0); + + // compare the first expectedString.length characters for equality + var outString = {}; + var count = cin.readString(expectedString.length, outString); + Assert.equal(count, expectedString.length); + Assert.equal(outString.value, expectedString); + + // if there's anything more in the stream, it's a bug + Assert.equal(0, cin.readString(1, outString)); + Assert.equal(outString.value, ""); +} diff --git a/dom/base/test/unit/test_xmlserializer.js b/dom/base/test/unit/test_xmlserializer.js new file mode 100644 index 0000000000..86f926def5 --- /dev/null +++ b/dom/base/test/unit/test_xmlserializer.js @@ -0,0 +1,179 @@ +/* -*- 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/. */ + +async function xmlEncode(aFile, aFlags, aCharset) { + if (aFlags == undefined) { + aFlags = 0; + } + if (aCharset == undefined) { + aCharset = "UTF-8"; + } + + var doc = await do_parse_document(aFile, "text/xml"); + + var encoder = Cu.createDocumentEncoder("text/xml"); + encoder.setCharset(aCharset); + encoder.init(doc, "text/xml", aFlags); + return encoder.encodeToString(); +} + +add_task(async function test_encoding() { + var result, expected; + const de = Ci.nsIDocumentEncoder; + + result = await xmlEncode("1_original.xml", de.OutputLFLineBreak); + expected = loadContentFile("1_result.xml"); + Assert.equal(expected, result); + + result = await xmlEncode("2_original.xml", de.OutputLFLineBreak); + expected = loadContentFile("2_result_1.xml"); + Assert.equal(expected, result); + + result = await xmlEncode("2_original.xml", de.OutputCRLineBreak); + expected = expected.replace(/\n/g, "\r"); + Assert.equal(expected, result); + + result = await xmlEncode( + "2_original.xml", + de.OutputLFLineBreak | de.OutputCRLineBreak + ); + expected = expected.replace(/\r/gm, "\r\n"); + Assert.equal(expected, result); + + result = await xmlEncode( + "2_original.xml", + de.OutputLFLineBreak | de.OutputFormatted + ); + expected = loadContentFile("2_result_2.xml"); + Assert.equal(expected, result); + + result = await xmlEncode( + "2_original.xml", + de.OutputLFLineBreak | de.OutputFormatted | de.OutputWrap + ); + expected = loadContentFile("2_result_3.xml"); + Assert.equal(expected, result); + + result = await xmlEncode( + "2_original.xml", + de.OutputLFLineBreak | de.OutputWrap + ); + expected = loadContentFile("2_result_4.xml"); + Assert.equal(expected, result); + + result = await xmlEncode( + "3_original.xml", + de.OutputLFLineBreak | de.OutputFormatted | de.OutputWrap + ); + expected = loadContentFile("3_result.xml"); + Assert.equal(expected, result); + + result = await xmlEncode( + "3_original.xml", + de.OutputLFLineBreak | de.OutputWrap + ); + expected = loadContentFile("3_result_2.xml"); + Assert.equal(expected, result); + + // tests on namespaces + var doc = await do_parse_document("4_original.xml", "text/xml"); + + var encoder = Cu.createDocumentEncoder("text/xml"); + encoder.setCharset("UTF-8"); + encoder.init(doc, "text/xml", de.OutputLFLineBreak); + + result = encoder.encodeToString(); + expected = loadContentFile("4_result_1.xml"); + Assert.equal(expected, result); + + encoder.setNode(doc.documentElement.childNodes[9]); + result = encoder.encodeToString(); + expected = loadContentFile("4_result_2.xml"); + Assert.equal(expected, result); + + encoder.setNode(doc.documentElement.childNodes[7].childNodes[1]); + result = encoder.encodeToString(); + expected = loadContentFile("4_result_3.xml"); + Assert.equal(expected, result); + + encoder.setNode(doc.documentElement.childNodes[11].childNodes[1]); + result = encoder.encodeToString(); + expected = loadContentFile("4_result_4.xml"); + Assert.equal(expected, result); + + encoder.setCharset("UTF-8"); + // it doesn't support this flags + encoder.init( + doc, + "text/xml", + de.OutputLFLineBreak | de.OutputFormatted | de.OutputWrap + ); + encoder.setWrapColumn(40); + result = encoder.encodeToString(); + expected = loadContentFile("4_result_5.xml"); + Assert.equal(expected, result); + + encoder.init(doc, "text/xml", de.OutputLFLineBreak | de.OutputWrap); + encoder.setWrapColumn(40); + result = encoder.encodeToString(); + expected = loadContentFile("4_result_6.xml"); + Assert.equal(expected, result); +}); + +// OutputRaw should cause OutputWrap and OutputFormatted to be ignored. +// Check by encoding each test file and making sure the result matches what +// was fed in. +add_task(async function test_outputRaw() { + let result, expected; + const de = Ci.nsIDocumentEncoder; + + expected = loadContentFile("2_original.xml"); + result = await xmlEncode( + "2_original.xml", + de.OutputRaw | de.OutputLFLineBreak | de.OutputWrap + ); + Assert.equal(expected, result); + + result = await xmlEncode( + "2_original.xml", + de.OutputRaw | de.OutputLFLineBreak | de.OutputFormatted | de.OutputWrap + ); + Assert.equal(expected, result); + + expected = loadContentFile("3_original.xml"); + result = await xmlEncode( + "3_original.xml", + de.OutputRaw | de.OutputLFLineBreak | de.OutputWrap + ); + Assert.equal(expected, result); + + result = await xmlEncode( + "3_original.xml", + de.OutputRaw | de.OutputLFLineBreak | de.OutputFormatted | de.OutputWrap + ); + Assert.equal(expected, result); + + expected = loadContentFile("4_original.xml"); + let doc = await do_parse_document("4_original.xml", "text/xml"); + let encoder = Cu.createDocumentEncoder("text/xml"); + encoder.setCharset("UTF-8"); + encoder.init( + doc, + "text/xml", + de.OutputRaw | de.OutputLFLineBreak | de.OutputWrap + ); + encoder.setWrapColumn(40); + result = encoder.encodeToString(); + Assert.equal(expected, result); + + encoder.init( + doc, + "text/xml", + de.OutputRaw | de.OutputLFLineBreak | de.OutputFormatted | de.OutputWrap + ); + encoder.setWrapColumn(40); + result = encoder.encodeToString(); + Assert.equal(expected, result); +}); diff --git a/dom/base/test/unit/xpcshell.toml b/dom/base/test/unit/xpcshell.toml new file mode 100644 index 0000000000..81910e11b6 --- /dev/null +++ b/dom/base/test/unit/xpcshell.toml @@ -0,0 +1,97 @@ +[DEFAULT] +head = "head_utilities.js" +support-files = [ + "1_original.xml", + "1_result.xml", + "2_original.xml", + "2_result_1.xml", + "2_result_2.xml", + "2_result_3.xml", + "2_result_4.xml", + "3_original.xml", + "3_result.xml", + "3_result_2.xml", + "4_original.xml", + "4_result_1.xml", + "4_result_2.xml", + "4_result_3.xml", + "4_result_4.xml", + "4_result_5.xml", + "4_result_6.xml", + "empty_document.xml", + "isequalnode_data.xml", + "nodelist_data_1.xml", + "nodelist_data_2.xhtml", + "test_delete_range.xml", +] + +["test_blockParsing.js"] +skip-if = [ + "debug", # We fail an assertion if we block parsing on a self-closing element + "os == 'android'", +] + +["test_bug553888.js"] + +["test_bug737966.js"] + +["test_cancelPrefetch.js"] + +["test_chromeutils_base64.js"] + +["test_chromeutils_defineLazyGetter.js"] + +["test_chromeutils_getXPCOMErrorName.js"] + +["test_chromeutils_shallowclone.js"] + +["test_error_codes.js"] +run-sequentially = "Hardcoded 4444 port." +# Bug 1018414: hardcoded localhost doesn't work properly on some OS X installs +skip-if = ["os == 'mac'"] + +["test_generate_xpath.js"] +head = "head_xml.js" + +["test_htmlserializer.js"] + +["test_isequalnode.js"] +head = "head_xml.js" + +["test_js_dev_error_interceptor.js"] +# This feature is implemented only in NIGHTLY. +run-if = ["nightly_build"] + +["test_nodelist.js"] +head = "head_xml.js" + +["test_normalize.js"] +head = "head_xml.js" + +["test_range.js"] +head = "head_xml.js" + +["test_serializers_entities.js"] + +["test_serializers_entities_in_attr.js"] + +["test_structuredcloneholder.js"] + +["test_thirdpartyutil.js"] + +["test_treewalker.js"] +head = "head_xml.js" + +["test_xhr_document.js"] + +["test_xhr_origin_attributes.js"] + +["test_xhr_standalone.js"] + +["test_xml_parser.js"] +head = "head_xml.js" + +["test_xml_serializer.js"] +head = "head_xml.js" + +["test_xmlserializer.js"] |