New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Core: Update isFunction to handle unusual-@@toStringTag input #3617
Core: Update isFunction to handle unusual-@@toStringTag input #3617
Conversation
…nction This change allows detection of functions with unusual @@toStringTag values.
@gibson042, thanks for your PR! By analyzing the history of the files in this pull request, we identified @markelog, @mgol and @jeresig to be potential reviewers. |
test/unit/core.js
Outdated
doc.write( "<body onload='window.parent.iframeDone( function() {} );'>" ); | ||
doc.close(); | ||
} catch ( e ) { | ||
window.iframeDone( function() {}, "iframes not supported" ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How this could happen?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The block was patterned off a similar one for isPlainObject, and is quite old as it turns out. I'll remove the try/catch.
test/unit/core.js
Outdated
@@ -489,8 +493,56 @@ QUnit.test( "isFunction", function( assert ) { | |||
callme( function() { | |||
callme( function() {} ); | |||
} ); | |||
|
|||
// Functions from other windows should be matched | |||
Globals.register( "iframeDone" ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we move this to separate test as well?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess, but it seems to fit with the rest of these assertions pretty well. Would anyone else like to weigh in?
test/unit/deferred.js
Outdated
@@ -537,6 +539,14 @@ QUnit.test( "jQuery.Deferred.then - spec compatibility", function( assert ) { | |||
assert.ok( true, "errors in .done callbacks don't stop .then handlers" ); | |||
} ); | |||
|
|||
function faker() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Separate test?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why?
test/unit/deferred.js
Outdated
@@ -861,7 +871,19 @@ 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, | |||
function customToStringThen() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Separate test?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd really rather not; this test runs four assertions per input thenable.
src/core.js
Outdated
|
||
// Support: Chrome <=57, Firefox <=52 | ||
// Don't classify callable <object>s as functions | ||
return typeof obj === "function" && typeof obj.nodeType !== "number"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
typeof obj.nodeType !== "number"
, what is this? Which test is related to this change?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's preexisting. This nodeType
guard protects us against what would would otherwise be a regression caused by changing from jQuery.type
to typeof
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would you mind adding this clarification comment there, right now it looks so bizarre :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It reads as Don't classify callable <object> elements as functions
now; what would clarify further?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If <object>
is referenced to typeof
result for objects, how it could bypass out initial typeof obj === "function"
?
Then function
type is a subtype of object
so why we can't classify function object as function?
Most of all, why we check presence of nodeType
property to see if object
is callable and why do we care?
"elements" part provides some clarification, but only if you know that this is the reference to the DOM elements. And even so why "element"? Why not "node"? If the logic is meant only for elements indeed then how come it cannot reach this part for other nodes? I think it also deserves some explanation as well as some examples of such an input.
I think I know answers to these questions, but after some months I might forget, you might forget, and others who going to read this might not know of course
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"Don't classify callable <object> elements as functions" is perfectly clear to me. Can you just tell me what you want to see here?
Can we augment logic of |
On tests perspective: In my opinion: one feature – one test, one regression – one test for regression (i.e. for bug), preferably with issue number in test description. The simpler the better, microservices or docker containers analogies come to mind since otherwise It's really hard to support monolith and god methods with potential merge conflicts if/when we would have to revert if those constructions were already changed at the time of revert. For the same reason we no longer put HTML fixtures inside the test suite, but create them inside the test. I think we already discuss this many times? One issue, one pull request, independent tests, one atomic change, one commit |
I don't think it is necessary that pull requests have one commit, but I agree with atomicity of commits. As for this particular case, I wouldn't mind rebasing all 4 of these commits into one as they all seem to address the same problem. Still, this is less important than the "how". How we address the problem is always the bigger question, and I'm wondering if we shouldn't align with Lodash here and treat generator functions as functions as well. |
As for testing, I really like the efficiency of table-driven tests, and want to see us move towards them where practical, rather than away. And merge conflicts wouldn't be an issue if we set expectations from the list of cases. |
Tests updated. This should be good to go. |
@@ -212,7 +212,10 @@ jQuery.extend( { | |||
noop: function() {}, | |||
|
|||
isFunction: function( obj ) { | |||
return jQuery.type( obj ) === "function"; | |||
|
|||
// Support: Chrome <=57, Firefox <=52 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we add only
to both or not yet because Chrome 58 & Firefox 53 are not stable yet?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not yet; we can update this after official releases if anyone cares.
test/unit/deferred.js
Outdated
function faker() { | ||
assert.ok( true, "handler with non-'Function' @@toStringTag gets invoked" ); | ||
} | ||
faker[ typeof Symbol === "function" && Symbol.toStringTag ] = "String"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not wrap the statement with if
? In the current way we'll be writing to a "false"
property in non-supporting browsers which may create confusion.
These are tests so even size is not important so I don't see the gains of the current form.
test/unit/deferred.js
Outdated
onFulfilled(); | ||
} | ||
}; | ||
promise.then[ typeof Symbol === "function" && Symbol.toStringTag ] = "String"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similarly, I'd prefer an if
instead of writing to a "false"
property.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fair enough; updated.
onFulfilled(); | ||
} | ||
}; | ||
if ( typeof Symbol === "function" ) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This check is not enough, many browsers (e.g. old Firefox, Edge <15) support many symbols but not @@toStringTag
.
This applies to here & a few other places.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we care about those cases because they coincide with impossibility of setting @@toStringTag, but I'll see what I can do anyway.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I decided to keep this one for its harmless property set, but the others are updated to skip tests unless Symbol.toStringTag
is truthy.
@@ -464,7 +467,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 ) ) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice, did we already deprecated isWindow
btw?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Doesn't look like it: https://github.com/jquery/jquery/issues?q=is%3Aissue%20isWindow
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Created – #3629 :)
src/core.js
Outdated
|
||
// Support: Chrome <=57, Firefox <=52 | ||
// Don't classify callable <object>s as functions | ||
return typeof obj === "function" && typeof obj.nodeType !== "number"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
?
src/core.js
Outdated
return jQuery.type( obj ) === "function"; | ||
|
||
// Support: Chrome <=57, Firefox <=52 | ||
// Don't classify callable <object> elements as functions |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@markelog ping... what do you want instead of this text?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, long week. Some in the lines of
// Browsers mentioned above will erroneously equate `typeof`
// call on some of the DOM nodes to "function":
//
// jQuery.isFunction(document.createElement("object")) === true
//
// but not
//
// jQuery.isFunction(document.createElement("div")) !== true
//
// To circumvent it we check presense of correct `nodeType` property on
// provided argument, which all DOM nodes shoud have
test/unit/core.js
Outdated
@@ -439,6 +439,8 @@ QUnit.test( "isFunction", function( assert ) { | |||
fn = function() {}; | |||
assert.ok( jQuery.isFunction( fn ), "Normal Function" ); | |||
|
|||
assert.ok( !jQuery.isFunction( Object.create( fn ) ), "custom Function subclass" ); | |||
|
|||
obj = document.createElement( "object" ); | |||
|
|||
// Firefox says this is a function |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Chrome does the same, correct?
test/unit/core.js
Outdated
@@ -439,6 +439,8 @@ QUnit.test( "isFunction", function( assert ) { | |||
fn = function() {}; | |||
assert.ok( jQuery.isFunction( fn ), "Normal Function" ); | |||
|
|||
assert.ok( !jQuery.isFunction( Object.create( fn ) ), "custom Function subclass" ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can use notOk
here instead? It seems more explicit that way, nitpick of course
Summary
Generators and async functions are still functions (cf. ECMAScript Language Specification), and we can recognize them with
typeof
(but still need a guard to reject elements).Fixes gh-3600
Fixes gh-3596
Checklist