Core: Update isFunction to handle unusual-@@toStringTag input

Ref gh-3597
Fixes gh-3600
Fixes gh-3596
Closes gh-3617
This commit is contained in:
Richard Gibson 2017-04-24 12:39:25 -04:00 committed by GitHub
parent 1d2df772b4
commit a16339b893
3 changed files with 107 additions and 10 deletions

View File

@ -212,7 +212,12 @@ jQuery.extend( {
noop: function() {},
isFunction: function( obj ) {
return jQuery.type( obj ) === "function";
// Support: Chrome <=57, Firefox <=52
// In some browsers, typeof returns "function" for HTML <object> elements
// (i.e., `typeof document.createElement( "object" ) === "function"`).
// We don't want to classify *any* DOM node as a function.
return typeof obj === "function" && typeof obj.nodeType !== "number";
},
isWindow: function( obj ) {
@ -464,7 +469,7 @@ function isArrayLike( obj ) {
var length = !!obj && "length" in obj && obj.length,
type = jQuery.type( obj );
if ( type === "function" || jQuery.isWindow( obj ) ) {
if ( jQuery.isFunction( obj ) || jQuery.isWindow( obj ) ) {
return false;
}

View File

@ -406,7 +406,7 @@ QUnit[ "assign" in Object ? "test" : "skip" ]( "isPlainObject(Object.assign(...)
QUnit.test( "isFunction", function( assert ) {
assert.expect( 19 );
assert.expect( 20 );
var mystr, myarr, myfunction, fn, obj, nodes, first, input, a;
@ -439,9 +439,11 @@ QUnit.test( "isFunction", function( assert ) {
fn = function() {};
assert.ok( jQuery.isFunction( fn ), "Normal Function" );
assert.notOk( jQuery.isFunction( Object.create( fn ) ), "custom Function subclass" );
obj = document.createElement( "object" );
// Firefox says this is a function
// Some versions of Firefox and Chrome say this is a function
assert.ok( !jQuery.isFunction( obj ), "Object Element" );
// Since 1.3, this isn't supported (#2968)
@ -491,6 +493,64 @@ QUnit.test( "isFunction", function( assert ) {
} );
} );
QUnit.test( "isFunction(cross-realm function)", function( assert ) {
assert.expect( 1 );
var iframe, doc,
done = assert.async();
// Functions from other windows should be matched
Globals.register( "iframeDone" );
window.iframeDone = function( fn, detail ) {
window.iframeDone = undefined;
assert.ok( jQuery.isFunction( fn ), "cross-realm function" +
( detail ? " - " + detail : "" ) );
done();
};
iframe = jQuery( "#qunit-fixture" )[ 0 ].appendChild( document.createElement( "iframe" ) );
doc = iframe.contentDocument || iframe.contentWindow.document;
doc.open();
doc.write( "<body onload='window.parent.iframeDone( function() {} );'>" );
doc.close();
} );
supportjQuery.each(
{
GeneratorFunction: "function*() {}",
AsyncFunction: "async function() {}"
},
function( subclass, source ) {
var fn;
try {
fn = Function( "return " + source )();
} catch ( e ) {}
QUnit[ fn ? "test" : "skip" ]( "isFunction(" + subclass + ")",
function( assert ) {
assert.expect( 1 );
assert.equal( jQuery.isFunction( fn ), true, source );
}
);
}
);
QUnit[ typeof Symbol === "function" && Symbol.toStringTag ? "test" : "skip" ](
"isFunction(custom @@toStringTag)",
function( assert ) {
assert.expect( 2 );
var obj = {},
fn = function() {};
obj[ Symbol.toStringTag ] = "Function";
fn[ Symbol.toStringTag ] = "Object";
assert.equal( jQuery.isFunction( obj ), false, "function-mimicking object" );
assert.equal( jQuery.isFunction( fn ), true, "object-mimicking function" );
}
);
QUnit.test( "isNumeric", function( assert ) {
assert.expect( 43 );

View File

@ -526,9 +526,10 @@ QUnit.test( "jQuery.Deferred.then - spec compatibility", function( assert ) {
assert.expect( 1 );
var done = assert.async();
var done = assert.async(),
defer = jQuery.Deferred();
var defer = jQuery.Deferred().done( function() {
defer.done( function() {
setTimeout( done );
throw new Error();
} );
@ -542,6 +543,26 @@ QUnit.test( "jQuery.Deferred.then - spec compatibility", function( assert ) {
} catch ( _ ) {}
} );
QUnit[ typeof Symbol === "function" && Symbol.toStringTag ? "test" : "skip" ](
"jQuery.Deferred.then - IsCallable determination (gh-3596)",
function( assert ) {
assert.expect( 1 );
var done = assert.async(),
defer = jQuery.Deferred();
function faker() {
assert.ok( true, "handler with non-'Function' @@toStringTag gets invoked" );
}
faker[ Symbol.toStringTag ] = "String";
defer.then( faker ).then( done );
defer.resolve();
}
);
// Test fails in IE9 but is skipped there because console is not active
QUnit[ window.console ? "test" : "skip" ]( "jQuery.Deferred.exceptionHook", function( assert ) {
@ -861,8 +882,16 @@ QUnit.test( "jQuery.when(nonThenable) - like Promise.resolve", function( assert
QUnit.test( "jQuery.when(thenable) - like Promise.resolve", function( assert ) {
"use strict";
var CASES = 16,
slice = [].slice,
var customToStringThen = {
then: function( onFulfilled ) {
onFulfilled();
}
};
if ( typeof Symbol === "function" ) {
customToStringThen.then[ Symbol.toStringTag ] = "String";
}
var slice = [].slice,
sentinel = { context: "explicit" },
eventuallyFulfilled = jQuery.Deferred().notify( true ),
eventuallyRejected = jQuery.Deferred().notify( true ),
@ -870,6 +899,7 @@ QUnit.test( "jQuery.when(thenable) - like Promise.resolve", function( assert ) {
secondaryRejected = jQuery.Deferred().resolve( eventuallyRejected ),
inputs = {
promise: Promise.resolve( true ),
customToStringThen: customToStringThen,
rejectedPromise: Promise.reject( false ),
deferred: jQuery.Deferred().resolve( true ),
eventuallyFulfilled: eventuallyFulfilled,
@ -894,6 +924,7 @@ QUnit.test( "jQuery.when(thenable) - like Promise.resolve", function( assert ) {
},
willSucceed = {
promise: [ true ],
customToStringThen: [],
deferred: [ true ],
eventuallyFulfilled: [ true ],
secondaryFulfilled: [ true ],
@ -912,14 +943,15 @@ QUnit.test( "jQuery.when(thenable) - like Promise.resolve", function( assert ) {
rejectedDeferredWith: [ false ],
multiRejectedDeferredWith: [ "baz", "quux" ]
},
numCases = Object.keys( willSucceed ).length + Object.keys( willError ).length,
// Support: Android 4.0 only
// Strict mode functions invoked without .call/.apply get global-object context
defaultContext = ( function getDefaultContext() { return this; } ).call(),
done = assert.async( CASES * 2 );
done = assert.async( numCases * 2 );
assert.expect( CASES * 4 );
assert.expect( numCases * 4 );
jQuery.each( inputs, function( message, value ) {
var code = "jQuery.when( " + message + " )",