git-svn-id: https://develop.svn.wordpress.org/trunk@29570 602fd350-edb4-49c9-b593-d223f7449a82
This commit is contained in:
Andrew Ozz
2014-08-21 23:52:28 +00:00
parent 7a504df72a
commit 208645191d
11 changed files with 251 additions and 82 deletions

View File

@@ -144,7 +144,7 @@ tinymce.PluginManager.add('media', function(editor, url) {
};
function updateValueOnChange() {
data = htmlToData( this.value() );
data = htmlToData(this.value());
this.parent().parent().fromJSON(data);
}
@@ -381,6 +381,56 @@ tinymce.PluginManager.add('media', function(editor, url) {
return {};
}
function sanitize(html) {
if (editor.settings.media_filter_html === false) {
return html;
}
var writer = new tinymce.html.Writer();
new tinymce.html.SaxParser({
validate: false,
allow_conditional_comments: false,
special: 'script,noscript',
comment: function(text) {
writer.comment(text);
},
cdata: function(text) {
writer.cdata(text);
},
text: function(text, raw) {
writer.text(text, raw);
},
start: function(name, attrs, empty) {
if (name == 'script' || name == 'noscript') {
return;
}
for (var i = 0; i < attrs.length; i++) {
if (attrs[i].name.indexOf('on') === 0) {
return;
}
}
writer.start(name, attrs, empty);
},
end: function(name) {
if (name == 'script' || name == 'noscript') {
return;
}
writer.end(name);
}
}, new tinymce.html.Schema({})).parse(html);
return writer.getContent();
}
function updateHtml(html, data, updateAll) {
var writer = new tinymce.html.Writer();
var sourceCount = 0, hasImage;
@@ -674,7 +724,7 @@ tinymce.PluginManager.add('media', function(editor, url) {
if (innerHtml) {
innerNode = new tinymce.html.Node('#text', 3);
innerNode.raw = true;
innerNode.value = unescape(innerHtml);
innerNode.value = sanitize(unescape(innerHtml));
realElm.append(innerNode);
}

File diff suppressed because one or more lines are too long

View File

@@ -620,8 +620,12 @@ define("tinymce/pasteplugin/Clipboard", [
});
editor.on('paste', function(e) {
// Getting content from the Clipboard can take some time
var clipboardTimer = new Date().getTime();
var clipboardContent = getClipboardContent(e);
var isKeyBoardPaste = new Date().getTime() - keyboardPasteTimeStamp < 1000;
var clipboardDelay = new Date().getTime() - clipboardTimer;
var isKeyBoardPaste = (new Date().getTime() - keyboardPasteTimeStamp - clipboardDelay) < 1000;
var plainTextMode = self.pasteFormat == "text" || keyboardPastePlainTextState;
keyboardPastePlainTextState = false;

File diff suppressed because one or more lines are too long

View File

@@ -1,4 +1,4 @@
// 4.1.3 (2014-07-29)
// 4.1.4 (2014-08-21)
/**
* Compiled inline version. (Library mode)
@@ -166,11 +166,11 @@ define("tinymce/dom/EventUtils", [], function() {
var doc = eventDoc.documentElement;
var body = eventDoc.body;
event.pageX = originalEvent.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0 ) -
( doc && doc.clientLeft || body && body.clientLeft || 0);
event.pageX = originalEvent.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) -
(doc && doc.clientLeft || body && body.clientLeft || 0);
event.pageY = originalEvent.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0 ) -
( doc && doc.clientTop || body && body.clientTop || 0);
event.pageY = originalEvent.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) -
(doc && doc.clientTop || body && body.clientTop || 0);
}
// Add preventDefault method
@@ -3936,7 +3936,7 @@ define("tinymce/dom/DomQuery", [
self[i].innerHTML = value;
}
} catch (ex) {
// Workaround for "Unkown runtime error" when DIV is added to P on IE
// Workaround for "Unknown runtime error" when DIV is added to P on IE
DomQuery(self[i]).empty().append(value);
}
@@ -4527,10 +4527,18 @@ define("tinymce/dom/DomQuery", [
text: Sizzle.getText,
contains: Sizzle.contains,
filter: function(expr, elems, not) {
var i = elems.length;
if (not) {
expr = ":not(" + expr + ")";
}
while (i--) {
if (elems[i].nodeType != 1) {
elems.splice(i, 1);
}
}
if (elems.length === 1) {
elems = DomQuery.find.matchesSelector(elems[0], expr) ? [elems[0]] : [];
} else {
@@ -9523,8 +9531,8 @@ define("tinymce/NodeChange", [
editor.on('SelectionChange', function() {
var startElm = editor.selection.getStart(true);
// Selection change might fire when focus is lost so check if the start is still within the body
if (!isSameElementPath(startElm) && editor.dom.isChildOf(startElm, editor.getBody())) {
// Fire a nodechange only when the selection isn't collapsed since focusout will collapse and remove the selection
if (!editor.selection.isCollapsed() && !isSameElementPath(startElm) && editor.dom.isChildOf(startElm, editor.getBody())) {
editor.nodeChanged({selectionChange: true});
}
});
@@ -9811,7 +9819,7 @@ define("tinymce/html/Node", [], function() {
unwrap: function() {
var self = this, node, next;
for (node = self.firstChild; node; ) {
for (node = self.firstChild; node;) {
next = node.next;
self.insert(node, self, true);
node = next;
@@ -10013,11 +10021,11 @@ define("tinymce/html/Node", [], function() {
return false;
}
// Keep elements with data attributes or name attribute like <a name="1"></a>
// Keep bookmark nodes and name attribute like <a name="1"></a>
i = node.attributes.length;
while (i--) {
name = node.attributes[i].name;
if (name === "name" || name.indexOf('data-mce-') === 0) {
if (name === "name" || name.indexOf('data-mce-bookmark') === 0) {
return false;
}
}
@@ -11656,7 +11664,7 @@ define("tinymce/html/DomParser", [
tempNode = currentNode;
}
for (childNode = parents[i].firstChild; childNode && childNode != parents[i + 1]; ) {
for (childNode = parents[i].firstChild; childNode && childNode != parents[i + 1];) {
nextNode = childNode.next;
tempNode.append(childNode);
childNode = nextNode;
@@ -11905,7 +11913,7 @@ define("tinymce/html/DomParser", [
function removeWhitespaceBefore(node) {
var textNode, textVal, sibling;
for (textNode = node.prev; textNode && textNode.type === 3; ) {
for (textNode = node.prev; textNode && textNode.type === 3;) {
textVal = textNode.value.replace(endWhiteSpaceRegExp, '');
if (textVal.length > 0) {
@@ -13732,8 +13740,8 @@ define("tinymce/dom/ControlSelection", [
'z-index: 10000' +
'}' +
rootClass + ' .mce-resize-helper {' +
'background-color: #555;' +
'background-color: rgba(0,0,0,0.75);' +
'background: #555;' +
'background: rgba(0,0,0,0.75);' +
'border-radius: 3px;' +
'border: 1px;' +
'color: white;' +
@@ -14043,6 +14051,11 @@ define("tinymce/dom/ControlSelection", [
}
}
// Ignore all events while resizing
if (resizeStarted) {
return;
}
// Remove data-mce-selected from all elements since they might have been copied using Ctrl+c/v
each(dom.select('img[data-mce-selected],hr[data-mce-selected]'), function(img) {
img.removeAttribute('data-mce-selected');
@@ -15125,7 +15138,7 @@ define("tinymce/dom/Selection", [
getRng: function(w3c) {
var self = this, selection, rng, elm, doc = self.win.document, ieRng;
function tryCompareBounderyPoints(how, sourceRange, destinationRange) {
function tryCompareBoundaryPoints(how, sourceRange, destinationRange) {
try {
return sourceRange.compareBoundaryPoints(how, destinationRange);
} catch (ex) {
@@ -15205,8 +15218,8 @@ define("tinymce/dom/Selection", [
}
if (self.selectedRange && self.explicitRange) {
if (tryCompareBounderyPoints(rng.START_TO_START, rng, self.selectedRange) === 0 &&
tryCompareBounderyPoints(rng.END_TO_END, rng, self.selectedRange) === 0) {
if (tryCompareBoundaryPoints(rng.START_TO_START, rng, self.selectedRange) === 0 &&
tryCompareBoundaryPoints(rng.END_TO_END, rng, self.selectedRange) === 0) {
// Safari, Opera and Chrome only ever select text which causes the range to change.
// This lets us use the originally set range if the selection hasn't been changed by the user.
rng = self.explicitRange;
@@ -15547,6 +15560,30 @@ define("tinymce/dom/Selection", [
}
},
placeCaretAt: function(clientX, clientY) {
var doc = this.editor.getDoc(), rng, point;
if (doc.caretPositionFromPoint) {
point = doc.caretPositionFromPoint(clientX, clientY);
rng = doc.createRange();
rng.setStart(point.offsetNode, point.offset);
rng.collapse(true);
} else if (doc.caretRangeFromPoint) {
rng = doc.caretRangeFromPoint(clientX, clientY);
} else if (doc.body.createTextRange) {
rng = doc.body.createTextRange();
try {
rng.moveToPoint(clientX, clientY);
rng.collapse(true);
} catch (ex) {
rng.collapse(clientY < doc.body.clientHeight);
}
}
this.setRng(rng);
},
_moveEndPoint: function(rng, node, start) {
var root = node, walker = new TreeWalker(node, root);
var nonEmptyElementsMap = this.dom.schema.getNonEmptyElements();
@@ -17263,7 +17300,7 @@ define("tinymce/Formatter", [
offset = node.nodeType === 3 ? node.length : node.childNodes.length;
}
}
return { node: node, offset: offset };
return {node: node, offset: offset};
}
// If index based start position then resolve it
@@ -18227,7 +18264,7 @@ define("tinymce/UndoManager", [
* @return {String} HTML contents of the editor excluding some internal bogus elements.
*/
function getContent() {
var content = trim(editor.getContent({format: 'raw', no_events: 1}));
var content = editor.getContent({format: 'raw', no_events: 1});
var bogusAllRegExp = /<(\w+) [^>]*data-mce-bogus="all"[^>]*>/g;
var endTagIndex, index, matchLength, matches, shortEndedElements, schema = editor.schema;
@@ -18249,7 +18286,7 @@ define("tinymce/UndoManager", [
bogusAllRegExp.lastIndex = index - matchLength;
}
return content;
return trim(content);
}
function addNonTypingUndoLevel(e) {
@@ -19184,7 +19221,7 @@ define("tinymce/EnterKey", [
dom.setAttrib(newBlock, 'id', ''); // Remove ID since it needs to be document unique
// Allow custom handling of new blocks
editor.fire('NewBlock', { newBlock: newBlock });
editor.fire('NewBlock', {newBlock: newBlock});
undoManager.add();
}
@@ -19724,7 +19761,7 @@ define("tinymce/EditorCommands", [
// Setup parser and serializer
parser = editor.parser;
serializer = new Serializer({}, editor.schema);
bookmarkHtml = '<span id="mce_marker" data-mce-type="bookmark">&#xFEFF;&#200B;</span>';
bookmarkHtml = '<span id="mce_marker" data-mce-type="bookmark">&#xFEFF;&#x200B;</span>';
// Run beforeSetContent handlers on the HTML to be inserted
args = {content: value, format: 'html', selection: true};
@@ -19772,7 +19809,9 @@ define("tinymce/EditorCommands", [
for (node = node.prev; node; node = node.walk(true)) {
if (node.type == 3 || !dom.isBlock(node.name)) {
node.parent.insert(marker, node, node.name === 'br');
if (editor.schema.isValidChild(node.parent.name, 'span')) {
node.parent.insert(marker, node, node.name === 'br');
}
break;
}
}
@@ -23687,7 +23726,7 @@ define("tinymce/ui/KeyboardNavigation", [
/**
* Returns the navigation root control for the specified control. The navigation root
* is the control that the keyboard navigation gets scoped to for example a menubar or toolbar group.
* It will look for parents of the specified target control or the currenty focused control if this option is omitted.
* It will look for parents of the specified target control or the currently focused control if this option is omitted.
*
* @private
* @param {tinymce.ui.Control} targetControl Optional target control to find root of.
@@ -26139,7 +26178,7 @@ define("tinymce/WindowManager", [
self.open = function(args, params) {
var win;
editor.editorManager.activeEditor = editor;
editor.editorManager.setActive(editor);
args.title = args.title || ' ';
@@ -26796,17 +26835,19 @@ define("tinymce/util/Quirks", [
*/
function selectControlElements() {
editor.on('click', function(e) {
e = e.target;
var target = e.target;
// Workaround for bug, http://bugs.webkit.org/show_bug.cgi?id=12250
// WebKit can't even do simple things like selecting an image
// Needs tobe the setBaseAndExtend or it will fail to select floated images
if (/^(IMG|HR)$/.test(e.nodeName)) {
selection.getSel().setBaseAndExtent(e, 0, e, 1);
if (/^(IMG|HR)$/.test(target.nodeName)) {
e.preventDefault();
selection.getSel().setBaseAndExtent(target, 0, target, 1);
}
if (e.nodeName == 'A' && dom.hasClass(e, 'mce-item-anchor')) {
selection.select(e);
if (target.nodeName == 'A' && dom.hasClass(target, 'mce-item-anchor')) {
e.preventDefault();
selection.select(target);
}
});
}
@@ -27439,6 +27480,13 @@ define("tinymce/util/Quirks", [
});
args = editor.fire('click', args);
if (!args.isDefaultPrevented()) {
// iOS WebKit can't place the caret properly once
// you bind touch events so we need to do this manually
// TODO: Expand to the closest word? Touble tap still works.
editor.selection.placeCaretAt(endTouch.clientX, endTouch.clientY);
}
});
});
}
@@ -28636,6 +28684,10 @@ define("tinymce/Editor", [
bodyClass = bodyClass[self.id] || '';
}
if (settings.content_security_policy) {
self.iframeHTML += '<meta http-equiv="Content-Security-Policy" content="' + settings.content_security_policy + '" />';
}
self.iframeHTML += '</head><body id="' + bodyId +
'" class="mce-content-body ' + bodyClass +
'" data-id="' + self.id + '"><br></body></html>';
@@ -28674,7 +28726,7 @@ define("tinymce/Editor", [
self.fire("load");
};
DOM.setAttrib("src", url || 'javascript:""');
DOM.setAttrib(ifr, "src", url || 'javascript:""');
self.contentAreaContainer = o.iframeContainer;
self.iframeElement = ifr;
@@ -29025,7 +29077,7 @@ define("tinymce/Editor", [
* @param {Boolean} skipFocus Skip DOM focus. Just set is as the active editor.
*/
focus: function(skipFocus) {
var oed, self = this, selection = self.selection, contentEditable = self.settings.content_editable, rng;
var self = this, selection = self.selection, contentEditable = self.settings.content_editable, rng;
var controlElm, doc = self.getDoc(), body;
if (!skipFocus) {
@@ -29079,15 +29131,7 @@ define("tinymce/Editor", [
}
}
if (self.editorManager.activeEditor != self) {
if ((oed = self.editorManager.activeEditor)) {
oed.fire('deactivate', {relatedTarget: self});
}
self.fire('activate', {relatedTarget: oed});
}
self.editorManager.activeEditor = self;
self.editorManager.setActive(self);
},
/**
@@ -30343,7 +30387,11 @@ define("tinymce/FocusManager", [
if (editor.inline || Env.ie) {
// Use the onbeforedeactivate event when available since it works better see #7023
if ("onbeforedeactivate" in document && Env.ie < 9) {
editor.dom.bind(editor.getBody(), 'beforedeactivate', function() {
editor.dom.bind(editor.getBody(), 'beforedeactivate', function(e) {
if (e.target != editor.getBody()) {
return;
}
try {
editor.lastRng = editor.selection.getRng();
} catch (ex) {
@@ -30414,7 +30462,7 @@ define("tinymce/FocusManager", [
focusedEditor.fire('blur', {focusedEditor: editor});
}
editorManager.activeEditor = editor;
editorManager.setActive(editor);
editorManager.focusedEditor = editor;
editor.fire('focus', {blurredEditor: focusedEditor});
editor.focus(true);
@@ -30454,7 +30502,7 @@ define("tinymce/FocusManager", [
}
// Fire a blur event if the element isn't a UI element
if (!isUIElement(e.target) && editorManager.focusedEditor == activeEditor) {
if (e.target != document.body && !isUIElement(e.target) && editorManager.focusedEditor == activeEditor) {
activeEditor.fire('blur', {focusedEditor: null});
editorManager.focusedEditor = null;
}
@@ -30614,7 +30662,7 @@ define("tinymce/EditorManager", [
* @property minorVersion
* @type String
*/
minorVersion: '1.3',
minorVersion: '1.4',
/**
* Release date of TinyMCE build.
@@ -30622,7 +30670,7 @@ define("tinymce/EditorManager", [
* @property releaseDate
* @type String
*/
releaseDate: '2014-07-29',
releaseDate: '2014-08-21',
/**
* Collection of editor instances.
@@ -30760,7 +30808,7 @@ define("tinymce/EditorManager", [
* });
*/
init: function(settings) {
var self = this, editors = [], editor;
var self = this, editors = [];
function createId(elm) {
var id = elm.id;
@@ -30785,6 +30833,7 @@ define("tinymce/EditorManager", [
function createEditor(id, settings, targetElm) {
if (!purgeDestroyedEditor(self.get(id))) {
var editor = new Editor(id, settings, self);
editor.targetElm = editor.targetElm || targetElm;
editors.push(editor);
editor.render();
@@ -30838,18 +30887,18 @@ define("tinymce/EditorManager", [
l = settings.elements || '';
if (l.length > 0) {
each(explode(l), function(v) {
if (DOM.get(v)) {
editor = new Editor(v, settings, self);
editors.push(editor);
editor.render();
each(explode(l), function(id) {
var elm;
if ((elm = DOM.get(id))) {
createEditor(id, settings, elm);
} else {
each(document.forms, function(f) {
each(f.elements, function(e) {
if (e.name === v) {
v = 'mce_editor_' + instanceCounter++;
DOM.setAttrib(e, 'id', v);
createEditor(v, settings, e);
if (e.name === id) {
id = 'mce_editor_' + instanceCounter++;
DOM.setAttrib(e, 'id', id);
createEditor(id, settings, e);
}
});
});
@@ -30945,6 +30994,8 @@ define("tinymce/EditorManager", [
editors[editor.id] = editor;
editors.push(editor);
// Doesn't call setActive method since we don't want
// to fire a bunch of activate/deactivate calls while initializing
self.activeEditor = editor;
/**
@@ -31137,6 +31188,26 @@ define("tinymce/EditorManager", [
*/
translate: function(text) {
return I18n.translate(text);
},
/**
* Sets the active editor instance and fires the deactivate/activate events.
*
* @method setActive
* @param {tinymce.Editor} editor Editor instance to set as the active instance.
*/
setActive: function(editor) {
var activeEditor = this.activeEditor;
if (this.activeEditor != editor) {
if (activeEditor) {
activeEditor.fire('deactivate', {relatedTarget: editor});
}
editor.fire('activate', {relatedTarget: activeEditor});
}
this.activeEditor = editor;
}
};
@@ -33063,7 +33134,10 @@ define("tinymce/ui/ColorBox", [
var self = this;
settings.spellcheck = false;
settings.icon = 'none';
if (settings.onaction) {
settings.icon = 'none';
}
self._super(settings);
@@ -33074,7 +33148,15 @@ define("tinymce/ui/ColorBox", [
},
repaintColor: function(value) {
this.getEl().getElementsByTagName('i')[0].style.background = value;
var elm = this.getEl().getElementsByTagName('i')[0];
if (elm) {
try {
elm.style.background = value;
} catch (ex) {
// Ignore
}
}
},
value: function(value) {

File diff suppressed because one or more lines are too long

View File

@@ -18,7 +18,7 @@ $wp_db_version = 29188;
*
* @global string $tinymce_version
*/
$tinymce_version = '4103-20140809';
$tinymce_version = '4104-20140821';
/**
* Holds the required PHP version

View File

@@ -17,6 +17,10 @@ module("tinymce.plugins.Media", {
QUnit.start();
}
});
},
teardown: function() {
delete editor.settings.media_filter_html;
}
});
@@ -31,8 +35,8 @@ test("Object retain as is", function() {
equal(editor.getContent(),
'<p><object width="425" height="355" classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000">' +
'<param name="movie" value="someurl">' +
'<param name="wmode" value="transparent">' +
'<param name="movie" value="someurl" />' +
'<param name="wmode" value="transparent" />' +
'<embed src="someurl" type="application/x-shockwave-flash" wmode="transparent" width="425" height="355" />' +
'</object></p>'
);
@@ -82,8 +86,8 @@ test("Audio retain as is", function() {
equal(editor.getContent(),
'<p>' +
'<audio src="sound.mp3">' +
'<track kind="captions" src="foo.en.vtt" srclang="en" label="English">' +
'<track kind="captions" src="foo.sv.vtt" srclang="sv" label="Svenska">' +
'<track kind="captions" src="foo.en.vtt" srclang="en" label="English" />' +
'<track kind="captions" src="foo.sv.vtt" srclang="sv" label="Svenska" />' +
'text<a href="#">link</a>' +
'</audio>' +
'</p>'
@@ -107,6 +111,7 @@ test("Resize complex object", function() {
placeholderElm.width = 100;
placeholderElm.height = 200;
editor.fire('objectResized', {target: placeholderElm, width: placeholderElm.width, height: placeholderElm.height});
editor.settings.media_filter_html = false;
equal(editor.getContent(),
'<p>' +
@@ -145,3 +150,15 @@ test("Media script elements", function() {
'</p>'
);
});
test("XSS content", function() {
function testXss(input, expectedOutput) {
editor.setContent(input);
equal(editor.getContent(), expectedOutput);
}
testXss('<video><a href="javascript:alert(1);">a</a></video>', '<p><video width="300" height="150"><a>a</a></video></p>');
testXss('<video><img src="x" onload="alert(1)"></video>', '<p><video width="300" height=\"150\"></video></p>');
testXss('<video><img src="x"></video>', '<p><video width="300" height="150"><img src="x" /></video></p>');
testXss('<video><!--[if IE]><img src="x"><![endif]--></video>', '<p><video width="300" height="150"><!-- [if IE]><img src="x"><![endif]--></video></p>');
});

View File

@@ -25,8 +25,8 @@
win.close();
}
delete editor.settings.table_adv_tab;
delete editor.settings.table_cell_adv_tab;
delete editor.settings.table_advtab;
delete editor.settings.table_cell_advtab;
delete editor.settings.table_class_list;
delete editor.settings.table_cell_class_list;
delete editor.settings.table_row_class_list;
@@ -65,7 +65,7 @@
});
test("Table properties dialog (get/set data from/to plain table, no adv tab)", function() {
editor.settings.table_adv_tab = false;
editor.settings.table_advtab = false;
editor.setContent('<table><tr><td>X</td></tr></table>');
Utils.setSelection('td', 0);
@@ -247,7 +247,7 @@
});
test("Table cell properties dialog (get/set data from/to plain cell, no adv tab)", function() {
editor.settings.table_cell_adv_tab = false;
editor.settings.table_cell_advtab = false;
editor.setContent('<table><tr><td>X</td></tr></table>');
Utils.setSelection('td', 0);

View File

@@ -332,6 +332,20 @@ test('mceInsertContent - span in span different style color', function() {
equal(editor.getContent(), '<p><span style="color: #ff0000;">a<span style="color: #00ff00;">b</span>c</span></p>');
});
test('mceInsertContent - select with option element', function() {
editor.getBody().innerHTML = '<p>1</p>';
Utils.setSelection('p', 1);
editor.execCommand('mceInsertContent', false, '2<select><option selected="selected">3</option></select>');
equal(editor.getContent(), '<p>12<select><option selected="selected">3</option></select></p>');
});
test('mceInsertContent - insert P in span style element #7090', function() {
editor.setContent('<p><span style="color: red">1</span></p><p>3</p>');
Utils.setSelection('span', 1);
editor.execCommand('mceInsertContent', false, '<p>2</p>');
equal(editor.getContent(), '<p><span style="color: red;">1</span></p><p>2</p><p>3</p>');
});
test('InsertHorizontalRule', function() {
var rng;

View File

@@ -566,6 +566,8 @@
strictEqual($('<b></b><i></i><u></u>').filter(function(i, elm) {
return i != 2;
}).length, 2);
strictEqual($([document, window, document.createTextNode('x')]).filter('*').length, 0);
strictEqual($.filter('*', [document, window, document.createTextNode('x')]).length, 0);
});
test(prefix + 'each() collection', function() {