Skip to content

Commit b734666

Browse files
gibson042dmethvin
authored andcommitted
Fix #13265 #13332: traversing methods with text nodes. Close jquerygh-1145.
1 parent 1d5d959 commit b734666

File tree

3 files changed

+68
-37
lines changed

3 files changed

+68
-37
lines changed

src/traversing.js

Lines changed: 35 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -80,14 +80,18 @@ jQuery.fn.extend({
8080
0;
8181

8282
for ( ; i < l; i++ ) {
83-
cur = this[ i ];
83+
for ( cur = this[i]; cur && cur !== context; cur = cur.parentNode ) {
84+
// Always skip document fragments
85+
if ( cur.nodeType < 11 && (pos ?
86+
pos.index(cur) > -1 :
8487

85-
while ( cur && cur.ownerDocument && cur !== context ) {
86-
if ( pos ? pos.index( cur ) > -1 : jQuery.find.matchesSelector( cur, selectors ) ) {
87-
matched.push( cur );
88+
// Don't pass non-elements to Sizzle
89+
cur.nodeType === 1 &&
90+
jQuery.find.matchesSelector(cur, selectors)) ) {
91+
92+
cur = matched.push( cur );
8893
break;
8994
}
90-
cur = cur.parentElement;
9195
}
9296
}
9397

@@ -134,42 +138,46 @@ jQuery.fn.extend({
134138

135139
jQuery.fn.andSelf = jQuery.fn.addBack;
136140

141+
function sibling( cur, dir ) {
142+
while ( (cur = cur[dir]) && cur.nodeType !== 1 ) {}
143+
144+
return cur;
145+
}
146+
137147
jQuery.each({
138148
parent: function( elem ) {
139-
return elem.parentElement;
149+
var parent = elem.parentNode;
150+
return parent && parent.nodeType !== 11 ? parent : null;
140151
},
141152
parents: function( elem ) {
142-
return jQuery.dir( elem, "parentElement" );
153+
return jQuery.dir( elem, "parentNode" );
143154
},
144155
parentsUntil: function( elem, i, until ) {
145-
return jQuery.dir( elem, "parentElement", until );
156+
return jQuery.dir( elem, "parentNode", until );
146157
},
147158
next: function( elem ) {
148-
return elem.nextElementSibling;
159+
return sibling( elem, "nextSibling" );
149160
},
150161
prev: function( elem ) {
151-
return elem.previousElementSibling;
162+
return sibling( elem, "previousSibling" );
152163
},
153164
nextAll: function( elem ) {
154-
return jQuery.dir( elem, "nextElementSibling" );
165+
return jQuery.dir( elem, "nextSibling" );
155166
},
156167
prevAll: function( elem ) {
157-
return jQuery.dir( elem, "previousElementSibling" );
168+
return jQuery.dir( elem, "previousSibling" );
158169
},
159170
nextUntil: function( elem, i, until ) {
160-
return jQuery.dir( elem, "nextElementSibling", until );
171+
return jQuery.dir( elem, "nextSibling", until );
161172
},
162173
prevUntil: function( elem, i, until ) {
163-
return jQuery.dir( elem, "previousElementSibling", until );
174+
return jQuery.dir( elem, "previousSibling", until );
164175
},
165176
siblings: function( elem ) {
166177
return jQuery.sibling( ( elem.parentNode || {} ).firstChild, elem );
167178
},
168179
children: function( elem ) {
169-
var children = elem.children;
170-
171-
// documentFragment or document does not have children property
172-
return children ? jQuery.merge( [], children ) : jQuery.sibling( elem.firstChild );
180+
return jQuery.sibling( elem.firstChild );
173181
},
174182
contents: function( elem ) {
175183
return jQuery.nodeName( elem, "iframe" ) ?
@@ -214,14 +222,17 @@ jQuery.extend({
214222
},
215223

216224
dir: function( elem, dir, until ) {
217-
var cur = elem[ dir ],
218-
matched = [];
225+
var matched = [],
226+
truncate = until !== undefined;
219227

220-
while ( cur && ( !until || !jQuery( cur ).is( until ) ) ) {
221-
matched.push( cur );
222-
cur = cur[ dir ];
228+
while ( (elem = elem[ dir ]) && elem.nodeType !== 9 ) {
229+
if ( elem.nodeType === 1 ) {
230+
if ( truncate && jQuery( elem ).is( until ) ) {
231+
break;
232+
}
233+
matched.push( elem );
234+
}
223235
}
224-
225236
return matched;
226237
},
227238

test/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,7 @@
247247
<input type="checkbox" name="checkedtestcheckboxes" />
248248
</div>
249249
</form>
250-
<div id="nonnodes"><span>hi</span> there <!-- mon ami --></div>
250+
<div id="nonnodes"><span id="nonnodesElement">hi</span> there <!-- mon ami --></div>
251251
<div id="t2037">
252252
<div><div class="hidden">hidden</div></div>
253253
</div>

test/unit/traversing.js

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,9 @@ test("filter() with positional selectors", function() {
279279
});
280280

281281
test("closest()", function() {
282-
expect( 14 );
282+
expect( 15 );
283+
284+
var jq;
283285

284286
deepEqual( jQuery("body").closest("body").get(), q("body"), "closest(body)" );
285287
deepEqual( jQuery("body").closest("html").get(), q("html"), "closest(html)" );
@@ -290,7 +292,7 @@ test("closest()", function() {
290292
deepEqual( jQuery("#qunit-fixture div").closest("body:first div:last").get(), q("fx-tests"), "closest(body:first div:last)" );
291293

292294
// Test .closest() limited by the context
293-
var jq = jQuery("#nothiddendivchild");
295+
jq = jQuery("#nothiddendivchild");
294296
deepEqual( jq.closest("html", document.body).get(), [], "Context limited." );
295297
deepEqual( jq.closest("body", document.body).get(), [], "Context limited." );
296298
deepEqual( jq.closest("#nothiddendiv", document.body).get(), q("nothiddendiv"), "Context not reached." );
@@ -306,6 +308,9 @@ test("closest()", function() {
306308
equal( jQuery("<div>text</div>").closest("[lang]").length, 0, "Disconnected nodes with text and non-existent attribute selector" );
307309

308310
ok( !jQuery(document).closest("#foo").length, "Calling closest on a document fails silently" );
311+
312+
jq = jQuery("<div>text</div>");
313+
deepEqual( jq.contents().closest("*").get(), jq.get(), "Text node input (#13332)" );
309314
});
310315

311316
test("closest(jQuery)", function() {
@@ -430,8 +435,9 @@ test("addBack()", function() {
430435
});
431436

432437
test("siblings([String])", function() {
433-
expect(7);
438+
expect(8);
434439
deepEqual( jQuery("#en").siblings().get(), q("sndp", "sap"), "Check for siblings" );
440+
deepEqual( jQuery("#nonnodes").contents().eq(1).siblings().get(), q("nonnodesElement"), "Check for text node siblings" );
435441
deepEqual( jQuery("#sndp").siblings(":has(code)").get(), q("sap"), "Check for filtered siblings (has code child element)" );
436442
deepEqual( jQuery("#sndp").siblings(":has(a)").get(), q("en", "sap"), "Check for filtered siblings (has anchor child element)" );
437443
deepEqual( jQuery("#foo").siblings("form, b").get(), q("form", "floatTest", "lengthtest", "name-tests", "testForm"), "Check for multiple filters" );
@@ -449,32 +455,40 @@ test("children([String])", function() {
449455
});
450456

451457
test("parent([String])", function() {
452-
expect(5);
458+
expect(6);
459+
460+
var $el;
461+
453462
equal( jQuery("#groups").parent()[0].id, "ap", "Simple parent check" );
454463
equal( jQuery("#groups").parent("p")[0].id, "ap", "Filtered parent check" );
455464
equal( jQuery("#groups").parent("div").length, 0, "Filtered parent check, no match" );
456465
equal( jQuery("#groups").parent("div, p")[0].id, "ap", "Check for multiple filters" );
457466
deepEqual( jQuery("#en, #sndp").parent().get(), q("foo"), "Check for unique results from parent" );
467+
468+
$el = jQuery("<div>text</div>");
469+
deepEqual( $el.contents().parent().get(), $el.get(), "Check for parent of text node (#13265)" );
458470
});
459471

460472
test("parents([String])", function() {
461-
expect(5);
473+
expect(6);
462474
equal( jQuery("#groups").parents()[0].id, "ap", "Simple parents check" );
475+
deepEqual( jQuery("#nonnodes").contents().eq(1).parents().eq(0).get(), q("nonnodes"), "Text node parents check" );
463476
equal( jQuery("#groups").parents("p")[0].id, "ap", "Filtered parents check" );
464477
equal( jQuery("#groups").parents("div")[0].id, "qunit-fixture", "Filtered parents check2" );
465478
deepEqual( jQuery("#groups").parents("p, div").get(), q("ap", "qunit-fixture"), "Check for multiple filters" );
466479
deepEqual( jQuery("#en, #sndp").parents().get(), q("foo", "qunit-fixture", "dl", "body", "html"), "Check for unique results from parents" );
467480
});
468481

469482
test("parentsUntil([String])", function() {
470-
expect(9);
483+
expect(10);
471484

472485
var parents = jQuery("#groups").parents();
473486

474487
deepEqual( jQuery("#groups").parentsUntil().get(), parents.get(), "parentsUntil with no selector (nextAll)" );
475488
deepEqual( jQuery("#groups").parentsUntil(".foo").get(), parents.get(), "parentsUntil with invalid selector (nextAll)" );
476489
deepEqual( jQuery("#groups").parentsUntil("#html").get(), parents.not(":last").get(), "Simple parentsUntil check" );
477490
equal( jQuery("#groups").parentsUntil("#ap").length, 0, "Simple parentsUntil check" );
491+
deepEqual( jQuery("#nonnodes").contents().eq(1).parentsUntil("#html").eq(0).get(), q("nonnodes"), "Text node parentsUntil check" );
478492
deepEqual( jQuery("#groups").parentsUntil("#html, #body").get(), parents.slice( 0, 3 ).get(), "Less simple parentsUntil check" );
479493
deepEqual( jQuery("#groups").parentsUntil("#html", "div").get(), jQuery("#qunit-fixture").get(), "Filtered parentsUntil check" );
480494
deepEqual( jQuery("#groups").parentsUntil("#html", "p,div,dl").get(), parents.slice( 0, 3 ).get(), "Multiple-filtered parentsUntil check" );
@@ -483,50 +497,55 @@ test("parentsUntil([String])", function() {
483497
});
484498

485499
test("next([String])", function() {
486-
expect(5);
500+
expect(6);
487501
equal( jQuery("#ap").next()[0].id, "foo", "Simple next check" );
502+
equal( jQuery("<div>text<a id='element'></a></div>").contents().eq(0).next().attr("id"), "element", "Text node next check" );
488503
equal( jQuery("#ap").next("div")[0].id, "foo", "Filtered next check" );
489504
equal( jQuery("#ap").next("p").length, 0, "Filtered next check, no match" );
490505
equal( jQuery("#ap").next("div, p")[0].id, "foo", "Multiple filters" );
491506
equal( jQuery("body").next().length, 0, "Simple next check, no match" );
492507
});
493508

494509
test("prev([String])", function() {
495-
expect(4);
510+
expect(5);
496511
equal( jQuery("#foo").prev()[0].id, "ap", "Simple prev check" );
512+
deepEqual( jQuery("#nonnodes").contents().eq(1).prev().get(), q("nonnodesElement"), "Text node prev check" );
497513
equal( jQuery("#foo").prev("p")[0].id, "ap", "Filtered prev check" );
498514
equal( jQuery("#foo").prev("div").length, 0, "Filtered prev check, no match" );
499515
equal( jQuery("#foo").prev("p, div")[0].id, "ap", "Multiple filters" );
500516
});
501517

502518
test("nextAll([String])", function() {
503-
expect(4);
519+
expect(5);
504520

505521
var elems = jQuery("#form").children();
506522

507523
deepEqual( jQuery("#label-for").nextAll().get(), elems.not(":first").get(), "Simple nextAll check" );
524+
equal( jQuery("<div>text<a id='element'></a></div>").contents().eq(0).nextAll().attr("id"), "element", "Text node nextAll check" );
508525
deepEqual( jQuery("#label-for").nextAll("input").get(), elems.not(":first").filter("input").get(), "Filtered nextAll check" );
509526
deepEqual( jQuery("#label-for").nextAll("input,select").get(), elems.not(":first").filter("input,select").get(), "Multiple-filtered nextAll check" );
510527
deepEqual( jQuery("#label-for, #hidden1").nextAll("input,select").get(), elems.not(":first").filter("input,select").get(), "Multi-source, multiple-filtered nextAll check" );
511528
});
512529

513530
test("prevAll([String])", function() {
514-
expect(4);
531+
expect(5);
515532

516533
var elems = jQuery( jQuery("#form").children().slice(0, 12).get().reverse() );
517534

518535
deepEqual( jQuery("#area1").prevAll().get(), elems.get(), "Simple prevAll check" );
536+
deepEqual( jQuery("#nonnodes").contents().eq(1).prevAll().get(), q("nonnodesElement"), "Text node prevAll check" );
519537
deepEqual( jQuery("#area1").prevAll("input").get(), elems.filter("input").get(), "Filtered prevAll check" );
520538
deepEqual( jQuery("#area1").prevAll("input,select").get(), elems.filter("input,select").get(), "Multiple-filtered prevAll check" );
521539
deepEqual( jQuery("#area1, #hidden1").prevAll("input,select").get(), elems.filter("input,select").get(), "Multi-source, multiple-filtered prevAll check" );
522540
});
523541

524542
test("nextUntil([String])", function() {
525-
expect(11);
543+
expect(12);
526544

527545
var elems = jQuery("#form").children().slice( 2, 12 );
528546

529547
deepEqual( jQuery("#text1").nextUntil().get(), jQuery("#text1").nextAll().get(), "nextUntil with no selector (nextAll)" );
548+
equal( jQuery("<div>text<a id='element'></a></div>").contents().eq(0).nextUntil().attr("id"), "element", "Text node nextUntil with no selector (nextAll)" );
530549
deepEqual( jQuery("#text1").nextUntil(".foo").get(), jQuery("#text1").nextAll().get(), "nextUntil with invalid selector (nextAll)" );
531550
deepEqual( jQuery("#text1").nextUntil("#area1").get(), elems.get(), "Simple nextUntil check" );
532551
equal( jQuery("#text1").nextUntil("#text2").length, 0, "Simple nextUntil check" );
@@ -541,11 +560,12 @@ test("nextUntil([String])", function() {
541560
});
542561

543562
test("prevUntil([String])", function() {
544-
expect(10);
563+
expect(11);
545564

546565
var elems = jQuery("#area1").prevAll();
547566

548567
deepEqual( jQuery("#area1").prevUntil().get(), elems.get(), "prevUntil with no selector (prevAll)" );
568+
deepEqual( jQuery("#nonnodes").contents().eq(1).prevUntil().get(), q("nonnodesElement"), "Text node prevUntil with no selector (prevAll)" );
549569
deepEqual( jQuery("#area1").prevUntil(".foo").get(), elems.get(), "prevUntil with invalid selector (prevAll)" );
550570
deepEqual( jQuery("#area1").prevUntil("label").get(), elems.not(":last").get(), "Simple prevUntil check" );
551571
equal( jQuery("#area1").prevUntil("#button").length, 0, "Simple prevUntil check" );

0 commit comments

Comments
 (0)