🌐 AI搜索 & 代理 主页
Skip to content

Commit a16339b

Browse files
authored
Core: Update isFunction to handle unusual-@@toStringTag input
Ref gh-3597 Fixes gh-3600 Fixes gh-3596 Closes gh-3617
1 parent 1d2df77 commit a16339b

File tree

3 files changed

+107
-10
lines changed

3 files changed

+107
-10
lines changed

src/core.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,12 @@ jQuery.extend( {
212212
noop: function() {},
213213

214214
isFunction: function( obj ) {
215-
return jQuery.type( obj ) === "function";
215+
216+
// Support: Chrome <=57, Firefox <=52
217+
// In some browsers, typeof returns "function" for HTML <object> elements
218+
// (i.e., `typeof document.createElement( "object" ) === "function"`).
219+
// We don't want to classify *any* DOM node as a function.
220+
return typeof obj === "function" && typeof obj.nodeType !== "number";
216221
},
217222

218223
isWindow: function( obj ) {
@@ -464,7 +469,7 @@ function isArrayLike( obj ) {
464469
var length = !!obj && "length" in obj && obj.length,
465470
type = jQuery.type( obj );
466471

467-
if ( type === "function" || jQuery.isWindow( obj ) ) {
472+
if ( jQuery.isFunction( obj ) || jQuery.isWindow( obj ) ) {
468473
return false;
469474
}
470475

test/unit/core.js

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -406,7 +406,7 @@ QUnit[ "assign" in Object ? "test" : "skip" ]( "isPlainObject(Object.assign(...)
406406

407407

408408
QUnit.test( "isFunction", function( assert ) {
409-
assert.expect( 19 );
409+
assert.expect( 20 );
410410

411411
var mystr, myarr, myfunction, fn, obj, nodes, first, input, a;
412412

@@ -439,9 +439,11 @@ QUnit.test( "isFunction", function( assert ) {
439439
fn = function() {};
440440
assert.ok( jQuery.isFunction( fn ), "Normal Function" );
441441

442+
assert.notOk( jQuery.isFunction( Object.create( fn ) ), "custom Function subclass" );
443+
442444
obj = document.createElement( "object" );
443445

444-
// Firefox says this is a function
446+
// Some versions of Firefox and Chrome say this is a function
445447
assert.ok( !jQuery.isFunction( obj ), "Object Element" );
446448

447449
// Since 1.3, this isn't supported (#2968)
@@ -491,6 +493,64 @@ QUnit.test( "isFunction", function( assert ) {
491493
} );
492494
} );
493495

496+
QUnit.test( "isFunction(cross-realm function)", function( assert ) {
497+
assert.expect( 1 );
498+
499+
var iframe, doc,
500+
done = assert.async();
501+
502+
// Functions from other windows should be matched
503+
Globals.register( "iframeDone" );
504+
window.iframeDone = function( fn, detail ) {
505+
window.iframeDone = undefined;
506+
assert.ok( jQuery.isFunction( fn ), "cross-realm function" +
507+
( detail ? " - " + detail : "" ) );
508+
done();
509+
};
510+
511+
iframe = jQuery( "#qunit-fixture" )[ 0 ].appendChild( document.createElement( "iframe" ) );
512+
doc = iframe.contentDocument || iframe.contentWindow.document;
513+
doc.open();
514+
doc.write( "<body onload='window.parent.iframeDone( function() {} );'>" );
515+
doc.close();
516+
} );
517+
518+
supportjQuery.each(
519+
{
520+
GeneratorFunction: "function*() {}",
521+
AsyncFunction: "async function() {}"
522+
},
523+
function( subclass, source ) {
524+
var fn;
525+
try {
526+
fn = Function( "return " + source )();
527+
} catch ( e ) {}
528+
529+
QUnit[ fn ? "test" : "skip" ]( "isFunction(" + subclass + ")",
530+
function( assert ) {
531+
assert.expect( 1 );
532+
533+
assert.equal( jQuery.isFunction( fn ), true, source );
534+
}
535+
);
536+
}
537+
);
538+
539+
QUnit[ typeof Symbol === "function" && Symbol.toStringTag ? "test" : "skip" ](
540+
"isFunction(custom @@toStringTag)",
541+
function( assert ) {
542+
assert.expect( 2 );
543+
544+
var obj = {},
545+
fn = function() {};
546+
obj[ Symbol.toStringTag ] = "Function";
547+
fn[ Symbol.toStringTag ] = "Object";
548+
549+
assert.equal( jQuery.isFunction( obj ), false, "function-mimicking object" );
550+
assert.equal( jQuery.isFunction( fn ), true, "object-mimicking function" );
551+
}
552+
);
553+
494554
QUnit.test( "isNumeric", function( assert ) {
495555
assert.expect( 43 );
496556

test/unit/deferred.js

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -526,9 +526,10 @@ QUnit.test( "jQuery.Deferred.then - spec compatibility", function( assert ) {
526526

527527
assert.expect( 1 );
528528

529-
var done = assert.async();
529+
var done = assert.async(),
530+
defer = jQuery.Deferred();
530531

531-
var defer = jQuery.Deferred().done( function() {
532+
defer.done( function() {
532533
setTimeout( done );
533534
throw new Error();
534535
} );
@@ -542,6 +543,26 @@ QUnit.test( "jQuery.Deferred.then - spec compatibility", function( assert ) {
542543
} catch ( _ ) {}
543544
} );
544545

546+
QUnit[ typeof Symbol === "function" && Symbol.toStringTag ? "test" : "skip" ](
547+
"jQuery.Deferred.then - IsCallable determination (gh-3596)",
548+
function( assert ) {
549+
550+
assert.expect( 1 );
551+
552+
var done = assert.async(),
553+
defer = jQuery.Deferred();
554+
555+
function faker() {
556+
assert.ok( true, "handler with non-'Function' @@toStringTag gets invoked" );
557+
}
558+
faker[ Symbol.toStringTag ] = "String";
559+
560+
defer.then( faker ).then( done );
561+
562+
defer.resolve();
563+
}
564+
);
565+
545566
// Test fails in IE9 but is skipped there because console is not active
546567
QUnit[ window.console ? "test" : "skip" ]( "jQuery.Deferred.exceptionHook", function( assert ) {
547568

@@ -861,15 +882,24 @@ QUnit.test( "jQuery.when(nonThenable) - like Promise.resolve", function( assert
861882
QUnit.test( "jQuery.when(thenable) - like Promise.resolve", function( assert ) {
862883
"use strict";
863884

864-
var CASES = 16,
865-
slice = [].slice,
885+
var customToStringThen = {
886+
then: function( onFulfilled ) {
887+
onFulfilled();
888+
}
889+
};
890+
if ( typeof Symbol === "function" ) {
891+
customToStringThen.then[ Symbol.toStringTag ] = "String";
892+
}
893+
894+
var slice = [].slice,
866895
sentinel = { context: "explicit" },
867896
eventuallyFulfilled = jQuery.Deferred().notify( true ),
868897
eventuallyRejected = jQuery.Deferred().notify( true ),
869898
secondaryFulfilled = jQuery.Deferred().resolve( eventuallyFulfilled ),
870899
secondaryRejected = jQuery.Deferred().resolve( eventuallyRejected ),
871900
inputs = {
872901
promise: Promise.resolve( true ),
902+
customToStringThen: customToStringThen,
873903
rejectedPromise: Promise.reject( false ),
874904
deferred: jQuery.Deferred().resolve( true ),
875905
eventuallyFulfilled: eventuallyFulfilled,
@@ -894,6 +924,7 @@ QUnit.test( "jQuery.when(thenable) - like Promise.resolve", function( assert ) {
894924
},
895925
willSucceed = {
896926
promise: [ true ],
927+
customToStringThen: [],
897928
deferred: [ true ],
898929
eventuallyFulfilled: [ true ],
899930
secondaryFulfilled: [ true ],
@@ -912,14 +943,15 @@ QUnit.test( "jQuery.when(thenable) - like Promise.resolve", function( assert ) {
912943
rejectedDeferredWith: [ false ],
913944
multiRejectedDeferredWith: [ "baz", "quux" ]
914945
},
946+
numCases = Object.keys( willSucceed ).length + Object.keys( willError ).length,
915947

916948
// Support: Android 4.0 only
917949
// Strict mode functions invoked without .call/.apply get global-object context
918950
defaultContext = ( function getDefaultContext() { return this; } ).call(),
919951

920-
done = assert.async( CASES * 2 );
952+
done = assert.async( numCases * 2 );
921953

922-
assert.expect( CASES * 4 );
954+
assert.expect( numCases * 4 );
923955

924956
jQuery.each( inputs, function( message, value ) {
925957
var code = "jQuery.when( " + message + " )",

0 commit comments

Comments
 (0)