Sat, 03 Jan 2015 20:18:00 +0100
Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.
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 }