mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-09-08 00:38:39 +09:00
import FIREFOX_52_6_0esr_RELEASE from mozilla-esr52 hg repo
This commit is contained in:
commit
dcd9973243
150858 changed files with 23884658 additions and 0 deletions
44
testing/web-platform/tests/selection/Document-open.html
Normal file
44
testing/web-platform/tests/selection/Document-open.html
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
<!doctype html>
|
||||
<title>Selection Document.open() tests</title>
|
||||
<div id=log></div>
|
||||
<script src=/resources/testharness.js></script>
|
||||
<script src=/resources/testharnessreport.js></script>
|
||||
<script>
|
||||
"use strict";
|
||||
|
||||
// This tests the HTML spec requirement "Replace the Document's singleton
|
||||
// objects with new instances of those objects. (This includes in particular
|
||||
// the Window, Location, History, ApplicationCache, and Navigator, objects, the
|
||||
// various BarProp objects, the two Storage objects, the various HTMLCollection
|
||||
// objects, and objects defined by other specifications, like Selection and the
|
||||
// document's UndoManager. It also includes all the Web IDL prototypes in the
|
||||
// JavaScript binding, including the Document object's prototype.)" in the
|
||||
// document.open() algorithm.
|
||||
|
||||
var iframe = document.createElement("iframe");
|
||||
var t = async_test("Selection must be replaced with a new object after document.open()");
|
||||
iframe.onload = function() {
|
||||
t.step(function() {
|
||||
var originalSelection = iframe.contentWindow.getSelection();
|
||||
assert_equals(originalSelection.rangeCount, 0,
|
||||
"Sanity check: rangeCount must be initially 0");
|
||||
iframe.contentDocument.body.appendChild(
|
||||
iframe.contentDocument.createTextNode("foo"));
|
||||
var range = iframe.contentDocument.createRange();
|
||||
range.selectNodeContents(iframe.contentDocument.body);
|
||||
iframe.contentWindow.getSelection().addRange(range);
|
||||
assert_equals(originalSelection.rangeCount, 1,
|
||||
"Sanity check: rangeCount must be 1 after adding a range");
|
||||
|
||||
iframe.contentDocument.open();
|
||||
|
||||
assert_not_equals(iframe.contentWindow.getSelection(), originalSelection,
|
||||
"After document.open(), the Selection object must no longer be the same");
|
||||
assert_equals(iframe.contentWindow.getSelection().rangeCount, 0,
|
||||
"After document.open(), rangeCount must be 0 again");
|
||||
});
|
||||
t.done();
|
||||
document.body.removeChild(iframe);
|
||||
};
|
||||
document.body.appendChild(iframe);
|
||||
</script>
|
||||
1
testing/web-platform/tests/selection/OWNERS
Normal file
1
testing/web-platform/tests/selection/OWNERS
Normal file
|
|
@ -0,0 +1 @@
|
|||
@ayg
|
||||
178
testing/web-platform/tests/selection/addRange.html
Normal file
178
testing/web-platform/tests/selection/addRange.html
Normal file
|
|
@ -0,0 +1,178 @@
|
|||
<!doctype html>
|
||||
<title>Selection.addRange() tests</title>
|
||||
<div id=log></div>
|
||||
<meta name="timeout" content="long">
|
||||
<script src=/resources/testharness.js></script>
|
||||
<script src=/resources/testharnessreport.js></script>
|
||||
<script src=common.js></script>
|
||||
<script>
|
||||
"use strict";
|
||||
|
||||
function testAddRange(exception, range, endpoints, qualifier, testName) {
|
||||
test(function() {
|
||||
assert_equals(exception, null, "Test setup must not throw exceptions");
|
||||
|
||||
selection.addRange(range);
|
||||
|
||||
assert_equals(range.startContainer, endpoints[0],
|
||||
"addRange() must not modify the startContainer of the Range it's given");
|
||||
assert_equals(range.startOffset, endpoints[1],
|
||||
"addRange() must not modify the startOffset of the Range it's given");
|
||||
assert_equals(range.endContainer, endpoints[2],
|
||||
"addRange() must not modify the endContainer of the Range it's given");
|
||||
assert_equals(range.endOffset, endpoints[3],
|
||||
"addRange() must not modify the endOffset of the Range it's given");
|
||||
}, testName + ": " + qualifier + " addRange() must not throw exceptions or modify the range it's given");
|
||||
|
||||
test(function() {
|
||||
assert_equals(exception, null, "Test setup must not throw exceptions");
|
||||
|
||||
assert_equals(selection.rangeCount, 1, "rangeCount must be 1");
|
||||
}, testName + ": " + qualifier + " addRange() must result in rangeCount being 1");
|
||||
|
||||
// From here on out we check selection.getRangeAt(selection.rangeCount - 1)
|
||||
// so as not to double-fail Gecko.
|
||||
|
||||
test(function() {
|
||||
assert_equals(exception, null, "Test setup must not throw exceptions");
|
||||
assert_not_equals(selection.rangeCount, 0, "Cannot proceed with tests if rangeCount is 0");
|
||||
|
||||
var newRange = selection.getRangeAt(selection.rangeCount - 1);
|
||||
|
||||
assert_not_equals(newRange, null,
|
||||
"getRangeAt(rangeCount - 1) must not return null");
|
||||
assert_equals(typeof newRange, "object",
|
||||
"getRangeAt(rangeCount - 1) must return an object");
|
||||
|
||||
assert_equals(newRange.startContainer, range.startContainer,
|
||||
"startContainer of the Selection's last Range must match the added Range");
|
||||
assert_equals(newRange.startOffset, range.startOffset,
|
||||
"startOffset of the Selection's last Range must match the added Range");
|
||||
assert_equals(newRange.endContainer, range.endContainer,
|
||||
"endContainer of the Selection's last Range must match the added Range");
|
||||
assert_equals(newRange.endOffset, range.endOffset,
|
||||
"endOffset of the Selection's last Range must match the added Range");
|
||||
}, testName + ": " + qualifier + " addRange() must result in the selection's last range having the specified endpoints");
|
||||
|
||||
test(function() {
|
||||
assert_equals(exception, null, "Test setup must not throw exceptions");
|
||||
assert_not_equals(selection.rangeCount, 0, "Cannot proceed with tests if rangeCount is 0");
|
||||
|
||||
assert_equals(selection.getRangeAt(selection.rangeCount - 1), range,
|
||||
"getRangeAt(rangeCount - 1) must return the same object we added");
|
||||
}, testName + ": " + qualifier + " addRange() must result in the selection's last range being the same object we added");
|
||||
|
||||
// Let's not test many different modifications -- one should be enough.
|
||||
test(function() {
|
||||
assert_equals(exception, null, "Test setup must not throw exceptions");
|
||||
assert_not_equals(selection.rangeCount, 0, "Cannot proceed with tests if rangeCount is 0");
|
||||
|
||||
if (range.startContainer == paras[0].firstChild
|
||||
&& range.startOffset == 0
|
||||
&& range.endContainer == paras[0].firstChild
|
||||
&& range.endOffset == 2) {
|
||||
// Just in case . . .
|
||||
range.setStart(paras[0].firstChild, 1);
|
||||
} else {
|
||||
range.setStart(paras[0].firstChild, 0);
|
||||
range.setEnd(paras[0].firstChild, 2);
|
||||
}
|
||||
|
||||
var newRange = selection.getRangeAt(selection.rangeCount - 1);
|
||||
|
||||
assert_equals(newRange.startContainer, range.startContainer,
|
||||
"After mutating the " + qualifier + " added Range, startContainer of the Selection's last Range must match the added Range");
|
||||
assert_equals(newRange.startOffset, range.startOffset,
|
||||
"After mutating the " + qualifier + " added Range, startOffset of the Selection's last Range must match the added Range");
|
||||
assert_equals(newRange.endContainer, range.endContainer,
|
||||
"After mutating the " + qualifier + " added Range, endContainer of the Selection's last Range must match the added Range");
|
||||
assert_equals(newRange.endOffset, range.endOffset,
|
||||
"After mutating the " + qualifier + " added Range, endOffset of the Selection's last Range must match the added Range");
|
||||
}, testName + ": modifying the " + qualifier + " added range must modify the Selection's last Range");
|
||||
|
||||
// Now test the other way too.
|
||||
test(function() {
|
||||
assert_equals(exception, null, "Test setup must not throw exceptions");
|
||||
assert_not_equals(selection.rangeCount, 0, "Cannot proceed with tests if rangeCount is 0");
|
||||
|
||||
var newRange = selection.getRangeAt(selection.rangeCount - 1);
|
||||
|
||||
if (newRange.startContainer == paras[0].firstChild
|
||||
&& newRange.startOffset == 4
|
||||
&& newRange.endContainer == paras[0].firstChild
|
||||
&& newRange.endOffset == 6) {
|
||||
newRange.setStart(paras[0].firstChild, 5);
|
||||
} else {
|
||||
newRange.setStart(paras[0].firstChild, 4);
|
||||
newRange.setStart(paras[0].firstChild, 6);
|
||||
}
|
||||
|
||||
assert_equals(newRange.startContainer, range.startContainer,
|
||||
"After " + qualifier + " addRange(), after mutating the Selection's last Range, startContainer of the Selection's last Range must match the added Range");
|
||||
assert_equals(newRange.startOffset, range.startOffset,
|
||||
"After " + qualifier + " addRange(), after mutating the Selection's last Range, startOffset of the Selection's last Range must match the added Range");
|
||||
assert_equals(newRange.endContainer, range.endContainer,
|
||||
"After " + qualifier + " addRange(), after mutating the Selection's last Range, endContainer of the Selection's last Range must match the added Range");
|
||||
assert_equals(newRange.endOffset, range.endOffset,
|
||||
"After " + qualifier + " addRange(), after mutating the Selection's last Range, endOffset of the Selection's last Range must match the added Range");
|
||||
}, testName + ": modifying the Selection's last Range must modify the " + qualifier + " added Range");
|
||||
}
|
||||
|
||||
// Do only n evals, not n^2
|
||||
var testRangesEvaled = testRanges.map(eval);
|
||||
|
||||
for (var i = 0; i < testRanges.length; i++) {
|
||||
for (var j = 0; j < testRanges.length; j++) {
|
||||
var testName = "Range " + i + " " + testRanges[i]
|
||||
+ " followed by Range " + j + " " + testRanges[j];
|
||||
|
||||
var exception = null;
|
||||
try {
|
||||
selection.removeAllRanges();
|
||||
|
||||
var endpoints1 = testRangesEvaled[i];
|
||||
var range1 = ownerDocument(endpoints1[0]).createRange();
|
||||
range1.setStart(endpoints1[0], endpoints1[1]);
|
||||
range1.setEnd(endpoints1[2], endpoints1[3]);
|
||||
|
||||
if (range1.startContainer !== endpoints1[0]) {
|
||||
throw "Sanity check: the first Range we created must have the desired startContainer";
|
||||
}
|
||||
if (range1.startOffset !== endpoints1[1]) {
|
||||
throw "Sanity check: the first Range we created must have the desired startOffset";
|
||||
}
|
||||
if (range1.endContainer !== endpoints1[2]) {
|
||||
throw "Sanity check: the first Range we created must have the desired endContainer";
|
||||
}
|
||||
if (range1.endOffset !== endpoints1[3]) {
|
||||
throw "Sanity check: the first Range we created must have the desired endOffset";
|
||||
}
|
||||
|
||||
var endpoints2 = testRangesEvaled[j];
|
||||
var range2 = ownerDocument(endpoints2[0]).createRange();
|
||||
range2.setStart(endpoints2[0], endpoints2[1]);
|
||||
range2.setEnd(endpoints2[2], endpoints2[3]);
|
||||
|
||||
if (range2.startContainer !== endpoints2[0]) {
|
||||
throw "Sanity check: the second Range we created must have the desired startContainer";
|
||||
}
|
||||
if (range2.startOffset !== endpoints2[1]) {
|
||||
throw "Sanity check: the second Range we created must have the desired startOffset";
|
||||
}
|
||||
if (range2.endContainer !== endpoints2[2]) {
|
||||
throw "Sanity check: the second Range we created must have the desired endContainer";
|
||||
}
|
||||
if (range2.endOffset !== endpoints2[3]) {
|
||||
throw "Sanity check: the second Range we created must have the desired endOffset";
|
||||
}
|
||||
} catch (e) {
|
||||
exception = e;
|
||||
}
|
||||
|
||||
testAddRange(exception, range1, endpoints1, "first", testName);
|
||||
testAddRange(exception, range2, endpoints2, "second", testName);
|
||||
}
|
||||
}
|
||||
|
||||
testDiv.style.display = "none";
|
||||
</script>
|
||||
88
testing/web-platform/tests/selection/collapse.html
Normal file
88
testing/web-platform/tests/selection/collapse.html
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
<!doctype html>
|
||||
<title>Selection.collapse() tests</title>
|
||||
<meta name=timeout content=long>
|
||||
<div id=log></div>
|
||||
<script src=/resources/testharness.js></script>
|
||||
<script src=/resources/testharnessreport.js></script>
|
||||
<script src=common.js></script>
|
||||
<script>
|
||||
"use strict";
|
||||
|
||||
function testCollapse(range, point) {
|
||||
selection.removeAllRanges();
|
||||
var addedRange;
|
||||
if (range) {
|
||||
addedRange = range.cloneRange();
|
||||
selection.addRange(addedRange);
|
||||
}
|
||||
|
||||
if (point[0].nodeType == Node.DOCUMENT_TYPE_NODE) {
|
||||
assert_throws("INVALID_NODE_TYPE_ERR", function() {
|
||||
selection.collapse(point[0], point[1]);
|
||||
}, "Must throw INVALID_NODE_TYPE_ERR when collapse()ing if the node is a DocumentType");
|
||||
return;
|
||||
}
|
||||
|
||||
if (point[1] < 0 || point[1] > getNodeLength(point[0])) {
|
||||
assert_throws("INDEX_SIZE_ERR", function() {
|
||||
selection.collapse(point[0], point[1]);
|
||||
}, "Must throw INDEX_SIZE_ERR when collapse()ing if the offset is negative or greater than the node's length");
|
||||
return;
|
||||
}
|
||||
|
||||
selection.collapse(point[0], point[1]);
|
||||
|
||||
assert_equals(selection.rangeCount, 1,
|
||||
"selection.rangeCount must equal 1 after collapse()");
|
||||
assert_equals(selection.focusNode, point[0],
|
||||
"focusNode must equal the node we collapse()d to");
|
||||
assert_equals(selection.focusOffset, point[1],
|
||||
"focusOffset must equal the offset we collapse()d to");
|
||||
assert_equals(selection.focusNode, selection.anchorNode,
|
||||
"focusNode and anchorNode must be equal after collapse()");
|
||||
assert_equals(selection.focusOffset, selection.anchorOffset,
|
||||
"focusOffset and anchorOffset must be equal after collapse()");
|
||||
if (range) {
|
||||
assert_equals(addedRange.startContainer, range.startContainer,
|
||||
"collapse() must not change the startContainer of a preexisting Range");
|
||||
assert_equals(addedRange.endContainer, range.endContainer,
|
||||
"collapse() must not change the endContainer of a preexisting Range");
|
||||
assert_equals(addedRange.startOffset, range.startOffset,
|
||||
"collapse() must not change the startOffset of a preexisting Range");
|
||||
assert_equals(addedRange.endOffset, range.endOffset,
|
||||
"collapse() must not change the endOffset of a preexisting Range");
|
||||
}
|
||||
}
|
||||
|
||||
// Also test a selection with no ranges
|
||||
testRanges.unshift("[]");
|
||||
|
||||
// Don't want to eval() each point a bazillion times
|
||||
var testPointsCached = [];
|
||||
for (var i = 0; i < testPoints.length; i++) {
|
||||
testPointsCached.push(eval(testPoints[i]));
|
||||
}
|
||||
|
||||
var tests = [];
|
||||
for (var i = 0; i < testRanges.length; i++) {
|
||||
var endpoints = eval(testRanges[i]);
|
||||
var range;
|
||||
test(function() {
|
||||
if (endpoints.length) {
|
||||
range = ownerDocument(endpoints[0]).createRange();
|
||||
range.setStart(endpoints[0], endpoints[1]);
|
||||
range.setEnd(endpoints[2], endpoints[3]);
|
||||
} else {
|
||||
// Empty selection
|
||||
range = null;
|
||||
}
|
||||
}, "Set up range " + i + " " + testRanges[i]);
|
||||
for (var j = 0; j < testPoints.length; j++) {
|
||||
tests.push(["Range " + i + " " + testRanges[i] + ", point " + j + " " + testPoints[j], range, testPointsCached[j]]);
|
||||
}
|
||||
}
|
||||
|
||||
generate_tests(testCollapse, tests);
|
||||
|
||||
testDiv.style.display = "none";
|
||||
</script>
|
||||
121
testing/web-platform/tests/selection/collapseToStartEnd.html
Normal file
121
testing/web-platform/tests/selection/collapseToStartEnd.html
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
<!doctype html>
|
||||
<title>Selection.collapseTo(Start|End)() tests</title>
|
||||
<div id=log></div>
|
||||
<script src=/resources/testharness.js></script>
|
||||
<script src=/resources/testharnessreport.js></script>
|
||||
<script src=common.js></script>
|
||||
<script>
|
||||
"use strict";
|
||||
|
||||
// Also test a selection with no ranges
|
||||
testRanges.unshift("[]");
|
||||
|
||||
for (var i = 0; i < testRanges.length; i++) {
|
||||
test(function() {
|
||||
selection.removeAllRanges();
|
||||
var endpoints = eval(testRanges[i]);
|
||||
if (!endpoints.length) {
|
||||
assert_throws("INVALID_STATE_ERR", function() {
|
||||
selection.collapseToStart();
|
||||
}, "Must throw InvalidStateErr if the selection's range is null");
|
||||
return;
|
||||
}
|
||||
|
||||
var addedRange = ownerDocument(endpoints[0]).createRange();
|
||||
addedRange.setStart(endpoints[0], endpoints[1]);
|
||||
addedRange.setEnd(endpoints[2], endpoints[3]);
|
||||
selection.addRange(addedRange);
|
||||
|
||||
// We don't penalize browsers here for mishandling addRange() and
|
||||
// adding a different range than we specified. They fail addRange()
|
||||
// tests for that, and don't have to fail collapseToStart/End() tests
|
||||
// too. They do fail if they throw unexpectedly, though. I also fail
|
||||
// them if there's no range at all, because otherwise they could pass
|
||||
// all tests if addRange() always does nothing and collapseToStart()
|
||||
// always throws.
|
||||
assert_equals(selection.rangeCount, 1,
|
||||
"Sanity check: rangeCount must equal 1 after addRange()");
|
||||
|
||||
var expectedEndpoint = [
|
||||
selection.getRangeAt(0).startContainer,
|
||||
selection.getRangeAt(0).startOffset
|
||||
];
|
||||
|
||||
selection.collapseToStart();
|
||||
|
||||
assert_equals(selection.rangeCount, 1,
|
||||
"selection.rangeCount must equal 1");
|
||||
assert_equals(selection.focusNode, expectedEndpoint[0],
|
||||
"focusNode must equal the original start node");
|
||||
assert_equals(selection.focusOffset, expectedEndpoint[1],
|
||||
"focusOffset must equal the original start offset");
|
||||
assert_equals(selection.anchorNode, expectedEndpoint[0],
|
||||
"anchorNode must equal the original start node");
|
||||
assert_equals(selection.anchorOffset, expectedEndpoint[1],
|
||||
"anchorOffset must equal the original start offset");
|
||||
assert_equals(addedRange.startContainer, endpoints[0],
|
||||
"collapseToStart() must not change the startContainer of the selection's original range");
|
||||
assert_equals(addedRange.startOffset, endpoints[1],
|
||||
"collapseToStart() must not change the startOffset of the selection's original range");
|
||||
assert_equals(addedRange.endContainer, endpoints[2],
|
||||
"collapseToStart() must not change the endContainer of the selection's original range");
|
||||
assert_equals(addedRange.endOffset, endpoints[3],
|
||||
"collapseToStart() must not change the endOffset of the selection's original range");
|
||||
}, "Range " + i + " " + testRanges[i] + " collapseToStart()");
|
||||
|
||||
// Copy-paste of above
|
||||
test(function() {
|
||||
selection.removeAllRanges();
|
||||
var endpoints = eval(testRanges[i]);
|
||||
if (!endpoints.length) {
|
||||
assert_throws("INVALID_STATE_ERR", function() {
|
||||
selection.collapseToEnd();
|
||||
}, "Must throw InvalidStateErr if the selection's range is null");
|
||||
return;
|
||||
}
|
||||
|
||||
var addedRange = ownerDocument(endpoints[0]).createRange();
|
||||
addedRange.setStart(endpoints[0], endpoints[1]);
|
||||
addedRange.setEnd(endpoints[2], endpoints[3]);
|
||||
selection.addRange(addedRange);
|
||||
|
||||
// We don't penalize browsers here for mishandling addRange() and
|
||||
// adding a different range than we specified. They fail addRange()
|
||||
// tests for that, and don't have to fail collapseToStart/End() tests
|
||||
// too. They do fail if they throw unexpectedly, though. I also fail
|
||||
// them if there's no range at all, because otherwise they could pass
|
||||
// all tests if addRange() always does nothing and collapseToStart()
|
||||
// always throws.
|
||||
assert_equals(selection.rangeCount, 1,
|
||||
"Sanity check: rangeCount must equal 1 after addRange()");
|
||||
|
||||
var expectedEndpoint = [
|
||||
selection.getRangeAt(0).endContainer,
|
||||
selection.getRangeAt(0).endOffset
|
||||
];
|
||||
|
||||
selection.collapseToEnd();
|
||||
|
||||
assert_equals(selection.rangeCount, 1,
|
||||
"selection.rangeCount must equal 1");
|
||||
assert_equals(selection.focusNode, expectedEndpoint[0],
|
||||
"focusNode must equal the original end node");
|
||||
assert_equals(selection.focusOffset, expectedEndpoint[1],
|
||||
"focusOffset must equal the original end offset");
|
||||
assert_equals(selection.anchorNode, expectedEndpoint[0],
|
||||
"anchorNode must equal the original end node");
|
||||
assert_equals(selection.anchorOffset, expectedEndpoint[1],
|
||||
"anchorOffset must equal the original end offset");
|
||||
assert_equals(addedRange.startContainer, endpoints[0],
|
||||
"collapseToEnd() must not change the startContainer of the selection's original range");
|
||||
assert_equals(addedRange.startOffset, endpoints[1],
|
||||
"collapseToEnd() must not change the startOffset of the selection's original range");
|
||||
assert_equals(addedRange.endContainer, endpoints[2],
|
||||
"collapseToEnd() must not change the endContainer of the selection's original range");
|
||||
assert_equals(addedRange.endOffset, endpoints[3],
|
||||
"collapseToEnd() must not change the endOffset of the selection's original range");
|
||||
}, "Range " + i + " " + testRanges[i] + " collapseToEnd()");
|
||||
}
|
||||
|
||||
testDiv.style.display = "none";
|
||||
</script>
|
||||
952
testing/web-platform/tests/selection/common.js
Normal file
952
testing/web-platform/tests/selection/common.js
Normal file
|
|
@ -0,0 +1,952 @@
|
|||
"use strict";
|
||||
// TODO: iframes, contenteditable/designMode
|
||||
|
||||
// Everything is done in functions in this test harness, so we have to declare
|
||||
// all the variables before use to make sure they can be reused.
|
||||
var selection;
|
||||
var testDiv, paras, detachedDiv, detachedPara1, detachedPara2,
|
||||
foreignDoc, foreignPara1, foreignPara2, xmlDoc, xmlElement,
|
||||
detachedXmlElement, detachedTextNode, foreignTextNode,
|
||||
detachedForeignTextNode, xmlTextNode, detachedXmlTextNode,
|
||||
processingInstruction, detachedProcessingInstruction, comment,
|
||||
detachedComment, foreignComment, detachedForeignComment, xmlComment,
|
||||
detachedXmlComment, docfrag, foreignDocfrag, xmlDocfrag, doctype,
|
||||
foreignDoctype, xmlDoctype;
|
||||
var testRanges, testPoints, testNodes;
|
||||
|
||||
function setupRangeTests() {
|
||||
selection = getSelection();
|
||||
testDiv = document.querySelector("#test");
|
||||
if (testDiv) {
|
||||
testDiv.parentNode.removeChild(testDiv);
|
||||
}
|
||||
testDiv = document.createElement("div");
|
||||
testDiv.id = "test";
|
||||
document.body.insertBefore(testDiv, document.body.firstChild);
|
||||
// Test some diacritics, to make sure browsers are using code units here
|
||||
// and not something like grapheme clusters.
|
||||
testDiv.innerHTML = "<p id=a>Äb̈c̈d̈ëf̈g̈ḧ\n"
|
||||
+ "<p id=b style=display:none>Ijklmnop\n"
|
||||
+ "<p id=c>Qrstuvwx"
|
||||
+ "<p id=d style=display:none>Yzabcdef"
|
||||
+ "<p id=e style=display:none>Ghijklmn";
|
||||
paras = testDiv.querySelectorAll("p");
|
||||
|
||||
detachedDiv = document.createElement("div");
|
||||
detachedPara1 = document.createElement("p");
|
||||
detachedPara1.appendChild(document.createTextNode("Opqrstuv"));
|
||||
detachedPara2 = document.createElement("p");
|
||||
detachedPara2.appendChild(document.createTextNode("Wxyzabcd"));
|
||||
detachedDiv.appendChild(detachedPara1);
|
||||
detachedDiv.appendChild(detachedPara2);
|
||||
|
||||
// Opera doesn't automatically create a doctype for a new HTML document,
|
||||
// contrary to spec. It also doesn't let you add doctypes to documents
|
||||
// after the fact through any means I've tried. So foreignDoc in Opera
|
||||
// will have no doctype, foreignDoctype will be null, and Opera will fail
|
||||
// some tests somewhat mysteriously as a result.
|
||||
foreignDoc = document.implementation.createHTMLDocument("");
|
||||
foreignPara1 = foreignDoc.createElement("p");
|
||||
foreignPara1.appendChild(foreignDoc.createTextNode("Efghijkl"));
|
||||
foreignPara2 = foreignDoc.createElement("p");
|
||||
foreignPara2.appendChild(foreignDoc.createTextNode("Mnopqrst"));
|
||||
foreignDoc.body.appendChild(foreignPara1);
|
||||
foreignDoc.body.appendChild(foreignPara2);
|
||||
|
||||
// Now we get to do really silly stuff, which nobody in the universe is
|
||||
// ever going to actually do, but the spec defines behavior, so too bad.
|
||||
// Testing is fun!
|
||||
xmlDoctype = document.implementation.createDocumentType("qorflesnorf", "abcde", "x\"'y");
|
||||
xmlDoc = document.implementation.createDocument(null, null, xmlDoctype);
|
||||
detachedXmlElement = xmlDoc.createElement("everyone-hates-hyphenated-element-names");
|
||||
detachedTextNode = document.createTextNode("Uvwxyzab");
|
||||
detachedForeignTextNode = foreignDoc.createTextNode("Cdefghij");
|
||||
detachedXmlTextNode = xmlDoc.createTextNode("Klmnopqr");
|
||||
// PIs only exist in XML documents, so don't bother with document or
|
||||
// foreignDoc.
|
||||
detachedProcessingInstruction = xmlDoc.createProcessingInstruction("whippoorwill", "chirp chirp chirp");
|
||||
detachedComment = document.createComment("Stuvwxyz");
|
||||
// Hurrah, we finally got to "z" at the end!
|
||||
detachedForeignComment = foreignDoc.createComment("אריה יהודה");
|
||||
detachedXmlComment = xmlDoc.createComment("בן חיים אליעזר");
|
||||
|
||||
// We should also test with document fragments that actually contain stuff
|
||||
// . . . but, maybe later.
|
||||
docfrag = document.createDocumentFragment();
|
||||
foreignDocfrag = foreignDoc.createDocumentFragment();
|
||||
xmlDocfrag = xmlDoc.createDocumentFragment();
|
||||
|
||||
xmlElement = xmlDoc.createElement("igiveuponcreativenames");
|
||||
xmlTextNode = xmlDoc.createTextNode("do re mi fa so la ti");
|
||||
xmlElement.appendChild(xmlTextNode);
|
||||
processingInstruction = xmlDoc.createProcessingInstruction("somePI", 'Did you know that ":syn sync fromstart" is very useful when using vim to edit large amounts of JavaScript embedded in HTML?');
|
||||
xmlDoc.appendChild(xmlElement);
|
||||
xmlDoc.appendChild(processingInstruction);
|
||||
xmlComment = xmlDoc.createComment("I maliciously created a comment that will break incautious XML serializers, but Firefox threw an exception, so all I got was this lousy T-shirt");
|
||||
xmlDoc.appendChild(xmlComment);
|
||||
|
||||
comment = document.createComment("Alphabet soup?");
|
||||
testDiv.appendChild(comment);
|
||||
|
||||
foreignComment = foreignDoc.createComment('"Commenter" and "commentator" mean different things. I\'ve seen non-native speakers trip up on this.');
|
||||
foreignDoc.appendChild(foreignComment);
|
||||
foreignTextNode = foreignDoc.createTextNode("I admit that I harbor doubts about whether we really need so many things to test, but it's too late to stop now.");
|
||||
foreignDoc.body.appendChild(foreignTextNode);
|
||||
|
||||
doctype = document.doctype;
|
||||
foreignDoctype = foreignDoc.doctype;
|
||||
|
||||
testRanges = [
|
||||
// Various ranges within the text node children of different
|
||||
// paragraphs. All should be valid.
|
||||
"[paras[0].firstChild, 0, paras[0].firstChild, 0]",
|
||||
"[paras[0].firstChild, 0, paras[0].firstChild, 1]",
|
||||
"[paras[0].firstChild, 2, paras[0].firstChild, 8]",
|
||||
"[paras[0].firstChild, 2, paras[0].firstChild, 9]",
|
||||
"[paras[1].firstChild, 0, paras[1].firstChild, 0]",
|
||||
"[paras[1].firstChild, 0, paras[1].firstChild, 1]",
|
||||
"[paras[1].firstChild, 2, paras[1].firstChild, 8]",
|
||||
"[paras[1].firstChild, 2, paras[1].firstChild, 9]",
|
||||
"[detachedPara1.firstChild, 0, detachedPara1.firstChild, 0]",
|
||||
"[detachedPara1.firstChild, 0, detachedPara1.firstChild, 1]",
|
||||
"[detachedPara1.firstChild, 2, detachedPara1.firstChild, 8]",
|
||||
"[foreignPara1.firstChild, 0, foreignPara1.firstChild, 0]",
|
||||
"[foreignPara1.firstChild, 0, foreignPara1.firstChild, 1]",
|
||||
"[foreignPara1.firstChild, 2, foreignPara1.firstChild, 8]",
|
||||
// Now try testing some elements, not just text nodes.
|
||||
"[document.documentElement, 0, document.documentElement, 1]",
|
||||
"[document.documentElement, 0, document.documentElement, 2]",
|
||||
"[document.documentElement, 1, document.documentElement, 2]",
|
||||
"[document.head, 1, document.head, 1]",
|
||||
"[document.body, 0, document.body, 1]",
|
||||
"[foreignDoc.documentElement, 0, foreignDoc.documentElement, 1]",
|
||||
"[foreignDoc.head, 1, foreignDoc.head, 1]",
|
||||
"[foreignDoc.body, 0, foreignDoc.body, 0]",
|
||||
"[paras[0], 0, paras[0], 0]",
|
||||
"[paras[0], 0, paras[0], 1]",
|
||||
"[detachedPara1, 0, detachedPara1, 0]",
|
||||
"[detachedPara1, 0, detachedPara1, 1]",
|
||||
// Now try some ranges that span elements.
|
||||
"[paras[0].firstChild, 0, paras[1].firstChild, 0]",
|
||||
"[paras[0].firstChild, 0, paras[1].firstChild, 8]",
|
||||
"[paras[0].firstChild, 3, paras[3], 1]",
|
||||
// How about something that spans a node and its descendant?
|
||||
"[paras[0], 0, paras[0].firstChild, 7]",
|
||||
"[testDiv, 2, paras[4], 1]",
|
||||
"[testDiv, 1, paras[2].firstChild, 5]",
|
||||
"[document.documentElement, 1, document.body, 0]",
|
||||
"[foreignDoc.documentElement, 1, foreignDoc.body, 0]",
|
||||
// Then a few more interesting things just for good measure.
|
||||
"[document, 0, document, 1]",
|
||||
"[document, 0, document, 2]",
|
||||
"[document, 1, document, 2]",
|
||||
"[testDiv, 0, comment, 5]",
|
||||
"[paras[2].firstChild, 4, comment, 2]",
|
||||
"[paras[3], 1, comment, 8]",
|
||||
"[foreignDoc, 0, foreignDoc, 0]",
|
||||
"[foreignDoc, 1, foreignComment, 2]",
|
||||
"[foreignDoc.body, 0, foreignTextNode, 36]",
|
||||
"[xmlDoc, 0, xmlDoc, 0]",
|
||||
// Opera 11 crashes if you extractContents() a range that ends at offset
|
||||
// zero in a comment. Comment out this line to run the tests successfully.
|
||||
"[xmlDoc, 1, xmlComment, 0]",
|
||||
"[detachedTextNode, 0, detachedTextNode, 8]",
|
||||
"[detachedForeignTextNode, 7, detachedForeignTextNode, 7]",
|
||||
"[detachedForeignTextNode, 0, detachedForeignTextNode, 8]",
|
||||
"[detachedXmlTextNode, 7, detachedXmlTextNode, 7]",
|
||||
"[detachedXmlTextNode, 0, detachedXmlTextNode, 8]",
|
||||
"[detachedComment, 3, detachedComment, 4]",
|
||||
"[detachedComment, 5, detachedComment, 5]",
|
||||
"[detachedForeignComment, 0, detachedForeignComment, 1]",
|
||||
"[detachedForeignComment, 4, detachedForeignComment, 4]",
|
||||
"[detachedXmlComment, 2, detachedXmlComment, 6]",
|
||||
"[docfrag, 0, docfrag, 0]",
|
||||
"[foreignDocfrag, 0, foreignDocfrag, 0]",
|
||||
"[xmlDocfrag, 0, xmlDocfrag, 0]",
|
||||
];
|
||||
|
||||
testPoints = [
|
||||
// Various positions within the page, some invalid. Remember that
|
||||
// paras[0] is visible, and paras[1] is display: none.
|
||||
"[paras[0].firstChild, -1]",
|
||||
"[paras[0].firstChild, 0]",
|
||||
"[paras[0].firstChild, 1]",
|
||||
"[paras[0].firstChild, 2]",
|
||||
"[paras[0].firstChild, 8]",
|
||||
"[paras[0].firstChild, 9]",
|
||||
"[paras[0].firstChild, 10]",
|
||||
"[paras[0].firstChild, 65535]",
|
||||
"[paras[1].firstChild, -1]",
|
||||
"[paras[1].firstChild, 0]",
|
||||
"[paras[1].firstChild, 1]",
|
||||
"[paras[1].firstChild, 2]",
|
||||
"[paras[1].firstChild, 8]",
|
||||
"[paras[1].firstChild, 9]",
|
||||
"[paras[1].firstChild, 10]",
|
||||
"[paras[1].firstChild, 65535]",
|
||||
"[detachedPara1.firstChild, 0]",
|
||||
"[detachedPara1.firstChild, 1]",
|
||||
"[detachedPara1.firstChild, 8]",
|
||||
"[detachedPara1.firstChild, 9]",
|
||||
"[foreignPara1.firstChild, 0]",
|
||||
"[foreignPara1.firstChild, 1]",
|
||||
"[foreignPara1.firstChild, 8]",
|
||||
"[foreignPara1.firstChild, 9]",
|
||||
// Now try testing some elements, not just text nodes.
|
||||
"[document.documentElement, -1]",
|
||||
"[document.documentElement, 0]",
|
||||
"[document.documentElement, 1]",
|
||||
"[document.documentElement, 2]",
|
||||
"[document.documentElement, 7]",
|
||||
"[document.head, 1]",
|
||||
"[document.body, 3]",
|
||||
"[foreignDoc.documentElement, 0]",
|
||||
"[foreignDoc.documentElement, 1]",
|
||||
"[foreignDoc.head, 0]",
|
||||
"[foreignDoc.body, 1]",
|
||||
"[paras[0], 0]",
|
||||
"[paras[0], 1]",
|
||||
"[paras[0], 2]",
|
||||
"[paras[1], 0]",
|
||||
"[paras[1], 1]",
|
||||
"[paras[1], 2]",
|
||||
"[detachedPara1, 0]",
|
||||
"[detachedPara1, 1]",
|
||||
"[testDiv, 0]",
|
||||
"[testDiv, 3]",
|
||||
// Then a few more interesting things just for good measure.
|
||||
"[document, -1]",
|
||||
"[document, 0]",
|
||||
"[document, 1]",
|
||||
"[document, 2]",
|
||||
"[document, 3]",
|
||||
"[comment, -1]",
|
||||
"[comment, 0]",
|
||||
"[comment, 4]",
|
||||
"[comment, 96]",
|
||||
"[foreignDoc, 0]",
|
||||
"[foreignDoc, 1]",
|
||||
"[foreignComment, 2]",
|
||||
"[foreignTextNode, 0]",
|
||||
"[foreignTextNode, 36]",
|
||||
"[xmlDoc, -1]",
|
||||
"[xmlDoc, 0]",
|
||||
"[xmlDoc, 1]",
|
||||
"[xmlDoc, 5]",
|
||||
"[xmlComment, 0]",
|
||||
"[xmlComment, 4]",
|
||||
"[processingInstruction, 0]",
|
||||
"[processingInstruction, 5]",
|
||||
"[processingInstruction, 9]",
|
||||
"[detachedTextNode, 0]",
|
||||
"[detachedTextNode, 8]",
|
||||
"[detachedForeignTextNode, 0]",
|
||||
"[detachedForeignTextNode, 8]",
|
||||
"[detachedXmlTextNode, 0]",
|
||||
"[detachedXmlTextNode, 8]",
|
||||
"[detachedProcessingInstruction, 12]",
|
||||
"[detachedComment, 3]",
|
||||
"[detachedComment, 5]",
|
||||
"[detachedForeignComment, 0]",
|
||||
"[detachedForeignComment, 4]",
|
||||
"[detachedXmlComment, 2]",
|
||||
"[docfrag, 0]",
|
||||
"[foreignDocfrag, 0]",
|
||||
"[xmlDocfrag, 0]",
|
||||
"[doctype, 0]",
|
||||
"[doctype, -17]",
|
||||
"[doctype, 1]",
|
||||
"[foreignDoctype, 0]",
|
||||
"[xmlDoctype, 0]",
|
||||
];
|
||||
|
||||
testNodes = [
|
||||
"paras[0]",
|
||||
"paras[0].firstChild",
|
||||
"paras[1]",
|
||||
"paras[1].firstChild",
|
||||
"foreignPara1",
|
||||
"foreignPara1.firstChild",
|
||||
"detachedPara1",
|
||||
"detachedPara1.firstChild",
|
||||
"detachedPara1",
|
||||
"detachedPara1.firstChild",
|
||||
"testDiv",
|
||||
"document",
|
||||
"detachedDiv",
|
||||
"detachedPara2",
|
||||
"foreignDoc",
|
||||
"foreignPara2",
|
||||
"xmlDoc",
|
||||
"xmlElement",
|
||||
"detachedXmlElement",
|
||||
"detachedTextNode",
|
||||
"foreignTextNode",
|
||||
"detachedForeignTextNode",
|
||||
"xmlTextNode",
|
||||
"detachedXmlTextNode",
|
||||
"processingInstruction",
|
||||
"detachedProcessingInstruction",
|
||||
"comment",
|
||||
"detachedComment",
|
||||
"foreignComment",
|
||||
"detachedForeignComment",
|
||||
"xmlComment",
|
||||
"detachedXmlComment",
|
||||
"docfrag",
|
||||
"foreignDocfrag",
|
||||
"xmlDocfrag",
|
||||
"doctype",
|
||||
"foreignDoctype",
|
||||
"xmlDoctype",
|
||||
];
|
||||
}
|
||||
if ("setup" in window) {
|
||||
setup(setupRangeTests);
|
||||
} else {
|
||||
// Presumably we're running from within an iframe or something
|
||||
setupRangeTests();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the length of a node as specified in DOM Range.
|
||||
*/
|
||||
function getNodeLength(node) {
|
||||
if (node.nodeType == Node.DOCUMENT_TYPE_NODE) {
|
||||
return 0;
|
||||
}
|
||||
if (node.nodeType == Node.TEXT_NODE || node.nodeType == Node.PROCESSING_INSTRUCTION_NODE || node.nodeType == Node.COMMENT_NODE) {
|
||||
return node.length;
|
||||
}
|
||||
return node.childNodes.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the furthest ancestor of a Node as defined by the spec.
|
||||
*/
|
||||
function furthestAncestor(node) {
|
||||
var root = node;
|
||||
while (root.parentNode != null) {
|
||||
root = root.parentNode;
|
||||
}
|
||||
return root;
|
||||
}
|
||||
|
||||
/**
|
||||
* "The ancestor containers of a Node are the Node itself and all its
|
||||
* ancestors."
|
||||
*
|
||||
* Is node1 an ancestor container of node2?
|
||||
*/
|
||||
function isAncestorContainer(node1, node2) {
|
||||
return node1 == node2 ||
|
||||
(node2.compareDocumentPosition(node1) & Node.DOCUMENT_POSITION_CONTAINS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the first Node that's after node in tree order, or null if node is
|
||||
* the last Node.
|
||||
*/
|
||||
function nextNode(node) {
|
||||
if (node.hasChildNodes()) {
|
||||
return node.firstChild;
|
||||
}
|
||||
return nextNodeDescendants(node);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the last Node that's before node in tree order, or null if node is
|
||||
* the first Node.
|
||||
*/
|
||||
function previousNode(node) {
|
||||
if (node.previousSibling) {
|
||||
node = node.previousSibling;
|
||||
while (node.hasChildNodes()) {
|
||||
node = node.lastChild;
|
||||
}
|
||||
return node;
|
||||
}
|
||||
return node.parentNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the next Node that's after node and all its descendants in tree
|
||||
* order, or null if node is the last Node or an ancestor of it.
|
||||
*/
|
||||
function nextNodeDescendants(node) {
|
||||
while (node && !node.nextSibling) {
|
||||
node = node.parentNode;
|
||||
}
|
||||
if (!node) {
|
||||
return null;
|
||||
}
|
||||
return node.nextSibling;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ownerDocument of the Node, or the Node itself if it's a
|
||||
* Document.
|
||||
*/
|
||||
function ownerDocument(node) {
|
||||
return node.nodeType == Node.DOCUMENT_NODE
|
||||
? node
|
||||
: node.ownerDocument;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if ancestor is an ancestor of descendant, false otherwise.
|
||||
*/
|
||||
function isAncestor(ancestor, descendant) {
|
||||
if (!ancestor || !descendant) {
|
||||
return false;
|
||||
}
|
||||
while (descendant && descendant != ancestor) {
|
||||
descendant = descendant.parentNode;
|
||||
}
|
||||
return descendant == ancestor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if descendant is a descendant of ancestor, false otherwise.
|
||||
*/
|
||||
function isDescendant(descendant, ancestor) {
|
||||
return isAncestor(ancestor, descendant);
|
||||
}
|
||||
|
||||
/**
|
||||
* The position of two boundary points relative to one another, as defined by
|
||||
* the spec.
|
||||
*/
|
||||
function getPosition(nodeA, offsetA, nodeB, offsetB) {
|
||||
// "If node A is the same as node B, return equal if offset A equals offset
|
||||
// B, before if offset A is less than offset B, and after if offset A is
|
||||
// greater than offset B."
|
||||
if (nodeA == nodeB) {
|
||||
if (offsetA == offsetB) {
|
||||
return "equal";
|
||||
}
|
||||
if (offsetA < offsetB) {
|
||||
return "before";
|
||||
}
|
||||
if (offsetA > offsetB) {
|
||||
return "after";
|
||||
}
|
||||
}
|
||||
|
||||
// "If node A is after node B in tree order, compute the position of (node
|
||||
// B, offset B) relative to (node A, offset A). If it is before, return
|
||||
// after. If it is after, return before."
|
||||
if (nodeB.compareDocumentPosition(nodeA) & Node.DOCUMENT_POSITION_FOLLOWING) {
|
||||
var pos = getPosition(nodeB, offsetB, nodeA, offsetA);
|
||||
if (pos == "before") {
|
||||
return "after";
|
||||
}
|
||||
if (pos == "after") {
|
||||
return "before";
|
||||
}
|
||||
}
|
||||
|
||||
// "If node A is an ancestor of node B:"
|
||||
if (nodeB.compareDocumentPosition(nodeA) & Node.DOCUMENT_POSITION_CONTAINS) {
|
||||
// "Let child equal node B."
|
||||
var child = nodeB;
|
||||
|
||||
// "While child is not a child of node A, set child to its parent."
|
||||
while (child.parentNode != nodeA) {
|
||||
child = child.parentNode;
|
||||
}
|
||||
|
||||
// "If the index of child is less than offset A, return after."
|
||||
if (indexOf(child) < offsetA) {
|
||||
return "after";
|
||||
}
|
||||
}
|
||||
|
||||
// "Return before."
|
||||
return "before";
|
||||
}
|
||||
|
||||
/**
|
||||
* "contained" as defined by DOM Range: "A Node node is contained in a range
|
||||
* range if node's furthest ancestor is the same as range's root, and (node, 0)
|
||||
* is after range's start, and (node, length of node) is before range's end."
|
||||
*/
|
||||
function isContained(node, range) {
|
||||
var pos1 = getPosition(node, 0, range.startContainer, range.startOffset);
|
||||
var pos2 = getPosition(node, getNodeLength(node), range.endContainer, range.endOffset);
|
||||
|
||||
return furthestAncestor(node) == furthestAncestor(range.startContainer)
|
||||
&& pos1 == "after"
|
||||
&& pos2 == "before";
|
||||
}
|
||||
|
||||
/**
|
||||
* "partially contained" as defined by DOM Range: "A Node is partially
|
||||
* contained in a range if it is an ancestor container of the range's start but
|
||||
* not its end, or vice versa."
|
||||
*/
|
||||
function isPartiallyContained(node, range) {
|
||||
var cond1 = isAncestorContainer(node, range.startContainer);
|
||||
var cond2 = isAncestorContainer(node, range.endContainer);
|
||||
return (cond1 && !cond2) || (cond2 && !cond1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Index of a node as defined by the spec.
|
||||
*/
|
||||
function indexOf(node) {
|
||||
if (!node.parentNode) {
|
||||
// No preceding sibling nodes, right?
|
||||
return 0;
|
||||
}
|
||||
var i = 0;
|
||||
while (node != node.parentNode.childNodes[i]) {
|
||||
i++;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
/**
|
||||
* extractContents() implementation, following the spec. If an exception is
|
||||
* supposed to be thrown, will return a string with the name (e.g.,
|
||||
* "HIERARCHY_REQUEST_ERR") instead of a document fragment. It might also
|
||||
* return an arbitrary human-readable string if a condition is hit that implies
|
||||
* a spec bug.
|
||||
*/
|
||||
function myExtractContents(range) {
|
||||
// "If the context object's detached flag is set, raise an
|
||||
// INVALID_STATE_ERR exception and abort these steps."
|
||||
try {
|
||||
range.collapsed;
|
||||
} catch (e) {
|
||||
return "INVALID_STATE_ERR";
|
||||
}
|
||||
|
||||
// "Let frag be a new DocumentFragment whose ownerDocument is the same as
|
||||
// the ownerDocument of the context object's start node."
|
||||
var ownerDoc = range.startContainer.nodeType == Node.DOCUMENT_NODE
|
||||
? range.startContainer
|
||||
: range.startContainer.ownerDocument;
|
||||
var frag = ownerDoc.createDocumentFragment();
|
||||
|
||||
// "If the context object's start and end are the same, abort this method,
|
||||
// returning frag."
|
||||
if (range.startContainer == range.endContainer
|
||||
&& range.startOffset == range.endOffset) {
|
||||
return frag;
|
||||
}
|
||||
|
||||
// "Let original start node, original start offset, original end node, and
|
||||
// original end offset be the context object's start and end nodes and
|
||||
// offsets, respectively."
|
||||
var originalStartNode = range.startContainer;
|
||||
var originalStartOffset = range.startOffset;
|
||||
var originalEndNode = range.endContainer;
|
||||
var originalEndOffset = range.endOffset;
|
||||
|
||||
// "If original start node and original end node are the same, and they are
|
||||
// a Text or Comment node:"
|
||||
if (range.startContainer == range.endContainer
|
||||
&& (range.startContainer.nodeType == Node.TEXT_NODE
|
||||
|| range.startContainer.nodeType == Node.COMMENT_NODE)) {
|
||||
// "Let clone be the result of calling cloneNode(false) on original
|
||||
// start node."
|
||||
var clone = originalStartNode.cloneNode(false);
|
||||
|
||||
// "Set the data of clone to the result of calling
|
||||
// substringData(original start offset, original end offset − original
|
||||
// start offset) on original start node."
|
||||
clone.data = originalStartNode.substringData(originalStartOffset,
|
||||
originalEndOffset - originalStartOffset);
|
||||
|
||||
// "Append clone as the last child of frag."
|
||||
frag.appendChild(clone);
|
||||
|
||||
// "Call deleteData(original start offset, original end offset −
|
||||
// original start offset) on original start node."
|
||||
originalStartNode.deleteData(originalStartOffset,
|
||||
originalEndOffset - originalStartOffset);
|
||||
|
||||
// "Abort this method, returning frag."
|
||||
return frag;
|
||||
}
|
||||
|
||||
// "Let common ancestor equal original start node."
|
||||
var commonAncestor = originalStartNode;
|
||||
|
||||
// "While common ancestor is not an ancestor container of original end
|
||||
// node, set common ancestor to its own parent."
|
||||
while (!isAncestorContainer(commonAncestor, originalEndNode)) {
|
||||
commonAncestor = commonAncestor.parentNode;
|
||||
}
|
||||
|
||||
// "If original start node is an ancestor container of original end node,
|
||||
// let first partially contained child be null."
|
||||
var firstPartiallyContainedChild;
|
||||
if (isAncestorContainer(originalStartNode, originalEndNode)) {
|
||||
firstPartiallyContainedChild = null;
|
||||
// "Otherwise, let first partially contained child be the first child of
|
||||
// common ancestor that is partially contained in the context object."
|
||||
} else {
|
||||
for (var i = 0; i < commonAncestor.childNodes.length; i++) {
|
||||
if (isPartiallyContained(commonAncestor.childNodes[i], range)) {
|
||||
firstPartiallyContainedChild = commonAncestor.childNodes[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!firstPartiallyContainedChild) {
|
||||
throw "Spec bug: no first partially contained child!";
|
||||
}
|
||||
}
|
||||
|
||||
// "If original end node is an ancestor container of original start node,
|
||||
// let last partially contained child be null."
|
||||
var lastPartiallyContainedChild;
|
||||
if (isAncestorContainer(originalEndNode, originalStartNode)) {
|
||||
lastPartiallyContainedChild = null;
|
||||
// "Otherwise, let last partially contained child be the last child of
|
||||
// common ancestor that is partially contained in the context object."
|
||||
} else {
|
||||
for (var i = commonAncestor.childNodes.length - 1; i >= 0; i--) {
|
||||
if (isPartiallyContained(commonAncestor.childNodes[i], range)) {
|
||||
lastPartiallyContainedChild = commonAncestor.childNodes[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!lastPartiallyContainedChild) {
|
||||
throw "Spec bug: no last partially contained child!";
|
||||
}
|
||||
}
|
||||
|
||||
// "Let contained children be a list of all children of common ancestor
|
||||
// that are contained in the context object, in tree order."
|
||||
//
|
||||
// "If any member of contained children is a DocumentType, raise a
|
||||
// HIERARCHY_REQUEST_ERR exception and abort these steps."
|
||||
var containedChildren = [];
|
||||
for (var i = 0; i < commonAncestor.childNodes.length; i++) {
|
||||
if (isContained(commonAncestor.childNodes[i], range)) {
|
||||
if (commonAncestor.childNodes[i].nodeType
|
||||
== Node.DOCUMENT_TYPE_NODE) {
|
||||
return "HIERARCHY_REQUEST_ERR";
|
||||
}
|
||||
containedChildren.push(commonAncestor.childNodes[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// "If original start node is an ancestor container of original end node,
|
||||
// set new node to original start node and new offset to original start
|
||||
// offset."
|
||||
var newNode, newOffset;
|
||||
if (isAncestorContainer(originalStartNode, originalEndNode)) {
|
||||
newNode = originalStartNode;
|
||||
newOffset = originalStartOffset;
|
||||
// "Otherwise:"
|
||||
} else {
|
||||
// "Let reference node equal original start node."
|
||||
var referenceNode = originalStartNode;
|
||||
|
||||
// "While reference node's parent is not null and is not an ancestor
|
||||
// container of original end node, set reference node to its parent."
|
||||
while (referenceNode.parentNode
|
||||
&& !isAncestorContainer(referenceNode.parentNode, originalEndNode)) {
|
||||
referenceNode = referenceNode.parentNode;
|
||||
}
|
||||
|
||||
// "Set new node to the parent of reference node, and new offset to one
|
||||
// plus the index of reference node."
|
||||
newNode = referenceNode.parentNode;
|
||||
newOffset = 1 + indexOf(referenceNode);
|
||||
}
|
||||
|
||||
// "If first partially contained child is a Text or Comment node:"
|
||||
if (firstPartiallyContainedChild
|
||||
&& (firstPartiallyContainedChild.nodeType == Node.TEXT_NODE
|
||||
|| firstPartiallyContainedChild.nodeType == Node.COMMENT_NODE)) {
|
||||
// "Let clone be the result of calling cloneNode(false) on original
|
||||
// start node."
|
||||
var clone = originalStartNode.cloneNode(false);
|
||||
|
||||
// "Set the data of clone to the result of calling substringData() on
|
||||
// original start node, with original start offset as the first
|
||||
// argument and (length of original start node − original start offset)
|
||||
// as the second."
|
||||
clone.data = originalStartNode.substringData(originalStartOffset,
|
||||
getNodeLength(originalStartNode) - originalStartOffset);
|
||||
|
||||
// "Append clone as the last child of frag."
|
||||
frag.appendChild(clone);
|
||||
|
||||
// "Call deleteData() on original start node, with original start
|
||||
// offset as the first argument and (length of original start node −
|
||||
// original start offset) as the second."
|
||||
originalStartNode.deleteData(originalStartOffset,
|
||||
getNodeLength(originalStartNode) - originalStartOffset);
|
||||
// "Otherwise, if first partially contained child is not null:"
|
||||
} else if (firstPartiallyContainedChild) {
|
||||
// "Let clone be the result of calling cloneNode(false) on first
|
||||
// partially contained child."
|
||||
var clone = firstPartiallyContainedChild.cloneNode(false);
|
||||
|
||||
// "Append clone as the last child of frag."
|
||||
frag.appendChild(clone);
|
||||
|
||||
// "Let subrange be a new Range whose start is (original start node,
|
||||
// original start offset) and whose end is (first partially contained
|
||||
// child, length of first partially contained child)."
|
||||
var subrange = ownerDoc.createRange();
|
||||
subrange.setStart(originalStartNode, originalStartOffset);
|
||||
subrange.setEnd(firstPartiallyContainedChild,
|
||||
getNodeLength(firstPartiallyContainedChild));
|
||||
|
||||
// "Let subfrag be the result of calling extractContents() on
|
||||
// subrange."
|
||||
var subfrag = myExtractContents(subrange);
|
||||
|
||||
// "For each child of subfrag, in order, append that child to clone as
|
||||
// its last child."
|
||||
for (var i = 0; i < subfrag.childNodes.length; i++) {
|
||||
clone.appendChild(subfrag.childNodes[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// "For each contained child in contained children, append contained child
|
||||
// as the last child of frag."
|
||||
for (var i = 0; i < containedChildren.length; i++) {
|
||||
frag.appendChild(containedChildren[i]);
|
||||
}
|
||||
|
||||
// "If last partially contained child is a Text or Comment node:"
|
||||
if (lastPartiallyContainedChild
|
||||
&& (lastPartiallyContainedChild.nodeType == Node.TEXT_NODE
|
||||
|| lastPartiallyContainedChild.nodeType == Node.COMMENT_NODE)) {
|
||||
// "Let clone be the result of calling cloneNode(false) on original
|
||||
// end node."
|
||||
var clone = originalEndNode.cloneNode(false);
|
||||
|
||||
// "Set the data of clone to the result of calling substringData(0,
|
||||
// original end offset) on original end node."
|
||||
clone.data = originalEndNode.substringData(0, originalEndOffset);
|
||||
|
||||
// "Append clone as the last child of frag."
|
||||
frag.appendChild(clone);
|
||||
|
||||
// "Call deleteData(0, original end offset) on original end node."
|
||||
originalEndNode.deleteData(0, originalEndOffset);
|
||||
// "Otherwise, if last partially contained child is not null:"
|
||||
} else if (lastPartiallyContainedChild) {
|
||||
// "Let clone be the result of calling cloneNode(false) on last
|
||||
// partially contained child."
|
||||
var clone = lastPartiallyContainedChild.cloneNode(false);
|
||||
|
||||
// "Append clone as the last child of frag."
|
||||
frag.appendChild(clone);
|
||||
|
||||
// "Let subrange be a new Range whose start is (last partially
|
||||
// contained child, 0) and whose end is (original end node, original
|
||||
// end offset)."
|
||||
var subrange = ownerDoc.createRange();
|
||||
subrange.setStart(lastPartiallyContainedChild, 0);
|
||||
subrange.setEnd(originalEndNode, originalEndOffset);
|
||||
|
||||
// "Let subfrag be the result of calling extractContents() on
|
||||
// subrange."
|
||||
var subfrag = myExtractContents(subrange);
|
||||
|
||||
// "For each child of subfrag, in order, append that child to clone as
|
||||
// its last child."
|
||||
for (var i = 0; i < subfrag.childNodes.length; i++) {
|
||||
clone.appendChild(subfrag.childNodes[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// "Set the context object's start and end to (new node, new offset)."
|
||||
range.setStart(newNode, newOffset);
|
||||
range.setEnd(newNode, newOffset);
|
||||
|
||||
// "Return frag."
|
||||
return frag;
|
||||
}
|
||||
|
||||
/**
|
||||
* insertNode() implementation, following the spec. If an exception is
|
||||
* supposed to be thrown, will return a string with the name (e.g.,
|
||||
* "HIERARCHY_REQUEST_ERR") instead of a document fragment. It might also
|
||||
* return an arbitrary human-readable string if a condition is hit that implies
|
||||
* a spec bug.
|
||||
*/
|
||||
function myInsertNode(range, newNode) {
|
||||
// "If the context object's detached flag is set, raise an
|
||||
// INVALID_STATE_ERR exception and abort these steps."
|
||||
//
|
||||
// Assume that if accessing collapsed throws, it's detached.
|
||||
try {
|
||||
range.collapsed;
|
||||
} catch (e) {
|
||||
return "INVALID_STATE_ERR";
|
||||
}
|
||||
|
||||
// "If the context object's start node is a Text or Comment node and its
|
||||
// parent is null, raise an HIERARCHY_REQUEST_ERR exception and abort these
|
||||
// steps."
|
||||
if ((range.startContainer.nodeType == Node.TEXT_NODE
|
||||
|| range.startContainer.nodeType == Node.COMMENT_NODE)
|
||||
&& !range.startContainer.parentNode) {
|
||||
return "HIERARCHY_REQUEST_ERR";
|
||||
}
|
||||
|
||||
// "If the context object's start node is a Text node, run splitText() on
|
||||
// it with the context object's start offset as its argument, and let
|
||||
// reference node be the result."
|
||||
var referenceNode;
|
||||
if (range.startContainer.nodeType == Node.TEXT_NODE) {
|
||||
// We aren't testing how ranges vary under mutations, and browsers vary
|
||||
// in how they mutate for splitText, so let's just force the correct
|
||||
// way.
|
||||
var start = [range.startContainer, range.startOffset];
|
||||
var end = [range.endContainer, range.endOffset];
|
||||
|
||||
referenceNode = range.startContainer.splitText(range.startOffset);
|
||||
|
||||
if (start[0] == end[0]
|
||||
&& end[1] > start[1]) {
|
||||
end[0] = referenceNode;
|
||||
end[1] -= start[1];
|
||||
} else if (end[0] == start[0].parentNode
|
||||
&& end[1] > indexOf(referenceNode)) {
|
||||
end[1]++;
|
||||
}
|
||||
range.setStart(start[0], start[1]);
|
||||
range.setEnd(end[0], end[1]);
|
||||
// "Otherwise, if the context object's start node is a Comment, let
|
||||
// reference node be the context object's start node."
|
||||
} else if (range.startContainer.nodeType == Node.COMMENT_NODE) {
|
||||
referenceNode = range.startContainer;
|
||||
// "Otherwise, let reference node be the child of the context object's
|
||||
// start node with index equal to the context object's start offset, or
|
||||
// null if there is no such child."
|
||||
} else {
|
||||
referenceNode = range.startContainer.childNodes[range.startOffset];
|
||||
if (typeof referenceNode == "undefined") {
|
||||
referenceNode = null;
|
||||
}
|
||||
}
|
||||
|
||||
// "If reference node is null, let parent node be the context object's
|
||||
// start node."
|
||||
var parentNode;
|
||||
if (!referenceNode) {
|
||||
parentNode = range.startContainer;
|
||||
// "Otherwise, let parent node be the parent of reference node."
|
||||
} else {
|
||||
parentNode = referenceNode.parentNode;
|
||||
}
|
||||
|
||||
// "Call insertBefore(newNode, reference node) on parent node, re-raising
|
||||
// any exceptions that call raised."
|
||||
try {
|
||||
parentNode.insertBefore(newNode, referenceNode);
|
||||
} catch (e) {
|
||||
return getDomExceptionName(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that two nodes are equal, in the sense of isEqualNode(). If they
|
||||
* aren't, tries to print a relatively informative reason why not. TODO: Move
|
||||
* this to testharness.js?
|
||||
*/
|
||||
function assertNodesEqual(actual, expected, msg) {
|
||||
if (!actual.isEqualNode(expected)) {
|
||||
msg = "Actual and expected mismatch for " + msg + ". ";
|
||||
|
||||
while (actual && expected) {
|
||||
assert_true(actual.nodeType === expected.nodeType
|
||||
&& actual.nodeName === expected.nodeName
|
||||
&& actual.nodeValue === expected.nodeValue
|
||||
&& actual.childNodes.length === expected.childNodes.length,
|
||||
"First differing node: expected " + format_value(expected)
|
||||
+ ", got " + format_value(actual));
|
||||
actual = nextNode(actual);
|
||||
expected = nextNode(expected);
|
||||
}
|
||||
|
||||
assert_unreached("DOMs were not equal but we couldn't figure out why");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a DOMException, return the name (e.g., "HIERARCHY_REQUEST_ERR"). In
|
||||
* theory this should be just e.name, but in practice it's not. So I could
|
||||
* legitimately just return e.name, but then every engine but WebKit would fail
|
||||
* every test, since no one seems to care much for standardizing DOMExceptions.
|
||||
* Instead I mangle it to account for browser bugs, so as not to fail
|
||||
* insertNode() tests (for instance) for insertBefore() bugs. Of course, a
|
||||
* standards-compliant browser will work right in any event.
|
||||
*
|
||||
* If the exception has no string property called "name" or "message", we just
|
||||
* re-throw it.
|
||||
*/
|
||||
function getDomExceptionName(e) {
|
||||
if (typeof e.name == "string"
|
||||
&& /^[A-Z_]+_ERR$/.test(e.name)) {
|
||||
// Either following the standard, or prefixing NS_ERROR_DOM (I'm
|
||||
// looking at you, Gecko).
|
||||
return e.name.replace(/^NS_ERROR_DOM_/, "");
|
||||
}
|
||||
|
||||
if (typeof e.message == "string"
|
||||
&& /^[A-Z_]+_ERR$/.test(e.message)) {
|
||||
// Opera
|
||||
return e.message;
|
||||
}
|
||||
|
||||
if (typeof e.message == "string"
|
||||
&& /^DOM Exception:/.test(e.message)) {
|
||||
// IE
|
||||
return /[A-Z_]+_ERR/.exec(e.message)[0];
|
||||
}
|
||||
|
||||
throw e;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an array of endpoint data [start container, start offset, end
|
||||
* container, end offset], returns a Range with those endpoints.
|
||||
*/
|
||||
function rangeFromEndpoints(endpoints) {
|
||||
// If we just use document instead of the ownerDocument of endpoints[0],
|
||||
// WebKit will throw on setStart/setEnd. This is a WebKit bug, but it's in
|
||||
// range, not selection, so we don't want to fail anything for it.
|
||||
var range = ownerDocument(endpoints[0]).createRange();
|
||||
range.setStart(endpoints[0], endpoints[1]);
|
||||
range.setEnd(endpoints[2], endpoints[3]);
|
||||
return range;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an array of endpoint data [start container, start offset, end
|
||||
* container, end offset], sets the selection to have those endpoints. Uses
|
||||
* addRange, so the range will be forwards. Accepts an empty array for
|
||||
* endpoints, in which case the selection will just be emptied.
|
||||
*/
|
||||
function setSelectionForwards(endpoints) {
|
||||
selection.removeAllRanges();
|
||||
if (endpoints.length) {
|
||||
selection.addRange(rangeFromEndpoints(endpoints));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an array of endpoint data [start container, start offset, end
|
||||
* container, end offset], sets the selection to have those endpoints, with the
|
||||
* direction backwards. Uses extend, so it will throw in IE. Accepts an empty
|
||||
* array for endpoints, in which case the selection will just be emptied.
|
||||
*/
|
||||
function setSelectionBackwards(endpoints) {
|
||||
selection.removeAllRanges();
|
||||
if (endpoints.length) {
|
||||
selection.collapse(endpoints[2], endpoints[3]);
|
||||
selection.extend(endpoints[0], endpoints[1]);
|
||||
}
|
||||
}
|
||||
97
testing/web-platform/tests/selection/deleteFromDocument.html
Normal file
97
testing/web-platform/tests/selection/deleteFromDocument.html
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
<!doctype html>
|
||||
<title>Selection.deleteFromDocument() tests</title>
|
||||
<link rel=author title="Aryeh Gregor" href=ayg@aryeh.name>
|
||||
<p>To debug test failures, add a query parameter with the test id (like
|
||||
"?5"). Only that test will be run. Then you can look at the resulting
|
||||
iframes in the DOM.
|
||||
<div id=log></div>
|
||||
<script src=/resources/testharness.js></script>
|
||||
<script src=/resources/testharnessreport.js></script>
|
||||
<script src=common.js></script>
|
||||
<script>
|
||||
"use strict";
|
||||
|
||||
// We need to use explicit_done, because in Chrome 16 dev and Opera 12.00, the
|
||||
// second iframe doesn't block the load event -- even though it is added before
|
||||
// the load event.
|
||||
setup({explicit_done: true});
|
||||
|
||||
// Specified by WebIDL
|
||||
test(function() {
|
||||
assert_equals(Selection.prototype.deleteFromDocument.length, 0,
|
||||
"Selection.prototype.deleteFromDocument.length must equal 0");
|
||||
}, "Selection.prototype.deleteFromDocument.length must equal 0");
|
||||
|
||||
testDiv.parentNode.removeChild(testDiv);
|
||||
|
||||
// Test an empty selection too
|
||||
testRanges.unshift("empty");
|
||||
|
||||
var actualIframe = document.createElement("iframe");
|
||||
|
||||
var expectedIframe = document.createElement("iframe");
|
||||
|
||||
var referenceDoc = document.implementation.createHTMLDocument("");
|
||||
referenceDoc.removeChild(referenceDoc.documentElement);
|
||||
|
||||
actualIframe.onload = function() {
|
||||
expectedIframe.onload = function() {
|
||||
for (var i = 0; i < testRanges.length; i++) {
|
||||
if (location.search && i != Number(location.search)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
test(function() {
|
||||
initializeIframe(actualIframe, testRanges[i]);
|
||||
initializeIframe(expectedIframe, testRanges[i]);
|
||||
|
||||
var actualRange = actualIframe.contentWindow.testRange;
|
||||
var expectedRange = expectedIframe.contentWindow.testRange;
|
||||
|
||||
assert_equals(actualIframe.contentWindow.unexpectedException, null,
|
||||
"Unexpected exception thrown when setting up Range for actual deleteFromDocument");
|
||||
assert_equals(expectedIframe.contentWindow.unexpectedException, null,
|
||||
"Unexpected exception thrown when setting up Range for simulated deleteFromDocument");
|
||||
|
||||
actualIframe.contentWindow.getSelection().removeAllRanges();
|
||||
if (testRanges[i] != "empty") {
|
||||
assert_equals(typeof actualRange, "object",
|
||||
"Range produced in actual iframe must be an object");
|
||||
assert_equals(typeof expectedRange, "object",
|
||||
"Range produced in expected iframe must be an object");
|
||||
assert_true(actualRange instanceof actualIframe.contentWindow.Range,
|
||||
"Range produced in actual iframe must be instanceof Range");
|
||||
assert_true(expectedRange instanceof expectedIframe.contentWindow.Range,
|
||||
"Range produced in expected iframe must be instanceof Range");
|
||||
actualIframe.contentWindow.getSelection().addRange(actualIframe.contentWindow.testRange);
|
||||
expectedIframe.contentWindow.testRange.deleteContents();
|
||||
}
|
||||
actualIframe.contentWindow.getSelection().deleteFromDocument();
|
||||
|
||||
assertNodesEqual(actualIframe.contentDocument, expectedIframe.contentDocument, "DOM contents");
|
||||
}, "Range " + i + ": " + testRanges[i]);
|
||||
}
|
||||
actualIframe.style.display = "none";
|
||||
expectedIframe.style.display = "none";
|
||||
done();
|
||||
};
|
||||
expectedIframe.src = "test-iframe.html";
|
||||
document.body.appendChild(expectedIframe);
|
||||
referenceDoc.appendChild(actualIframe.contentDocument.documentElement.cloneNode(true));
|
||||
};
|
||||
actualIframe.src = "test-iframe.html";
|
||||
document.body.appendChild(actualIframe);
|
||||
|
||||
function initializeIframe(iframe, endpoints) {
|
||||
while (iframe.contentDocument.firstChild) {
|
||||
iframe.contentDocument.removeChild(iframe.contentDocument.lastChild);
|
||||
}
|
||||
iframe.contentDocument.appendChild(iframe.contentDocument.implementation.createDocumentType("html", "", ""));
|
||||
iframe.contentDocument.appendChild(referenceDoc.documentElement.cloneNode(true));
|
||||
iframe.contentWindow.setupRangeTests();
|
||||
if (endpoints != "empty") {
|
||||
iframe.contentWindow.testRangeInput = endpoints;
|
||||
iframe.contentWindow.run();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
106
testing/web-platform/tests/selection/dir-manual.html
Normal file
106
testing/web-platform/tests/selection/dir-manual.html
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
<!doctype html>
|
||||
<title>Selection direction tests</title>
|
||||
<meta charset=utf-8>
|
||||
<div id=test>
|
||||
<p>This is a manual test, since there's no way to synthesize keyboard or
|
||||
mouse input. Click after the letter "c" in the following paragraph and
|
||||
drag backwards so that both the "b" and the "c" are highlighted, then click
|
||||
the "Test" button:
|
||||
|
||||
<p>abcd <button onclick=testDirection()>Test</button>
|
||||
|
||||
<p>efghi
|
||||
</div>
|
||||
<div id=log></div>
|
||||
<script src=/resources/testharness.js></script>
|
||||
<script src=/resources/testharnessreport.js></script>
|
||||
<script>
|
||||
setup({explicit_done: true});
|
||||
|
||||
function testDirection() {
|
||||
var testDiv = document.getElementById("test");
|
||||
var p = testDiv.getElementsByTagName("p")[1].firstChild;
|
||||
var selection = getSelection();
|
||||
var range = selection.getRangeAt(0);
|
||||
test(function() {
|
||||
assert_equals(range.toString(), "bc");
|
||||
}, "The expected range is selected");
|
||||
test(function() {
|
||||
assert_equals(selection.anchorNode, p);
|
||||
assert_equals(selection.focusNode, p);
|
||||
}, "Expected node is initially selected");
|
||||
test(function() {
|
||||
assert_array_equals([selection.anchorOffset, selection.focusOffset].sort(), [1, 3]);
|
||||
}, "Expected offsets are initially selected (maybe not in order)");
|
||||
test(function() {
|
||||
assert_equals(selection.anchorOffset, 3);
|
||||
assert_equals(selection.focusOffset, 1);
|
||||
}, "Offsets are backwards for initial selection"),
|
||||
test(function() {
|
||||
assert_equals(selection.anchorNode, range.endContainer);
|
||||
assert_equals(selection.anchorOffset, range.endOffset);
|
||||
assert_equals(selection.focusNode, range.startContainer);
|
||||
assert_equals(selection.focusOffset, range.startOffset);
|
||||
}, "Offsets match the range for initial selection");
|
||||
|
||||
// Per spec, the direction of the selection remains even if you zap a range
|
||||
// and add a new one.
|
||||
test(function() {
|
||||
selection.removeRange(range);
|
||||
range = document.createRange();
|
||||
p = testDiv.getElementsByTagName("p")[0].firstChild;
|
||||
range.setStart(p, 0);
|
||||
range.setEnd(p, 4);
|
||||
assert_equals(range.toString(), "This");
|
||||
selection.addRange(range);
|
||||
}, "removeRange()/addRange() successful");
|
||||
test(function() {
|
||||
assert_equals(selection.anchorNode, p);
|
||||
assert_equals(selection.focusNode, p);
|
||||
}, "Expected node is selected after remove/addRange()");
|
||||
test(function() {
|
||||
assert_array_equals([selection.anchorOffset, selection.focusOffset].sort(), [0, 4]);
|
||||
}, "Expected offsets are selected after remove/addRange() (maybe not in order)");
|
||||
test(function() {
|
||||
assert_equals(selection.anchorOffset, 4);
|
||||
assert_equals(selection.focusOffset, 0);
|
||||
}, "Offsets are backwards after remove/addRange()"),
|
||||
test(function() {
|
||||
assert_equals(selection.anchorNode, range.endContainer);
|
||||
assert_equals(selection.anchorOffset, range.endOffset);
|
||||
assert_equals(selection.focusNode, range.startContainer);
|
||||
assert_equals(selection.focusOffset, range.startOffset);
|
||||
}, "Offsets match the range after remove/addRange()");
|
||||
|
||||
// But if you call removeAllRanges(), the direction should reset to
|
||||
// forwards.
|
||||
test(function() {
|
||||
selection.removeAllRanges();
|
||||
range = document.createRange();
|
||||
p = testDiv.getElementsByTagName("p")[2].firstChild;
|
||||
range.setStart(p, 2);
|
||||
range.setEnd(p, 5);
|
||||
assert_equals(range.toString(), "ghi");
|
||||
selection.addRange(range);
|
||||
}, "removeAllRanges() successful");
|
||||
test(function() {
|
||||
assert_equals(selection.anchorNode, p);
|
||||
assert_equals(selection.focusNode, p);
|
||||
}, "Expected node is selected after removeAllRanges()");
|
||||
test(function() {
|
||||
assert_array_equals([selection.anchorOffset, selection.focusOffset].sort(), [2, 5]);
|
||||
}, "Expected offsets are selected after removeAllRanges() (maybe not in order)");
|
||||
test(function() {
|
||||
assert_equals(selection.anchorOffset, 2);
|
||||
assert_equals(selection.focusOffset, 5);
|
||||
}, "Offsets are forwards after removeAllRanges()");
|
||||
test(function() {
|
||||
assert_equals(selection.anchorNode, range.startContainer);
|
||||
assert_equals(selection.anchorOffset, range.startOffset);
|
||||
assert_equals(selection.focusNode, range.endContainer);
|
||||
assert_equals(selection.focusOffset, range.endOffset);
|
||||
}, "Offsets match the range after removeAllRanges()");
|
||||
|
||||
done();
|
||||
}
|
||||
</script>
|
||||
149
testing/web-platform/tests/selection/extend.html
Normal file
149
testing/web-platform/tests/selection/extend.html
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
<!doctype html>
|
||||
<title>Selection extend() tests</title>
|
||||
<meta charset=utf-8>
|
||||
<meta name=timeout content=long>
|
||||
<body>
|
||||
<script src=/resources/testharness.js></script>
|
||||
<script src=/resources/testharnessreport.js></script>
|
||||
<script src=common.js></script>
|
||||
<div id=log></div>
|
||||
<script>
|
||||
"use strict";
|
||||
|
||||
// Also test a selection with no ranges
|
||||
testRanges.unshift("[]");
|
||||
|
||||
/**
|
||||
* We test Selections that go both forwards and backwards here. In the latter
|
||||
* case we need to use extend() to force it to go backwards, which is fair
|
||||
* enough, since that's what we're testing. We test collapsed selections only
|
||||
* once.
|
||||
*/
|
||||
for (var i = 0; i < testRanges.length; i++) {
|
||||
var endpoints = eval(testRanges[i]);
|
||||
for (var j = 0; j < testPoints.length; j++) {
|
||||
if (endpoints[0] == endpoints[2]
|
||||
&& endpoints[1] == endpoints[3]) {
|
||||
// Test collapsed selections only once
|
||||
test(function() {
|
||||
setSelectionForwards(endpoints);
|
||||
testExtend(endpoints, eval(testPoints[j]));
|
||||
}, "extend() with range " + i + " " + testRanges[i]
|
||||
+ " and point " + j + " " + testPoints[j]);
|
||||
} else {
|
||||
test(function() {
|
||||
setSelectionForwards(endpoints);
|
||||
testExtend(endpoints, eval(testPoints[j]));
|
||||
}, "extend() forwards with range " + i + " " + testRanges[i]
|
||||
+ " and point " + j + " " + testPoints[j]);
|
||||
|
||||
test(function() {
|
||||
setSelectionBackwards(endpoints);
|
||||
testExtend(endpoints, eval(testPoints[j]));
|
||||
}, "extend() backwards with range " + i + " " + testRanges[i]
|
||||
+ " and point " + j + " " + testPoints[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function testExtend(endpoints, target) {
|
||||
assert_equals(getSelection().rangeCount, endpoints.length/4,
|
||||
"Sanity check: rangeCount must be correct");
|
||||
|
||||
var node = target[0];
|
||||
var offset = target[1];
|
||||
|
||||
// "If the context object's range is null, throw an InvalidStateError
|
||||
// exception and abort these steps."
|
||||
if (getSelection().rangeCount == 0) {
|
||||
assert_throws("INVALID_STATE_ERR", function() {
|
||||
selection.extend(node, offset);
|
||||
}, "extend() when rangeCount is 0 must throw InvalidStateError");
|
||||
return;
|
||||
}
|
||||
|
||||
assert_equals(getSelection().getRangeAt(0).startContainer, endpoints[0],
|
||||
"Sanity check: startContainer must be correct");
|
||||
assert_equals(getSelection().getRangeAt(0).startOffset, endpoints[1],
|
||||
"Sanity check: startOffset must be correct");
|
||||
assert_equals(getSelection().getRangeAt(0).endContainer, endpoints[2],
|
||||
"Sanity check: endContainer must be correct");
|
||||
assert_equals(getSelection().getRangeAt(0).endOffset, endpoints[3],
|
||||
"Sanity check: endOffset must be correct");
|
||||
|
||||
// "Let anchor and focus be the context object's anchor and focus, and let
|
||||
// new focus be the boundary point (node, offset)."
|
||||
var anchorNode = getSelection().anchorNode;
|
||||
var anchorOffset = getSelection().anchorOffset;
|
||||
var focusNode = getSelection().focusNode;
|
||||
var focusOffset = getSelection().focusOffset;
|
||||
|
||||
// "Let new range be a new range."
|
||||
//
|
||||
// We'll always be setting either new range's start or its end to new
|
||||
// focus, so we'll always throw at some point. Test that now.
|
||||
//
|
||||
// From DOM4's "set the start or end of a range": "If node is a doctype,
|
||||
// throw an "InvalidNodeTypeError" exception and terminate these steps."
|
||||
if (node.nodeType == Node.DOCUMENT_TYPE_NODE) {
|
||||
assert_throws("INVALID_NODE_TYPE_ERR", function() {
|
||||
selection.extend(node, offset);
|
||||
}, "extend() to a doctype must throw InvalidNodeTypeError");
|
||||
return;
|
||||
}
|
||||
|
||||
// From DOM4's "set the start or end of a range": "If offset is greater
|
||||
// than node's length, throw an "IndexSizeError" exception and terminate
|
||||
// these steps."
|
||||
//
|
||||
// FIXME: We should be casting offset to an unsigned int per WebIDL. Until
|
||||
// we do, we need the offset < 0 check too.
|
||||
if (offset < 0 || offset > getNodeLength(node)) {
|
||||
assert_throws("INDEX_SIZE_ERR", function() {
|
||||
selection.extend(node, offset);
|
||||
}, "extend() to an offset that's greater than node length (" + getNodeLength(node) + ") must throw IndexSizeError");
|
||||
return;
|
||||
}
|
||||
|
||||
// Now back to the editing spec.
|
||||
var originalRange = getSelection().getRangeAt(0);
|
||||
|
||||
// "If node's root is not the same as the context object's range's root,
|
||||
// set new range's start and end to (node, offset)."
|
||||
//
|
||||
// "Otherwise, if anchor is before or equal to new focus, set new range's
|
||||
// start to anchor, then set its end to new focus."
|
||||
//
|
||||
// "Otherwise, set new range's start to new focus, then set its end to
|
||||
// anchor."
|
||||
//
|
||||
// "Set the context object's range to new range."
|
||||
//
|
||||
// "If new focus is before anchor, set the context object's direction to
|
||||
// backwards. Otherwise, set it to forwards."
|
||||
//
|
||||
// The upshot of all these is summed up by just testing the anchor and
|
||||
// offset.
|
||||
getSelection().extend(node, offset);
|
||||
|
||||
if (furthestAncestor(anchorNode) == furthestAncestor(node)) {
|
||||
assert_equals(getSelection().anchorNode, anchorNode,
|
||||
"anchorNode must not change if the node passed to extend() has the same root as the original range");
|
||||
assert_equals(getSelection().anchorOffset, anchorOffset,
|
||||
"anchorOffset must not change if the node passed to extend() has the same root as the original range");
|
||||
} else {
|
||||
assert_equals(getSelection().anchorNode, node,
|
||||
"anchorNode must be the node passed to extend() if it has a different root from the original range");
|
||||
assert_equals(getSelection().anchorOffset, offset,
|
||||
"anchorOffset must be the offset passed to extend() if the node has a different root from the original range");
|
||||
}
|
||||
assert_equals(getSelection().focusNode, node,
|
||||
"focusNode must be the node passed to extend()");
|
||||
assert_equals(getSelection().focusOffset, offset,
|
||||
"focusOffset must be the offset passed to extend()");
|
||||
assert_not_equals(getSelection().getRangeAt(0), originalRange,
|
||||
"extend() must replace any existing range with a new one, not mutate the existing one");
|
||||
}
|
||||
|
||||
testDiv.style.display = "none";
|
||||
</script>
|
||||
14
testing/web-platform/tests/selection/getRangeAt.html
Normal file
14
testing/web-platform/tests/selection/getRangeAt.html
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
<!DOCTYPE html>
|
||||
<title>The getRangeAt method</title>
|
||||
<div id=log></div>
|
||||
<script src=/resources/testharness.js></script>
|
||||
<script src=/resources/testharnessreport.js></script>
|
||||
<script>
|
||||
test(function() {
|
||||
var sel = getSelection();
|
||||
var range = document.createRange();
|
||||
sel.addRange(range);
|
||||
assert_throws("INDEX_SIZE_ERR", function() { sel.getRangeAt(-1); })
|
||||
assert_throws("INDEX_SIZE_ERR", function() { sel.getRangeAt(1); })
|
||||
});
|
||||
</script>
|
||||
160
testing/web-platform/tests/selection/getSelection.html
Normal file
160
testing/web-platform/tests/selection/getSelection.html
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
<!doctype html>
|
||||
<title>getSelection() tests</title>
|
||||
<div id=log></div>
|
||||
<script src=/resources/testharness.js></script>
|
||||
<script src=/resources/testharnessreport.js></script>
|
||||
<script>
|
||||
"use strict";
|
||||
|
||||
// TODO: Figure out more places where defaultView is or is not guaranteed to be
|
||||
// null, and test whether getSelection() is null.
|
||||
//
|
||||
// TODO: Figure out a good way to test display: none iframes.
|
||||
|
||||
test(function() {
|
||||
// Sanity checks like this are to flag known browser bugs with clearer
|
||||
// error messages, instead of throwing inscrutable exceptions.
|
||||
assert_true("Selection" in window,
|
||||
"Sanity check: window must have Selection property");
|
||||
|
||||
assert_true(window.getSelection() instanceof Selection);
|
||||
}, "window.getSelection() instanceof Selection");
|
||||
|
||||
test(function() {
|
||||
assert_equals(window.getSelection(), window.getSelection());
|
||||
}, "window.getSelection() === window.getSelection()");
|
||||
|
||||
test(function() {
|
||||
assert_true("Selection" in window,
|
||||
"Sanity check: window must have Selection property");
|
||||
// This sanity check (which occurs a number of times below, too) is because
|
||||
// document.getSelection() is supposed to return null if defaultView is
|
||||
// null, so we need to figure out whether defaultView is null or not before
|
||||
// we can make correct assertions about getSelection().
|
||||
assert_not_equals(document.defaultView, null,
|
||||
"Sanity check: document.defaultView must not be null");
|
||||
|
||||
assert_equals(typeof document.getSelection(), "object",
|
||||
"document.getSelection() must be an object");
|
||||
assert_true(document.getSelection() instanceof Selection);
|
||||
}, "document.getSelection() instanceof Selection");
|
||||
|
||||
test(function() {
|
||||
assert_not_equals(document.defaultView, null,
|
||||
"Sanity check: document.defaultView must not be null");
|
||||
assert_equals(document.getSelection(), document.getSelection());
|
||||
}, "document.getSelection() === document.getSelection()");
|
||||
|
||||
test(function() {
|
||||
assert_not_equals(document.defaultView, null,
|
||||
"Sanity check: document.defaultView must not be null");
|
||||
assert_equals(window.getSelection(), document.getSelection());
|
||||
}, "window.getSelection() === document.getSelection()");
|
||||
|
||||
// "Each selection is associated with a single range, which may be null and is
|
||||
// initially null."
|
||||
//
|
||||
// "The rangeCount attribute must return 0 if the context object's range is
|
||||
// null, otherwise 1."
|
||||
test(function() {
|
||||
assert_equals(window.getSelection().rangeCount, 0,
|
||||
"window.getSelection().rangeCount must initially be 0");
|
||||
assert_equals(typeof document.getSelection(), "object",
|
||||
"Sanity check: document.getSelection() must be an object");
|
||||
assert_equals(document.getSelection().rangeCount, 0,
|
||||
"document.getSelection().rangeCount must initially be 0");
|
||||
}, "Selection's range must initially be null");
|
||||
|
||||
test(function() {
|
||||
var doc = document.implementation.createHTMLDocument("");
|
||||
assert_equals(doc.defaultView, null,
|
||||
"Sanity check: defaultView of created HTML document must be null");
|
||||
assert_equals(doc.getSelection(), null);
|
||||
}, "getSelection() on HTML document with null defaultView must be null");
|
||||
|
||||
test(function() {
|
||||
var xmlDoc = document.implementation.createDocument(null, "", null);
|
||||
|
||||
assert_true("getSelection" in xmlDoc, "XML document must have getSelection()");
|
||||
|
||||
assert_equals(xmlDoc.defaultView, null,
|
||||
"Sanity check: defaultView of created XML document must be null");
|
||||
assert_equals(xmlDoc.getSelection(), null);
|
||||
}, "getSelection() on XML document with null defaultView must be null");
|
||||
|
||||
|
||||
// Run a bunch of iframe tests, once immediately after the iframe is appended
|
||||
// to the document and once onload. This makes a difference, because browsers
|
||||
// differ (at the time of this writing) in whether they load about:blank in
|
||||
// iframes synchronously or not. Per the HTML spec, there must be a browsing
|
||||
// context associated with the iframe as soon as it's appended to the document,
|
||||
// so there should be a selection too.
|
||||
var iframe = document.createElement("iframe");
|
||||
add_completion_callback(function() {
|
||||
document.body.removeChild(iframe);
|
||||
});
|
||||
|
||||
var testDescs = [];
|
||||
var testFuncs = [];
|
||||
testDescs.push("window.getSelection() instanceof Selection in an iframe");
|
||||
testFuncs.push(function() {
|
||||
assert_true("Selection" in iframe.contentWindow,
|
||||
"Sanity check: window must have Selection property");
|
||||
assert_not_equals(iframe.contentWindow.document.defaultView, null,
|
||||
"Sanity check: document.defaultView must not be null");
|
||||
assert_not_equals(iframe.contentWindow.getSelection(), null,
|
||||
"window.getSelection() must not be null");
|
||||
assert_true(iframe.contentWindow.getSelection() instanceof iframe.contentWindow.Selection);
|
||||
});
|
||||
|
||||
testDescs.push("document.getSelection() instanceof Selection in an iframe");
|
||||
testFuncs.push(function() {
|
||||
assert_true("Selection" in iframe.contentWindow,
|
||||
"Sanity check: window must have Selection property");
|
||||
assert_not_equals(iframe.contentDocument.defaultView, null,
|
||||
"Sanity check: document.defaultView must not be null");
|
||||
assert_not_equals(iframe.contentDocument.getSelection(), null,
|
||||
"document.getSelection() must not be null");
|
||||
assert_equals(typeof iframe.contentDocument.getSelection(), "object",
|
||||
"document.getSelection() must be an object");
|
||||
assert_true(iframe.contentDocument.getSelection() instanceof iframe.contentWindow.Selection);
|
||||
});
|
||||
|
||||
testDescs.push("window.getSelection() === document.getSelection() in an iframe");
|
||||
testFuncs.push(function() {
|
||||
assert_not_equals(iframe.contentDocument.defaultView, null,
|
||||
"Sanity check: document.defaultView must not be null");
|
||||
assert_equals(iframe.contentWindow.getSelection(), iframe.contentDocument.getSelection());
|
||||
});
|
||||
|
||||
testDescs.push("getSelection() inside and outside iframe must return different objects");
|
||||
testFuncs.push(function() {
|
||||
assert_not_equals(iframe.contentWindow.getSelection(), getSelection());
|
||||
});
|
||||
|
||||
testDescs.push("getSelection() on HTML document with null defaultView must be null inside an iframe");
|
||||
testFuncs.push(function() {
|
||||
var doc = iframe.contentDocument.implementation.createHTMLDocument("");
|
||||
assert_equals(doc.defaultView, null,
|
||||
"Sanity check: defaultView of created HTML document must be null");
|
||||
assert_equals(doc.getSelection(), null);
|
||||
});
|
||||
|
||||
var asyncTests = [];
|
||||
testDescs.forEach(function(desc) {
|
||||
asyncTests.push(async_test(desc + " onload"));
|
||||
});
|
||||
|
||||
iframe.onload = function() {
|
||||
asyncTests.forEach(function(t, i) {
|
||||
t.step(testFuncs[i]);
|
||||
t.done();
|
||||
});
|
||||
};
|
||||
|
||||
document.body.appendChild(iframe);
|
||||
|
||||
testDescs.forEach(function(desc, i) {
|
||||
test(testFuncs[i], desc + " immediately after appendChild");
|
||||
});
|
||||
</script>
|
||||
41
testing/web-platform/tests/selection/interfaces.html
Normal file
41
testing/web-platform/tests/selection/interfaces.html
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
<!doctype html>
|
||||
<title>Selection interface tests</title>
|
||||
<div id=log></div>
|
||||
<script src=/resources/testharness.js></script>
|
||||
<script src=/resources/testharnessreport.js></script>
|
||||
<script src=/resources/WebIDLParser.js></script>
|
||||
<script src=/resources/idlharness.js></script>
|
||||
<script type=text/plain>
|
||||
interface Selection {
|
||||
readonly attribute Node? anchorNode;
|
||||
readonly attribute unsigned long anchorOffset;
|
||||
readonly attribute Node? focusNode;
|
||||
readonly attribute unsigned long focusOffset;
|
||||
|
||||
readonly attribute boolean isCollapsed;
|
||||
void collapse(Node node, unsigned long offset);
|
||||
void collapseToStart();
|
||||
void collapseToEnd();
|
||||
|
||||
void extend(Node node, unsigned long offset);
|
||||
|
||||
void selectAllChildren(Node node);
|
||||
void deleteFromDocument();
|
||||
|
||||
readonly attribute unsigned long rangeCount;
|
||||
Range getRangeAt(unsigned long index);
|
||||
void addRange(Range range);
|
||||
void removeRange(Range range);
|
||||
void removeAllRanges();
|
||||
|
||||
stringifier;
|
||||
};
|
||||
</script>
|
||||
<script>
|
||||
"use strict";
|
||||
|
||||
var idlArray = new IdlArray();
|
||||
idlArray.add_idls(document.querySelector("script[type=text\\/plain]").textContent);
|
||||
idlArray.add_objects({Selection: ['getSelection()']});
|
||||
idlArray.test();
|
||||
</script>
|
||||
31
testing/web-platform/tests/selection/isCollapsed.html
Normal file
31
testing/web-platform/tests/selection/isCollapsed.html
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
<!doctype html>
|
||||
<title>Selection.isCollapsed tests</title>
|
||||
<div id=log></div>
|
||||
<script src=/resources/testharness.js></script>
|
||||
<script src=/resources/testharnessreport.js></script>
|
||||
<script src=common.js></script>
|
||||
<script>
|
||||
"use strict";
|
||||
|
||||
test(function() {
|
||||
selection.removeAllRanges();
|
||||
assert_true(selection.isCollapsed, "isCollapsed must be true if both anchor and focus are null");
|
||||
}, "Empty selection");
|
||||
|
||||
for (var i = 0; i < testRanges.length; i++) {
|
||||
test(function() {
|
||||
selection.removeAllRanges();
|
||||
var endpoints = eval(testRanges[i]);
|
||||
var range = ownerDocument(endpoints[0]).createRange();
|
||||
range.setStart(endpoints[0], endpoints[1]);
|
||||
range.setEnd(endpoints[2], endpoints[3]);
|
||||
selection.addRange(range);
|
||||
|
||||
assert_equals(selection.isCollapsed,
|
||||
endpoints[0] === endpoints[2] && endpoints[1] === endpoints[3],
|
||||
"Value of isCollapsed");
|
||||
}, "Range " + i + " " + testRanges[i]);
|
||||
}
|
||||
|
||||
testDiv.style.display = "none";
|
||||
</script>
|
||||
45
testing/web-platform/tests/selection/removeAllRanges.html
Normal file
45
testing/web-platform/tests/selection/removeAllRanges.html
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
<!doctype html>
|
||||
<title>Selection.removeAllRanges() tests</title>
|
||||
<div id=log></div>
|
||||
<script src=/resources/testharness.js></script>
|
||||
<script src=/resources/testharnessreport.js></script>
|
||||
<script src=common.js></script>
|
||||
<script>
|
||||
"use strict";
|
||||
|
||||
// Also test a selection with no ranges
|
||||
testRanges.unshift("[]");
|
||||
|
||||
var range = rangeFromEndpoints([paras[0].firstChild, 0, paras[0].firstChild, 1]);
|
||||
|
||||
for (var i = 0; i < testRanges.length; i++) {
|
||||
test(function() {
|
||||
setSelectionForwards(eval(testRanges[i]));
|
||||
selection.removeAllRanges();
|
||||
assert_equals(selection.rangeCount, 0,
|
||||
"After removeAllRanges(), rangeCount must be 0");
|
||||
// Test that it's forwards
|
||||
selection.addRange(range);
|
||||
assert_equals(selection.anchorOffset, selection.getRangeAt(0).startOffset,
|
||||
"After removeAllRanges(), addRange() must be forwards, so anchorOffset must equal startOffset rather than endOffset");
|
||||
assert_equals(selection.focusOffset, selection.getRangeAt(0).endOffset,
|
||||
"After removeAllRanges(), addRange() must be forwards, so focusOffset must equal endOffset rather than startOffset");
|
||||
}, "Range " + i + " " + testRanges[i] + " forwards");
|
||||
|
||||
// Copy-pasted from above
|
||||
test(function() {
|
||||
setSelectionBackwards(eval(testRanges[i]));
|
||||
selection.removeAllRanges();
|
||||
assert_equals(selection.rangeCount, 0,
|
||||
"After removeAllRanges(), rangeCount must be 0");
|
||||
// Test that it's forwards
|
||||
selection.addRange(range);
|
||||
assert_equals(selection.anchorOffset, selection.getRangeAt(0).startOffset,
|
||||
"After removeAllRanges(), addRange() must be forwards, so anchorOffset must equal startOffset rather than endOffset");
|
||||
assert_equals(selection.focusOffset, selection.getRangeAt(0).endOffset,
|
||||
"After removeAllRanges(), addRange() must be forwards, so focusOffset must equal endOffset rather than startOffset");
|
||||
}, "Range " + i + " " + testRanges[i] + " backwards");
|
||||
}
|
||||
|
||||
testDiv.style.display = "none";
|
||||
</script>
|
||||
53
testing/web-platform/tests/selection/selectAllChildren.html
Normal file
53
testing/web-platform/tests/selection/selectAllChildren.html
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
<!doctype html>
|
||||
<title>Selection.selectAllChildren tests</title>
|
||||
<div id=log></div>
|
||||
<script src=/resources/testharness.js></script>
|
||||
<script src=/resources/testharnessreport.js></script>
|
||||
<script src=common.js></script>
|
||||
<script>
|
||||
"use strict";
|
||||
|
||||
testRanges.unshift("[]");
|
||||
|
||||
for (var i = 0; i < testRanges.length; i++) {
|
||||
var endpoints = eval(testRanges[i]);
|
||||
|
||||
for (var j = 0; j < testNodes.length; j++) {
|
||||
var node = eval(testNodes[j]);
|
||||
|
||||
test(function() {
|
||||
setSelectionForwards(endpoints);
|
||||
var originalRange = getSelection().rangeCount
|
||||
? getSelection().getRangeAt(0)
|
||||
: null;
|
||||
|
||||
if (node.nodeType == Node.DOCUMENT_TYPE_NODE) {
|
||||
assert_throws("INVALID_NODE_TYPE_ERR", function() {
|
||||
selection.selectAllChildren(node);
|
||||
}, "selectAllChildren() on a DocumentType must throw InvalidNodeTypeError");
|
||||
return;
|
||||
}
|
||||
|
||||
selection.selectAllChildren(node);
|
||||
// This implicitly tests that the selection is forwards, by using
|
||||
// anchorOffset/focusOffset instead of getRangeAt.
|
||||
assert_equals(selection.rangeCount, 1,
|
||||
"After selectAllChildren, rangeCount must be 1");
|
||||
assert_equals(selection.anchorNode, node,
|
||||
"After selectAllChildren, anchorNode must be the given node");
|
||||
assert_equals(selection.anchorOffset, 0,
|
||||
"After selectAllChildren, anchorOffset must be 0");
|
||||
assert_equals(selection.focusNode, node,
|
||||
"After selectAllChildren, focusNode must be the given node");
|
||||
assert_equals(selection.focusOffset, node.childNodes.length,
|
||||
"After selectAllChildren, focusOffset must be the given node's number of children");
|
||||
if (originalRange) {
|
||||
assert_not_equals(getSelection().getRangeAt(0), originalRange,
|
||||
"selectAllChildren must replace any existing range, not mutate it");
|
||||
}
|
||||
}, "Range " + i + " " + testRanges[i] + ", node " + j + " " + testNodes[j]);
|
||||
}
|
||||
}
|
||||
|
||||
testDiv.style.display = "none";
|
||||
</script>
|
||||
33
testing/web-platform/tests/selection/test-iframe.html
Normal file
33
testing/web-platform/tests/selection/test-iframe.html
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
<!doctype html>
|
||||
<title>Selection test iframe</title>
|
||||
<link rel=author title="Aryeh Gregor" href=ayg@aryeh.name>
|
||||
<body>
|
||||
<script src=common.js></script>
|
||||
<script>
|
||||
"use strict";
|
||||
|
||||
// This script only exists because we want to evaluate the range endpoints
|
||||
// in each iframe using that iframe's local variables set up by common.js. It
|
||||
// just creates a range with the endpoints given by
|
||||
// eval(window.testRangeInput), and assigns the result to window.testRange. If
|
||||
// there's an exception, it's assigned to window.unexpectedException.
|
||||
// Everything else is to be done by the script that created the iframe.
|
||||
window.unexpectedException = null;
|
||||
|
||||
function run() {
|
||||
window.unexpectedException = null;
|
||||
try {
|
||||
window.testRange = rangeFromEndpoints(eval(window.testRangeInput));
|
||||
} catch(e) {
|
||||
window.unexpectedException = e;
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the scripts so they don't run repeatedly when the iframe is
|
||||
// reinitialized
|
||||
[].forEach.call(document.querySelectorAll("script"), function(script) {
|
||||
script.parentNode.removeChild(script);
|
||||
});
|
||||
|
||||
testDiv.style.display = "none";
|
||||
</script>
|
||||
Loading…
Add table
Add a link
Reference in a new issue