content/test/unit/test_range.js

Tue, 06 Jan 2015 21:39:09 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Tue, 06 Jan 2015 21:39:09 +0100
branch
TOR_BUG_9701
changeset 8
97036ab72558
permissions
-rw-r--r--

Conditionally force memory storage according to privacy.thirdparty.isolate;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.

michael@0 1 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 2 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 4
michael@0 5 const C_i = Components.interfaces;
michael@0 6
michael@0 7 const UNORDERED_TYPE = C_i.nsIDOMXPathResult.ANY_UNORDERED_NODE_TYPE;
michael@0 8
michael@0 9 /**
michael@0 10 * Determine if the data node has only ignorable white-space.
michael@0 11 *
michael@0 12 * @return nsIDOMNodeFilter.FILTER_SKIP if it does.
michael@0 13 * @return nsIDOMNodeFilter.FILTER_ACCEPT otherwise.
michael@0 14 */
michael@0 15 function isWhitespace(aNode) {
michael@0 16 return ((/\S/).test(aNode.nodeValue)) ?
michael@0 17 C_i.nsIDOMNodeFilter.FILTER_SKIP :
michael@0 18 C_i.nsIDOMNodeFilter.FILTER_ACCEPT;
michael@0 19 }
michael@0 20
michael@0 21 /**
michael@0 22 * Create a DocumentFragment with cloned children equaling a node's children.
michael@0 23 *
michael@0 24 * @param aNode The node to copy from.
michael@0 25 *
michael@0 26 * @return DocumentFragment node.
michael@0 27 */
michael@0 28 function getFragment(aNode) {
michael@0 29 var frag = aNode.ownerDocument.createDocumentFragment();
michael@0 30 for (var i = 0; i < aNode.childNodes.length; i++) {
michael@0 31 frag.appendChild(aNode.childNodes.item(i).cloneNode(true));
michael@0 32 }
michael@0 33 return frag;
michael@0 34 }
michael@0 35
michael@0 36 // Goodies from head_content.js
michael@0 37 const serializer = new DOMSerializer();
michael@0 38 const parser = new DOMParser();
michael@0 39
michael@0 40 /**
michael@0 41 * Dump the contents of a document fragment to the console.
michael@0 42 *
michael@0 43 * @param aFragment The fragment to serialize.
michael@0 44 */
michael@0 45 function dumpFragment(aFragment) {
michael@0 46 dump(serializer.serializeToString(aFragment) + "\n\n");
michael@0 47 }
michael@0 48
michael@0 49 /**
michael@0 50 * Translate an XPath to a DOM node. This method uses a document
michael@0 51 * fragment as context node.
michael@0 52 *
michael@0 53 * @param aContextNode The context node to apply the XPath to.
michael@0 54 * @param aPath The XPath to use.
michael@0 55 *
michael@0 56 * @return nsIDOMNode The target node retrieved from the XPath.
michael@0 57 */
michael@0 58 function evalXPathInDocumentFragment(aContextNode, aPath) {
michael@0 59 do_check_true(aContextNode instanceof C_i.nsIDOMDocumentFragment);
michael@0 60 do_check_true(aContextNode.childNodes.length > 0);
michael@0 61 if (aPath == ".") {
michael@0 62 return aContextNode;
michael@0 63 }
michael@0 64
michael@0 65 // Separate the fragment's xpath lookup from the rest.
michael@0 66 var firstSlash = aPath.indexOf("/");
michael@0 67 if (firstSlash == -1) {
michael@0 68 firstSlash = aPath.length;
michael@0 69 }
michael@0 70 var prefix = aPath.substr(0, firstSlash);
michael@0 71 var realPath = aPath.substr(firstSlash + 1);
michael@0 72 if (!realPath) {
michael@0 73 realPath = ".";
michael@0 74 }
michael@0 75
michael@0 76 // Set up a special node filter to look among the fragment's child nodes.
michael@0 77 var childIndex = 1;
michael@0 78 var bracketIndex = prefix.indexOf("[");
michael@0 79 if (bracketIndex != -1) {
michael@0 80 childIndex = Number(prefix.substring(bracketIndex + 1, prefix.indexOf("]")));
michael@0 81 do_check_true(childIndex > 0);
michael@0 82 prefix = prefix.substr(0, bracketIndex);
michael@0 83 }
michael@0 84
michael@0 85 var targetType = C_i.nsIDOMNodeFilter.SHOW_ELEMENT;
michael@0 86 var targetNodeName = prefix;
michael@0 87 if (prefix.indexOf("processing-instruction(") == 0) {
michael@0 88 targetType = C_i.nsIDOMNodeFilter.SHOW_PROCESSING_INSTRUCTION;
michael@0 89 targetNodeName = prefix.substring(prefix.indexOf("(") + 2, prefix.indexOf(")") - 1);
michael@0 90 }
michael@0 91 switch (prefix) {
michael@0 92 case "text()":
michael@0 93 targetType = C_i.nsIDOMNodeFilter.SHOW_TEXT |
michael@0 94 C_i.nsIDOMNodeFilter.SHOW_CDATA_SECTION;
michael@0 95 targetNodeName = null;
michael@0 96 break;
michael@0 97 case "comment()":
michael@0 98 targetType = C_i.nsIDOMNodeFilter.SHOW_COMMENT;
michael@0 99 targetNodeName = null;
michael@0 100 break;
michael@0 101 case "node()":
michael@0 102 targetType = C_i.nsIDOMNodeFilter.SHOW_ALL;
michael@0 103 targetNodeName = null;
michael@0 104 }
michael@0 105
michael@0 106 var filter = {
michael@0 107 count: 0,
michael@0 108
michael@0 109 // nsIDOMNodeFilter
michael@0 110 acceptNode: function acceptNode(aNode) {
michael@0 111 if (aNode.parentNode != aContextNode) {
michael@0 112 // Don't bother looking at kids either.
michael@0 113 return C_i.nsIDOMNodeFilter.FILTER_REJECT;
michael@0 114 }
michael@0 115
michael@0 116 if (targetNodeName && targetNodeName != aNode.nodeName) {
michael@0 117 return C_i.nsIDOMNodeFilter.FILTER_SKIP;
michael@0 118 }
michael@0 119
michael@0 120 this.count++;
michael@0 121 if (this.count != childIndex) {
michael@0 122 return C_i.nsIDOMNodeFilter.FILTER_SKIP;
michael@0 123 }
michael@0 124
michael@0 125 return C_i.nsIDOMNodeFilter.FILTER_ACCEPT;
michael@0 126 }
michael@0 127 };
michael@0 128
michael@0 129 // Look for the node matching the step from the document fragment.
michael@0 130 var walker = aContextNode.ownerDocument.createTreeWalker(
michael@0 131 aContextNode,
michael@0 132 targetType,
michael@0 133 filter);
michael@0 134 var targetNode = walker.nextNode();
michael@0 135 do_check_neq(targetNode, null);
michael@0 136
michael@0 137 // Apply our remaining xpath to the found node.
michael@0 138 var expr = aContextNode.ownerDocument.createExpression(realPath, null);
michael@0 139 var result = expr.evaluate(targetNode, UNORDERED_TYPE, null);
michael@0 140 do_check_true(result instanceof C_i.nsIDOMXPathResult);
michael@0 141 return result.singleNodeValue;
michael@0 142 }
michael@0 143
michael@0 144 /**
michael@0 145 * Get a DOM range corresponding to the test's source node.
michael@0 146 *
michael@0 147 * @param aSourceNode <source/> element with range information.
michael@0 148 * @param aFragment DocumentFragment generated with getFragment().
michael@0 149 *
michael@0 150 * @return Range object.
michael@0 151 */
michael@0 152 function getRange(aSourceNode, aFragment) {
michael@0 153 do_check_true(aSourceNode instanceof C_i.nsIDOMElement);
michael@0 154 do_check_true(aFragment instanceof C_i.nsIDOMDocumentFragment);
michael@0 155 var doc = aSourceNode.ownerDocument;
michael@0 156
michael@0 157 var containerPath = aSourceNode.getAttribute("startContainer");
michael@0 158 var startContainer = evalXPathInDocumentFragment(aFragment, containerPath);
michael@0 159 var startOffset = Number(aSourceNode.getAttribute("startOffset"));
michael@0 160
michael@0 161 containerPath = aSourceNode.getAttribute("endContainer");
michael@0 162 var endContainer = evalXPathInDocumentFragment(aFragment, containerPath);
michael@0 163 var endOffset = Number(aSourceNode.getAttribute("endOffset"));
michael@0 164
michael@0 165 var range = doc.createRange();
michael@0 166 range.setStart(startContainer, startOffset);
michael@0 167 range.setEnd(endContainer, endOffset);
michael@0 168 return range;
michael@0 169 }
michael@0 170
michael@0 171 /**
michael@0 172 * Get the document for a given path, and clean it up for our tests.
michael@0 173 *
michael@0 174 * @param aPath The path to the local document.
michael@0 175 */
michael@0 176 function getParsedDocument(aPath) {
michael@0 177 var doc = do_parse_document(aPath, "application/xml");
michael@0 178 do_check_true(doc.documentElement.localName != "parsererror");
michael@0 179 do_check_true(doc instanceof C_i.nsIDOMXPathEvaluator);
michael@0 180 do_check_true(doc instanceof C_i.nsIDOMDocument);
michael@0 181
michael@0 182 // Clean out whitespace.
michael@0 183 var walker = doc.createTreeWalker(doc,
michael@0 184 C_i.nsIDOMNodeFilter.SHOW_TEXT |
michael@0 185 C_i.nsIDOMNodeFilter.SHOW_CDATA_SECTION,
michael@0 186 isWhitespace);
michael@0 187 while (walker.nextNode()) {
michael@0 188 var parent = walker.currentNode.parentNode;
michael@0 189 parent.removeChild(walker.currentNode);
michael@0 190 walker.currentNode = parent;
michael@0 191 }
michael@0 192
michael@0 193 // Clean out mandatory splits between nodes.
michael@0 194 var splits = doc.getElementsByTagName("split");
michael@0 195 var i;
michael@0 196 for (i = splits.length - 1; i >= 0; i--) {
michael@0 197 var node = splits.item(i);
michael@0 198 node.parentNode.removeChild(node);
michael@0 199 }
michael@0 200 splits = null;
michael@0 201
michael@0 202 // Replace empty CDATA sections.
michael@0 203 var emptyData = doc.getElementsByTagName("empty-cdata");
michael@0 204 for (i = emptyData.length - 1; i >= 0; i--) {
michael@0 205 var node = emptyData.item(i);
michael@0 206 var cdata = doc.createCDATASection("");
michael@0 207 node.parentNode.replaceChild(cdata, node);
michael@0 208 }
michael@0 209
michael@0 210 return doc;
michael@0 211 }
michael@0 212
michael@0 213 /**
michael@0 214 * Run the extraction tests.
michael@0 215 */
michael@0 216 function run_extract_test() {
michael@0 217 var filePath = "test_delete_range.xml";
michael@0 218 var doc = getParsedDocument(filePath);
michael@0 219 var tests = doc.getElementsByTagName("test");
michael@0 220
michael@0 221 // Run our deletion, extraction tests.
michael@0 222 for (var i = 0; i < tests.length; i++) {
michael@0 223 dump("Configuring for test " + i + "\n");
michael@0 224 var currentTest = tests.item(i);
michael@0 225
michael@0 226 // Validate the test is properly formatted for what this harness expects.
michael@0 227 var baseSource = currentTest.firstChild;
michael@0 228 do_check_eq(baseSource.nodeName, "source");
michael@0 229 var baseResult = baseSource.nextSibling;
michael@0 230 do_check_eq(baseResult.nodeName, "result");
michael@0 231 var baseExtract = baseResult.nextSibling;
michael@0 232 do_check_eq(baseExtract.nodeName, "extract");
michael@0 233 do_check_eq(baseExtract.nextSibling, null);
michael@0 234
michael@0 235 /* We do all our tests on DOM document fragments, derived from the test
michael@0 236 element's children. This lets us rip the various fragments to shreds,
michael@0 237 while preserving the original elements so we can make more copies of
michael@0 238 them.
michael@0 239
michael@0 240 After the range's extraction or deletion is done, we use
michael@0 241 nsIDOMNode.isEqualNode() between the altered source fragment and the
michael@0 242 result fragment. We also run isEqualNode() between the extracted
michael@0 243 fragment and the fragment from the baseExtract node. If they are not
michael@0 244 equal, we have failed a test.
michael@0 245
michael@0 246 We also have to ensure the original nodes on the end points of the
michael@0 247 range are still in the source fragment. This is bug 332148. The nodes
michael@0 248 may not be replaced with equal but separate nodes. The range extraction
michael@0 249 may alter these nodes - in the case of text containers, they will - but
michael@0 250 the nodes must stay there, to preserve references such as user data,
michael@0 251 event listeners, etc.
michael@0 252
michael@0 253 First, an extraction test.
michael@0 254 */
michael@0 255
michael@0 256 var resultFrag = getFragment(baseResult);
michael@0 257 var extractFrag = getFragment(baseExtract);
michael@0 258
michael@0 259 dump("Extract contents test " + i + "\n\n");
michael@0 260 var baseFrag = getFragment(baseSource);
michael@0 261 var baseRange = getRange(baseSource, baseFrag);
michael@0 262 var startContainer = baseRange.startContainer;
michael@0 263 var endContainer = baseRange.endContainer;
michael@0 264
michael@0 265 var cutFragment = baseRange.extractContents();
michael@0 266 dump("cutFragment: " + cutFragment + "\n");
michael@0 267 if (cutFragment) {
michael@0 268 do_check_true(extractFrag.isEqualNode(cutFragment));
michael@0 269 } else {
michael@0 270 do_check_eq(extractFrag.firstChild, null);
michael@0 271 }
michael@0 272 do_check_true(baseFrag.isEqualNode(resultFrag));
michael@0 273
michael@0 274 dump("Ensure the original nodes weren't extracted - test " + i + "\n\n");
michael@0 275 var walker = doc.createTreeWalker(baseFrag,
michael@0 276 C_i.nsIDOMNodeFilter.SHOW_ALL,
michael@0 277 null);
michael@0 278 var foundStart = false;
michael@0 279 var foundEnd = false;
michael@0 280 do {
michael@0 281 if (walker.currentNode == startContainer) {
michael@0 282 foundStart = true;
michael@0 283 }
michael@0 284
michael@0 285 if (walker.currentNode == endContainer) {
michael@0 286 // An end container node should not come before the start container node.
michael@0 287 do_check_true(foundStart);
michael@0 288 foundEnd = true;
michael@0 289 break;
michael@0 290 }
michael@0 291 } while (walker.nextNode())
michael@0 292 do_check_true(foundEnd);
michael@0 293
michael@0 294 /* Now, we reset our test for the deleteContents case. This one differs
michael@0 295 from the extractContents case only in that there is no extracted document
michael@0 296 fragment to compare against. So we merely compare the starting fragment,
michael@0 297 minus the extracted content, against the result fragment.
michael@0 298 */
michael@0 299 dump("Delete contents test " + i + "\n\n");
michael@0 300 baseFrag = getFragment(baseSource);
michael@0 301 baseRange = getRange(baseSource, baseFrag);
michael@0 302 var startContainer = baseRange.startContainer;
michael@0 303 var endContainer = baseRange.endContainer;
michael@0 304 baseRange.deleteContents();
michael@0 305 do_check_true(baseFrag.isEqualNode(resultFrag));
michael@0 306
michael@0 307 dump("Ensure the original nodes weren't deleted - test " + i + "\n\n");
michael@0 308 walker = doc.createTreeWalker(baseFrag,
michael@0 309 C_i.nsIDOMNodeFilter.SHOW_ALL,
michael@0 310 null);
michael@0 311 foundStart = false;
michael@0 312 foundEnd = false;
michael@0 313 do {
michael@0 314 if (walker.currentNode == startContainer) {
michael@0 315 foundStart = true;
michael@0 316 }
michael@0 317
michael@0 318 if (walker.currentNode == endContainer) {
michael@0 319 // An end container node should not come before the start container node.
michael@0 320 do_check_true(foundStart);
michael@0 321 foundEnd = true;
michael@0 322 break;
michael@0 323 }
michael@0 324 } while (walker.nextNode())
michael@0 325 do_check_true(foundEnd);
michael@0 326
michael@0 327 // Clean up after ourselves.
michael@0 328 walker = null;
michael@0 329 }
michael@0 330 }
michael@0 331
michael@0 332 /**
michael@0 333 * Miscellaneous tests not covered above.
michael@0 334 */
michael@0 335 function run_miscellaneous_tests() {
michael@0 336 var filePath = "test_delete_range.xml";
michael@0 337 var doc = getParsedDocument(filePath);
michael@0 338 var tests = doc.getElementsByTagName("test");
michael@0 339
michael@0 340 // Let's try some invalid inputs to our DOM range and see what happens.
michael@0 341 var currentTest = tests.item(0);
michael@0 342 var baseSource = currentTest.firstChild;
michael@0 343 var baseResult = baseSource.nextSibling;
michael@0 344 var baseExtract = baseResult.nextSibling;
michael@0 345
michael@0 346 var baseFrag = getFragment(baseSource);
michael@0 347
michael@0 348 var baseRange = getRange(baseSource, baseFrag);
michael@0 349 var startContainer = baseRange.startContainer;
michael@0 350 var endContainer = baseRange.endContainer;
michael@0 351 var startOffset = baseRange.startOffset;
michael@0 352 var endOffset = baseRange.endOffset;
michael@0 353
michael@0 354 // Text range manipulation.
michael@0 355 if ((endOffset > startOffset) &&
michael@0 356 (startContainer == endContainer) &&
michael@0 357 (startContainer instanceof C_i.nsIDOMText)) {
michael@0 358 // Invalid start node
michael@0 359 try {
michael@0 360 baseRange.setStart(null, 0);
michael@0 361 do_throw("Should have thrown NOT_OBJECT_ERR!");
michael@0 362 } catch (e) {
michael@0 363 do_check_eq(e.constructor.name, "TypeError");
michael@0 364 }
michael@0 365
michael@0 366 // Invalid start node
michael@0 367 try {
michael@0 368 baseRange.setStart({}, 0);
michael@0 369 do_throw("Should have thrown SecurityError!");
michael@0 370 } catch (e) {
michael@0 371 do_check_eq(e.constructor.name, "TypeError");
michael@0 372 }
michael@0 373
michael@0 374 // Invalid index
michael@0 375 try {
michael@0 376 baseRange.setStart(startContainer, -1);
michael@0 377 do_throw("Should have thrown IndexSizeError!");
michael@0 378 } catch (e) {
michael@0 379 do_check_eq(e.name, "IndexSizeError");
michael@0 380 }
michael@0 381
michael@0 382 // Invalid index
michael@0 383 var newOffset = startContainer instanceof C_i.nsIDOMText ?
michael@0 384 startContainer.nodeValue.length + 1 :
michael@0 385 startContainer.childNodes.length + 1;
michael@0 386 try {
michael@0 387 baseRange.setStart(startContainer, newOffset);
michael@0 388 do_throw("Should have thrown IndexSizeError!");
michael@0 389 } catch (e) {
michael@0 390 do_check_eq(e.name, "IndexSizeError");
michael@0 391 }
michael@0 392
michael@0 393 newOffset--;
michael@0 394 // Valid index
michael@0 395 baseRange.setStart(startContainer, newOffset);
michael@0 396 do_check_eq(baseRange.startContainer, baseRange.endContainer);
michael@0 397 do_check_eq(baseRange.startOffset, newOffset);
michael@0 398 do_check_true(baseRange.collapsed);
michael@0 399
michael@0 400 // Valid index
michael@0 401 baseRange.setEnd(startContainer, 0);
michael@0 402 do_check_eq(baseRange.startContainer, baseRange.endContainer);
michael@0 403 do_check_eq(baseRange.startOffset, 0);
michael@0 404 do_check_true(baseRange.collapsed);
michael@0 405 } else {
michael@0 406 do_throw("The first test should be a text-only range test. Test is invalid.")
michael@0 407 }
michael@0 408
michael@0 409 /* See what happens when a range has a startContainer in one fragment, and an
michael@0 410 endContainer in another. According to the DOM spec, section 2.4, the range
michael@0 411 should collapse to the new container and offset. */
michael@0 412 baseRange = getRange(baseSource, baseFrag);
michael@0 413 startContainer = baseRange.startContainer;
michael@0 414 var startOffset = baseRange.startOffset;
michael@0 415 endContainer = baseRange.endContainer;
michael@0 416 var endOffset = baseRange.endOffset;
michael@0 417
michael@0 418 dump("External fragment test\n\n");
michael@0 419
michael@0 420 var externalTest = tests.item(1);
michael@0 421 var externalSource = externalTest.firstChild;
michael@0 422 var externalFrag = getFragment(externalSource);
michael@0 423 var externalRange = getRange(externalSource, externalFrag);
michael@0 424
michael@0 425 baseRange.setEnd(externalRange.endContainer, 0);
michael@0 426 do_check_eq(baseRange.startContainer, externalRange.endContainer);
michael@0 427 do_check_eq(baseRange.startOffset, 0);
michael@0 428 do_check_true(baseRange.collapsed);
michael@0 429
michael@0 430 /*
michael@0 431 // XXX ajvincent if rv == WRONG_DOCUMENT_ERR, return false?
michael@0 432 do_check_false(baseRange.isPointInRange(startContainer, startOffset));
michael@0 433 do_check_false(baseRange.isPointInRange(startContainer, startOffset + 1));
michael@0 434 do_check_false(baseRange.isPointInRange(endContainer, endOffset));
michael@0 435 */
michael@0 436
michael@0 437 // Requested by smaug: A range involving a comment as a document child.
michael@0 438 doc = parser.parseFromString("<!-- foo --><foo/>", "application/xml");
michael@0 439 do_check_true(doc instanceof C_i.nsIDOMDocument);
michael@0 440 do_check_eq(doc.childNodes.length, 2);
michael@0 441 baseRange = doc.createRange();
michael@0 442 baseRange.setStart(doc.firstChild, 1);
michael@0 443 baseRange.setEnd(doc.firstChild, 2);
michael@0 444 var frag = baseRange.extractContents();
michael@0 445 do_check_eq(frag.childNodes.length, 1);
michael@0 446 do_check_true(frag.firstChild instanceof C_i.nsIDOMComment);
michael@0 447 do_check_eq(frag.firstChild.nodeValue, "f");
michael@0 448
michael@0 449 /* smaug also requested attribute tests. Sadly, those are not yet supported
michael@0 450 in ranges - see https://bugzilla.mozilla.org/show_bug.cgi?id=302775.
michael@0 451 */
michael@0 452 }
michael@0 453
michael@0 454 function run_test() {
michael@0 455 run_extract_test();
michael@0 456 run_miscellaneous_tests();
michael@0 457 }

mercurial