зеркало из https://github.com/mozilla/pjs.git
Merge m-c to m-i.
This commit is contained in:
Коммит
94f9583d11
|
@ -1498,7 +1498,7 @@ nsHyperTextAccessible::DeleteText(PRInt32 aStartPos, PRInt32 aEndPos)
|
|||
nsresult rv = SetSelectionRange(aStartPos, aEndPos);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
return editor->DeleteSelection(nsIEditor::eNone);
|
||||
return editor->DeleteSelection(nsIEditor::eNone, nsIEditor::eStrip);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
|
|
|
@ -11275,7 +11275,7 @@ define("examples/textview/textStyler", ['orion/textview/annotations'], function(
|
|||
"target", "target-name", "target-new", "target-position", "text-align", "text-align-last", "text-decoration", "text-emphasis",
|
||||
"text-height", "text-indent", "text-justify", "text-outline", "text-shadow", "text-transform", "text-wrap", "top", "transform",
|
||||
"transform-origin", "transform-style", "transition", "transition-delay", "transition-duration", "transition-property",
|
||||
"transition-timing-function", "unicode-bidi", "vertical-align", "visibility", "voice-balance", "voice-duration", "voice-family",
|
||||
"transition-timing-function", "unicode-bidi", "vector-effect", "vertical-align", "visibility", "voice-balance", "voice-duration", "voice-family",
|
||||
"voice-pitch", "voice-pitch-range", "voice-rate", "voice-stress", "voice-volume", "volume", "white-space", "white-space-collapse",
|
||||
"widows", "width", "word-break", "word-spacing", "word-wrap", "z-index"
|
||||
];
|
||||
|
|
|
@ -1378,6 +1378,7 @@ GK_ATOM(y2, "y2")
|
|||
GK_ATOM(yChannelSelector, "yChannelSelector")
|
||||
GK_ATOM(z, "z")
|
||||
GK_ATOM(zoomAndPan, "zoomAndPan")
|
||||
GK_ATOM(vector_effect, "vector-effect")
|
||||
|
||||
GK_ATOM(accumulate, "accumulate")
|
||||
GK_ATOM(additive, "additive")
|
||||
|
|
|
@ -628,6 +628,7 @@ nsIAtom** const kAttributesSVG[] = {
|
|||
// v-ideographic
|
||||
// v-mathematical
|
||||
&nsGkAtoms::values, // values
|
||||
&nsGkAtoms::vector_effect, // vector-effect
|
||||
// vert-adv-y
|
||||
// vert-origin-x
|
||||
// vert-origin-y
|
||||
|
|
|
@ -1863,7 +1863,7 @@ nsTextEditorState::SetValue(const nsAString& aValue, bool aUserInput)
|
|||
plaintextEditor->SetMaxTextLength(-1);
|
||||
|
||||
if (insertValue.IsEmpty()) {
|
||||
mEditor->DeleteSelection(nsIEditor::eNone);
|
||||
mEditor->DeleteSelection(nsIEditor::eNone, nsIEditor::eStrip);
|
||||
} else {
|
||||
plaintextEditor->InsertText(insertValue);
|
||||
}
|
||||
|
|
|
@ -281,6 +281,7 @@ nsSMILCSSProperty::IsPropertyAnimatable(nsCSSProperty aPropID)
|
|||
case eCSSProperty_text_decoration:
|
||||
case eCSSProperty_text_decoration_line:
|
||||
case eCSSProperty_text_rendering:
|
||||
case eCSSProperty_vector_effect:
|
||||
case eCSSProperty_visibility:
|
||||
case eCSSProperty_word_spacing:
|
||||
return true;
|
||||
|
|
|
@ -463,6 +463,9 @@ var gFromToBundles = [
|
|||
new TestcaseBundle(gPropList.unicode_bidi, [
|
||||
new AnimTestcaseFromTo("embed", "bidi-override"),
|
||||
]),
|
||||
new TestcaseBundle(gPropList.vector_effect, [
|
||||
new AnimTestcaseFromTo("none", "non-scaling-stroke"),
|
||||
]),
|
||||
new TestcaseBundle(gPropList.visibility, [
|
||||
new AnimTestcaseFromTo("visible", "hidden"),
|
||||
new AnimTestcaseFromTo("hidden", "collapse"),
|
||||
|
|
|
@ -116,6 +116,7 @@ var gPropList =
|
|||
text_decoration: new NonAdditiveAttribute("text-decoration", "CSS", "text"),
|
||||
text_rendering: new NonAdditiveAttribute("text-rendering", "CSS", "text"),
|
||||
unicode_bidi: new NonAnimatableAttribute("unicode-bidi", "CSS", "text"),
|
||||
vector_effect: new NonAdditiveAttribute("vector-effect", "CSS", "rect"),
|
||||
visibility: new NonAdditiveAttribute("visibility", "CSS", "rect"),
|
||||
word_spacing: new AdditiveAttribute("word-spacing", "CSS", "text"),
|
||||
writing_mode:
|
||||
|
|
|
@ -931,6 +931,7 @@ nsSVGElement::sFillStrokeMap[] = {
|
|||
{ &nsGkAtoms::stroke_miterlimit },
|
||||
{ &nsGkAtoms::stroke_opacity },
|
||||
{ &nsGkAtoms::stroke_width },
|
||||
{ &nsGkAtoms::vector_effect },
|
||||
{ nsnull }
|
||||
};
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@ text { font: 20px monospace; }
|
|||
<g transform="scale(2)">
|
||||
<rect id="rect3" x="25" y="80" width="50" height="50" fill="green"/>
|
||||
<rect id="rect3a" x="25" y="80" width="50" height="50" fill="none" stroke-width="4" stroke="blue"/>
|
||||
<rect id="rect3b" vector-effect="non-scaling-stroke" x="100" y="100" width="25" height="25" fill="orange" stroke-width="4" stroke="yellow"/>
|
||||
</g>
|
||||
<g transform="scale(2) rotate(45 175 75)">
|
||||
<rect id="rect4" x="150" y="50" width="50" height="50" fill="yellow"/>
|
||||
|
|
До Ширина: | Высота: | Размер: 1.4 KiB После Ширина: | Высота: | Размер: 1.5 KiB |
|
@ -93,6 +93,7 @@ function runTest()
|
|||
var rect1aBounds = doc.getElementById("rect1a").getBoundingClientRect();
|
||||
var rect2aBounds = doc.getElementById("rect2a").getBoundingClientRect();
|
||||
var rect3aBounds = doc.getElementById("rect3a").getBoundingClientRect();
|
||||
var rect3bBounds = doc.getElementById("rect3b").getBoundingClientRect();
|
||||
var rect4aBounds = doc.getElementById("rect4a").getBoundingClientRect();
|
||||
|
||||
is(rect1aBounds.left, 48, "rect1a.getBoundingClientRect().left");
|
||||
|
@ -111,6 +112,11 @@ function runTest()
|
|||
is(rect3aBounds.width, 108, "rect3a.getBoundingClientRect().width");
|
||||
is(rect3aBounds.height, 108, "rect3a.getBoundingClientRect().height");
|
||||
|
||||
is(rect3bBounds.left, 198, "rect3b.getBoundingClientRect().left");
|
||||
is(rect3bBounds.top, 198, "rect3b.getBoundingClientRect().top");
|
||||
is(rect3bBounds.width, 54, "rect3b.getBoundingClientRect().width");
|
||||
is(rect3bBounds.height, 54, "rect3b.getBoundingClientRect().height");
|
||||
|
||||
rect = new Rect(350 - 108 * sin45, 150 - 108 * sin45, 108 * sin45 * 2, 108 * sin45 * 2);
|
||||
isWithAbsTolerance(rect4aBounds.left, rect.left, 0.1, "rect4a.getBoundingClientRect().left");
|
||||
isWithAbsTolerance(rect4aBounds.top, rect.top, 0.1, "rect4a.getBoundingClientRect().top");
|
||||
|
|
|
@ -9,8 +9,14 @@ VPATH = @srcdir@
|
|||
relativesrcdir = dom/imported-tests
|
||||
|
||||
DIRS = \
|
||||
failures/editing/conformancetest \
|
||||
failures/editing/selecttest \
|
||||
failures/webapps/WebStorage/tests/submissions/Ms2ger \
|
||||
failures/webapps/WebStorage/tests/submissions/Infraware \
|
||||
failures/webapps/DOMCore/tests/submissions/Opera \
|
||||
$(NULL)
|
||||
|
||||
include $(srcdir)/editing.mk
|
||||
include $(srcdir)/html.mk
|
||||
include $(srcdir)/webapps.mk
|
||||
include $(DEPTH)/config/autoconf.mk
|
||||
|
@ -24,8 +30,5 @@ _SUPPORT_FILES = \
|
|||
WebIDLParser.js \
|
||||
$(NULL)
|
||||
|
||||
testharnessreport.js: testharnessreport.js.in writeReporter.py html.json webapps.json
|
||||
$(PYTHON_PATH) $(srcdir)/writeReporter.py $<
|
||||
|
||||
libs:: $(_SUPPORT_FILES)
|
||||
$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/resources
|
||||
|
|
|
@ -38,7 +38,7 @@ Source; Usage and purpose; License
|
|||
<https://bitbucket.org/ms2ger/test-runner/raw/tip/manifests.txt>.
|
||||
MIT License
|
||||
|
||||
* testharnessreport.js.in
|
||||
* testharnessreport.js
|
||||
Glue between testharness.js and our Mochitest runner.
|
||||
MPL
|
||||
|
||||
|
@ -54,11 +54,10 @@ Source; Usage and purpose; License
|
|||
includes a .mk file for each repository.
|
||||
MPL
|
||||
|
||||
* failures.txt
|
||||
List of JSON files with expected failures.
|
||||
|
||||
* html.json / webapps.json / ...
|
||||
Expected failures for tests in the webapps repository.
|
||||
* failures/
|
||||
Expected failures for tests in each repository. Each test's failures, if
|
||||
any, are in a file with the same path and name with .json appended. New
|
||||
expected fail files currently needed to be added manually to makefiles.
|
||||
|
||||
* html.mk / webapps.mk / ...
|
||||
Generated by importTestsuite.py from webapps.txt.
|
||||
|
@ -74,11 +73,6 @@ Source; Usage and purpose; License
|
|||
Actual tests.
|
||||
W3C Test Suite License / W3C 3-clause BSD License
|
||||
|
||||
* writeReporter.py
|
||||
Generates testharness.js from testharnessreport.js.in and the JSON files for
|
||||
repositories listed in failures.txt.
|
||||
MPL
|
||||
|
||||
|
||||
=====================================================================
|
||||
Importing an additional directory from an already-imported repository
|
||||
|
@ -94,4 +88,5 @@ Importing a new test suite
|
|||
==========================
|
||||
|
||||
Create a data file in the format documented above, and run the
|
||||
importTestsuite.py script, passing the data file as its argument.
|
||||
importTestsuite.py script, passing the data file as its argument. Add any
|
||||
necessary files in failures/.
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
DIRS += \
|
||||
editing/ \
|
||||
$(NULL)
|
|
@ -0,0 +1,2 @@
|
|||
https://dvcs.w3.org/hg/editing|editing
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
# THIS FILE IS AUTOGENERATED BY importTestsuite.py - DO NOT EDIT
|
||||
|
||||
DEPTH = ../../..
|
||||
|
||||
topsrcdir = @top_srcdir@
|
||||
srcdir = @srcdir@
|
||||
VPATH = @srcdir@
|
||||
relativesrcdir = dom/imptests/editing
|
||||
|
||||
DIRS = \
|
||||
css \
|
||||
conformancetest \
|
||||
selecttest \
|
||||
$(NULL)
|
||||
|
||||
include $(DEPTH)/config/autoconf.mk
|
||||
include $(topsrcdir)/config/rules.mk
|
||||
|
||||
_TESTS = \
|
||||
$(NULL)
|
||||
|
||||
_TESTS += \
|
||||
implementation.js \
|
||||
tests.js \
|
||||
$(NULL)
|
||||
|
||||
libs:: $(_TESTS)
|
||||
$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/tests/$(relativesrcdir)
|
|
@ -0,0 +1,26 @@
|
|||
# THIS FILE IS AUTOGENERATED BY importTestsuite.py - DO NOT EDIT
|
||||
|
||||
DEPTH = ../../../..
|
||||
|
||||
topsrcdir = @top_srcdir@
|
||||
srcdir = @srcdir@
|
||||
VPATH = @srcdir@
|
||||
relativesrcdir = dom/imptests/editing/conformancetest
|
||||
|
||||
DIRS = \
|
||||
$(NULL)
|
||||
|
||||
include $(DEPTH)/config/autoconf.mk
|
||||
include $(topsrcdir)/config/rules.mk
|
||||
|
||||
_TESTS = \
|
||||
test_runtest.html \
|
||||
test_event.html \
|
||||
$(NULL)
|
||||
|
||||
_TESTS += \
|
||||
data.js \
|
||||
$(NULL)
|
||||
|
||||
libs:: $(_TESTS)
|
||||
$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/tests/$(relativesrcdir)
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,289 @@
|
|||
<!doctype html>
|
||||
<title>Editing event tests</title>
|
||||
<script src=/resources/testharness.js></script>
|
||||
<script src=/resources/testharnessreport.js></script>
|
||||
<div id=test></div>
|
||||
<div id=log></div>
|
||||
<script>
|
||||
"use strict";
|
||||
|
||||
var div = document.querySelector("#test");
|
||||
add_completion_callback(function() { div.parentNode.removeChild(div) });
|
||||
|
||||
function copyEvent(e) {
|
||||
var ret = {};
|
||||
ret.original = e;
|
||||
["type", "target", "currentTarget", "eventPhase", "bubbles", "cancelable",
|
||||
"defaultPrevented", "isTrusted", "command", "value"].forEach(function(k) {
|
||||
ret[k] = e[k];
|
||||
});
|
||||
return ret;
|
||||
}
|
||||
|
||||
var tests = [
|
||||
{
|
||||
name: "Simple editable div",
|
||||
html: "<div contenteditable>foo<b>bar</b>baz</div>",
|
||||
initRange: function(range) {
|
||||
range.setStart(div.querySelector("b").firstChild, 0);
|
||||
range.setEnd(div.querySelector("b"), 1);
|
||||
},
|
||||
target: function() { return div.firstChild },
|
||||
command: "bold",
|
||||
value: "",
|
||||
},
|
||||
{
|
||||
name: "Editable b",
|
||||
html: "foo<b contenteditable>bar</b>baz",
|
||||
initRange: function(range) {
|
||||
range.setStart(div.querySelector("b").firstChild, 0);
|
||||
range.setEnd(div.querySelector("b"), 1);
|
||||
},
|
||||
target: function() { return div.querySelector("b") },
|
||||
command: "bold",
|
||||
value: "",
|
||||
},
|
||||
{
|
||||
name: "No editable content",
|
||||
html: "foo<b>bar</b>baz",
|
||||
initRange: function(range) {
|
||||
range.setStart(div.querySelector("b").firstChild, 0);
|
||||
range.setEnd(div.querySelector("b"), 1);
|
||||
},
|
||||
target: function() { return null },
|
||||
command: "bold",
|
||||
value: "",
|
||||
},
|
||||
{
|
||||
name: "Partially-selected editable content",
|
||||
html: "foo<b contenteditable>bar</b>baz",
|
||||
initRange: function(range) {
|
||||
range.setStart(div.querySelector("b").firstChild, 0);
|
||||
range.setEnd(div, 3);
|
||||
},
|
||||
target: function() { return null },
|
||||
command: "bold",
|
||||
value: "",
|
||||
},
|
||||
{
|
||||
name: "Selection spans two editing hosts",
|
||||
html: "<div contenteditable>foo</div><div contenteditable>bar</div>",
|
||||
initRange: function(range) {
|
||||
range.setStart(div.querySelector("div").firstChild, 2);
|
||||
range.setEnd(div.querySelector("div + div").firstChild, 1);
|
||||
},
|
||||
target: function() { return null },
|
||||
command: "bold",
|
||||
value: "",
|
||||
},
|
||||
{
|
||||
name: "Selection includes two editing hosts",
|
||||
html: "foo<div contenteditable>bar</div>baz<div contenteditable>quz</div>qoz",
|
||||
initRange: function(range) {
|
||||
range.setStart(div.firstChild, 2);
|
||||
range.setEnd(div.lastChild, 1);
|
||||
},
|
||||
target: function() { return null },
|
||||
command: "bold",
|
||||
value: "",
|
||||
},
|
||||
{
|
||||
name: "Changing selection from handler",
|
||||
html: "<div contenteditable>foo</div><div contenteditable>bar</div>",
|
||||
initRange: function(range) {
|
||||
range.setStart(div.querySelector("div").firstChild, 0);
|
||||
range.setEnd(div.querySelector("div").firstChild, 3);
|
||||
},
|
||||
target: function() { return div.firstChild },
|
||||
finalTarget: function() { return div.lastChild },
|
||||
beforeInputAction: function() {
|
||||
getSelection().removeAllRanges();
|
||||
var range = document.createRange();
|
||||
range.setStart(div.querySelector("div + div").firstChild, 0);
|
||||
range.setEnd(div.querySelector("div + div").firstChild, 3);
|
||||
getSelection().addRange(range);
|
||||
},
|
||||
command: "bold",
|
||||
value: "",
|
||||
},
|
||||
];
|
||||
|
||||
var commandTests = {
|
||||
backColor: ["green"],
|
||||
createLink: ["http://www.w3.org/community/editing/"],
|
||||
fontName: ["serif", "Helvetica"],
|
||||
fontSize: ["6", "15px"],
|
||||
foreColor: ["green"],
|
||||
hiliteColor: ["green"],
|
||||
italic: [],
|
||||
removeFormat: [],
|
||||
strikeThrough: [],
|
||||
subscript: [],
|
||||
superscript: [],
|
||||
underline: [],
|
||||
unlink: [],
|
||||
delete: [],
|
||||
formatBlock: ["p"],
|
||||
forwardDelete: [],
|
||||
indent: [],
|
||||
insertHorizontalRule: ["id"],
|
||||
insertHTML: ["<b>hi</b>"],
|
||||
insertImage: ["http://example.com/some-image"],
|
||||
insertLineBreak: [],
|
||||
insertOrderedList: [],
|
||||
insertParagraph: [],
|
||||
insertText: ["abc"],
|
||||
insertUnorderedList: [],
|
||||
justifyCenter: [],
|
||||
justifyFull: [],
|
||||
justifyLeft: [],
|
||||
justifyRight: [],
|
||||
outdent: [],
|
||||
redo: [],
|
||||
selectAll: [],
|
||||
styleWithCSS: [],
|
||||
undo: [],
|
||||
useCSS: [],
|
||||
};
|
||||
|
||||
Object.keys(commandTests).forEach(function(command) {
|
||||
commandTests[command] = ["", "quasit"].concat(commandTests[command]);
|
||||
commandTests[command].forEach(function(value) {
|
||||
tests.push({
|
||||
name: "Command " + command + ", value " + format_value(value),
|
||||
html: "<div contenteditable>foo<b>bar</b>baz</div>",
|
||||
initRange: function(range) {
|
||||
range.setStart(div.querySelector("b").firstChild, 0);
|
||||
range.setEnd(div.querySelector("b"), 1);
|
||||
},
|
||||
target: function() {
|
||||
return ["redo", "selectAll", "styleWithCSS", "undo", "useCSS"]
|
||||
.indexOf(command) == -1 ? div.firstChild : null;
|
||||
},
|
||||
command: command,
|
||||
value: value,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
tests.forEach(function(obj) {
|
||||
[true, false].forEach(function(cancel) {
|
||||
// Kill all event handlers first
|
||||
var newDiv = div.cloneNode(false);
|
||||
div.parentNode.insertBefore(newDiv, div);
|
||||
div.parentNode.removeChild(div);
|
||||
div = newDiv;
|
||||
|
||||
div.innerHTML = obj.html;
|
||||
|
||||
var originalContents = div.cloneNode(true);
|
||||
|
||||
getSelection().removeAllRanges();
|
||||
var range = document.createRange();
|
||||
obj.initRange(range);
|
||||
getSelection().addRange(range);
|
||||
|
||||
var target = obj.target();
|
||||
var finalTarget = "finalTarget" in obj ? obj.finalTarget() : target;
|
||||
var command = obj.command;
|
||||
var value = obj.value;
|
||||
|
||||
var beforeInputEvents = [];
|
||||
var inputEvents = [];
|
||||
div.addEventListener("beforeinput", function(e) {
|
||||
var copied = copyEvent(e);
|
||||
copied.inputEventsLength = inputEvents.length;
|
||||
beforeInputEvents.push(copied);
|
||||
if (cancel) {
|
||||
e.preventDefault();
|
||||
}
|
||||
if ("beforeInputAction" in obj) {
|
||||
obj.beforeInputAction();
|
||||
}
|
||||
});
|
||||
div.addEventListener("input", function(e) { inputEvents.push(copyEvent(e)) });
|
||||
|
||||
// Uncomment this code instead of the execCommand() to make all the
|
||||
// tests pass, as a sanity check
|
||||
//var e = new Event("beforeinput", {bubbles: true, cancelable: true});
|
||||
//e.command = command;
|
||||
//e.value = value;
|
||||
//var ret = target ? target.dispatchEvent(e) : false;
|
||||
//if (ret) {
|
||||
// var e = new Event("input", {bubbles: true});
|
||||
// e.command = command;
|
||||
// e.value = value;
|
||||
// finalTarget.dispatchEvent(e);
|
||||
//}
|
||||
|
||||
var exception = null;
|
||||
try {
|
||||
document.execCommand(command, false, value);
|
||||
} catch(e) {
|
||||
exception = e;
|
||||
}
|
||||
|
||||
test(function() {
|
||||
assert_equals(exception, null, "Unexpected exception");
|
||||
}, obj.name + ": execCommand() must not throw, "
|
||||
+ (cancel ? "canceled" : "uncanceled"));
|
||||
|
||||
test(function() {
|
||||
assert_equals(beforeInputEvents.length, target ? 1 : 0,
|
||||
"number of beforeinput events fired");
|
||||
if (beforeInputEvents.length == 0) {
|
||||
assert_equals(inputEvents.length, 0, "number of input events fired");
|
||||
return;
|
||||
}
|
||||
var e = beforeInputEvents[0];
|
||||
assert_equals(e.inputEventsLength, 0, "number of input events fired");
|
||||
assert_equals(e.type, "beforeinput", "event.type");
|
||||
assert_equals(e.target, target, "event.target");
|
||||
assert_equals(e.currentTarget, div, "event.currentTarget");
|
||||
assert_equals(e.eventPhase, Event.BUBBLING_PHASE, "event.eventPhase");
|
||||
assert_equals(e.bubbles, true, "event.bubbles");
|
||||
assert_equals(e.cancelable, true, "event.cancelable");
|
||||
assert_equals(e.defaultPrevented, false, "event.defaultPrevented");
|
||||
assert_equals(e.command, command, "e.command");
|
||||
assert_equals(e.value, value, "e.value");
|
||||
assert_own_property(window, "EditingBeforeInputEvent",
|
||||
"window.EditingBeforeInputEvent must exist");
|
||||
assert_equals(Object.getPrototypeOf(e.original),
|
||||
EditingBeforeInputEvent.prototype,
|
||||
"event prototype");
|
||||
assert_true(originalContents.isEqualNode(div),
|
||||
"div contents not yet changed");
|
||||
assert_equals(e.isTrusted, true, "event.isTrusted");
|
||||
}, obj.name + ": beforeinput event, " + (cancel ? "canceled" : "uncanceled"));
|
||||
|
||||
test(function() {
|
||||
assert_equals(inputEvents.length, target && !cancel ? 1 : 0,
|
||||
"number of input events fired");
|
||||
if (!target || cancel) {
|
||||
assert_true(originalContents.isEqualNode(div),
|
||||
"div contents must not be changed");
|
||||
return;
|
||||
}
|
||||
var e = inputEvents[0];
|
||||
assert_equals(e.type, "input", "event.type");
|
||||
assert_equals(e.target, finalTarget, "event.target");
|
||||
assert_equals(e.currentTarget, div, "event.currentTarget");
|
||||
assert_equals(e.eventPhase, Event.BUBBLING_PHASE, "event.eventPhase");
|
||||
assert_equals(e.bubbles, true, "event.bubbles");
|
||||
assert_equals(e.cancelable, false, "event.cancelable");
|
||||
assert_equals(e.defaultPrevented, false, "event.defaultPrevented");
|
||||
assert_equals(e.command, command, "e.command");
|
||||
assert_equals(e.value, value, "e.value");
|
||||
assert_own_property(window, "EditingInputEvent",
|
||||
"window.EditingInputEvent must exist");
|
||||
assert_equals(Object.getPrototypeOf(e.original),
|
||||
EditingInputEvent.prototype,
|
||||
"event prototype");
|
||||
assert_equals(e.isTrusted, true, "event.isTrusted");
|
||||
}, obj.name + ": input event, " + (cancel ? "canceled" : "uncanceled"));
|
||||
});
|
||||
});
|
||||
|
||||
// Thanks, Gecko.
|
||||
document.body.bgColor = "";
|
||||
</script>
|
|
@ -0,0 +1,48 @@
|
|||
<!doctype html>
|
||||
<meta charset=utf-8>
|
||||
<link rel=stylesheet href=../css/reset.css>
|
||||
<title>HTML editing conformance tests</title>
|
||||
<p>See the <a href=editing.html#tests>Tests</a> section of the specification
|
||||
for documentation.
|
||||
|
||||
<p id=timing></p>
|
||||
|
||||
<div id=log></div>
|
||||
|
||||
<div id=test-container></div>
|
||||
|
||||
<script src=../implementation.js></script>
|
||||
<script>var testsJsLibraryOnly = true</script>
|
||||
<script src=../tests.js></script>
|
||||
<script src=data.js></script>
|
||||
<script src=/resources/testharness.js></script>
|
||||
<script src=/resources/testharnessreport.js></script>
|
||||
<script>
|
||||
"use strict";
|
||||
|
||||
runTests();
|
||||
|
||||
function runTests() {
|
||||
var startTime = Date.now();
|
||||
|
||||
// Make document.body.innerHTML more tidy by removing unnecessary things.
|
||||
// We can't remove the testharness.js script, because at the time of this
|
||||
// writing, for some reason that stops it from adding appropriate CSS.
|
||||
[].forEach.call(document.querySelectorAll("script"), function(node) {
|
||||
if (!/testharness\.js$/.test(node.src)) {
|
||||
node.parentNode.removeChild(node);
|
||||
}
|
||||
});
|
||||
|
||||
browserTests.forEach(runConformanceTest);
|
||||
|
||||
document.getElementById("test-container").parentNode
|
||||
.removeChild(document.getElementById("test-container"));
|
||||
|
||||
var elapsed = Math.round(Date.now() - startTime)/1000;
|
||||
document.getElementById("timing").textContent =
|
||||
"Time elapsed: " + Math.floor(elapsed/60) + ":"
|
||||
+ ((elapsed % 60) < 10 ? "0" : "")
|
||||
+ (elapsed % 60).toFixed(3) + " min.";
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,24 @@
|
|||
# THIS FILE IS AUTOGENERATED BY importTestsuite.py - DO NOT EDIT
|
||||
|
||||
DEPTH = ../../../..
|
||||
|
||||
topsrcdir = @top_srcdir@
|
||||
srcdir = @srcdir@
|
||||
VPATH = @srcdir@
|
||||
relativesrcdir = dom/imptests/editing/css
|
||||
|
||||
DIRS = \
|
||||
$(NULL)
|
||||
|
||||
include $(DEPTH)/config/autoconf.mk
|
||||
include $(topsrcdir)/config/rules.mk
|
||||
|
||||
_TESTS = \
|
||||
$(NULL)
|
||||
|
||||
_TESTS += \
|
||||
reset.css \
|
||||
$(NULL)
|
||||
|
||||
libs:: $(_TESTS)
|
||||
$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/tests/$(relativesrcdir)
|
|
@ -0,0 +1,27 @@
|
|||
/* Make sure various CSS values are what are expected, so that tests work
|
||||
* right. */
|
||||
body { font-family: serif }
|
||||
/* http://www.w3.org/Bugs/Public/show_bug.cgi?id=12154
|
||||
* https://bugzilla.mozilla.org/show_bug.cgi?id=589124
|
||||
* https://bugs.webkit.org/show_bug.cgi?id=56400 */
|
||||
b, strong { font-weight: bold }
|
||||
.bold { font-weight: bold }
|
||||
.notbold { font-weight: normal }
|
||||
.underline { text-decoration: underline }
|
||||
.line-through { text-decoration: line-through }
|
||||
.underline-and-line-through { text-decoration: underline line-through }
|
||||
#purple { color: purple }
|
||||
/* https://bugs.webkit.org/show_bug.cgi?id=56670 */
|
||||
dfn { font-style: italic }
|
||||
/* Opera has weird default blockquote style */
|
||||
blockquote { margin: 1em 40px }
|
||||
/* Some tests assume links are blue, for the sake of argument, but they aren't
|
||||
* blue in any browser. And :visited definitely isn't blue, except in engines
|
||||
* like Gecko that lie.
|
||||
*
|
||||
* This should really be #00e, probably. See:
|
||||
* http://www.w3.org/Bugs/Public/show_bug.cgi?id=13330 */
|
||||
:link, :visited { color: blue }
|
||||
/* http://www.w3.org/Bugs/Public/show_bug.cgi?id=14066
|
||||
* https://bugs.webkit.org/show_bug.cgi?id=68392 */
|
||||
quasit { text-align: inherit }
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,37 @@
|
|||
# THIS FILE IS AUTOGENERATED BY importTestsuite.py - DO NOT EDIT
|
||||
|
||||
DEPTH = ../../../..
|
||||
|
||||
topsrcdir = @top_srcdir@
|
||||
srcdir = @srcdir@
|
||||
VPATH = @srcdir@
|
||||
relativesrcdir = dom/imptests/editing/selecttest
|
||||
|
||||
DIRS = \
|
||||
$(NULL)
|
||||
|
||||
include $(DEPTH)/config/autoconf.mk
|
||||
include $(topsrcdir)/config/rules.mk
|
||||
|
||||
_TESTS = \
|
||||
test_addRange.html \
|
||||
test_collapse.html \
|
||||
test_collapseToStartEnd.html \
|
||||
test_deleteFromDocument.html \
|
||||
test_Document-open.html \
|
||||
test_extend.html \
|
||||
test_getRangeAt.html \
|
||||
test_getSelection.html \
|
||||
test_interfaces.html \
|
||||
test_isCollapsed.html \
|
||||
test_removeAllRanges.html \
|
||||
test_selectAllChildren.html \
|
||||
$(NULL)
|
||||
|
||||
_TESTS += \
|
||||
common.js \
|
||||
test-iframe.html \
|
||||
$(NULL)
|
||||
|
||||
libs:: $(_TESTS)
|
||||
$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/tests/$(relativesrcdir)
|
|
@ -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]);
|
||||
}
|
||||
}
|
|
@ -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>
|
|
@ -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>
|
|
@ -0,0 +1,177 @@
|
|||
<!doctype html>
|
||||
<title>Selection.addRange() 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";
|
||||
|
||||
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>
|
|
@ -0,0 +1,87 @@
|
|||
<!doctype html>
|
||||
<title>Selection.collapse() 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";
|
||||
|
||||
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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -0,0 +1,148 @@
|
|||
<!doctype html>
|
||||
<title>Selection extend() tests</title>
|
||||
<meta charset=utf-8>
|
||||
<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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -1,2 +0,0 @@
|
|||
html
|
||||
webapps
|
|
@ -0,0 +1,20 @@
|
|||
DEPTH = ../../../../..
|
||||
|
||||
topsrcdir = @top_srcdir@
|
||||
srcdir = @srcdir@
|
||||
VPATH = @srcdir@
|
||||
relativesrcdir = dom/imptests/failures/editing/conformancetest
|
||||
|
||||
DIRS = \
|
||||
$(NULL)
|
||||
|
||||
include $(DEPTH)/config/autoconf.mk
|
||||
include $(topsrcdir)/config/rules.mk
|
||||
|
||||
_TESTS = \
|
||||
test_event.html.json \
|
||||
test_runtest.html.json \
|
||||
$(NULL)
|
||||
|
||||
libs:: $(_TESTS)
|
||||
$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/tests/$(relativesrcdir)
|
|
@ -0,0 +1,310 @@
|
|||
{
|
||||
"Simple editable div: beforeinput event, canceled":true,
|
||||
"Simple editable div: input event, canceled":true,
|
||||
"Simple editable div: beforeinput event, uncanceled":true,
|
||||
"Simple editable div: input event, uncanceled":true,
|
||||
"Editable b: execCommand() must not throw, canceled":true,
|
||||
"Editable b: beforeinput event, canceled":true,
|
||||
"Editable b: input event, canceled":true,
|
||||
"Editable b: execCommand() must not throw, uncanceled":true,
|
||||
"Editable b: beforeinput event, uncanceled":true,
|
||||
"Editable b: input event, uncanceled":true,
|
||||
"No editable content: execCommand() must not throw, canceled":true,
|
||||
"No editable content: execCommand() must not throw, uncanceled":true,
|
||||
"Changing selection from handler: beforeinput event, canceled":true,
|
||||
"Changing selection from handler: input event, canceled":true,
|
||||
"Changing selection from handler: beforeinput event, uncanceled":true,
|
||||
"Changing selection from handler: input event, uncanceled":true,
|
||||
"Command backColor, value \"\": beforeinput event, canceled":true,
|
||||
"Command backColor, value \"\": beforeinput event, uncanceled":true,
|
||||
"Command backColor, value \"\": input event, uncanceled":true,
|
||||
"Command backColor, value \"quasit\": beforeinput event, canceled":true,
|
||||
"Command backColor, value \"quasit\": input event, canceled":true,
|
||||
"Command backColor, value \"quasit\": beforeinput event, uncanceled":true,
|
||||
"Command backColor, value \"quasit\": input event, uncanceled":true,
|
||||
"Command backColor, value \"green\": beforeinput event, canceled":true,
|
||||
"Command backColor, value \"green\": input event, canceled":true,
|
||||
"Command backColor, value \"green\": beforeinput event, uncanceled":true,
|
||||
"Command backColor, value \"green\": input event, uncanceled":true,
|
||||
"Command createLink, value \"\": execCommand() must not throw, canceled":true,
|
||||
"Command createLink, value \"\": beforeinput event, canceled":true,
|
||||
"Command createLink, value \"\": execCommand() must not throw, uncanceled":true,
|
||||
"Command createLink, value \"\": beforeinput event, uncanceled":true,
|
||||
"Command createLink, value \"\": input event, uncanceled":true,
|
||||
"Command createLink, value \"quasit\": beforeinput event, canceled":true,
|
||||
"Command createLink, value \"quasit\": input event, canceled":true,
|
||||
"Command createLink, value \"quasit\": beforeinput event, uncanceled":true,
|
||||
"Command createLink, value \"quasit\": input event, uncanceled":true,
|
||||
"Command createLink, value \"http://www.w3.org/community/editing/\": beforeinput event, canceled":true,
|
||||
"Command createLink, value \"http://www.w3.org/community/editing/\": input event, canceled":true,
|
||||
"Command createLink, value \"http://www.w3.org/community/editing/\": beforeinput event, uncanceled":true,
|
||||
"Command createLink, value \"http://www.w3.org/community/editing/\": input event, uncanceled":true,
|
||||
"Command fontName, value \"\": beforeinput event, canceled":true,
|
||||
"Command fontName, value \"\": beforeinput event, uncanceled":true,
|
||||
"Command fontName, value \"\": input event, uncanceled":true,
|
||||
"Command fontName, value \"quasit\": beforeinput event, canceled":true,
|
||||
"Command fontName, value \"quasit\": input event, canceled":true,
|
||||
"Command fontName, value \"quasit\": beforeinput event, uncanceled":true,
|
||||
"Command fontName, value \"quasit\": input event, uncanceled":true,
|
||||
"Command fontName, value \"serif\": beforeinput event, canceled":true,
|
||||
"Command fontName, value \"serif\": input event, canceled":true,
|
||||
"Command fontName, value \"serif\": beforeinput event, uncanceled":true,
|
||||
"Command fontName, value \"serif\": input event, uncanceled":true,
|
||||
"Command fontName, value \"Helvetica\": beforeinput event, canceled":true,
|
||||
"Command fontName, value \"Helvetica\": input event, canceled":true,
|
||||
"Command fontName, value \"Helvetica\": beforeinput event, uncanceled":true,
|
||||
"Command fontName, value \"Helvetica\": input event, uncanceled":true,
|
||||
"Command fontSize, value \"\": beforeinput event, canceled":true,
|
||||
"Command fontSize, value \"\": beforeinput event, uncanceled":true,
|
||||
"Command fontSize, value \"\": input event, uncanceled":true,
|
||||
"Command fontSize, value \"quasit\": beforeinput event, canceled":true,
|
||||
"Command fontSize, value \"quasit\": beforeinput event, uncanceled":true,
|
||||
"Command fontSize, value \"quasit\": input event, uncanceled":true,
|
||||
"Command fontSize, value \"6\": beforeinput event, canceled":true,
|
||||
"Command fontSize, value \"6\": input event, canceled":true,
|
||||
"Command fontSize, value \"6\": beforeinput event, uncanceled":true,
|
||||
"Command fontSize, value \"6\": input event, uncanceled":true,
|
||||
"Command fontSize, value \"15px\": beforeinput event, canceled":true,
|
||||
"Command fontSize, value \"15px\": input event, canceled":true,
|
||||
"Command fontSize, value \"15px\": beforeinput event, uncanceled":true,
|
||||
"Command fontSize, value \"15px\": input event, uncanceled":true,
|
||||
"Command foreColor, value \"\": beforeinput event, canceled":true,
|
||||
"Command foreColor, value \"\": beforeinput event, uncanceled":true,
|
||||
"Command foreColor, value \"\": input event, uncanceled":true,
|
||||
"Command foreColor, value \"quasit\": beforeinput event, canceled":true,
|
||||
"Command foreColor, value \"quasit\": input event, canceled":true,
|
||||
"Command foreColor, value \"quasit\": beforeinput event, uncanceled":true,
|
||||
"Command foreColor, value \"quasit\": input event, uncanceled":true,
|
||||
"Command foreColor, value \"green\": beforeinput event, canceled":true,
|
||||
"Command foreColor, value \"green\": input event, canceled":true,
|
||||
"Command foreColor, value \"green\": beforeinput event, uncanceled":true,
|
||||
"Command foreColor, value \"green\": input event, uncanceled":true,
|
||||
"Command hiliteColor, value \"\": beforeinput event, canceled":true,
|
||||
"Command hiliteColor, value \"\": beforeinput event, uncanceled":true,
|
||||
"Command hiliteColor, value \"\": input event, uncanceled":true,
|
||||
"Command hiliteColor, value \"quasit\": beforeinput event, canceled":true,
|
||||
"Command hiliteColor, value \"quasit\": input event, canceled":true,
|
||||
"Command hiliteColor, value \"quasit\": beforeinput event, uncanceled":true,
|
||||
"Command hiliteColor, value \"quasit\": input event, uncanceled":true,
|
||||
"Command hiliteColor, value \"green\": beforeinput event, canceled":true,
|
||||
"Command hiliteColor, value \"green\": input event, canceled":true,
|
||||
"Command hiliteColor, value \"green\": beforeinput event, uncanceled":true,
|
||||
"Command hiliteColor, value \"green\": input event, uncanceled":true,
|
||||
"Command italic, value \"\": beforeinput event, canceled":true,
|
||||
"Command italic, value \"\": input event, canceled":true,
|
||||
"Command italic, value \"\": beforeinput event, uncanceled":true,
|
||||
"Command italic, value \"\": input event, uncanceled":true,
|
||||
"Command italic, value \"quasit\": beforeinput event, canceled":true,
|
||||
"Command italic, value \"quasit\": input event, canceled":true,
|
||||
"Command italic, value \"quasit\": beforeinput event, uncanceled":true,
|
||||
"Command italic, value \"quasit\": input event, uncanceled":true,
|
||||
"Command removeFormat, value \"\": beforeinput event, canceled":true,
|
||||
"Command removeFormat, value \"\": input event, canceled":true,
|
||||
"Command removeFormat, value \"\": beforeinput event, uncanceled":true,
|
||||
"Command removeFormat, value \"\": input event, uncanceled":true,
|
||||
"Command removeFormat, value \"quasit\": beforeinput event, canceled":true,
|
||||
"Command removeFormat, value \"quasit\": input event, canceled":true,
|
||||
"Command removeFormat, value \"quasit\": beforeinput event, uncanceled":true,
|
||||
"Command removeFormat, value \"quasit\": input event, uncanceled":true,
|
||||
"Command strikeThrough, value \"\": beforeinput event, canceled":true,
|
||||
"Command strikeThrough, value \"\": input event, canceled":true,
|
||||
"Command strikeThrough, value \"\": beforeinput event, uncanceled":true,
|
||||
"Command strikeThrough, value \"\": input event, uncanceled":true,
|
||||
"Command strikeThrough, value \"quasit\": beforeinput event, canceled":true,
|
||||
"Command strikeThrough, value \"quasit\": input event, canceled":true,
|
||||
"Command strikeThrough, value \"quasit\": beforeinput event, uncanceled":true,
|
||||
"Command strikeThrough, value \"quasit\": input event, uncanceled":true,
|
||||
"Command subscript, value \"\": beforeinput event, canceled":true,
|
||||
"Command subscript, value \"\": input event, canceled":true,
|
||||
"Command subscript, value \"\": beforeinput event, uncanceled":true,
|
||||
"Command subscript, value \"\": input event, uncanceled":true,
|
||||
"Command subscript, value \"quasit\": beforeinput event, canceled":true,
|
||||
"Command subscript, value \"quasit\": input event, canceled":true,
|
||||
"Command subscript, value \"quasit\": beforeinput event, uncanceled":true,
|
||||
"Command subscript, value \"quasit\": input event, uncanceled":true,
|
||||
"Command superscript, value \"\": beforeinput event, canceled":true,
|
||||
"Command superscript, value \"\": input event, canceled":true,
|
||||
"Command superscript, value \"\": beforeinput event, uncanceled":true,
|
||||
"Command superscript, value \"\": input event, uncanceled":true,
|
||||
"Command superscript, value \"quasit\": beforeinput event, canceled":true,
|
||||
"Command superscript, value \"quasit\": input event, canceled":true,
|
||||
"Command superscript, value \"quasit\": beforeinput event, uncanceled":true,
|
||||
"Command superscript, value \"quasit\": input event, uncanceled":true,
|
||||
"Command underline, value \"\": beforeinput event, canceled":true,
|
||||
"Command underline, value \"\": input event, canceled":true,
|
||||
"Command underline, value \"\": beforeinput event, uncanceled":true,
|
||||
"Command underline, value \"\": input event, uncanceled":true,
|
||||
"Command underline, value \"quasit\": beforeinput event, canceled":true,
|
||||
"Command underline, value \"quasit\": input event, canceled":true,
|
||||
"Command underline, value \"quasit\": beforeinput event, uncanceled":true,
|
||||
"Command underline, value \"quasit\": input event, uncanceled":true,
|
||||
"Command unlink, value \"\": beforeinput event, canceled":true,
|
||||
"Command unlink, value \"\": beforeinput event, uncanceled":true,
|
||||
"Command unlink, value \"\": input event, uncanceled":true,
|
||||
"Command unlink, value \"quasit\": beforeinput event, canceled":true,
|
||||
"Command unlink, value \"quasit\": beforeinput event, uncanceled":true,
|
||||
"Command unlink, value \"quasit\": input event, uncanceled":true,
|
||||
"Command delete, value \"\": beforeinput event, canceled":true,
|
||||
"Command delete, value \"\": input event, canceled":true,
|
||||
"Command delete, value \"\": beforeinput event, uncanceled":true,
|
||||
"Command delete, value \"\": input event, uncanceled":true,
|
||||
"Command delete, value \"quasit\": beforeinput event, canceled":true,
|
||||
"Command delete, value \"quasit\": input event, canceled":true,
|
||||
"Command delete, value \"quasit\": beforeinput event, uncanceled":true,
|
||||
"Command delete, value \"quasit\": input event, uncanceled":true,
|
||||
"Command formatBlock, value \"\": beforeinput event, canceled":true,
|
||||
"Command formatBlock, value \"\": beforeinput event, uncanceled":true,
|
||||
"Command formatBlock, value \"\": input event, uncanceled":true,
|
||||
"Command formatBlock, value \"quasit\": beforeinput event, canceled":true,
|
||||
"Command formatBlock, value \"quasit\": beforeinput event, uncanceled":true,
|
||||
"Command formatBlock, value \"quasit\": input event, uncanceled":true,
|
||||
"Command formatBlock, value \"p\": beforeinput event, canceled":true,
|
||||
"Command formatBlock, value \"p\": input event, canceled":true,
|
||||
"Command formatBlock, value \"p\": beforeinput event, uncanceled":true,
|
||||
"Command formatBlock, value \"p\": input event, uncanceled":true,
|
||||
"Command forwardDelete, value \"\": beforeinput event, canceled":true,
|
||||
"Command forwardDelete, value \"\": beforeinput event, uncanceled":true,
|
||||
"Command forwardDelete, value \"\": input event, uncanceled":true,
|
||||
"Command forwardDelete, value \"quasit\": beforeinput event, canceled":true,
|
||||
"Command forwardDelete, value \"quasit\": beforeinput event, uncanceled":true,
|
||||
"Command forwardDelete, value \"quasit\": input event, uncanceled":true,
|
||||
"Command indent, value \"\": beforeinput event, canceled":true,
|
||||
"Command indent, value \"\": input event, canceled":true,
|
||||
"Command indent, value \"\": beforeinput event, uncanceled":true,
|
||||
"Command indent, value \"\": input event, uncanceled":true,
|
||||
"Command indent, value \"quasit\": beforeinput event, canceled":true,
|
||||
"Command indent, value \"quasit\": input event, canceled":true,
|
||||
"Command indent, value \"quasit\": beforeinput event, uncanceled":true,
|
||||
"Command indent, value \"quasit\": input event, uncanceled":true,
|
||||
"Command insertHorizontalRule, value \"\": beforeinput event, canceled":true,
|
||||
"Command insertHorizontalRule, value \"\": input event, canceled":true,
|
||||
"Command insertHorizontalRule, value \"\": beforeinput event, uncanceled":true,
|
||||
"Command insertHorizontalRule, value \"\": input event, uncanceled":true,
|
||||
"Command insertHorizontalRule, value \"quasit\": beforeinput event, canceled":true,
|
||||
"Command insertHorizontalRule, value \"quasit\": input event, canceled":true,
|
||||
"Command insertHorizontalRule, value \"quasit\": beforeinput event, uncanceled":true,
|
||||
"Command insertHorizontalRule, value \"quasit\": input event, uncanceled":true,
|
||||
"Command insertHorizontalRule, value \"id\": beforeinput event, canceled":true,
|
||||
"Command insertHorizontalRule, value \"id\": input event, canceled":true,
|
||||
"Command insertHorizontalRule, value \"id\": beforeinput event, uncanceled":true,
|
||||
"Command insertHorizontalRule, value \"id\": input event, uncanceled":true,
|
||||
"Command insertHTML, value \"\": execCommand() must not throw, canceled":true,
|
||||
"Command insertHTML, value \"\": beforeinput event, canceled":true,
|
||||
"Command insertHTML, value \"\": execCommand() must not throw, uncanceled":true,
|
||||
"Command insertHTML, value \"\": beforeinput event, uncanceled":true,
|
||||
"Command insertHTML, value \"\": input event, uncanceled":true,
|
||||
"Command insertHTML, value \"quasit\": beforeinput event, canceled":true,
|
||||
"Command insertHTML, value \"quasit\": input event, canceled":true,
|
||||
"Command insertHTML, value \"quasit\": beforeinput event, uncanceled":true,
|
||||
"Command insertHTML, value \"quasit\": input event, uncanceled":true,
|
||||
"Command insertHTML, value \"<b>hi</b>\": beforeinput event, canceled":true,
|
||||
"Command insertHTML, value \"<b>hi</b>\": input event, canceled":true,
|
||||
"Command insertHTML, value \"<b>hi</b>\": beforeinput event, uncanceled":true,
|
||||
"Command insertHTML, value \"<b>hi</b>\": input event, uncanceled":true,
|
||||
"Command insertImage, value \"\": execCommand() must not throw, canceled":true,
|
||||
"Command insertImage, value \"\": beforeinput event, canceled":true,
|
||||
"Command insertImage, value \"\": execCommand() must not throw, uncanceled":true,
|
||||
"Command insertImage, value \"\": beforeinput event, uncanceled":true,
|
||||
"Command insertImage, value \"\": input event, uncanceled":true,
|
||||
"Command insertImage, value \"quasit\": beforeinput event, canceled":true,
|
||||
"Command insertImage, value \"quasit\": input event, canceled":true,
|
||||
"Command insertImage, value \"quasit\": beforeinput event, uncanceled":true,
|
||||
"Command insertImage, value \"quasit\": input event, uncanceled":true,
|
||||
"Command insertImage, value \"http://example.com/some-image\": beforeinput event, canceled":true,
|
||||
"Command insertImage, value \"http://example.com/some-image\": input event, canceled":true,
|
||||
"Command insertImage, value \"http://example.com/some-image\": beforeinput event, uncanceled":true,
|
||||
"Command insertImage, value \"http://example.com/some-image\": input event, uncanceled":true,
|
||||
"Command insertLineBreak, value \"\": beforeinput event, canceled":true,
|
||||
"Command insertLineBreak, value \"\": beforeinput event, uncanceled":true,
|
||||
"Command insertLineBreak, value \"\": input event, uncanceled":true,
|
||||
"Command insertLineBreak, value \"quasit\": beforeinput event, canceled":true,
|
||||
"Command insertLineBreak, value \"quasit\": beforeinput event, uncanceled":true,
|
||||
"Command insertLineBreak, value \"quasit\": input event, uncanceled":true,
|
||||
"Command insertOrderedList, value \"\": beforeinput event, canceled":true,
|
||||
"Command insertOrderedList, value \"\": input event, canceled":true,
|
||||
"Command insertOrderedList, value \"\": beforeinput event, uncanceled":true,
|
||||
"Command insertOrderedList, value \"\": input event, uncanceled":true,
|
||||
"Command insertOrderedList, value \"quasit\": beforeinput event, canceled":true,
|
||||
"Command insertOrderedList, value \"quasit\": input event, canceled":true,
|
||||
"Command insertOrderedList, value \"quasit\": beforeinput event, uncanceled":true,
|
||||
"Command insertOrderedList, value \"quasit\": input event, uncanceled":true,
|
||||
"Command insertParagraph, value \"\": beforeinput event, canceled":true,
|
||||
"Command insertParagraph, value \"\": input event, canceled":true,
|
||||
"Command insertParagraph, value \"\": beforeinput event, uncanceled":true,
|
||||
"Command insertParagraph, value \"\": input event, uncanceled":true,
|
||||
"Command insertParagraph, value \"quasit\": beforeinput event, canceled":true,
|
||||
"Command insertParagraph, value \"quasit\": input event, canceled":true,
|
||||
"Command insertParagraph, value \"quasit\": beforeinput event, uncanceled":true,
|
||||
"Command insertParagraph, value \"quasit\": input event, uncanceled":true,
|
||||
"Command insertText, value \"\": beforeinput event, canceled":true,
|
||||
"Command insertText, value \"\": beforeinput event, uncanceled":true,
|
||||
"Command insertText, value \"\": input event, uncanceled":true,
|
||||
"Command insertText, value \"quasit\": beforeinput event, canceled":true,
|
||||
"Command insertText, value \"quasit\": beforeinput event, uncanceled":true,
|
||||
"Command insertText, value \"quasit\": input event, uncanceled":true,
|
||||
"Command insertText, value \"abc\": beforeinput event, canceled":true,
|
||||
"Command insertText, value \"abc\": beforeinput event, uncanceled":true,
|
||||
"Command insertText, value \"abc\": input event, uncanceled":true,
|
||||
"Command insertUnorderedList, value \"\": beforeinput event, canceled":true,
|
||||
"Command insertUnorderedList, value \"\": input event, canceled":true,
|
||||
"Command insertUnorderedList, value \"\": beforeinput event, uncanceled":true,
|
||||
"Command insertUnorderedList, value \"\": input event, uncanceled":true,
|
||||
"Command insertUnorderedList, value \"quasit\": beforeinput event, canceled":true,
|
||||
"Command insertUnorderedList, value \"quasit\": input event, canceled":true,
|
||||
"Command insertUnorderedList, value \"quasit\": beforeinput event, uncanceled":true,
|
||||
"Command insertUnorderedList, value \"quasit\": input event, uncanceled":true,
|
||||
"Command justifyCenter, value \"\": beforeinput event, canceled":true,
|
||||
"Command justifyCenter, value \"\": input event, canceled":true,
|
||||
"Command justifyCenter, value \"\": beforeinput event, uncanceled":true,
|
||||
"Command justifyCenter, value \"\": input event, uncanceled":true,
|
||||
"Command justifyCenter, value \"quasit\": beforeinput event, canceled":true,
|
||||
"Command justifyCenter, value \"quasit\": input event, canceled":true,
|
||||
"Command justifyCenter, value \"quasit\": beforeinput event, uncanceled":true,
|
||||
"Command justifyCenter, value \"quasit\": input event, uncanceled":true,
|
||||
"Command justifyFull, value \"\": beforeinput event, canceled":true,
|
||||
"Command justifyFull, value \"\": input event, canceled":true,
|
||||
"Command justifyFull, value \"\": beforeinput event, uncanceled":true,
|
||||
"Command justifyFull, value \"\": input event, uncanceled":true,
|
||||
"Command justifyFull, value \"quasit\": beforeinput event, canceled":true,
|
||||
"Command justifyFull, value \"quasit\": input event, canceled":true,
|
||||
"Command justifyFull, value \"quasit\": beforeinput event, uncanceled":true,
|
||||
"Command justifyFull, value \"quasit\": input event, uncanceled":true,
|
||||
"Command justifyLeft, value \"\": beforeinput event, canceled":true,
|
||||
"Command justifyLeft, value \"\": input event, canceled":true,
|
||||
"Command justifyLeft, value \"\": beforeinput event, uncanceled":true,
|
||||
"Command justifyLeft, value \"\": input event, uncanceled":true,
|
||||
"Command justifyLeft, value \"quasit\": beforeinput event, canceled":true,
|
||||
"Command justifyLeft, value \"quasit\": input event, canceled":true,
|
||||
"Command justifyLeft, value \"quasit\": beforeinput event, uncanceled":true,
|
||||
"Command justifyLeft, value \"quasit\": input event, uncanceled":true,
|
||||
"Command justifyRight, value \"\": beforeinput event, canceled":true,
|
||||
"Command justifyRight, value \"\": input event, canceled":true,
|
||||
"Command justifyRight, value \"\": beforeinput event, uncanceled":true,
|
||||
"Command justifyRight, value \"\": input event, uncanceled":true,
|
||||
"Command justifyRight, value \"quasit\": beforeinput event, canceled":true,
|
||||
"Command justifyRight, value \"quasit\": input event, canceled":true,
|
||||
"Command justifyRight, value \"quasit\": beforeinput event, uncanceled":true,
|
||||
"Command justifyRight, value \"quasit\": input event, uncanceled":true,
|
||||
"Command outdent, value \"\": beforeinput event, canceled":true,
|
||||
"Command outdent, value \"\": beforeinput event, uncanceled":true,
|
||||
"Command outdent, value \"\": input event, uncanceled":true,
|
||||
"Command outdent, value \"quasit\": beforeinput event, canceled":true,
|
||||
"Command outdent, value \"quasit\": beforeinput event, uncanceled":true,
|
||||
"Command outdent, value \"quasit\": input event, uncanceled":true,
|
||||
"Command redo, value \"\": beforeinput event, canceled":true,
|
||||
"Command redo, value \"\": input event, canceled":true,
|
||||
"Command redo, value \"\": beforeinput event, uncanceled":true,
|
||||
"Command redo, value \"\": input event, uncanceled":true,
|
||||
"Command redo, value \"quasit\": beforeinput event, canceled":true,
|
||||
"Command redo, value \"quasit\": input event, canceled":true,
|
||||
"Command redo, value \"quasit\": beforeinput event, uncanceled":true,
|
||||
"Command redo, value \"quasit\": input event, uncanceled":true,
|
||||
"Command undo, value \"\": beforeinput event, canceled":true,
|
||||
"Command undo, value \"\": input event, canceled":true,
|
||||
"Command undo, value \"\": beforeinput event, uncanceled":true,
|
||||
"Command undo, value \"\": input event, uncanceled":true,
|
||||
"Command undo, value \"quasit\": beforeinput event, canceled":true,
|
||||
"Command undo, value \"quasit\": input event, canceled":true,
|
||||
"Command undo, value \"quasit\": beforeinput event, uncanceled":true,
|
||||
"Command undo, value \"quasit\": input event, uncanceled":true
|
||||
}
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,22 @@
|
|||
DEPTH = ../../../../..
|
||||
|
||||
topsrcdir = @top_srcdir@
|
||||
srcdir = @srcdir@
|
||||
VPATH = @srcdir@
|
||||
relativesrcdir = dom/imptests/failures/editing/selecttest
|
||||
|
||||
DIRS = \
|
||||
$(NULL)
|
||||
|
||||
include $(DEPTH)/config/autoconf.mk
|
||||
include $(topsrcdir)/config/rules.mk
|
||||
|
||||
_TESTS = \
|
||||
test_addRange.html.json \
|
||||
test_Document-open.html.json \
|
||||
test_getSelection.html.json \
|
||||
test_interfaces.html.json \
|
||||
$(NULL)
|
||||
|
||||
libs:: $(_TESTS)
|
||||
$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/tests/$(relativesrcdir)
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"Selection must be replaced with a new object after document.open()":true
|
||||
}
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"getSelection() on HTML document with null defaultView must be null":true,
|
||||
"getSelection() on XML document with null defaultView must be null":true,
|
||||
"window.getSelection() instanceof Selection in an iframe immediately after appendChild":true,
|
||||
"document.getSelection() instanceof Selection in an iframe immediately after appendChild":true,
|
||||
"getSelection() on HTML document with null defaultView must be null inside an iframe onload":true
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"Selection interface: existence and properties of interface object":true,
|
||||
"Selection interface: existence and properties of interface prototype object":true,
|
||||
"Selection interface: existence and properties of interface prototype object's \"constructor\" property":true,
|
||||
"Selection interface: calling collapse() on getSelection() with too few arguments must throw TypeError":true,
|
||||
"Selection interface: calling extend() on getSelection() with too few arguments must throw TypeError":true,
|
||||
"Selection interface: calling selectAllChildren() on getSelection() with too few arguments must throw TypeError":true,
|
||||
"Selection interface: calling getRangeAt() on getSelection() with too few arguments must throw TypeError":true,
|
||||
"Selection interface: calling addRange() on getSelection() with too few arguments must throw TypeError":true,
|
||||
"Selection interface: calling removeRange() on getSelection() with too few arguments must throw TypeError":true
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
DEPTH = ../../../../../../../..
|
||||
|
||||
topsrcdir = @top_srcdir@
|
||||
srcdir = @srcdir@
|
||||
VPATH = @srcdir@
|
||||
relativesrcdir = dom/imptests/failures/webapps/DOMCore/tests/submissions/Opera
|
||||
|
||||
DIRS = \
|
||||
$(NULL)
|
||||
|
||||
include $(DEPTH)/config/autoconf.mk
|
||||
include $(topsrcdir)/config/rules.mk
|
||||
|
||||
_TESTS = \
|
||||
test_getElementsByClassName-10.xml.json \
|
||||
test_getElementsByClassName-11.xml.json \
|
||||
$(NULL)
|
||||
|
||||
libs:: $(_TESTS)
|
||||
$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/tests/$(relativesrcdir)
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"document.getElementsByClassName(): compound": true
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"document.getElementsByClassName(): \"tricky\" compound": true
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
DEPTH = ../../../../../../../..
|
||||
|
||||
topsrcdir = @top_srcdir@
|
||||
srcdir = @srcdir@
|
||||
VPATH = @srcdir@
|
||||
relativesrcdir = dom/imptests/failures/webapps/WebStorage/tests/submissions/Infraware
|
||||
|
||||
DIRS = \
|
||||
$(NULL)
|
||||
|
||||
include $(DEPTH)/config/autoconf.mk
|
||||
include $(topsrcdir)/config/rules.mk
|
||||
|
||||
_TESTS = \
|
||||
test_event_constructor.html.json \
|
||||
test_storage_local_security.html.json \
|
||||
$(NULL)
|
||||
|
||||
libs:: $(_TESTS)
|
||||
$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/tests/$(relativesrcdir)
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"storageeventinit test": true
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"storage local security test": true
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
DEPTH = ../../../../../../../..
|
||||
|
||||
topsrcdir = @top_srcdir@
|
||||
srcdir = @srcdir@
|
||||
VPATH = @srcdir@
|
||||
relativesrcdir = dom/imptests/failures/webapps/WebStorage/tests/submissions/Ms2ger
|
||||
|
||||
DIRS = \
|
||||
$(NULL)
|
||||
|
||||
include $(DEPTH)/config/autoconf.mk
|
||||
include $(topsrcdir)/config/rules.mk
|
||||
|
||||
_TESTS = \
|
||||
test_event_constructor_js.html.json \
|
||||
test_storage_local_in_js.html.json \
|
||||
test_storage_local_removeitem_js.html.json \
|
||||
test_storage_session_in_js.html.json \
|
||||
test_storage_session_removeitem_js.html.json \
|
||||
$(NULL)
|
||||
|
||||
libs:: $(_TESTS)
|
||||
$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/tests/$(relativesrcdir)
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"StorageEvent constructor and nulls": true
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"Web Storage 1": true
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"Web Storage 2": true,
|
||||
"Web Storage 3": true
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"Web Storage 1": true
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"Web Storage 2": true,
|
||||
"Web Storage 3": true
|
||||
}
|
|
@ -1,2 +0,0 @@
|
|||
{
|
||||
}
|
|
@ -55,8 +55,13 @@ def copy(thissrcdir, dest, directories):
|
|||
dirtocreate = dest
|
||||
|
||||
subdirs, mochitests, supportfiles = parseManifestFile(dest, d)
|
||||
sourcedir = "hg-%s/%s" % (dest, d)
|
||||
destdir = "%s/%s" % (dest, d)
|
||||
if d:
|
||||
sourcedir = "hg-%s/%s" % (dest, d)
|
||||
destdir = "%s/%s" % (dest, d)
|
||||
else:
|
||||
# Empty directory, i.e., the repository root
|
||||
sourcedir = "hg-%s" % dest
|
||||
destdir = dest
|
||||
os.makedirs(destdir)
|
||||
|
||||
for mochitest in mochitests:
|
||||
|
@ -65,7 +70,11 @@ def copy(thissrcdir, dest, directories):
|
|||
shutil.copy("%s/%s" % (sourcedir, support), "%s/%s" % (destdir, support))
|
||||
|
||||
if len(subdirs):
|
||||
importDirs(thissrcdir, dest, ["%s/%s" % (d, subdir) for subdir in subdirs])
|
||||
if d:
|
||||
importDirs(thissrcdir, dest, ["%s/%s" % (d, subdir) for subdir in subdirs])
|
||||
else:
|
||||
# Empty directory, i.e., the repository root
|
||||
importDirs(thissrcdir, dest, subdirs)
|
||||
|
||||
def printMakefile(dest, directories):
|
||||
"""Create a .mk file to be included into the main Makefile.in, which lists the
|
||||
|
@ -116,7 +125,11 @@ def printMakefiles(thissrcdir, dest, directories):
|
|||
"""
|
||||
print "Creating Makefile.ins..."
|
||||
for d in directories:
|
||||
path = "%s/%s" % (dest, d)
|
||||
if d:
|
||||
path = "%s/%s" % (dest, d)
|
||||
else:
|
||||
# Empty directory, i.e., the repository root
|
||||
path = dest
|
||||
abspath = "%s/%s" % (thissrcdir, path)
|
||||
print "Creating Makefile.in in %s..." % (path, )
|
||||
|
||||
|
|
|
@ -9,10 +9,8 @@ var W3CTest = {
|
|||
* names to either the boolean |true|, or the string "debug". The former
|
||||
* means that this test is expected to fail in all builds, and the latter
|
||||
* that it is only expected to fail in debug builds.
|
||||
*
|
||||
* This is filled in by the writeReporter.py script.
|
||||
*/
|
||||
"expectedFailures": ${expectations},
|
||||
"expectedFailures": {},
|
||||
|
||||
/**
|
||||
* List of test results, needed by TestRunner to update the UI.
|
||||
|
@ -68,14 +66,11 @@ var W3CTest = {
|
|||
/**
|
||||
* Returns true if this test is expected to fail, and false otherwise.
|
||||
*/
|
||||
"_todo": function(url, test) {
|
||||
if (!(url in this.expectedFailures)) {
|
||||
return false;
|
||||
}
|
||||
if (this.expectedFailures[url] === "all") {
|
||||
"_todo": function(test) {
|
||||
if (this.expectedFailures === "all") {
|
||||
return true;
|
||||
}
|
||||
var value = this.expectedFailures[url][test.name];
|
||||
var value = this.expectedFailures[test.name];
|
||||
return value === true || (value === "debug" && !!SpecialPowers.isDebugBuild);
|
||||
},
|
||||
|
||||
|
@ -86,9 +81,9 @@ var W3CTest = {
|
|||
"result": function(test) {
|
||||
var url = this.getURL();
|
||||
this.report({
|
||||
"message": test.message || test.name,
|
||||
"message": test.name + (test.message ? "; " + test.message : ""),
|
||||
"result": test.status === test.PASS,
|
||||
"todo": this._todo(url, test)
|
||||
"todo": this._todo(test)
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -99,7 +94,7 @@ var W3CTest = {
|
|||
"finish": function(tests, status) {
|
||||
var url = this.getURL();
|
||||
this.report({
|
||||
"message": "Finished test",
|
||||
"message": "Finished test, status " + status.status,
|
||||
"result": status.status === status.OK,
|
||||
"todo":
|
||||
url in this.expectedFailures &&
|
||||
|
@ -112,9 +107,21 @@ var W3CTest = {
|
|||
if (!W3CTest.runner) {
|
||||
return;
|
||||
}
|
||||
// Get expected fails. If there aren't any, there will be a 404, which is
|
||||
// fine. Anything else is unexpected.
|
||||
var request = new XMLHttpRequest();
|
||||
request.open("GET", "/tests/dom/imptests/failures/" + W3CTest.getURL() + ".json", false);
|
||||
request.send();
|
||||
if (request.status === 200) {
|
||||
W3CTest.expectedFailures = JSON.parse(request.responseText);
|
||||
} else if (request.status !== 404) {
|
||||
is(request.status, 404, "Request status neither 200 nor 404");
|
||||
}
|
||||
|
||||
add_result_callback(W3CTest.result.bind(W3CTest));
|
||||
add_completion_callback(W3CTest.finish.bind(W3CTest));
|
||||
setup({
|
||||
"output": false
|
||||
"output": false,
|
||||
"timeout": 1000000,
|
||||
});
|
||||
})();
|
|
@ -1,39 +0,0 @@
|
|||
{
|
||||
"webapps/DOMCore/tests/submissions/Opera/test_getElementsByClassName-10.xml": {
|
||||
"document.getElementsByClassName(): compound": true
|
||||
},
|
||||
|
||||
"webapps/DOMCore/tests/submissions/Opera/test_getElementsByClassName-11.xml": {
|
||||
"document.getElementsByClassName(): \"tricky\" compound": true
|
||||
},
|
||||
|
||||
"webapps/WebStorage/tests/submissions/Infraware/test_event_constructor.html": {
|
||||
"storageeventinit test": true
|
||||
},
|
||||
|
||||
"webapps/WebStorage/tests/submissions/Infraware/test_storage_local_security.html": {
|
||||
"storage local security test": true
|
||||
},
|
||||
|
||||
"webapps/WebStorage/tests/submissions/Ms2ger/test_event_constructor_js.html": {
|
||||
"StorageEvent constructor and nulls": true
|
||||
},
|
||||
|
||||
"webapps/WebStorage/tests/submissions/Ms2ger/test_storage_local_in_js.html": {
|
||||
"Web Storage 1": true
|
||||
},
|
||||
|
||||
"webapps/WebStorage/tests/submissions/Ms2ger/test_storage_local_removeitem_js.html": {
|
||||
"Web Storage 2": true,
|
||||
"Web Storage 3": true
|
||||
},
|
||||
|
||||
"webapps/WebStorage/tests/submissions/Ms2ger/test_storage_session_in_js.html": {
|
||||
"Web Storage 1": true
|
||||
},
|
||||
|
||||
"webapps/WebStorage/tests/submissions/Ms2ger/test_storage_session_removeitem_js.html": {
|
||||
"Web Storage 2": true,
|
||||
"Web Storage 3": true
|
||||
}
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
# You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
import os
|
||||
import sys
|
||||
import string
|
||||
|
||||
try:
|
||||
import json
|
||||
except ImportError:
|
||||
import simplejson as json
|
||||
|
||||
def writeReporter(src):
|
||||
src = src.replace("testharnessreport.js.in", "")
|
||||
expectations = {}
|
||||
fp = open(src + "failures.txt", "rb")
|
||||
for line in fp:
|
||||
gp = open(src + line.strip() + ".json")
|
||||
expectations.update(json.load(gp))
|
||||
gp.close()
|
||||
fp.close()
|
||||
fp = open(src + "testharnessreport.js.in", "rb")
|
||||
template = fp.read()
|
||||
fp.close()
|
||||
fp = open("testharnessreport.js", "wb")
|
||||
expjson = json.dumps(expectations,
|
||||
indent = 2,
|
||||
sort_keys = True,
|
||||
separators = (',', ': '))
|
||||
result = string.Template(template).substitute({ 'expectations': expjson })
|
||||
fp.write(result)
|
||||
fp.close()
|
||||
|
||||
if __name__ == "__main__":
|
||||
writeReporter(sys.argv[1])
|
|
@ -51,7 +51,7 @@
|
|||
* http://www.w3.org/TR/DOM-Level-2-Style
|
||||
*/
|
||||
|
||||
[builtinclass, scriptable, uuid(fc30df1b-9b5a-42f6-965b-cbcc67ac3c4c)]
|
||||
[builtinclass, scriptable, uuid(81085b2d-eea9-4aca-ac93-0b1eea6587d3)]
|
||||
interface nsIDOMCSS2Properties : nsISupports
|
||||
{
|
||||
attribute DOMString background;
|
||||
|
@ -475,6 +475,9 @@ interface nsIDOMCSS2Properties : nsISupports
|
|||
attribute DOMString textRendering;
|
||||
// raises(DOMException) on setting
|
||||
|
||||
attribute DOMString vectorEffect;
|
||||
// raises(DOMException) on setting
|
||||
|
||||
/* Non-DOM 2 extensions */
|
||||
|
||||
/* Mozilla extension CSS properties */
|
||||
|
|
|
@ -55,12 +55,13 @@ interface nsIEditActionListener;
|
|||
interface nsIInlineSpellChecker;
|
||||
interface nsITransferable;
|
||||
|
||||
[scriptable, uuid(7861fe14-9977-413f-a893-3e1000c40817)]
|
||||
[scriptable, uuid(7ad59e28-f3d5-4e14-8ea3-794ad4a86de3)]
|
||||
|
||||
interface nsIEditor : nsISupports
|
||||
{
|
||||
%{C++
|
||||
typedef short EDirection;
|
||||
typedef short EStripWrappers;
|
||||
%}
|
||||
const short eNone = 0;
|
||||
const short eNext = 1;
|
||||
|
@ -70,6 +71,9 @@ interface nsIEditor : nsISupports
|
|||
const short eToBeginningOfLine = 5;
|
||||
const short eToEndOfLine = 6;
|
||||
|
||||
const short eStrip = 0;
|
||||
const short eNoStrip = 1;
|
||||
|
||||
readonly attribute nsISelection selection;
|
||||
|
||||
/**
|
||||
|
@ -146,8 +150,12 @@ interface nsIEditor : nsISupports
|
|||
* DeleteSelection removes all nodes in the current selection.
|
||||
* @param aDir if eNext, delete to the right (for example, the DEL key)
|
||||
* if ePrevious, delete to the left (for example, the BACKSPACE key)
|
||||
* @param stripWrappers If eStrip, strip any empty inline elements left
|
||||
* behind after the deletion; if eNoStrip, don't. If in
|
||||
* doubt, pass eStrip -- eNoStrip is only for if you're
|
||||
* about to insert text or similar right after.
|
||||
*/
|
||||
void deleteSelection(in short action);
|
||||
void deleteSelection(in short action, in short stripWrappers);
|
||||
|
||||
|
||||
/* ------------ Document info and file methods -------------- */
|
||||
|
|
|
@ -7,6 +7,6 @@ load 430624-1.html
|
|||
load 459613.html
|
||||
load 475132-1.xhtml
|
||||
load 633709.xhtml
|
||||
asserts-if(!Android,6) load 636074-1.html # Bug 439258, charged to the wrong test due to bug 635550
|
||||
load 636074-1.html
|
||||
load 713427-1.html
|
||||
load 713427-2.xhtml
|
||||
|
|
|
@ -616,9 +616,10 @@ nsEditor::GetSelectionController(nsISelectionController **aSel)
|
|||
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsEditor::DeleteSelection(EDirection aAction)
|
||||
nsEditor::DeleteSelection(EDirection aAction, EStripWrappers aStripWrappers)
|
||||
{
|
||||
return DeleteSelectionImpl(aAction);
|
||||
MOZ_ASSERT(aStripWrappers == eStrip || aStripWrappers == eNoStrip);
|
||||
return DeleteSelectionImpl(aAction, aStripWrappers);
|
||||
}
|
||||
|
||||
|
||||
|
@ -634,6 +635,23 @@ nsEditor::GetSelection(nsISelection **aSelection)
|
|||
return selcon->GetSelection(nsISelectionController::SELECTION_NORMAL, aSelection); // does an addref
|
||||
}
|
||||
|
||||
nsTypedSelection*
|
||||
nsEditor::GetTypedSelection()
|
||||
{
|
||||
nsCOMPtr<nsISelection> sel;
|
||||
nsresult res = GetSelection(getter_AddRefs(sel));
|
||||
NS_ENSURE_SUCCESS(res, nsnull);
|
||||
|
||||
nsCOMPtr<nsISelectionPrivate> selPrivate = do_QueryInterface(sel);
|
||||
NS_ENSURE_TRUE(selPrivate, nsnull);
|
||||
|
||||
nsRefPtr<nsFrameSelection> frameSel;
|
||||
res = selPrivate->GetFrameSelection(getter_AddRefs(frameSel));
|
||||
NS_ENSURE_SUCCESS(res, nsnull);
|
||||
|
||||
return frameSel->GetSelection(nsISelectionController::SELECTION_NORMAL);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsEditor::DoTransaction(nsITransaction *aTxn)
|
||||
{
|
||||
|
@ -4313,8 +4331,11 @@ nsEditor::GetShouldTxnSetSelection()
|
|||
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsEditor::DeleteSelectionImpl(nsIEditor::EDirection aAction)
|
||||
nsEditor::DeleteSelectionImpl(EDirection aAction,
|
||||
EStripWrappers aStripWrappers)
|
||||
{
|
||||
MOZ_ASSERT(aStripWrappers == eStrip || aStripWrappers == eNoStrip);
|
||||
|
||||
nsCOMPtr<nsISelection>selection;
|
||||
nsresult res = GetSelection(getter_AddRefs(selection));
|
||||
NS_ENSURE_SUCCESS(res, res);
|
||||
|
@ -4440,7 +4461,7 @@ nsEditor::DeleteSelectionAndPrepareToCreateNode(nsCOMPtr<nsIDOMNode> &parentSele
|
|||
NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
|
||||
|
||||
if (!selection->Collapsed()) {
|
||||
result = DeleteSelection(nsIEditor::eNone);
|
||||
result = DeleteSelection(nsIEditor::eNone, nsIEditor::eStrip);
|
||||
if (NS_FAILED(result)) {
|
||||
return result;
|
||||
}
|
||||
|
@ -5099,7 +5120,7 @@ nsEditor::HandleKeyPressEvent(nsIDOMKeyEvent* aKeyEvent)
|
|||
nativeKeyEvent->IsMeta()) {
|
||||
return NS_OK;
|
||||
}
|
||||
DeleteSelection(nsIEditor::ePrevious);
|
||||
DeleteSelection(nsIEditor::ePrevious, nsIEditor::eStrip);
|
||||
aKeyEvent->PreventDefault(); // consumed
|
||||
return NS_OK;
|
||||
case nsIDOMKeyEvent::DOM_VK_DELETE:
|
||||
|
@ -5110,7 +5131,7 @@ nsEditor::HandleKeyPressEvent(nsIDOMKeyEvent* aKeyEvent)
|
|||
nativeKeyEvent->IsAlt() || nativeKeyEvent->IsMeta()) {
|
||||
return NS_OK;
|
||||
}
|
||||
DeleteSelection(nsIEditor::eNext);
|
||||
DeleteSelection(nsIEditor::eNext, nsIEditor::eStrip);
|
||||
aKeyEvent->PreventDefault(); // consumed
|
||||
return NS_OK;
|
||||
}
|
||||
|
|
|
@ -50,6 +50,8 @@
|
|||
#include "nsIAtom.h"
|
||||
#include "nsIDOMDocument.h"
|
||||
#include "nsISelection.h"
|
||||
#include "nsRange.h"
|
||||
#include "nsTypedSelection.h"
|
||||
#include "nsIDOMCharacterData.h"
|
||||
#include "nsIPrivateTextRange.h"
|
||||
#include "nsITransactionManager.h"
|
||||
|
@ -203,7 +205,8 @@ public:
|
|||
nsIDOMCharacterData *aTextNode,
|
||||
PRInt32 aOffset,
|
||||
bool aSuppressIME = false);
|
||||
NS_IMETHOD DeleteSelectionImpl(EDirection aAction);
|
||||
NS_IMETHOD DeleteSelectionImpl(EDirection aAction,
|
||||
EStripWrappers aStripWrappers);
|
||||
NS_IMETHOD DeleteSelectionAndCreateNode(const nsAString& aTag,
|
||||
nsIDOMNode ** aNewNode);
|
||||
|
||||
|
@ -620,6 +623,7 @@ public:
|
|||
#if DEBUG_JOE
|
||||
static void DumpNode(nsIDOMNode *aNode, PRInt32 indent=0);
|
||||
#endif
|
||||
nsTypedSelection* GetTypedSelection();
|
||||
|
||||
// Helpers to add a node to the selection.
|
||||
// Used by table cell selection methods
|
||||
|
|
|
@ -282,7 +282,7 @@ nsCutOrDeleteCommand::DoCommand(const char *aCommandName,
|
|||
nsCOMPtr<nsISelection> selection;
|
||||
nsresult rv = editor->GetSelection(getter_AddRefs(selection));
|
||||
if (NS_SUCCEEDED(rv) && selection && selection->Collapsed()) {
|
||||
return editor->DeleteSelection(nsIEditor::eNext);
|
||||
return editor->DeleteSelection(nsIEditor::eNext, nsIEditor::eStrip);
|
||||
}
|
||||
return editor->Cut();
|
||||
}
|
||||
|
@ -376,7 +376,7 @@ nsCopyOrDeleteCommand::DoCommand(const char *aCommandName,
|
|||
nsCOMPtr<nsISelection> selection;
|
||||
nsresult rv = editor->GetSelection(getter_AddRefs(selection));
|
||||
if (NS_SUCCEEDED(rv) && selection && selection->Collapsed()) {
|
||||
return editor->DeleteSelection(nsIEditor::eNextWord);
|
||||
return editor->DeleteSelection(nsIEditor::eNextWord, nsIEditor::eStrip);
|
||||
}
|
||||
return editor->Copy();
|
||||
}
|
||||
|
@ -619,7 +619,7 @@ nsDeleteCommand::DoCommand(const char *aCommandName, nsISupports *aCommandRefCon
|
|||
else if (!nsCRT::strcmp("cmd_deleteToEndOfLine",aCommandName))
|
||||
deleteDir = nsIEditor::eToEndOfLine;
|
||||
|
||||
return editor->DeleteSelection(deleteDir);
|
||||
return editor->DeleteSelection(deleteDir, nsIEditor::eStrip);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
|
|
|
@ -122,7 +122,7 @@ nsresult TextEditorTest::InitDoc()
|
|||
{
|
||||
nsresult result = mEditor->SelectAll();
|
||||
TEST_RESULT(result);
|
||||
result = mEditor->DeleteSelection(nsIEditor::eNext);
|
||||
result = mEditor->DeleteSelection(nsIEditor::eNext, nsIEditor::eStrip);
|
||||
TEST_RESULT(result);
|
||||
return result;
|
||||
}
|
||||
|
|
|
@ -245,6 +245,9 @@ nsHTMLEditor::DeleteRefToAnonymousNode(nsIDOMElement* aElement,
|
|||
if (document)
|
||||
docObserver->BeginUpdate(document, UPDATE_CONTENT_MODEL);
|
||||
|
||||
// XXX This is wrong (bug 439258). Once it's fixed, the NS_WARNING
|
||||
// in nsCSSFrameConstructor::RestyleForRemove should be changed back
|
||||
// to an assertion.
|
||||
docObserver->ContentRemoved(content->GetCurrentDoc(),
|
||||
aParentContent, content, -1,
|
||||
content->GetPreviousSibling());
|
||||
|
|
|
@ -201,7 +201,7 @@ NS_IMETHODIMP nsHTMLEditor::LoadHTML(const nsAString & aInputString)
|
|||
{
|
||||
// Delete Selection, but only if it isn't collapsed, see bug #106269
|
||||
if (!selection->Collapsed()) {
|
||||
rv = DeleteSelection(eNone);
|
||||
rv = DeleteSelection(eNone, eStrip);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
}
|
||||
|
||||
|
@ -283,21 +283,20 @@ nsHTMLEditor::DoInsertHTMLWithContext(const nsAString & aInputString,
|
|||
nsAutoRules beginRulesSniffing(this, kOpHTMLPaste, nsIEditor::eNext);
|
||||
|
||||
// Get selection
|
||||
nsCOMPtr<nsISelection>selection;
|
||||
nsresult rv = GetSelection(getter_AddRefs(selection));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
nsRefPtr<nsTypedSelection> selection = GetTypedSelection();
|
||||
NS_ENSURE_STATE(selection);
|
||||
|
||||
// create a dom document fragment that represents the structure to paste
|
||||
nsCOMPtr<nsIDOMNode> fragmentAsNode, streamStartParent, streamEndParent;
|
||||
PRInt32 streamStartOffset = 0, streamEndOffset = 0;
|
||||
|
||||
rv = CreateDOMFragmentFromPaste(aInputString, aContextStr, aInfoStr,
|
||||
address_of(fragmentAsNode),
|
||||
address_of(streamStartParent),
|
||||
address_of(streamEndParent),
|
||||
&streamStartOffset,
|
||||
&streamEndOffset,
|
||||
aTrustedInput);
|
||||
nsresult rv = CreateDOMFragmentFromPaste(aInputString, aContextStr, aInfoStr,
|
||||
address_of(fragmentAsNode),
|
||||
address_of(streamStartParent),
|
||||
address_of(streamEndParent),
|
||||
&streamStartOffset,
|
||||
&streamEndOffset,
|
||||
aTrustedInput);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
nsCOMPtr<nsIDOMNode> targetNode, tempNode;
|
||||
|
@ -346,7 +345,7 @@ nsHTMLEditor::DoInsertHTMLWithContext(const nsAString & aInputString,
|
|||
// Use an auto tracker so that our drop point is correctly
|
||||
// positioned after the delete.
|
||||
nsAutoTrackDOMPoint tracker(mRangeUpdater, &targetNode, &targetOffset);
|
||||
rv = DeleteSelection(eNone);
|
||||
rv = DeleteSelection(eNone, eStrip);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
}
|
||||
|
||||
|
@ -400,7 +399,10 @@ nsHTMLEditor::DoInsertHTMLWithContext(const nsAString & aInputString,
|
|||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
// pasting does not inherit local inline styles
|
||||
rv = RemoveAllInlineProperties();
|
||||
nsCOMPtr<nsIDOMNode> tmpNode =
|
||||
do_QueryInterface(selection->GetAnchorNode());
|
||||
PRInt32 tmpOffset = selection->GetAnchorOffset();
|
||||
rv = ClearStyle(address_of(tmpNode), &tmpOffset, nsnull, nsnull);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
}
|
||||
else
|
||||
|
|
|
@ -109,6 +109,22 @@ static bool IsInlineNode(nsIDOMNode* node)
|
|||
{
|
||||
return !IsBlockNode(node);
|
||||
}
|
||||
|
||||
static bool
|
||||
IsStyleCachePreservingAction(nsEditor::OperationID action)
|
||||
{
|
||||
return action == nsEditor::kOpDeleteSelection ||
|
||||
action == nsEditor::kOpInsertBreak ||
|
||||
action == nsEditor::kOpMakeList ||
|
||||
action == nsEditor::kOpIndent ||
|
||||
action == nsEditor::kOpOutdent ||
|
||||
action == nsEditor::kOpAlign ||
|
||||
action == nsEditor::kOpMakeBasicBlock ||
|
||||
action == nsEditor::kOpRemoveList ||
|
||||
action == nsEditor::kOpMakeDefListItem ||
|
||||
action == nsEditor::kOpInsertElement ||
|
||||
action == nsEditor::kOpInsertQuotation;
|
||||
}
|
||||
|
||||
class nsTableCellAndListItemFunctor : public nsBoolDomIterFunctor
|
||||
{
|
||||
|
@ -335,11 +351,10 @@ nsHTMLEditRules::BeforeEdit(nsEditor::OperationID action,
|
|||
}
|
||||
|
||||
// remember current inline styles for deletion and normal insertion operations
|
||||
if ((action == nsEditor::kOpInsertText) ||
|
||||
(action == nsEditor::kOpInsertIMEText) ||
|
||||
(action == nsEditor::kOpDeleteSelection) ||
|
||||
(action == nsEditor::kOpInsertBreak))
|
||||
{
|
||||
if (action == nsEditor::kOpInsertText ||
|
||||
action == nsEditor::kOpInsertIMEText ||
|
||||
action == nsEditor::kOpDeleteSelection ||
|
||||
IsStyleCachePreservingAction(action)) {
|
||||
nsCOMPtr<nsIDOMNode> selNode = selStartNode;
|
||||
if (aDirection == nsIEditor::eNext)
|
||||
selNode = selEndNode;
|
||||
|
@ -502,11 +517,10 @@ nsHTMLEditRules::AfterEditInner(nsEditor::OperationID action,
|
|||
}
|
||||
|
||||
// check for any styles which were removed inappropriately
|
||||
if ((action == nsEditor::kOpInsertText) ||
|
||||
(action == nsEditor::kOpInsertIMEText) ||
|
||||
(action == nsEditor::kOpDeleteSelection) ||
|
||||
(action == nsEditor::kOpInsertBreak))
|
||||
{
|
||||
if (action == nsEditor::kOpInsertText ||
|
||||
action == nsEditor::kOpInsertIMEText ||
|
||||
action == nsEditor::kOpDeleteSelection ||
|
||||
IsStyleCachePreservingAction(action)) {
|
||||
mHTMLEditor->mTypeInState->UpdateSelState(selection);
|
||||
res = ReapplyCachedStyles();
|
||||
NS_ENSURE_SUCCESS(res, res);
|
||||
|
@ -616,7 +630,8 @@ nsHTMLEditRules::WillDoAction(nsISelection *aSelection,
|
|||
case nsEditor::kOpInsertBreak:
|
||||
return WillInsertBreak(aSelection, aCancel, aHandled);
|
||||
case nsEditor::kOpDeleteSelection:
|
||||
return WillDeleteSelection(aSelection, info->collapsedAction, aCancel, aHandled);
|
||||
return WillDeleteSelection(aSelection, info->collapsedAction,
|
||||
info->stripWrappers, aCancel, aHandled);
|
||||
case nsEditor::kOpMakeList:
|
||||
return WillMakeList(aSelection, info->blockType, info->entireList, info->bulletType, aCancel, aHandled);
|
||||
case nsEditor::kOpIndent:
|
||||
|
@ -1256,21 +1271,22 @@ nsHTMLEditRules::WillInsert(nsISelection *aSelection, bool *aCancel)
|
|||
}
|
||||
}
|
||||
|
||||
// we need to get the doc
|
||||
nsCOMPtr<nsIDOMDocument> doc = mHTMLEditor->GetDOMDocument();
|
||||
NS_ENSURE_TRUE(doc, NS_ERROR_NOT_INITIALIZED);
|
||||
|
||||
// for every property that is set, insert a new inline style node
|
||||
return CreateStyleForInsertText(aSelection, doc);
|
||||
}
|
||||
if (mDidDeleteSelection &&
|
||||
(mTheAction == nsEditor::kOpInsertText ||
|
||||
mTheAction == nsEditor::kOpInsertIMEText ||
|
||||
mTheAction == nsEditor::kOpDeleteSelection)) {
|
||||
res = ReapplyCachedStyles();
|
||||
NS_ENSURE_SUCCESS(res, res);
|
||||
}
|
||||
// For most actions we want to clear the cached styles, but there are
|
||||
// exceptions
|
||||
if (!IsStyleCachePreservingAction(mTheAction)) {
|
||||
res = ClearCachedStyles();
|
||||
NS_ENSURE_SUCCESS(res, res);
|
||||
}
|
||||
|
||||
#ifdef XXX_DEAD_CODE
|
||||
nsresult
|
||||
nsHTMLEditRules::DidInsert(nsISelection *aSelection, nsresult aResult)
|
||||
{
|
||||
return nsTextEditRules::DidInsert(aSelection, aResult);
|
||||
}
|
||||
#endif
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsHTMLEditRules::WillInsertText(nsEditor::OperationID aAction,
|
||||
|
@ -1303,7 +1319,7 @@ nsHTMLEditRules::WillInsertText(nsEditor::OperationID aAction,
|
|||
|
||||
// if the selection isn't collapsed, delete it.
|
||||
if (!aSelection->Collapsed()) {
|
||||
res = mHTMLEditor->DeleteSelection(nsIEditor::eNone);
|
||||
res = mHTMLEditor->DeleteSelection(nsIEditor::eNone, nsIEditor::eStrip);
|
||||
NS_ENSURE_SUCCESS(res, res);
|
||||
}
|
||||
|
||||
|
@ -1312,6 +1328,14 @@ nsHTMLEditRules::WillInsertText(nsEditor::OperationID aAction,
|
|||
// initialize out param
|
||||
// we want to ignore result of WillInsert()
|
||||
*aCancel = false;
|
||||
|
||||
// we need to get the doc
|
||||
nsCOMPtr<nsIDOMDocument> doc = mHTMLEditor->GetDOMDocument();
|
||||
NS_ENSURE_TRUE(doc, NS_ERROR_NOT_INITIALIZED);
|
||||
|
||||
// for every property that is set, insert a new inline style node
|
||||
res = CreateStyleForInsertText(aSelection, doc);
|
||||
NS_ENSURE_SUCCESS(res, res);
|
||||
|
||||
// get the (collapsed) selection location
|
||||
res = mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(selNode), &selOffset);
|
||||
|
@ -1322,10 +1346,6 @@ nsHTMLEditRules::WillInsertText(nsEditor::OperationID aAction,
|
|||
!mHTMLEditor->CanContainTag(selNode, nsGkAtoms::textTagName)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
// we need to get the doc
|
||||
nsCOMPtr<nsIDOMDocument> doc = mHTMLEditor->GetDOMDocument();
|
||||
NS_ENSURE_TRUE(doc, NS_ERROR_NOT_INITIALIZED);
|
||||
|
||||
if (aAction == nsEditor::kOpInsertIMEText) {
|
||||
// Right now the nsWSRunObject code bails on empty strings, but IME needs
|
||||
|
@ -1509,7 +1529,7 @@ nsHTMLEditRules::WillInsertBreak(nsISelection* aSelection,
|
|||
// if the selection isn't collapsed, delete it.
|
||||
nsresult res = NS_OK;
|
||||
if (!aSelection->Collapsed()) {
|
||||
res = mHTMLEditor->DeleteSelection(nsIEditor::eNone);
|
||||
res = mHTMLEditor->DeleteSelection(nsIEditor::eNone, nsIEditor::eStrip);
|
||||
NS_ENSURE_SUCCESS(res, res);
|
||||
}
|
||||
|
||||
|
@ -1814,11 +1834,14 @@ nsHTMLEditRules::SplitMailCites(nsISelection *aSelection, bool aPlaintext, bool
|
|||
|
||||
|
||||
nsresult
|
||||
nsHTMLEditRules::WillDeleteSelection(nsISelection *aSelection,
|
||||
nsIEditor::EDirection aAction,
|
||||
bool *aCancel,
|
||||
bool *aHandled)
|
||||
nsHTMLEditRules::WillDeleteSelection(nsISelection* aSelection,
|
||||
nsIEditor::EDirection aAction,
|
||||
nsIEditor::EStripWrappers aStripWrappers,
|
||||
bool* aCancel,
|
||||
bool* aHandled)
|
||||
{
|
||||
MOZ_ASSERT(aStripWrappers == nsIEditor::eStrip ||
|
||||
aStripWrappers == nsIEditor::eNoStrip);
|
||||
|
||||
if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; }
|
||||
// initialize out param
|
||||
|
@ -1977,7 +2000,8 @@ nsHTMLEditRules::WillDeleteSelection(nsISelection *aSelection,
|
|||
{
|
||||
res = mHTMLEditor->DeleteNode(visNode);
|
||||
NS_ENSURE_SUCCESS(res, res);
|
||||
return WillDeleteSelection(aSelection, aAction, aCancel, aHandled);
|
||||
return WillDeleteSelection(aSelection, aAction, aStripWrappers,
|
||||
aCancel, aHandled);
|
||||
}
|
||||
|
||||
// special handling for backspace when positioned after <hr>
|
||||
|
@ -2281,14 +2305,17 @@ nsHTMLEditRules::WillDeleteSelection(nsISelection *aSelection,
|
|||
}
|
||||
|
||||
{
|
||||
// track end location of where we are deleting
|
||||
nsAutoTrackDOMPoint tracker(mHTMLEditor->mRangeUpdater, address_of(endNode), &endOffset);
|
||||
// track location of where we are deleting
|
||||
nsAutoTrackDOMPoint startTracker(mHTMLEditor->mRangeUpdater,
|
||||
address_of(startNode), &startOffset);
|
||||
nsAutoTrackDOMPoint endTracker(mHTMLEditor->mRangeUpdater,
|
||||
address_of(endNode), &endOffset);
|
||||
// we are handling all ranged deletions directly now.
|
||||
*aHandled = true;
|
||||
|
||||
if (endNode == startNode)
|
||||
{
|
||||
res = mHTMLEditor->DeleteSelectionImpl(aAction);
|
||||
res = mHTMLEditor->DeleteSelectionImpl(aAction, aStripWrappers);
|
||||
NS_ENSURE_SUCCESS(res, res);
|
||||
}
|
||||
else
|
||||
|
@ -2328,7 +2355,7 @@ nsHTMLEditRules::WillDeleteSelection(nsISelection *aSelection,
|
|||
// are endpoint block parents the same? use default deletion
|
||||
if (leftParent == rightParent)
|
||||
{
|
||||
res = mHTMLEditor->DeleteSelectionImpl(aAction);
|
||||
res = mHTMLEditor->DeleteSelectionImpl(aAction, aStripWrappers);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -2349,7 +2376,7 @@ nsHTMLEditRules::WillDeleteSelection(nsISelection *aSelection,
|
|||
if (nsHTMLEditUtils::IsParagraph(leftParent))
|
||||
{
|
||||
// first delete the selection
|
||||
res = mHTMLEditor->DeleteSelectionImpl(aAction);
|
||||
res = mHTMLEditor->DeleteSelectionImpl(aAction, aStripWrappers);
|
||||
NS_ENSURE_SUCCESS(res, res);
|
||||
// then join para's, insert break
|
||||
res = mHTMLEditor->JoinNodeDeep(leftParent,rightParent,address_of(selNode),&selOffset);
|
||||
|
@ -2362,7 +2389,7 @@ nsHTMLEditRules::WillDeleteSelection(nsISelection *aSelection,
|
|||
|| nsHTMLEditUtils::IsHeader(leftParent))
|
||||
{
|
||||
// first delete the selection
|
||||
res = mHTMLEditor->DeleteSelectionImpl(aAction);
|
||||
res = mHTMLEditor->DeleteSelectionImpl(aAction, aStripWrappers);
|
||||
NS_ENSURE_SUCCESS(res, res);
|
||||
// join blocks
|
||||
res = mHTMLEditor->JoinNodeDeep(leftParent,rightParent,address_of(selNode),&selOffset);
|
||||
|
@ -2912,15 +2939,18 @@ nsHTMLEditRules::DidDeleteSelection(nsISelection *aSelection,
|
|||
}
|
||||
|
||||
nsresult
|
||||
nsHTMLEditRules::WillMakeList(nsISelection *aSelection,
|
||||
const nsAString *aListType,
|
||||
nsHTMLEditRules::WillMakeList(nsISelection* aSelection,
|
||||
const nsAString* aListType,
|
||||
bool aEntireList,
|
||||
const nsAString *aBulletType,
|
||||
bool *aCancel,
|
||||
bool *aHandled,
|
||||
const nsAString *aItemType)
|
||||
const nsAString* aBulletType,
|
||||
bool* aCancel,
|
||||
bool* aHandled,
|
||||
const nsAString* aItemType)
|
||||
{
|
||||
if (!aSelection || !aListType || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; }
|
||||
if (!aSelection || !aListType || !aCancel || !aHandled) {
|
||||
return NS_ERROR_NULL_POINTER;
|
||||
}
|
||||
nsCOMPtr<nsIAtom> listTypeAtom = do_GetAtom(*aListType);
|
||||
|
||||
nsresult res = WillInsert(aSelection, aCancel);
|
||||
NS_ENSURE_SUCCESS(res, res);
|
||||
|
@ -2932,160 +2962,148 @@ nsHTMLEditRules::WillMakeList(nsISelection *aSelection,
|
|||
|
||||
// deduce what tag to use for list items
|
||||
nsAutoString itemType;
|
||||
if (aItemType)
|
||||
if (aItemType) {
|
||||
itemType = *aItemType;
|
||||
else if (aListType->LowerCaseEqualsLiteral("dl"))
|
||||
} else if (listTypeAtom == nsGkAtoms::dl) {
|
||||
itemType.AssignLiteral("dd");
|
||||
else
|
||||
} else {
|
||||
itemType.AssignLiteral("li");
|
||||
|
||||
}
|
||||
|
||||
// convert the selection ranges into "promoted" selection ranges:
|
||||
// this basically just expands the range to include the immediate
|
||||
// block parent, and then further expands to include any ancestors
|
||||
// whose children are all in the range
|
||||
|
||||
|
||||
*aHandled = true;
|
||||
|
||||
res = NormalizeSelection(aSelection);
|
||||
NS_ENSURE_SUCCESS(res, res);
|
||||
nsAutoSelectionReset selectionResetter(aSelection, mHTMLEditor);
|
||||
|
||||
|
||||
nsCOMArray<nsIDOMNode> arrayOfNodes;
|
||||
res = GetListActionNodes(arrayOfNodes, aEntireList);
|
||||
NS_ENSURE_SUCCESS(res, res);
|
||||
|
||||
|
||||
PRInt32 listCount = arrayOfNodes.Count();
|
||||
|
||||
|
||||
// check if all our nodes are <br>s, or empty inlines
|
||||
bool bOnlyBreaks = true;
|
||||
PRInt32 j;
|
||||
for (j=0; j<listCount; j++)
|
||||
{
|
||||
for (PRInt32 j = 0; j < listCount; j++) {
|
||||
nsIDOMNode* curNode = arrayOfNodes[j];
|
||||
// if curNode is not a Break or empty inline, we're done
|
||||
if ( (!nsTextEditUtils::IsBreak(curNode)) && (!IsEmptyInline(curNode)) )
|
||||
{
|
||||
if (!nsTextEditUtils::IsBreak(curNode) && !IsEmptyInline(curNode)) {
|
||||
bOnlyBreaks = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// if no nodes, we make empty list. Ditto if the user tried to make a list of some # of breaks.
|
||||
if (!listCount || bOnlyBreaks)
|
||||
{
|
||||
|
||||
// if no nodes, we make empty list. Ditto if the user tried to make a list
|
||||
// of some # of breaks.
|
||||
if (!listCount || bOnlyBreaks) {
|
||||
nsCOMPtr<nsIDOMNode> parent, theList, theListItem;
|
||||
PRInt32 offset;
|
||||
|
||||
// if only breaks, delete them
|
||||
if (bOnlyBreaks)
|
||||
{
|
||||
for (j=0; j<(PRInt32)listCount; j++)
|
||||
{
|
||||
if (bOnlyBreaks) {
|
||||
for (PRInt32 j = 0; j < (PRInt32)listCount; j++) {
|
||||
res = mHTMLEditor->DeleteNode(arrayOfNodes[j]);
|
||||
NS_ENSURE_SUCCESS(res, res);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// get selection location
|
||||
res = mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(parent), &offset);
|
||||
res = mHTMLEditor->GetStartNodeAndOffset(aSelection,
|
||||
getter_AddRefs(parent), &offset);
|
||||
NS_ENSURE_SUCCESS(res, res);
|
||||
|
||||
|
||||
// make sure we can put a list here
|
||||
nsCOMPtr<nsIAtom> listTypeAtom = do_GetAtom(*aListType);
|
||||
if (!mHTMLEditor->CanContainTag(parent, listTypeAtom)) {
|
||||
*aCancel = true;
|
||||
return NS_OK;
|
||||
}
|
||||
res = SplitAsNeeded(aListType, address_of(parent), &offset);
|
||||
NS_ENSURE_SUCCESS(res, res);
|
||||
res = mHTMLEditor->CreateNode(*aListType, parent, offset, getter_AddRefs(theList));
|
||||
res = mHTMLEditor->CreateNode(*aListType, parent, offset,
|
||||
getter_AddRefs(theList));
|
||||
NS_ENSURE_SUCCESS(res, res);
|
||||
res = mHTMLEditor->CreateNode(itemType, theList, 0, getter_AddRefs(theListItem));
|
||||
res = mHTMLEditor->CreateNode(itemType, theList, 0,
|
||||
getter_AddRefs(theListItem));
|
||||
NS_ENSURE_SUCCESS(res, res);
|
||||
// remember our new block for postprocessing
|
||||
mNewBlock = theListItem;
|
||||
// put selection in new list item
|
||||
res = aSelection->Collapse(theListItem,0);
|
||||
selectionResetter.Abort(); // to prevent selection reseter from overriding us.
|
||||
res = aSelection->Collapse(theListItem, 0);
|
||||
// to prevent selection resetter from overriding us
|
||||
selectionResetter.Abort();
|
||||
*aHandled = true;
|
||||
return res;
|
||||
}
|
||||
|
||||
// if there is only one node in the array, and it is a list, div, or blockquote,
|
||||
// then look inside of it until we find inner list or content.
|
||||
// if there is only one node in the array, and it is a list, div, or
|
||||
// blockquote, then look inside of it until we find inner list or content.
|
||||
|
||||
res = LookInsideDivBQandList(arrayOfNodes);
|
||||
NS_ENSURE_SUCCESS(res, res);
|
||||
NS_ENSURE_SUCCESS(res, res);
|
||||
|
||||
// Ok, now go through all the nodes and put then in the list,
|
||||
// Ok, now go through all the nodes and put then in the list,
|
||||
// or whatever is approriate. Wohoo!
|
||||
|
||||
listCount = arrayOfNodes.Count();
|
||||
nsCOMPtr<nsIDOMNode> curParent;
|
||||
nsCOMPtr<nsIDOMNode> curList;
|
||||
nsCOMPtr<nsIDOMNode> prevListItem;
|
||||
|
||||
PRInt32 i;
|
||||
for (i=0; i<listCount; i++)
|
||||
{
|
||||
|
||||
for (PRInt32 i = 0; i < listCount; i++) {
|
||||
// here's where we actually figure out what to do
|
||||
nsCOMPtr<nsIDOMNode> newBlock;
|
||||
nsCOMPtr<nsIDOMNode> curNode = arrayOfNodes[i];
|
||||
PRInt32 offset;
|
||||
res = nsEditor::GetNodeLocation(curNode, address_of(curParent), &offset);
|
||||
NS_ENSURE_SUCCESS(res, res);
|
||||
|
||||
// make sure we don't assemble content that is in different table cells into the same list.
|
||||
// respect table cell boundaries when listifying.
|
||||
if (curList)
|
||||
{
|
||||
|
||||
// make sure we don't assemble content that is in different table cells
|
||||
// into the same list. respect table cell boundaries when listifying.
|
||||
if (curList) {
|
||||
bool bInDifTblElems;
|
||||
res = InDifferentTableElements(curList, curNode, &bInDifTblElems);
|
||||
NS_ENSURE_SUCCESS(res, res);
|
||||
if (bInDifTblElems)
|
||||
if (bInDifTblElems) {
|
||||
curList = nsnull;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// if curNode is a Break, delete it, and quit remembering prev list item
|
||||
if (nsTextEditUtils::IsBreak(curNode))
|
||||
{
|
||||
if (nsTextEditUtils::IsBreak(curNode)) {
|
||||
res = mHTMLEditor->DeleteNode(curNode);
|
||||
NS_ENSURE_SUCCESS(res, res);
|
||||
prevListItem = 0;
|
||||
continue;
|
||||
}
|
||||
// if curNode is an empty inline container, delete it
|
||||
else if (IsEmptyInline(curNode))
|
||||
{
|
||||
} else if (IsEmptyInline(curNode)) {
|
||||
// if curNode is an empty inline container, delete it
|
||||
res = mHTMLEditor->DeleteNode(curNode);
|
||||
NS_ENSURE_SUCCESS(res, res);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (nsHTMLEditUtils::IsList(curNode))
|
||||
{
|
||||
nsAutoString existingListStr;
|
||||
res = mHTMLEditor->GetTagString(curNode, existingListStr);
|
||||
ToLowerCase(existingListStr);
|
||||
|
||||
if (nsHTMLEditUtils::IsList(curNode)) {
|
||||
// do we have a curList already?
|
||||
if (curList && !nsEditorUtils::IsDescendantOf(curNode, curList))
|
||||
{
|
||||
// move all of our children into curList.
|
||||
// cheezy way to do it: move whole list and then
|
||||
// RemoveContainer() on the list.
|
||||
// ConvertListType first: that routine
|
||||
// handles converting the list item types, if needed
|
||||
if (curList && !nsEditorUtils::IsDescendantOf(curNode, curList)) {
|
||||
// move all of our children into curList. cheezy way to do it: move
|
||||
// whole list and then RemoveContainer() on the list. ConvertListType
|
||||
// first: that routine handles converting the list item types, if
|
||||
// needed
|
||||
res = mHTMLEditor->MoveNode(curNode, curList, -1);
|
||||
NS_ENSURE_SUCCESS(res, res);
|
||||
res = ConvertListType(curNode, address_of(newBlock), *aListType, itemType);
|
||||
res = ConvertListType(curNode, address_of(newBlock),
|
||||
*aListType, itemType);
|
||||
NS_ENSURE_SUCCESS(res, res);
|
||||
res = mHTMLEditor->RemoveBlockContainer(newBlock);
|
||||
NS_ENSURE_SUCCESS(res, res);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
// replace list with new list type
|
||||
res = ConvertListType(curNode, address_of(newBlock), *aListType, itemType);
|
||||
res = ConvertListType(curNode, address_of(newBlock),
|
||||
*aListType, itemType);
|
||||
NS_ENSURE_SUCCESS(res, res);
|
||||
curList = newBlock;
|
||||
}
|
||||
|
@ -3093,55 +3111,45 @@ nsHTMLEditRules::WillMakeList(nsISelection *aSelection,
|
|||
continue;
|
||||
}
|
||||
|
||||
if (nsHTMLEditUtils::IsListItem(curNode))
|
||||
{
|
||||
nsAutoString existingListStr;
|
||||
res = mHTMLEditor->GetTagString(curParent, existingListStr);
|
||||
ToLowerCase(existingListStr);
|
||||
if ( existingListStr != *aListType )
|
||||
{
|
||||
// list item is in wrong type of list.
|
||||
// if we don't have a curList, split the old list
|
||||
// and make a new list of correct type.
|
||||
if (!curList || nsEditorUtils::IsDescendantOf(curNode, curList))
|
||||
{
|
||||
res = mHTMLEditor->SplitNode(curParent, offset, getter_AddRefs(newBlock));
|
||||
if (nsHTMLEditUtils::IsListItem(curNode)) {
|
||||
if (mHTMLEditor->GetTag(curParent) != listTypeAtom) {
|
||||
// list item is in wrong type of list. if we don't have a curList,
|
||||
// split the old list and make a new list of correct type.
|
||||
if (!curList || nsEditorUtils::IsDescendantOf(curNode, curList)) {
|
||||
res = mHTMLEditor->SplitNode(curParent, offset,
|
||||
getter_AddRefs(newBlock));
|
||||
NS_ENSURE_SUCCESS(res, res);
|
||||
nsCOMPtr<nsIDOMNode> p;
|
||||
PRInt32 o;
|
||||
res = nsEditor::GetNodeLocation(curParent, address_of(p), &o);
|
||||
nsCOMPtr<nsIDOMNode> parent;
|
||||
PRInt32 offset;
|
||||
res = nsEditor::GetNodeLocation(curParent, address_of(parent),
|
||||
&offset);
|
||||
NS_ENSURE_SUCCESS(res, res);
|
||||
res = mHTMLEditor->CreateNode(*aListType, p, o, getter_AddRefs(curList));
|
||||
res = mHTMLEditor->CreateNode(*aListType, parent, offset,
|
||||
getter_AddRefs(curList));
|
||||
NS_ENSURE_SUCCESS(res, res);
|
||||
}
|
||||
// move list item to new list
|
||||
res = mHTMLEditor->MoveNode(curNode, curList, -1);
|
||||
NS_ENSURE_SUCCESS(res, res);
|
||||
// convert list item type if needed
|
||||
if (!mHTMLEditor->NodeIsTypeString(curNode,itemType))
|
||||
{
|
||||
res = mHTMLEditor->ReplaceContainer(curNode, address_of(newBlock), itemType);
|
||||
if (!mHTMLEditor->NodeIsTypeString(curNode, itemType)) {
|
||||
res = mHTMLEditor->ReplaceContainer(curNode, address_of(newBlock),
|
||||
itemType);
|
||||
NS_ENSURE_SUCCESS(res, res);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
// item is in right type of list. But we might still have to move it.
|
||||
// and we might need to convert list item types.
|
||||
if (!curList)
|
||||
if (!curList) {
|
||||
curList = curParent;
|
||||
else
|
||||
{
|
||||
if (curParent != curList)
|
||||
{
|
||||
// move list item to new list
|
||||
res = mHTMLEditor->MoveNode(curNode, curList, -1);
|
||||
NS_ENSURE_SUCCESS(res, res);
|
||||
}
|
||||
} else if (curParent != curList) {
|
||||
// move list item to new list
|
||||
res = mHTMLEditor->MoveNode(curNode, curList, -1);
|
||||
NS_ENSURE_SUCCESS(res, res);
|
||||
}
|
||||
if (!mHTMLEditor->NodeIsTypeString(curNode,itemType))
|
||||
{
|
||||
res = mHTMLEditor->ReplaceContainer(curNode, address_of(newBlock), itemType);
|
||||
if (!mHTMLEditor->NodeIsTypeString(curNode, itemType)) {
|
||||
res = mHTMLEditor->ReplaceContainer(curNode, address_of(newBlock),
|
||||
itemType);
|
||||
NS_ENSURE_SUCCESS(res, res);
|
||||
}
|
||||
}
|
||||
|
@ -3149,20 +3157,18 @@ nsHTMLEditRules::WillMakeList(nsISelection *aSelection,
|
|||
NS_NAMED_LITERAL_STRING(typestr, "type");
|
||||
if (aBulletType && !aBulletType->IsEmpty()) {
|
||||
res = mHTMLEditor->SetAttribute(curElement, typestr, *aBulletType);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
res = mHTMLEditor->RemoveAttribute(curElement, typestr);
|
||||
}
|
||||
NS_ENSURE_SUCCESS(res, res);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
// if we hit a div clear our prevListItem, insert divs contents
|
||||
// into our node array, and remove the div
|
||||
if (nsHTMLEditUtils::IsDiv(curNode))
|
||||
{
|
||||
if (nsHTMLEditUtils::IsDiv(curNode)) {
|
||||
prevListItem = nsnull;
|
||||
PRInt32 j=i+1;
|
||||
PRInt32 j = i + 1;
|
||||
res = GetInnerContent(curNode, arrayOfNodes, &j);
|
||||
NS_ENSURE_SUCCESS(res, res);
|
||||
res = mHTMLEditor->RemoveContainer(curNode);
|
||||
|
@ -3170,57 +3176,52 @@ nsHTMLEditRules::WillMakeList(nsISelection *aSelection,
|
|||
listCount = arrayOfNodes.Count();
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
// need to make a list to put things in if we haven't already,
|
||||
if (!curList)
|
||||
{
|
||||
if (!curList) {
|
||||
res = SplitAsNeeded(aListType, address_of(curParent), &offset);
|
||||
NS_ENSURE_SUCCESS(res, res);
|
||||
res = mHTMLEditor->CreateNode(*aListType, curParent, offset, getter_AddRefs(curList));
|
||||
res = mHTMLEditor->CreateNode(*aListType, curParent, offset,
|
||||
getter_AddRefs(curList));
|
||||
NS_ENSURE_SUCCESS(res, res);
|
||||
// remember our new block for postprocessing
|
||||
mNewBlock = curList;
|
||||
// curList is now the correct thing to put curNode in
|
||||
prevListItem = 0;
|
||||
}
|
||||
|
||||
|
||||
// if curNode isn't a list item, we must wrap it in one
|
||||
nsCOMPtr<nsIDOMNode> listItem;
|
||||
if (!nsHTMLEditUtils::IsListItem(curNode))
|
||||
{
|
||||
if (IsInlineNode(curNode) && prevListItem)
|
||||
{
|
||||
if (!nsHTMLEditUtils::IsListItem(curNode)) {
|
||||
if (IsInlineNode(curNode) && prevListItem) {
|
||||
// this is a continuation of some inline nodes that belong together in
|
||||
// the same list item. use prevListItem
|
||||
res = mHTMLEditor->MoveNode(curNode, prevListItem, -1);
|
||||
NS_ENSURE_SUCCESS(res, res);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
// don't wrap li around a paragraph. instead replace paragraph with li
|
||||
if (nsHTMLEditUtils::IsParagraph(curNode))
|
||||
{
|
||||
res = mHTMLEditor->ReplaceContainer(curNode, address_of(listItem), itemType);
|
||||
}
|
||||
else
|
||||
{
|
||||
res = mHTMLEditor->InsertContainerAbove(curNode, address_of(listItem), itemType);
|
||||
if (nsHTMLEditUtils::IsParagraph(curNode)) {
|
||||
res = mHTMLEditor->ReplaceContainer(curNode, address_of(listItem),
|
||||
itemType);
|
||||
} else {
|
||||
res = mHTMLEditor->InsertContainerAbove(curNode,
|
||||
address_of(listItem),
|
||||
itemType);
|
||||
}
|
||||
NS_ENSURE_SUCCESS(res, res);
|
||||
if (IsInlineNode(curNode))
|
||||
if (IsInlineNode(curNode)) {
|
||||
prevListItem = listItem;
|
||||
else
|
||||
} else {
|
||||
prevListItem = nsnull;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
listItem = curNode;
|
||||
}
|
||||
|
||||
if (listItem) // if we made a new list item, deal with it
|
||||
{
|
||||
// tuck the listItem into the end of the active list
|
||||
|
||||
if (listItem) {
|
||||
// if we made a new list item, deal with it: tuck the listItem into the
|
||||
// end of the active list
|
||||
res = mHTMLEditor->MoveNode(listItem, curList, -1);
|
||||
NS_ENSURE_SUCCESS(res, res);
|
||||
}
|
||||
|
@ -4321,152 +4322,81 @@ nsHTMLEditRules::ConvertListType(nsIDOMNode *aList,
|
|||
///////////////////////////////////////////////////////////////////////////
|
||||
// CreateStyleForInsertText: take care of clearing and setting appropriate
|
||||
// style nodes for text insertion.
|
||||
//
|
||||
//
|
||||
nsresult
|
||||
nsHTMLEditRules::CreateStyleForInsertText(nsISelection *aSelection, nsIDOMDocument *aDoc)
|
||||
//
|
||||
//
|
||||
nsresult
|
||||
nsHTMLEditRules::CreateStyleForInsertText(nsISelection *aSelection,
|
||||
nsIDOMDocument *aDoc)
|
||||
{
|
||||
NS_ENSURE_TRUE(aSelection && aDoc, NS_ERROR_NULL_POINTER);
|
||||
NS_ENSURE_TRUE(mHTMLEditor->mTypeInState, NS_ERROR_NULL_POINTER);
|
||||
|
||||
bool weDidSometing = false;
|
||||
MOZ_ASSERT(aSelection && aDoc && mHTMLEditor->mTypeInState);
|
||||
|
||||
bool weDidSomething = false;
|
||||
nsCOMPtr<nsIDOMNode> node, tmp;
|
||||
PRInt32 offset;
|
||||
nsresult res = mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(node), &offset);
|
||||
NS_ENSURE_SUCCESS(res, res);
|
||||
|
||||
// if we deleted selection then also for cached styles
|
||||
if (mDidDeleteSelection &&
|
||||
((mTheAction == nsEditor::kOpInsertText ) ||
|
||||
(mTheAction == nsEditor::kOpInsertIMEText) ||
|
||||
(mTheAction == nsEditor::kOpInsertBreak) ||
|
||||
(mTheAction == nsEditor::kOpDeleteSelection)))
|
||||
{
|
||||
res = ReapplyCachedStyles();
|
||||
NS_ENSURE_SUCCESS(res, res);
|
||||
}
|
||||
// either way we clear the cached styles array
|
||||
res = ClearCachedStyles();
|
||||
nsresult res = mHTMLEditor->GetStartNodeAndOffset(aSelection,
|
||||
getter_AddRefs(node),
|
||||
&offset);
|
||||
NS_ENSURE_SUCCESS(res, res);
|
||||
|
||||
// next examine our present style and make sure default styles are either present or
|
||||
// explicitly overridden. If neither, add the default style to the TypeInState
|
||||
PRInt32 j, defcon = mHTMLEditor->mDefaultStyles.Length();
|
||||
for (j=0; j<defcon; j++)
|
||||
{
|
||||
PropItem *propItem = mHTMLEditor->mDefaultStyles[j];
|
||||
NS_ENSURE_TRUE(propItem, NS_ERROR_NULL_POINTER);
|
||||
// next examine our present style and make sure default styles are either
|
||||
// present or explicitly overridden. If neither, add the default style to
|
||||
// the TypeInState
|
||||
PRInt32 length = mHTMLEditor->mDefaultStyles.Length();
|
||||
for (PRInt32 j = 0; j < length; j++) {
|
||||
PropItem* propItem = mHTMLEditor->mDefaultStyles[j];
|
||||
MOZ_ASSERT(propItem);
|
||||
bool bFirst, bAny, bAll;
|
||||
|
||||
// GetInlineProperty also examine TypeInState. The only gotcha here is that a cleared
|
||||
// property looks like an unset property. For now I'm assuming that's not a problem:
|
||||
// that default styles will always be multivalue styles (like font face or size) where
|
||||
// clearing the style means we want to go back to the default. If we ever wanted a
|
||||
// "toggle" style like bold for a default, though, I'll have to add code to detect the
|
||||
// difference between unset and explicitly cleared, else user would never be able to
|
||||
// unbold, for instance.
|
||||
// GetInlineProperty also examine TypeInState. The only gotcha here is
|
||||
// that a cleared property looks like an unset property. For now I'm
|
||||
// assuming that's not a problem: that default styles will always be
|
||||
// multivalue styles (like font face or size) where clearing the style
|
||||
// means we want to go back to the default. If we ever wanted a "toggle"
|
||||
// style like bold for a default, though, I'll have to add code to detect
|
||||
// the difference between unset and explicitly cleared, else user would
|
||||
// never be able to unbold, for instance.
|
||||
nsAutoString curValue;
|
||||
res = mHTMLEditor->GetInlinePropertyBase(propItem->tag, &(propItem->attr), nsnull,
|
||||
&bFirst, &bAny, &bAll, &curValue, false);
|
||||
res = mHTMLEditor->GetInlinePropertyBase(propItem->tag, &propItem->attr,
|
||||
nsnull, &bFirst, &bAny, &bAll,
|
||||
&curValue, false);
|
||||
NS_ENSURE_SUCCESS(res, res);
|
||||
|
||||
if (!bAny) // no style set for this prop/attr
|
||||
{
|
||||
mHTMLEditor->mTypeInState->SetProp(propItem->tag, propItem->attr, propItem->value);
|
||||
|
||||
if (!bAny) {
|
||||
// no style set for this prop/attr
|
||||
mHTMLEditor->mTypeInState->SetProp(propItem->tag, propItem->attr,
|
||||
propItem->value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
nsCOMPtr<nsIDOMElement> rootElement;
|
||||
res = aDoc->GetDocumentElement(getter_AddRefs(rootElement));
|
||||
NS_ENSURE_SUCCESS(res, res);
|
||||
|
||||
// process clearing any styles first
|
||||
nsAutoPtr<PropItem> item(mHTMLEditor->mTypeInState->TakeClearProperty());
|
||||
while (item && node != rootElement)
|
||||
{
|
||||
nsCOMPtr<nsIDOMNode> leftNode, rightNode, secondSplitParent, newSelParent, savedBR;
|
||||
res = mHTMLEditor->SplitStyleAbovePoint(address_of(node), &offset, item->tag, &item->attr, address_of(leftNode), address_of(rightNode));
|
||||
while (item && node != rootElement) {
|
||||
res = mHTMLEditor->ClearStyle(address_of(node), &offset,
|
||||
item->tag, &item->attr);
|
||||
NS_ENSURE_SUCCESS(res, res);
|
||||
bool bIsEmptyNode;
|
||||
if (leftNode)
|
||||
{
|
||||
mHTMLEditor->IsEmptyNode(leftNode, &bIsEmptyNode, false, true);
|
||||
if (bIsEmptyNode)
|
||||
{
|
||||
// delete leftNode if it became empty
|
||||
res = mEditor->DeleteNode(leftNode);
|
||||
NS_ENSURE_SUCCESS(res, res);
|
||||
}
|
||||
}
|
||||
if (rightNode)
|
||||
{
|
||||
secondSplitParent = mHTMLEditor->GetLeftmostChild(rightNode);
|
||||
// don't try to split non-containers (br's, images, hr's, etc)
|
||||
if (!secondSplitParent) secondSplitParent = rightNode;
|
||||
if (!mHTMLEditor->IsContainer(secondSplitParent))
|
||||
{
|
||||
if (nsTextEditUtils::IsBreak(secondSplitParent))
|
||||
savedBR = secondSplitParent;
|
||||
|
||||
secondSplitParent->GetParentNode(getter_AddRefs(tmp));
|
||||
secondSplitParent = tmp;
|
||||
}
|
||||
offset = 0;
|
||||
res = mHTMLEditor->SplitStyleAbovePoint(address_of(secondSplitParent), &offset, item->tag, &(item->attr), address_of(leftNode), address_of(rightNode));
|
||||
NS_ENSURE_SUCCESS(res, res);
|
||||
// should be impossible to not get a new leftnode here
|
||||
NS_ENSURE_TRUE(leftNode, NS_ERROR_FAILURE);
|
||||
newSelParent = mHTMLEditor->GetLeftmostChild(leftNode);
|
||||
if (!newSelParent) newSelParent = leftNode;
|
||||
// if rightNode starts with a br, suck it out of right node and into leftNode.
|
||||
// This is so we you don't revert back to the previous style if you happen to click at the end of a line.
|
||||
if (savedBR)
|
||||
{
|
||||
res = mEditor->MoveNode(savedBR, newSelParent, 0);
|
||||
NS_ENSURE_SUCCESS(res, res);
|
||||
}
|
||||
mHTMLEditor->IsEmptyNode(rightNode, &bIsEmptyNode, false, true);
|
||||
if (bIsEmptyNode)
|
||||
{
|
||||
// delete rightNode if it became empty
|
||||
res = mEditor->DeleteNode(rightNode);
|
||||
NS_ENSURE_SUCCESS(res, res);
|
||||
}
|
||||
// remove the style on this new hierarchy
|
||||
PRInt32 newSelOffset = 0;
|
||||
{
|
||||
// track the point at the new hierarchy.
|
||||
// This is so we can know where to put the selection after we call
|
||||
// RemoveStyleInside(). RemoveStyleInside() could remove any and all of those nodes,
|
||||
// so I have to use the range tracking system to find the right spot to put selection.
|
||||
nsAutoTrackDOMPoint tracker(mHTMLEditor->mRangeUpdater, address_of(newSelParent), &newSelOffset);
|
||||
res = mHTMLEditor->RemoveStyleInside(leftNode, item->tag, &(item->attr));
|
||||
NS_ENSURE_SUCCESS(res, res);
|
||||
}
|
||||
// reset our node offset values to the resulting new sel point
|
||||
node = newSelParent;
|
||||
offset = newSelOffset;
|
||||
}
|
||||
item = mHTMLEditor->mTypeInState->TakeClearProperty();
|
||||
weDidSometing = true;
|
||||
weDidSomething = true;
|
||||
}
|
||||
|
||||
|
||||
// then process setting any styles
|
||||
PRInt32 relFontSize = mHTMLEditor->mTypeInState->TakeRelativeFontSize();
|
||||
item = mHTMLEditor->mTypeInState->TakeSetProperty();
|
||||
|
||||
if (item || relFontSize) // we have at least one style to add; make a
|
||||
{ // new text node to insert style nodes above.
|
||||
if (mHTMLEditor->IsTextNode(node))
|
||||
{
|
||||
|
||||
if (item || relFontSize) {
|
||||
// we have at least one style to add; make a new text node to insert style
|
||||
// nodes above.
|
||||
if (mHTMLEditor->IsTextNode(node)) {
|
||||
// if we are in a text node, split it
|
||||
res = mHTMLEditor->SplitNodeDeep(node, node, offset, &offset);
|
||||
NS_ENSURE_SUCCESS(res, res);
|
||||
node->GetParentNode(getter_AddRefs(tmp));
|
||||
node = tmp;
|
||||
}
|
||||
if (!mHTMLEditor->IsContainer(node))
|
||||
{
|
||||
if (!mHTMLEditor->IsContainer(node)) {
|
||||
return NS_OK;
|
||||
}
|
||||
nsCOMPtr<nsIDOMNode> newNode;
|
||||
|
@ -4479,32 +4409,30 @@ nsHTMLEditRules::CreateStyleForInsertText(nsISelection *aSelection, nsIDOMDocume
|
|||
NS_ENSURE_SUCCESS(res, res);
|
||||
node = newNode;
|
||||
offset = 0;
|
||||
weDidSometing = true;
|
||||
weDidSomething = true;
|
||||
|
||||
if (relFontSize)
|
||||
{
|
||||
PRInt32 j, dir;
|
||||
if (relFontSize) {
|
||||
// dir indicated bigger versus smaller. 1 = bigger, -1 = smaller
|
||||
if (relFontSize > 0) dir=1;
|
||||
else dir = -1;
|
||||
for (j=0; j<abs(relFontSize); j++)
|
||||
{
|
||||
res = mHTMLEditor->RelativeFontChangeOnTextNode(dir, nodeAsText, 0, -1);
|
||||
PRInt32 dir = relFontSize > 0 ? 1 : -1;
|
||||
for (PRInt32 j = 0; j < abs(relFontSize); j++) {
|
||||
res = mHTMLEditor->RelativeFontChangeOnTextNode(dir, nodeAsText,
|
||||
0, -1);
|
||||
NS_ENSURE_SUCCESS(res, res);
|
||||
}
|
||||
}
|
||||
|
||||
while (item)
|
||||
{
|
||||
res = mHTMLEditor->SetInlinePropertyOnNode(node, item->tag, &item->attr, &item->value);
|
||||
|
||||
while (item) {
|
||||
res = mHTMLEditor->SetInlinePropertyOnNode(node, item->tag, &item->attr,
|
||||
&item->value);
|
||||
NS_ENSURE_SUCCESS(res, res);
|
||||
item = mHTMLEditor->mTypeInState->TakeSetProperty();
|
||||
}
|
||||
}
|
||||
if (weDidSometing)
|
||||
if (weDidSomething) {
|
||||
return aSelection->Collapse(node, offset);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
||||
|
@ -5204,69 +5132,6 @@ nsHTMLEditRules::ExpandSelectionForDeletion(nsISelection *aSelection)
|
|||
return res;
|
||||
}
|
||||
|
||||
#ifdef XXX_DEAD_CODE
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// AtStartOfBlock: is node/offset at the start of the editable material in this block?
|
||||
//
|
||||
bool
|
||||
nsHTMLEditRules::AtStartOfBlock(nsIDOMNode *aNode, PRInt32 aOffset, nsIDOMNode *aBlock)
|
||||
{
|
||||
nsCOMPtr<nsIDOMCharacterData> nodeAsText = do_QueryInterface(aNode);
|
||||
if (nodeAsText && aOffset) return false; // there are chars in front of us
|
||||
|
||||
nsCOMPtr<nsIDOMNode> priorNode;
|
||||
nsresult res = mHTMLEditor->GetPriorHTMLNode(aNode, aOffset, address_of(priorNode));
|
||||
NS_ENSURE_SUCCESS(res, true);
|
||||
NS_ENSURE_TRUE(priorNode, true);
|
||||
nsCOMPtr<nsIDOMNode> blockParent = mHTMLEditor->GetBlockNodeParent(priorNode);
|
||||
if (blockParent && (blockParent == aBlock)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// AtEndOfBlock: is node/offset at the end of the editable material in this block?
|
||||
//
|
||||
bool
|
||||
nsHTMLEditRules::AtEndOfBlock(nsIDOMNode *aNode, PRInt32 aOffset, nsIDOMNode *aBlock)
|
||||
{
|
||||
nsCOMPtr<nsIDOMCharacterData> nodeAsText = do_QueryInterface(aNode);
|
||||
if (nodeAsText)
|
||||
{
|
||||
PRUint32 strLength;
|
||||
nodeAsText->GetLength(&strLength);
|
||||
if ((PRInt32)strLength > aOffset) return false; // there are chars in after us
|
||||
}
|
||||
nsCOMPtr<nsIDOMNode> nextNode;
|
||||
nsresult res = mHTMLEditor->GetNextHTMLNode(aNode, aOffset, address_of(nextNode));
|
||||
NS_ENSURE_SUCCESS(res, true);
|
||||
NS_ENSURE_TRUE(nextNode, true);
|
||||
nsCOMPtr<nsIDOMNode> blockParent = mHTMLEditor->GetBlockNodeParent(nextNode);
|
||||
if (blockParent && (blockParent == aBlock)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// CreateMozDiv: makes a div with type = _moz
|
||||
//
|
||||
nsresult
|
||||
nsHTMLEditRules::CreateMozDiv(nsIDOMNode *inParent, PRInt32 inOffset, nsCOMPtr<nsIDOMNode> *outDiv)
|
||||
{
|
||||
NS_ENSURE_TRUE(inParent && outDiv, NS_ERROR_NULL_POINTER);
|
||||
nsAutoString divType= "div";
|
||||
*outDiv = nsnull;
|
||||
nsresult res = mHTMLEditor->CreateNode(divType, inParent, inOffset, getter_AddRefs(*outDiv));
|
||||
NS_ENSURE_SUCCESS(res, res);
|
||||
// give it special moz attr
|
||||
nsCOMPtr<nsIDOMElement> mozDivElem = do_QueryInterface(*outDiv);
|
||||
res = mHTMLEditor->SetAttribute(mozDivElem, "type", "_moz");
|
||||
NS_ENSURE_SUCCESS(res, res);
|
||||
res = AddTrailerBR(*outDiv);
|
||||
return res;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// NormalizeSelection: tweak non-collapsed selections to be more "natural".
|
||||
|
@ -7407,10 +7272,6 @@ nsHTMLEditRules::ReapplyCachedStyles()
|
|||
// and see if any have been removed. If so, add typeinstate
|
||||
// for them, so that they will be reinserted when new
|
||||
// content is added.
|
||||
|
||||
// When we apply cached styles to TypeInState, we always want
|
||||
// to blow away prior TypeInState:
|
||||
mHTMLEditor->mTypeInState->Reset();
|
||||
|
||||
// remember if we are in css mode
|
||||
bool useCSS = mHTMLEditor->IsCSSEnabled();
|
||||
|
@ -7444,8 +7305,7 @@ nsHTMLEditRules::ReapplyCachedStyles()
|
|||
NS_ENSURE_SUCCESS(res, res);
|
||||
}
|
||||
// this style has disappeared through deletion. Add it onto our typeinstate:
|
||||
if (!bAny)
|
||||
{
|
||||
if (!bAny || IsStyleCachePreservingAction(mTheAction)) {
|
||||
mHTMLEditor->mTypeInState->SetProp(mCachedStyles[j].tag, mCachedStyles[j].attr, mCachedStyles[j].value);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -140,9 +140,6 @@ protected:
|
|||
|
||||
// nsHTMLEditRules implementation methods
|
||||
nsresult WillInsert(nsISelection *aSelection, bool *aCancel);
|
||||
#ifdef XXX_DEAD_CODE
|
||||
nsresult DidInsert(nsISelection *aSelection, nsresult aResult);
|
||||
#endif
|
||||
nsresult WillInsertText( nsEditor::OperationID aAction,
|
||||
nsISelection *aSelection,
|
||||
bool *aCancel,
|
||||
|
@ -155,8 +152,10 @@ protected:
|
|||
nsresult StandardBreakImpl(nsIDOMNode *aNode, PRInt32 aOffset, nsISelection *aSelection);
|
||||
nsresult DidInsertBreak(nsISelection *aSelection, nsresult aResult);
|
||||
nsresult SplitMailCites(nsISelection *aSelection, bool aPlaintext, bool *aHandled);
|
||||
nsresult WillDeleteSelection(nsISelection *aSelection, nsIEditor::EDirection aAction,
|
||||
bool *aCancel, bool *aHandled);
|
||||
nsresult WillDeleteSelection(nsISelection* aSelection,
|
||||
nsIEditor::EDirection aAction,
|
||||
nsIEditor::EStripWrappers aStripWrappers,
|
||||
bool* aCancel, bool* aHandled);
|
||||
nsresult DidDeleteSelection(nsISelection *aSelection,
|
||||
nsIEditor::EDirection aDir,
|
||||
nsresult aResult);
|
||||
|
@ -234,10 +233,6 @@ protected:
|
|||
nsresult ExpandSelectionForDeletion(nsISelection *aSelection);
|
||||
bool IsFirstNode(nsIDOMNode *aNode);
|
||||
bool IsLastNode(nsIDOMNode *aNode);
|
||||
#ifdef XXX_DEAD_CODE
|
||||
bool AtStartOfBlock(nsIDOMNode *aNode, PRInt32 aOffset, nsIDOMNode *aBlock);
|
||||
bool AtEndOfBlock(nsIDOMNode *aNode, PRInt32 aOffset, nsIDOMNode *aBlock);
|
||||
#endif
|
||||
nsresult NormalizeSelection(nsISelection *inSelection);
|
||||
nsresult GetPromotedPoint(RulesEndpoint aWhere, nsIDOMNode *aNode,
|
||||
PRInt32 aOffset, nsEditor::OperationID actionID,
|
||||
|
|
|
@ -1757,6 +1757,15 @@ nsHTMLEditor::InsertElementAtSelection(nsIDOMElement* aElement, bool aDeleteSele
|
|||
{
|
||||
if (aDeleteSelection)
|
||||
{
|
||||
if (!IsBlockNode(aElement)) {
|
||||
// E.g., inserting an image. In this case we don't need to delete any
|
||||
// inline wrappers before we do the insertion. Otherwise we let
|
||||
// DeleteSelectionAndPrepareToCreateNode do the deletion for us, which
|
||||
// calls DeleteSelection with aStripWrappers = eStrip.
|
||||
res = DeleteSelection(nsIEditor::eNone, nsIEditor::eNoStrip);
|
||||
NS_ENSURE_SUCCESS(res, res);
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIDOMNode> tempNode;
|
||||
PRInt32 tempOffset;
|
||||
nsresult result = DeleteSelectionAndPrepareToCreateNode(tempNode,tempOffset);
|
||||
|
@ -3425,6 +3434,66 @@ nsHTMLEditor::GetEmbeddedObjects(nsISupportsArray** aNodeList)
|
|||
}
|
||||
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsHTMLEditor::DeleteSelectionImpl(EDirection aAction,
|
||||
EStripWrappers aStripWrappers)
|
||||
{
|
||||
MOZ_ASSERT(aStripWrappers == eStrip || aStripWrappers == eNoStrip);
|
||||
|
||||
nsresult res = nsEditor::DeleteSelectionImpl(aAction, aStripWrappers);
|
||||
NS_ENSURE_SUCCESS(res, res);
|
||||
|
||||
// If we weren't asked to strip any wrappers, we're done.
|
||||
if (aStripWrappers == eNoStrip) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsRefPtr<nsTypedSelection> typedSel = GetTypedSelection();
|
||||
// Just checking that the selection itself is collapsed doesn't seem to work
|
||||
// right in the multi-range case
|
||||
NS_ENSURE_STATE(typedSel);
|
||||
NS_ENSURE_STATE(typedSel->GetAnchorFocusRange());
|
||||
NS_ENSURE_STATE(typedSel->GetAnchorFocusRange()->Collapsed());
|
||||
|
||||
nsCOMPtr<nsIContent> content = do_QueryInterface(typedSel->GetAnchorNode());
|
||||
NS_ENSURE_STATE(content);
|
||||
|
||||
// Don't strip wrappers if this is the only wrapper in the block. Then we'll
|
||||
// add a <br> later, so it won't be an empty wrapper in the end.
|
||||
nsCOMPtr<nsIContent> blockParent = content;
|
||||
while (!IsBlockNode(blockParent)) {
|
||||
blockParent = blockParent->GetParent();
|
||||
}
|
||||
bool emptyBlockParent;
|
||||
res = IsEmptyNode(blockParent, &emptyBlockParent);
|
||||
NS_ENSURE_SUCCESS(res, res);
|
||||
if (emptyBlockParent) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
if (content && !IsBlockNode(content) && !content->Length() &&
|
||||
content->IsEditable() && content != content->GetEditingHost()) {
|
||||
while (content->GetParent() && !IsBlockNode(content->GetParent()) &&
|
||||
content->GetParent()->Length() == 1 &&
|
||||
content->GetParent()->IsEditable() &&
|
||||
content->GetParent() != content->GetEditingHost()) {
|
||||
content = content->GetParent();
|
||||
}
|
||||
res = DeleteNode(content);
|
||||
NS_ENSURE_SUCCESS(res, res);
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
||||
nsresult
|
||||
nsHTMLEditor::DeleteNode(nsINode* aNode)
|
||||
{
|
||||
nsCOMPtr<nsIDOMNode> node = do_QueryInterface(aNode);
|
||||
return DeleteNode(node);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsHTMLEditor::DeleteNode(nsIDOMNode* aNode)
|
||||
{
|
||||
|
|
|
@ -329,6 +329,9 @@ public:
|
|||
virtual bool AreNodesSameType(nsIContent* aNode1, nsIContent* aNode2)
|
||||
MOZ_OVERRIDE;
|
||||
|
||||
NS_IMETHOD DeleteSelectionImpl(EDirection aAction,
|
||||
EStripWrappers aStripWrappers);
|
||||
nsresult DeleteNode(nsINode* aNode);
|
||||
NS_IMETHODIMP DeleteNode(nsIDOMNode * aNode);
|
||||
NS_IMETHODIMP DeleteText(nsIDOMCharacterData *aTextNode,
|
||||
PRUint32 aOffset,
|
||||
|
@ -762,6 +765,9 @@ protected:
|
|||
bool aDeleteSelection,
|
||||
bool aTrustedInput);
|
||||
|
||||
nsresult ClearStyle(nsCOMPtr<nsIDOMNode>* aNode, PRInt32* aOffset,
|
||||
nsIAtom* aProperty, const nsAString* aAttribute);
|
||||
|
||||
// Data members
|
||||
protected:
|
||||
|
||||
|
|
|
@ -145,7 +145,8 @@ nsHTMLEditorLog::RemoveInlineProperty(nsIAtom *aProperty, const nsAString &aAttr
|
|||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsHTMLEditorLog::DeleteSelection(nsIEditor::EDirection aAction)
|
||||
nsHTMLEditorLog::DeleteSelection(nsIEditor::EDirection aAction,
|
||||
nsIEditor::EStripWrappers aStripWrappers)
|
||||
{
|
||||
nsAutoHTMLEditorLogLock logLock(this);
|
||||
|
||||
|
@ -159,7 +160,7 @@ nsHTMLEditorLog::DeleteSelection(nsIEditor::EDirection aAction)
|
|||
Flush();
|
||||
}
|
||||
|
||||
return nsHTMLEditor::DeleteSelection(aAction);
|
||||
return nsHTMLEditor::DeleteSelection(aAction, aStripWrappers);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
|
|
|
@ -73,7 +73,8 @@ public:
|
|||
const nsAString & aValue);
|
||||
NS_IMETHOD SetParagraphFormat(const nsAString& aParagraphFormat);
|
||||
NS_IMETHOD RemoveInlineProperty(nsIAtom *aProperty, const nsAString& aAttribute);
|
||||
NS_IMETHOD DeleteSelection(nsIEditor::EDirection aAction);
|
||||
NS_IMETHOD DeleteSelection(nsIEditor::EDirection aAction,
|
||||
nsIEditor::EStripWrappers aStripWrappers);
|
||||
NS_IMETHOD InsertText(const nsAString& aStringToInsert);
|
||||
NS_IMETHOD InsertLineBreak();
|
||||
NS_IMETHOD Undo(PRUint32 aCount);
|
||||
|
|
|
@ -624,6 +624,85 @@ nsresult nsHTMLEditor::SplitStyleAbovePoint(nsCOMPtr<nsIDOMNode> *aNode,
|
|||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsHTMLEditor::ClearStyle(nsCOMPtr<nsIDOMNode>* aNode, PRInt32* aOffset,
|
||||
nsIAtom* aProperty, const nsAString* aAttribute)
|
||||
{
|
||||
nsCOMPtr<nsIDOMNode> leftNode, rightNode, tmp;
|
||||
nsresult res = SplitStyleAbovePoint(aNode, aOffset, aProperty, aAttribute,
|
||||
address_of(leftNode),
|
||||
address_of(rightNode));
|
||||
NS_ENSURE_SUCCESS(res, res);
|
||||
if (leftNode) {
|
||||
bool bIsEmptyNode;
|
||||
IsEmptyNode(leftNode, &bIsEmptyNode, false, true);
|
||||
if (bIsEmptyNode) {
|
||||
// delete leftNode if it became empty
|
||||
res = DeleteNode(leftNode);
|
||||
NS_ENSURE_SUCCESS(res, res);
|
||||
}
|
||||
}
|
||||
if (rightNode) {
|
||||
nsCOMPtr<nsIDOMNode> secondSplitParent = GetLeftmostChild(rightNode);
|
||||
// don't try to split non-containers (br's, images, hr's, etc)
|
||||
if (!secondSplitParent) {
|
||||
secondSplitParent = rightNode;
|
||||
}
|
||||
nsCOMPtr<nsIDOMNode> savedBR;
|
||||
if (!IsContainer(secondSplitParent)) {
|
||||
if (nsTextEditUtils::IsBreak(secondSplitParent)) {
|
||||
savedBR = secondSplitParent;
|
||||
}
|
||||
|
||||
secondSplitParent->GetParentNode(getter_AddRefs(tmp));
|
||||
secondSplitParent = tmp;
|
||||
}
|
||||
*aOffset = 0;
|
||||
res = SplitStyleAbovePoint(address_of(secondSplitParent),
|
||||
aOffset, aProperty, aAttribute,
|
||||
address_of(leftNode), address_of(rightNode));
|
||||
NS_ENSURE_SUCCESS(res, res);
|
||||
// should be impossible to not get a new leftnode here
|
||||
NS_ENSURE_TRUE(leftNode, NS_ERROR_FAILURE);
|
||||
nsCOMPtr<nsIDOMNode> newSelParent = GetLeftmostChild(leftNode);
|
||||
if (!newSelParent) {
|
||||
newSelParent = leftNode;
|
||||
}
|
||||
// If rightNode starts with a br, suck it out of right node and into
|
||||
// leftNode. This is so we you don't revert back to the previous style
|
||||
// if you happen to click at the end of a line.
|
||||
if (savedBR) {
|
||||
res = MoveNode(savedBR, newSelParent, 0);
|
||||
NS_ENSURE_SUCCESS(res, res);
|
||||
}
|
||||
bool bIsEmptyNode;
|
||||
IsEmptyNode(rightNode, &bIsEmptyNode, false, true);
|
||||
if (bIsEmptyNode) {
|
||||
// delete rightNode if it became empty
|
||||
res = DeleteNode(rightNode);
|
||||
NS_ENSURE_SUCCESS(res, res);
|
||||
}
|
||||
// remove the style on this new hierarchy
|
||||
PRInt32 newSelOffset = 0;
|
||||
{
|
||||
// Track the point at the new hierarchy. This is so we can know where
|
||||
// to put the selection after we call RemoveStyleInside().
|
||||
// RemoveStyleInside() could remove any and all of those nodes, so I
|
||||
// have to use the range tracking system to find the right spot to put
|
||||
// selection.
|
||||
nsAutoTrackDOMPoint tracker(mRangeUpdater,
|
||||
address_of(newSelParent), &newSelOffset);
|
||||
res = RemoveStyleInside(leftNode, aProperty, aAttribute);
|
||||
NS_ENSURE_SUCCESS(res, res);
|
||||
}
|
||||
// reset our node offset values to the resulting new sel point
|
||||
*aNode = newSelParent;
|
||||
*aOffset = newSelOffset;
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
bool nsHTMLEditor::NodeIsProperty(nsIDOMNode *aNode)
|
||||
{
|
||||
NS_ENSURE_TRUE(aNode, false);
|
||||
|
|
|
@ -749,7 +749,7 @@ nsHTMLEditor::DeleteTable2(nsIDOMElement *aTable, nsISelection *aSelection)
|
|||
res = AppendNodeToSelectionAsRange(aTable);
|
||||
NS_ENSURE_SUCCESS(res, res);
|
||||
|
||||
return DeleteSelection(nsIEditor::eNext);
|
||||
return DeleteSelection(nsIEditor::eNext, nsIEditor::eStrip);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
|
|
|
@ -968,8 +968,6 @@ const knownFailures = {
|
|||
"D-Proposed-CHAR-7_SC-dM": true,
|
||||
"D-Proposed-CHAR-7_SC-body": true,
|
||||
"D-Proposed-CHAR-7_SC-div": true,
|
||||
"D-Proposed-B-1_SW-dM": true,
|
||||
"D-Proposed-B-1_SW-body": true,
|
||||
"D-Proposed-B-1_SW-div": true,
|
||||
"D-Proposed-B-1_SL-dM": true,
|
||||
"D-Proposed-B-1_SL-body": true,
|
||||
|
|
|
@ -108,11 +108,11 @@ function runTests() {
|
|||
for (var i = 0; i < nodes.length; i++) {
|
||||
var node = nodes[i];
|
||||
node.focus();
|
||||
is(checkBR(node), 0, "This node should not have any <br> element yet.");
|
||||
is(checkBR(node), 0, node.textContent.trim() + ": This node should not have any <br> element yet.");
|
||||
for (var j = 0; j < 3; j++) { // CARET_BEGIN|MIDDLE|END
|
||||
split(node, j);
|
||||
ok(checkBR(node) > 0, "Pressing [Return] should add (at least) one <br> element.");
|
||||
is(getBlockCount(), count, "Pressing [Return] should not change the number of non-<br> elements.");
|
||||
ok(checkBR(node) > 0, node.textContent.trim() + " " + j + ": Pressing [Return] should add (at least) one <br> element.");
|
||||
is(getBlockCount(), count, node.textContent.trim() + " " + j + ": Pressing [Return] should not change the number of non-<br> elements.");
|
||||
document.execCommand("Undo", false, null);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -60,7 +60,7 @@ function runTests() {
|
|||
select(document.querySelector("#test2 span"));
|
||||
document.querySelector("#test2 [contenteditable]").focus();
|
||||
synthesizeKey("VK_DELETE", {});
|
||||
todo_is(document.querySelector("#test2 span"), null,
|
||||
is(document.querySelector("#test2 span"), null,
|
||||
"The <span> element should have been deleted.");
|
||||
|
||||
// done
|
||||
|
|
|
@ -109,7 +109,7 @@ nsresult nsPlaintextEditor::InsertTextAt(const nsAString &aStringToInsert,
|
|||
// Use an auto tracker so that our drop point is correctly
|
||||
// positioned after the delete.
|
||||
nsAutoTrackDOMPoint tracker(mRangeUpdater, &targetNode, &targetOffset);
|
||||
res = DeleteSelection(eNone);
|
||||
res = DeleteSelection(eNone, eStrip);
|
||||
NS_ENSURE_SUCCESS(res, res);
|
||||
}
|
||||
|
||||
|
|
|
@ -550,7 +550,7 @@ nsPlaintextEditor::InsertBR(nsCOMPtr<nsIDOMNode>* outBRNode)
|
|||
NS_ENSURE_SUCCESS(res, res);
|
||||
|
||||
if (!selection->Collapsed()) {
|
||||
res = DeleteSelection(nsIEditor::eNone);
|
||||
res = DeleteSelection(nsIEditor::eNone, nsIEditor::eStrip);
|
||||
NS_ENSURE_SUCCESS(res, res);
|
||||
}
|
||||
|
||||
|
@ -735,8 +735,12 @@ nsPlaintextEditor::ExtendSelectionForDelete(nsISelection *aSelection,
|
|||
return result;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP nsPlaintextEditor::DeleteSelection(nsIEditor::EDirection aAction)
|
||||
nsresult
|
||||
nsPlaintextEditor::DeleteSelection(EDirection aAction,
|
||||
EStripWrappers aStripWrappers)
|
||||
{
|
||||
MOZ_ASSERT(aStripWrappers == eStrip || aStripWrappers == eNoStrip);
|
||||
|
||||
if (!mRules) { return NS_ERROR_NOT_INITIALIZED; }
|
||||
|
||||
// Protect the edit rules object from dying
|
||||
|
@ -778,12 +782,13 @@ NS_IMETHODIMP nsPlaintextEditor::DeleteSelection(nsIEditor::EDirection aAction)
|
|||
|
||||
nsTextRulesInfo ruleInfo(kOpDeleteSelection);
|
||||
ruleInfo.collapsedAction = aAction;
|
||||
ruleInfo.stripWrappers = aStripWrappers;
|
||||
bool cancel, handled;
|
||||
result = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
|
||||
NS_ENSURE_SUCCESS(result, result);
|
||||
if (!cancel && !handled)
|
||||
{
|
||||
result = DeleteSelectionImpl(aAction);
|
||||
result = DeleteSelectionImpl(aAction, aStripWrappers);
|
||||
}
|
||||
if (!cancel)
|
||||
{
|
||||
|
@ -1282,7 +1287,7 @@ NS_IMETHODIMP nsPlaintextEditor::Cut()
|
|||
HandlingTrustedAction trusted(this);
|
||||
|
||||
if (FireClipboardEvent(NS_CUT))
|
||||
return DeleteSelection(eNone);
|
||||
return DeleteSelection(eNone, eStrip);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
|
|
@ -102,7 +102,8 @@ public:
|
|||
NS_IMETHOD GetDocumentIsEmpty(bool *aDocumentIsEmpty);
|
||||
NS_IMETHOD GetIsDocumentEditable(bool *aIsDocumentEditable);
|
||||
|
||||
NS_IMETHOD DeleteSelection(EDirection aAction);
|
||||
NS_IMETHOD DeleteSelection(EDirection aAction,
|
||||
EStripWrappers aStripWrappers);
|
||||
|
||||
NS_IMETHOD SetDocumentCharacterSet(const nsACString & characterSet);
|
||||
|
||||
|
|
|
@ -410,7 +410,7 @@ nsTextEditRules::WillInsertBreak(nsISelection *aSelection,
|
|||
NS_ENSURE_SUCCESS(res, res);
|
||||
if (!bCollapsed)
|
||||
{
|
||||
res = mEditor->DeleteSelection(nsIEditor::eNone);
|
||||
res = mEditor->DeleteSelection(nsIEditor::eNone, nsIEditor::eStrip);
|
||||
NS_ENSURE_SUCCESS(res, res);
|
||||
}
|
||||
|
||||
|
@ -638,7 +638,7 @@ nsTextEditRules::WillInsertText(nsEditor::OperationID aAction,
|
|||
NS_ENSURE_SUCCESS(res, res);
|
||||
if (!bCollapsed)
|
||||
{
|
||||
res = mEditor->DeleteSelection(nsIEditor::eNone);
|
||||
res = mEditor->DeleteSelection(nsIEditor::eNone, nsIEditor::eStrip);
|
||||
NS_ENSURE_SUCCESS(res, res);
|
||||
}
|
||||
|
||||
|
@ -878,7 +878,7 @@ nsTextEditRules::WillDeleteSelection(nsISelection *aSelection,
|
|||
NS_ENSURE_SUCCESS(res, res);
|
||||
}
|
||||
|
||||
res = mEditor->DeleteSelectionImpl(aCollapsedAction);
|
||||
res = mEditor->DeleteSelectionImpl(aCollapsedAction, nsIEditor::eStrip);
|
||||
NS_ENSURE_SUCCESS(res, res);
|
||||
|
||||
*aHandled = true;
|
||||
|
|
|
@ -275,6 +275,7 @@ class nsTextRulesInfo : public nsRulesInfo
|
|||
outputFormat(0),
|
||||
maxLength(-1),
|
||||
collapsedAction(nsIEditor::eNext),
|
||||
stripWrappers(nsIEditor::eStrip),
|
||||
bOrdered(false),
|
||||
entireList(false),
|
||||
bulletType(0),
|
||||
|
@ -293,6 +294,7 @@ class nsTextRulesInfo : public nsRulesInfo
|
|||
|
||||
// kDeleteSelection
|
||||
nsIEditor::EDirection collapsedAction;
|
||||
nsIEditor::EStripWrappers stripWrappers;
|
||||
|
||||
// kMakeList
|
||||
bool bOrdered;
|
||||
|
|
|
@ -47,25 +47,19 @@ using namespace mozilla;
|
|||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// IsBody: true if node an html body node
|
||||
//
|
||||
// Would use NodeIsType and the corresponding atom, but
|
||||
// the atom list isn't generationed in a plaintext-only
|
||||
// configured build.
|
||||
bool
|
||||
nsTextEditUtils::IsBody(nsIDOMNode *node)
|
||||
{
|
||||
return nsEditor::NodeIsTypeString(node, NS_LITERAL_STRING("body"));
|
||||
return nsEditor::NodeIsType(node, nsGkAtoms::body);
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// IsBreak: true if node an html break node
|
||||
//
|
||||
// See previous comment regarding NodeisType
|
||||
bool
|
||||
nsTextEditUtils::IsBreak(nsIDOMNode *node)
|
||||
{
|
||||
return nsEditor::NodeIsTypeString(node, NS_LITERAL_STRING("br"));
|
||||
return nsEditor::NodeIsType(node, nsGkAtoms::br);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -1377,7 +1377,7 @@ nsTextServicesDocument::DeleteSelection()
|
|||
|
||||
// Now delete the actual content!
|
||||
|
||||
result = editor->DeleteSelection(nsIEditor::ePrevious);
|
||||
result = editor->DeleteSelection(nsIEditor::ePrevious, nsIEditor::eStrip);
|
||||
|
||||
if (NS_FAILED(result))
|
||||
{
|
||||
|
|
|
@ -832,7 +832,7 @@ mozInlineSpellChecker::ReplaceWord(nsIDOMNode *aNode, PRInt32 aOffset,
|
|||
NS_ENSURE_SUCCESS(res, res);
|
||||
selection->RemoveAllRanges();
|
||||
selection->AddRange(range);
|
||||
editor->DeleteSelection(nsIEditor::eNone);
|
||||
editor->DeleteSelection(nsIEditor::eNone, nsIEditor::eStrip);
|
||||
|
||||
nsCOMPtr<nsIPlaintextEditor> textEditor(do_QueryReferent(mEditor));
|
||||
textEditor->InsertText(newword);
|
||||
|
|
|
@ -11519,8 +11519,12 @@ nsCSSFrameConstructor::RestyleForRemove(Element* aContainer,
|
|||
nsIContent* aOldChild,
|
||||
nsIContent* aFollowingSibling)
|
||||
{
|
||||
NS_ASSERTION(!aOldChild->IsRootOfAnonymousSubtree(),
|
||||
"anonymous nodes should not be in child lists");
|
||||
if (aOldChild->IsRootOfAnonymousSubtree()) {
|
||||
// This should be an assert, but this is called incorrectly in
|
||||
// nsHTMLEditor::DeleteRefToAnonymousNode and the assertions were clogging
|
||||
// up the logs. Make it an assert again when that's fixed.
|
||||
NS_WARNING("anonymous nodes should not be in child lists (bug 439258)");
|
||||
}
|
||||
PRUint32 selectorFlags =
|
||||
aContainer ? (aContainer->GetFlags() & NODE_ALL_SELECTOR_FLAGS) : 0;
|
||||
if (selectorFlags == 0)
|
||||
|
|
|
@ -876,6 +876,10 @@ static inline mozilla::css::Side operator++(mozilla::css::Side& side, int) {
|
|||
#define NS_STYLE_COLOR_INTERPOLATION_SRGB 1
|
||||
#define NS_STYLE_COLOR_INTERPOLATION_LINEARRGB 2
|
||||
|
||||
// vector-effect
|
||||
#define NS_STYLE_VECTOR_EFFECT_NONE 0
|
||||
#define NS_STYLE_VECTOR_EFFECT_NON_SCALING_STROKE 1
|
||||
|
||||
// 3d Transforms - Backface visibility
|
||||
#define NS_STYLE_BACKFACE_VISIBILITY_VISIBLE 1
|
||||
#define NS_STYLE_BACKFACE_VISIBILITY_HIDDEN 0
|
||||
|
|
|
@ -184,7 +184,7 @@ load 413582-1.xhtml
|
|||
load 413582-2.html
|
||||
load 413712-1.xhtml
|
||||
asserts-if(Android,6) load 414061-1.html
|
||||
asserts-if(!Android,6) load 414180-1.xul # Bug 439258
|
||||
load 414180-1.xul
|
||||
load 414719-1.html
|
||||
load 415685-1.html
|
||||
load 416264-1.html
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<style type="text/css" >
|
||||
rect {
|
||||
stroke-width: 15px;
|
||||
}
|
||||
</style>
|
||||
<defs>
|
||||
<linearGradient id="grad1" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0" stop-color="blue"/>
|
||||
<stop offset="1" stop-color="yellow"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="grad2" x1="180" y1="250" x2="280" y2="300" gradientUnits="userSpaceOnUse" gradientTransform="scale(0.25,1)">
|
||||
<stop offset="0" stop-color="blue"/>
|
||||
<stop offset="1" stop-color="yellow"/>
|
||||
</linearGradient>
|
||||
<pattern id="pattern" x="0" y="0" width="20" height="20" patternUnits="userSpaceOnUse" patternTransform="scale(1,0.5), skewX(45)">
|
||||
<rect x="0" y="0" width="10" height="10" fill="red"/>
|
||||
<rect x="10" y="0" width="10" height="10" fill="green"/>
|
||||
<rect x="0" y="10" width="10" height="10" fill="blue"/>
|
||||
<rect x="10" y="10" width="10" height="10" fill="yellow"/>
|
||||
</pattern>
|
||||
<rect id="rect" width="100" height="50" fill="none"/>
|
||||
</defs>
|
||||
|
||||
<rect x="20" y="20" width="100" height="50" fill="none" stroke="url(#grad1)"/>
|
||||
|
||||
<rect x="20" y="100" width="100" height="50" fill="none" stroke="url(#grad2)" />
|
||||
|
||||
<use xlink:href="#rect" transform="translate(20, 180)" stroke="url(#pattern)"/>
|
||||
|
||||
<use xlink:href="#rect" x="20" y="260" stroke="green"/>
|
||||
|
||||
</svg>
|
После Ширина: | Высота: | Размер: 1.3 KiB |
|
@ -0,0 +1,36 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<style type="text/css" >
|
||||
rect {
|
||||
stroke-width: 15px;
|
||||
vector-effect: non-scaling-stroke;
|
||||
}
|
||||
</style>
|
||||
<defs>
|
||||
<linearGradient id="grad1" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0" stop-color="blue"/>
|
||||
<stop offset="1" stop-color="yellow"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="grad2" x1="100" y1="150" x2="200" y2="200" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" stop-color="blue"/>
|
||||
<stop offset="1" stop-color="yellow"/>
|
||||
</linearGradient>
|
||||
<pattern id="pattern" x="0" y="0" width="20" height="20" patternUnits="userSpaceOnUse" patternTransform="scale(4,0.5), skewX(45)">
|
||||
<rect x="0" y="0" width="10" height="10" fill="red"/>
|
||||
<rect x="10" y="0" width="10" height="10" fill="green"/>
|
||||
<rect x="0" y="10" width="10" height="10" fill="blue"/>
|
||||
<rect x="10" y="10" width="10" height="10" fill="yellow"/>
|
||||
</pattern>
|
||||
<rect id="rect" width="400" height="50" fill="none"/>
|
||||
</defs>
|
||||
|
||||
<g transform="translate(20,20)">
|
||||
<rect width="400" height="50" fill="none" stroke="url(#grad1)" transform="scale(0.25,1)"/>
|
||||
</g>
|
||||
|
||||
<rect width="400" height="50" fill="none" stroke="url(#grad2)" transform="translate(20,100) scale(0.25,1)"/>
|
||||
|
||||
<use xlink:href="#rect" transform="translate(20, 180) scale(0.25,1)" stroke="url(#pattern)"/>
|
||||
|
||||
<use xlink:href="#rect" x="40" y="80" transform="translate(10, 180) scale(0.25,1)" stroke="green"/>
|
||||
|
||||
</svg>
|
После Ширина: | Высота: | Размер: 1.4 KiB |
|
@ -0,0 +1,33 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" overflow="hidden">
|
||||
<style type="text/css" >
|
||||
rect {
|
||||
stroke-width: 30px;
|
||||
}
|
||||
</style>
|
||||
<defs>
|
||||
<linearGradient id="grad1" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0" stop-color="blue"/>
|
||||
<stop offset="1" stop-color="yellow"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="grad2" x1="360" y1="500" x2="560" y2="600" gradientUnits="userSpaceOnUse" gradientTransform="scale(0.25,1)">
|
||||
<stop offset="0" stop-color="blue"/>
|
||||
<stop offset="1" stop-color="yellow"/>
|
||||
</linearGradient>
|
||||
<pattern id="pattern" x="0" y="0" width="40" height="40" patternUnits="userSpaceOnUse" patternTransform="scale(1,0.5), skewX(45)">
|
||||
<rect x="0" y="0" width="20" height="20" fill="red"/>
|
||||
<rect x="20" y="0" width="20" height="20" fill="green"/>
|
||||
<rect x="0" y="20" width="20" height="20" fill="blue"/>
|
||||
<rect x="20" y="20" width="20" height="20" fill="yellow"/>
|
||||
</pattern>
|
||||
<rect id="rect" width="200" height="100" fill="none"/>
|
||||
</defs>
|
||||
|
||||
<rect x="40" y="40" width="200" height="100" fill="none" stroke="url(#grad1)"/>
|
||||
|
||||
<rect x="40" y="200" width="200" height="100" fill="none" stroke="url(#grad2)" />
|
||||
|
||||
<use xlink:href="#rect" transform="translate(40, 360)" stroke="url(#pattern)"/>
|
||||
|
||||
<use xlink:href="#rect" x="40" y="520" stroke="green"/>
|
||||
|
||||
</svg>
|
После Ширина: | Высота: | Размер: 1.3 KiB |
|
@ -0,0 +1,36 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" reftest-zoom="2" overflow="hidden">
|
||||
<style type="text/css" >
|
||||
rect {
|
||||
stroke-width: 15px;
|
||||
vector-effect: non-scaling-stroke;
|
||||
}
|
||||
</style>
|
||||
<defs>
|
||||
<linearGradient id="grad1" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0" stop-color="blue"/>
|
||||
<stop offset="1" stop-color="yellow"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="grad2" x1="100" y1="150" x2="200" y2="200" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" stop-color="blue"/>
|
||||
<stop offset="1" stop-color="yellow"/>
|
||||
</linearGradient>
|
||||
<pattern id="pattern" x="0" y="0" width="20" height="20" patternUnits="userSpaceOnUse" patternTransform="scale(4,0.5), skewX(45)">
|
||||
<rect x="0" y="0" width="10" height="10" fill="red"/>
|
||||
<rect x="10" y="0" width="10" height="10" fill="green"/>
|
||||
<rect x="0" y="10" width="10" height="10" fill="blue"/>
|
||||
<rect x="10" y="10" width="10" height="10" fill="yellow"/>
|
||||
</pattern>
|
||||
<rect id="rect" width="400" height="50" fill="none"/>
|
||||
</defs>
|
||||
|
||||
<g transform="translate(20,20)">
|
||||
<rect width="400" height="50" fill="none" stroke="url(#grad1)" transform="scale(0.25,1)"/>
|
||||
</g>
|
||||
|
||||
<rect width="400" height="50" fill="none" stroke="url(#grad2)" transform="translate(20,100) scale(0.25,1)"/>
|
||||
|
||||
<use xlink:href="#rect" transform="translate(20, 180) scale(0.25,1)" stroke="url(#pattern)"/>
|
||||
|
||||
<use xlink:href="#rect" x="40" y="80" transform="translate(10, 180) scale(0.25,1)" stroke="green"/>
|
||||
|
||||
</svg>
|
После Ширина: | Высота: | Размер: 1.5 KiB |
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче