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.

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

mercurial