Fix #12838: hook point for non-jQuery.ajax synchronous script fetch/execute in domManip #1051

Closed
wants to merge 3 commits into
from

Projects

None yet

5 participants

@gibson042
Member

I'm not thrilled about the name, but at least it's unambiguous.

Sizes - compared to master @ c2d6847

    264083       (+44)  dist/jquery.js                                         
     92059       (+60)  dist/jquery.min.js                                     
     32626       (+11)  dist/jquery.min.js.gz
@rwaldron rwaldron and 2 others commented on an outdated diff Dec 2, 2012
test/unit/ajax.js
@@ -1666,8 +1666,10 @@ module( "ajax", {
//----------- jQuery.getScript()
asyncTest( "jQuery.getScript( String, Function ) - with callback", 2, function() {
+ Globals.register("foobar");
@rwaldron
rwaldron Dec 2, 2012 Member

I really love this Globals system :)

@jaubourg
jaubourg Dec 2, 2012 Member

Weird, I always test with the globals check on and this wasn't caught...

@gibson042
gibson042 Dec 2, 2012 Member

Really? I just tried on Chromium and found several globals leaking in ajax tests; mostly from jsonp. I assumed it was already on your radar, but now I'm wondering if it's environment-specific.

@rwaldron rwaldron and 3 others commented on an outdated diff Dec 2, 2012
src/ajax.js
getScript: function( url, callback ) {
return jQuery.get( url, undefined, callback, "script" );
},
- getJSON: function( url, data, callback ) {
- return jQuery.get( url, data, callback, "json" );
+ getScriptSync: function( url ) {
@rwaldron
rwaldron Dec 2, 2012 Member

It's late and I apologize if I've missed something, but I just read through that ticket and I don't see any evidence to support creating new API surface. Please point me in the right direction...

@jaubourg
jaubourg Dec 2, 2012 Member

I still think this should be an undocumented, private, method in manipulation.js. _domManipScript or something. It serves strictly no purpose outside of domManip. Other ajax implementations can just override it if and when needed.

@jaubourg
jaubourg Dec 2, 2012 Member

Let me insist the name is very misleading, contrary to $.getScript() this "helper" overrides global options implicitely. Also, @rwldrn: http://bugs.jquery.com/ticket/12838

@gibson042
gibson042 Dec 2, 2012 Member

To answer both of you, here are the design goals:

  • Meaningful errors when domManip tries to fetch a script in an ajax-free build
  • Maximum resilience against duckpunching of jQuery.ajax and subcomponents
  • A documented interface for implementers of ajaxy plugins to implement

I thought all of that came up in the ticket, but some of it might have been in side conversations between me, @dmethvin, and @jaubourg.

And while I could see prefixing the method name with an underscore, defining it in ajax.js better satisfies the first and third goals.

@dmethvin
dmethvin Dec 2, 2012 Member

Does it make sense to fire global events on a sync operation, public or not? Since it's a sync operation, the user has the xhr returned to them and can examine the success/failure. The ajaxStart/Stop aren't useful in this case for maintaining an activity indicator on the screen.

@jaubourg
jaubourg Dec 2, 2012 Member

If you want meaningful errors, just define a version of the helper that throws an exception in manipulation.js:

jQuery._executeDOMScript = function() {
    throw "ajax implementation needed";
};

Then, and only then, override it in ajax.js.

Duckpunching of jQuery.ajax is not an issue here. You can do far more damage with ajax prefilters, that will effectively bypass any explicit option. We're talking actual alternative implementations to ajax.js ones that would not have $.ajax() defined (obviously so that the implementor doesn't have to be backward compatible) or even another lib (like using a 3rd party for the ajax calls). That's where the hook is necessary.

Documenting is moot as long as the hook version that just throws is in manipulation.js and has proper comments: that way it is in versions of jQuery build without ajax.js and thus can actually be overriden. You cannot override something that doesn't exist in the first place which would be the case if you build a version of jQuery without ajax.js in.

@gibson042
gibson042 Dec 2, 2012 Member

I can't imagine this is worth the size increase of defining a method twice and including a hard-to-compress English phrase in the replaced version when a native "jQuery.getScriptSync is not defined" suffices just fine. And I agree that mangling subcomponents is a bigger risk, which is why I switched the ajax options to dataType: "text".

EDIT: changed "prefilters" to "subcomponents"

@jaubourg
jaubourg Dec 2, 2012 Member

Then put the helper in manipulation.js where it belongs.

@gibson042
gibson042 Dec 2, 2012 Member

Then the error would be "jQuery.ajax is not defined", which defeats the whole point of allowing people to get this functionality without the ajax module.

@jaubourg
jaubourg Dec 2, 2012 Member

How is it worse than "jQuery.getScriptSync is not defined"?

@jaubourg
jaubourg Dec 2, 2012 Member

The point is to have an overridable hook, not to silently fail if ajax is not here.

@dmethvin
dmethvin Dec 2, 2012 Member

I don't think we're looking for an overridable hook. We're trying to provide a flexible way for alternate ajax implementations to provide sync script evaluation by exposing an interface. Since the interface we need is ajaxy, it seems most logical to put it in ajax.js.

@gibson042
gibson042 Dec 2, 2012 Member

Since the interface we need is ajaxy, it seems most logical to put it in ajax.js

Bingo.

@jaubourg
jaubourg Dec 2, 2012 Member

I don't think we're looking for an overridable hook. We're trying to provide a flexible way for alternate ajax implementations to provide sync script evaluation by exposing an interface.

Quite the contrary I'm afraid.

We're doing all of this so that people can use whatever ajax solution they prefer without having the weight of the ajax module in their version of jQuery.

Let's consider doing so by asking implementations to expose a specific helper. We're talking about imposing the name and signature of a helper in that implementation. A helper this implementation will have to expose publicly and probably document. But that's supposing we're talking about a jQuery implementation in the first place.

Let's consider the alternative implementation is NOT a jQuery plugin but, say, library X (dojo, YUI, ...). At one point, I inject HTML with scripts in my DOM using jQuery and it complains: jQuery.getScriptSync is not defined. I search the documentation and find, in the ajax module section, the description of a helper with a note saying it's used internally by the manipulation module to execute script tags with a source found in injected HTML and has to be defined by any alternative ajax implementation (I tried to make the explanation as simple as I could but good luck having something that makes sense buried into the ajax doc).

Now, the solution is simple, in my app, I just add:

jQuery.getScriptSync( url ) {
    X.request(...);
}

But look what happened here: I didn't "expose" a method on library's X interface (getScriptSync is not part of the interface of library X). I just defined a function on the jQuery object to change the behaviour of jQuery (not throwing an exception anymore and actually loading scripts). That's the very definition of an overridable hook, even if the default implementation is undefined (which I have a big big problem with).

Since the interface we need is ajaxy, it seems most logical to put it in ajax.js.

You're asking yourself the wrong question. The actual question is: does using this new method outside of manipulation make any kind of sense? Absolutely, unequivocally: NO.

This method is specifically, narrowly, tailored for domManip. This method has absolutely no value as a generic method used outside of the realms of manipulation. Why on Earth we're so bent on putting it into the ajax module is a mystery to me (though I blame it on our the endless quest for bytes going very wrong).

It's a helper used internally by the manipulation module, not the ajax module and it belongs to manipulation.js. The fact it does an ajax request is irrelevant. As I said before, throwing an exception is an acceptable default implementation that doesn't do anything "ajaxy".

That's why we have modules that expose interfaces, so that one can use said methods rather than editing the module's source code to hack in their own function. Sadly, that's exactly the latter that is proposed here: let's put an internal method of manipulation.js in ajax.js because we want it exposed and it does something "ajaxy" anyway and we want things to fail when ajax.js is not in the build. Too bad this method doesn't use anyting internal to ajax.js, isn't used by anything inside of ajax.js, doesn't serve any generic purpose (this is not an ajax helper) and was formerly part of an undocumented internal function of another module entirely.

In conclusion:

  • we do want an overridable hook not some interface constraint on alternate ajax implementations (it doesn't even make sense when talking about non jQuery centric solutions)
  • this hook is extracted from an internal method of manipulation.js to be exposed so that it can be overriden to change the behaviour of said internal method. It is thus internal to manipulation.js and has nothing to do in ajax.js.
@dmethvin
dmethvin Dec 13, 2012 Member

Okay, we really need to resolve this and land something; we're already behind schedule.

Regardless of the whether the entry point is in ajax or manipulation, we have to expose and document an entry point here. I am not sure how an overridable hook is better for alternate ajax impls since they'll need to now punch in the hook. Plus, the stub will be left in manipulation.js. To me it just doesn't make sense to leave a reference to $.ajax inside manipulation and make code punch it out.

The method here is doing what it says on the tin, it's running scripts synchronously. The choice to avoid global events also makes sense, there is no asynchrony. So although it has limited utility and we wouldn't advise end-users to avail themselves of it, an ajax implementor needs to provide code for this case.

Any other people have opinions here?

@jaubourg
jaubourg Dec 13, 2012 Member

Okay, we really need to resolve this and land something; we're already behind schedule.

I didn't know this was a blocker. When did this happen?

Regardless of the whether the entry point is in ajax or manipulation, we have to expose and document an entry point here.

Question: where will this be documented? In the ajax module doc or the manipulation module doc? Elsewhere?

I am not sure how an overridable hook is better for alternate ajax impls since they'll need to now punch in the hook. Plus, the stub will be left in manipulation.js. To me it just doesn't make sense to leave a reference to $.ajax inside manipulation and make code punch it out.

I fail to see how "punching in a hook" is worse that incanting a method out of thin air. If some functionality has to be provided to the manipulation module for it to properly function, how does it make sense to define the hook in another module?

You're stating what I'm proposing (keeping the stub in manipulation.js) then you say it doesn't make sense to you to leave a reference to $.ajax inside manipulation. Why?

How is a reference to getScriptSync buried inside domManip "better" than a reference to ajax inside a 5-line long _execScriptFromSrcHook? Don't you think the former will give the impression you have to have the default ajax implementation in order for domManip to function? BTW, how is domManip relevant to someone implementing (or bridging with) another ajax implementation? Isn't a short method called _execScriptFromSrcHook with a simple call to ajax inside that much clearer than a getScriptSync that is nowhere to be found in the code?

You made it clear what you think is better but could you lay out the actual rationale behind it? I know you'd prefer this to be in the default ajax implementation but I still don't know why. How is this method belonging to the ajax module?

The method here is doing what it says on the tin, it's running scripts synchronously. The choice to avoid global events also makes sense, there is no asynchrony.

This method:

  • is inconsistent with other helpers (no callback parameter)
  • doesn't actually make a script request but a plain text one
  • will bypass script prefilters and handle default cache value improperly
  • disables global events with no other reasons than domManip needing to silence global events. Events can be triggered synchronously.

Also, as used in domManip, it will fail silently in case the request is cross-domain and the target server doesn't support the origin of the page.

None of this is advertised on the tin. The method is worthless as an ajax helper because it is not an ajax helper.

So although it has limited utility

It is far more than limited, it is unique to domManip. What other usage do you have in mind for this method outside of domManip?

and we wouldn't advise end-users to avail themselves of it,

Good luck with that. This, btw, is going against everything we've been trying to do lately regarding exposing API. If we expose the method on the ajax module, we'll have to document it there and people will use it. 3 versions down the line, we'll be debating whether or not we should make it more generic (it's not like it never happened before).

If we put it in the manipulation module and call it _execScriptFromSrcHook then end-users will have a harder time justifying using it for doing synchronous ajax requests and we won't have to debate how generic it should be in the future.

an ajax implementor needs to provide code for this case.

Someone using another ajax implementation (or the ajax implementation of another library) must be made aware of this. Hence the necessity of the presence of the hook in builds without the default ajax implementation.

What are the reasons that make you think the intent of this method is made clearer by defining it in the very module that will not be part of builds where said method needs to be provided?

I'm sincerely lost here: what are the actual arguments for putting this in the default ajax module?

@dmethvin
dmethvin Dec 13, 2012 Member

I'm sincerely lost here: what are the actual arguments for putting this in the default ajax module?

It uses ajax functionality. It's essentially the synchronous form of $.getScript. I think the bullet points you make above are consistent with this being a synchronous method, there's no need for global events or a callback if the caller gets control afterwards. Sorry to post a short reply to this but I'm not sure what other arguments to give. Again it would be great to get some perspectives from other team members.

@gibson042
gibson042 Dec 13, 2012 Member

I said above that I could see underscore-prefixing the method name. I am now convinced. I also agree with @jaubourg on testing it, and have been letting this sit in the hopes that it could be rebased on top of #1060 (for consistency), but it looks like that may not happen.

As for the meat of the disagreement, I'm still with @dmethvin on wanting the definition in ajax.js.

Question: where will this be documented? In the ajax module doc or the manipulation module doc? Elsewhere?

Realistically, it's probably best documented as a footnote on some manipulation summary page:

Note: When new content containing script elements with the src attribute set gets connected to the document, jQuery attempts to synchronously fetch and evaluate the scripts by calling the ajax module's jQuery._evalSrc. If you are using a build without the ajax module, you will have to define this method yourself in order to prevent these DOM manipulations from throwing exceptions.

I fail to see how "punching in a hook" is worse that incanting a method out of thin air. If some functionality has to be provided to the manipulation module for it to properly function, how does it make sense to define the hook in another module?

Because that functionality is provided by the other module. This is a dependency upon ajax, and in that sense is no different from the use of jQuery.css( elem, type, extra ) in dimensions.js.

How is a reference to getScriptSync buried inside domManip "better" than a reference to ajax inside a 5-line long _execScriptFromSrcHook? Don't you think the former will give the impression you have to have the default ajax implementation in order for domManip to function?

I don't think so. Error: $.foo is not defined suggests to me that I need to define $.foo. I most certainly don't want that to be ajax. Why make devs dig to find the specific method when we can let the browser's console do it for us and get a smaller library to boot?

BTW, how is domManip relevant to someone implementing (or bridging with) another ajax implementation? Isn't a short method called _execScriptFromSrcHook with a simple call to ajax inside that much clearer than a getScriptSync that is nowhere to be found in the code?

No, because no one will know anything about the method until ey start inspecting our source code.

None of this is advertised on the tin. The method is worthless as an ajax helper because it is not an ajax helper.

That's not entirely true. It is a way of bypassing some of the global ajax options that can make the module behave unpredictably, and if documented will certainly become the preferred method of synchronously executing script URLs (admittedly, probably not something we want to foster).

What are the reasons that make you think the intent of this method is made clearer by defining it in the very module that will not be part of builds where said method needs to be provided?

In short, excluding ajax creates an unsatisfied dependency in certain narrow uses of domManip, the nature of which I want to be obvious (i.e., method not defined) and the scope of which I want to be small (i.e., method !== "ajax").

@jaubourg
jaubourg Dec 13, 2012 Member

@gibson042 thank you sooooo much for a lenghty response. It will make it so much easier to get the conversation going.

Question: where will this be documented? In the ajax module doc or the manipulation module doc? Elsewhere?

Realistically, it's probably best documented as a footnote on some manipulation summary page:

Note: When new content containing script elements with the src attribute set gets connected to the document, jQuery attempts to synchronously fetch and evaluate the scripts by calling the ajax module's jQuery._evalSrc. If you are using a build without the ajax module, you will have to define this method yourself in order to prevent these DOM manipulations from throwing exceptions.

So we agree this should be documented as part of the manipulation module documentation and that the private method will never be mentionned in the ajax module documentation. So, from a documentation point of view, it is part of the manipulation module but the code is actually put in the ajax module. Am I the only one to see the obvious contradiction here? ;)

I fail to see how "punching in a hook" is worse that incanting a method out of thin air. If some functionality has to be provided to the manipulation module for it to properly function, how does it make sense to define the hook in another module?

Because that functionality is provided by the other module. This is a dependency upon ajax, and in that sense is no different from the use of jQuery.css( elem, type, extra ) in dimensions.js.

There are two problems with what you're saying here:

  • the dependency is not created by this $._evalSrcHook() method. If you put the method in manipulation.js, you still have a dependency from the manipulation module to the ajax module from within the method itself.
  • $.ajax() is providing the functionality, not $._evalSrcHook(). $._evalSrcHook() is nothing but a few lines of code extracted from $.domManip() in order for them to be overriden. A thin wrapper for a narrow use-case.

I'm sorry but I fail to see how dimensions.js depending on $.css() is relevant to the conversation here.

Let's look at actual method to method dependencies:

  • $.domManip() depends on $._evalSrcHook(),
  • $._evalSrcHook() depends on $.ajax().

But see, this says nothing about where to put $._evalSrcHook(). We just know it should be in the manipulation module or the ajax module. Whether it should be close to $.domManip() (and in the manipulation module) or close to $.ajax() (in the ajax module) depends on a single information: will it / should it / can it be used by something else but $.domManip()? Does it provide enough functionality, is it generic enough for it to be exposed by another module than where it is used internally? Do we want to encourage its use outside of $.domManip(). I do hope we both agree that it is not.

But that's beside the point actually. Let's just look at things from a fresh and simple perspective: let's ask ourselves some simple questions.

Please, if you disagree with the answers I come up with in the few paragraphs below, I'd love to know why because all of this seems pretty obvious to me and no matter how hard I try to contradict myself on those forthcoming points, I always come up with the same answers.

There's a simple mental exercise that will help us determine in which module $._evalSrcHook() has to reside: just pretend the module is no longer in the build and ask yourself if you still need the method in one form or another:

  • If we remove the ajax module, $._evalSrcHook() is still needed
  • If we remove the manipulation module, it is not needed anymore because it is not used by any other module in the whole library.

It makes it clear $._evalSrcHook() is part of the manipulation module.

If you're still not convinced, now that we're talking about an internal method (starting with an underscore) we can ask ourselves another simple question:

  • Is it used internally by the ajax module? No.
  • Is it used internally by the manipulation module? Yes.

So it is obviously an internal method of the manipulation module.

How is a reference to getScriptSync buried inside domManip "better" than a reference to ajax inside a 5-line long _execScriptFromSrcHook? Don't you think the former will give the impression you have to have the default ajax implementation in order for domManip to function?

I don't think so. Error: $.foo is not defined suggests to me that I need to define $.foo. I most certainly don't want that to be ajax. Why make devs dig to find the specific method when we can let the browser's console do it for us and get a smaller library to boot?

(as a side note, it would help this conversation a lot if we could put the 10 bytes the helper represents out of the equation. Byte size is not a proper way to handle modularity and dependencies, I hope I don't have to explain why)

Let's put my remark back in context:

  • Error: $.getScriptSync is not defined in domManip suggests something is missing in a function I never heard about before (I take the point of view of an end-user here).
  • Error: $.ajax is not defined in $._evalSrcHook suggests to me I have to override some hook (because I know I built without ajax and the name of the function where it is called has hook in it).

BTW, how is domManip relevant to someone implementing (or bridging with) another ajax implementation? Isn't a short method called _execScriptFromSrcHook with a simple call to ajax inside that much clearer than a getScriptSync that is nowhere to be found in the code?

No, because no one will know anything about the method until ey start inspecting our source code.

I'm sorry but my open-source developper senses are tiggling like crazy here. Source code is the first level of documentation. If we expect people to override something, then we'd better have a stub in the code or else we're doing things every kind of wrong. They should be able to click on the error message in their dev tool and be taken to a place where the intent of the code failing is made clear. If you put them inside $.domManip() where some undefined method is called, you're really not showing them much love. If, on the other hand, you put them in a very small method that demonstrates how the hook is implemented using the well-documented $.ajax(), they're in much better shape to grasp what's going on and fix up their own hook.

I like it when something fails in a lib I'm using and I get the answer by clicking on the error in the dev tool. No google, no search in docs. Don't you?

None of this is advertised on the tin. The method is worthless as an ajax helper because it is not an ajax helper.

That's not entirely true. It is a way of bypassing some of the global ajax options that can make the module behave unpredictably, and if documented will certainly become the preferred method of synchronously executing script URLs (admittedly, probably not something we want to foster).

(on a side not, I tried and explained to Dave in another thread what was wrong with global ajax options: the fact they mix actual global configuration information -- good -- and default values -- bad --. Things are far from being black and white here. The configuration stuff is pretty much mandatory and has to be overridable on a per-request basis -- configuration includes prefilters, transports and converters btw).

I seriously hope you're joking here or that you're tired or something (come to think of it, with the crazy day I had I could probably use some of what you drank while writing this ;P joke joke, don't hit me! :P)

This method:

  • pretends to make a script request but does not,
  • forces options unrelated to what it's advertised to do with no means to override them,
  • bypasses all of the internal architecture related to the dataType it's supposed to request
  • won't fire any callback attached to the returned promise if globalEval fails

I'm fine with it doing so since it's tailor-made for $.domManip() (though the fact that, as it is called in $.domManip(), it will fail silently for cross-domain requests to non-CORS compliant servers is a big issue) but it certainly isn't helper material, not in the slightest. I shudder to think what documentation for this would look like if done in the ajax module docs as part of the ajax API: "A helper that makes no sense whatsoever compared to all the others and even $.ajax itself!" :(

Can we agree we don't want this to be used by end-users, no matter how we feel about global ajax options and the ajax architecture in general? See why I'm insisting we put this where it belongs?

Be proud: you scared me greatly with a couple sentences, no small feat! Oo

What are the reasons that make you think the intent of this method is made clearer by defining it in the very module that will not be part of builds where said method needs to be provided?

In short, excluding ajax creates an unsatisfied dependency in certain narrow uses of domManip, the nature of which I want to be obvious (i.e., method not defined) and the scope of which I want to be small (i.e., method !== "ajax").

Well, I thought the goal is to provide a means for $.domManip() to work with alternative ajax implementations.

Forgive me if I'm not reading this correctly but nothing in your last sentence explains the part in parenthesis. How is your intend contradicted by method === "ajax"? The scope is small and the nature of the problem is obvious (I'd argue far more obvious than with a non-existing hardly documented method in an internal hardly documented other method -- $.domManip() -- see my remark about code being the first-level documentation above).

Anyway, thanks a lot for this detailed answer. It helped me understand your point of view a lot better and I hope this answer will help you understand why I disagree so strongly with it ;P

@gibson042
gibson042 Dec 14, 2012 Member

$.ajax() is providing the functionality, not $._evalSrcHook(). $._evalSrcHook() is nothing but a few lines of code extracted from $.domManip() in order for them to be overriden. A thin wrapper for a narrow use-case.

Right... if $.ajax is not available, we need the manipulation to fail in a way that devs can prevent by providing a definition for $._evalSrc. I don't think we disagree here.

If we remove the ajax module, $._evalSrcHook() is still needed

No, a call to it is still needed... but the functionality itself must be provided by someone else. Why would we define a method that is guaranteed to fail?

If we remove the manipulation module, it is not needed anymore because it is not used by any other module in the whole library.

The manipulation module is not excludable. Making it so would be a tremendous effort with little obvious benefit, especially since we use it so much internally. Your points become much more germane if we ever get to that point, but right now I'm just not seeing the value.

If you put them inside $.domManip() where some undefined method is called, you're really not showing them much love.

I started out disagreeing here, but there's such a small difference between the two approaches that I'm now neutral on it.

It will fail silently for cross-domain requests to non-CORS compliant servers is a big issue) but it certainly isn't helper material, not in the slightest.

+1. The former in particular is a problem right now, and might even be big enough to drop this crap completely.

I've already put far more effort into this than I think it's worth. Forgive my short reply here, but I doubt that either of us is going to convince the other. That being said, it's obvious that you care about this much more than I do, and I have no strong objections to seeing the hook in manipulation.js. Does someone else want to weigh in?

@jaubourg
jaubourg Dec 14, 2012 Member

It will fail silently for cross-domain requests to non-CORS compliant servers is a big issue) but it certainly isn't helper material, not in the slightest.

+1. The former in particular is a problem right now, and might even be big enough to drop this crap completely.

Well, we could use something along the line of what we do in the ajax unit test related to cross-domain detection: use a beforeSend handler.

beforeSend: function() {
    if ( this.crossDomain ) {
        throw "cross-domain not supported";
    }
}

But we'd probably need to add a context option in case it is defined inside ajaxSettings. You gotta love ajaxSettings :(

Another possibility to get around all the problems we have with those global options could be to add yet another option, let's call it settings that, if set to false, doesn't import any option from ajaxSettings. This would mean no converter either but I doubt we care here.

Default options suck.

@jaubourg jaubourg and 1 other commented on an outdated diff Dec 2, 2012
test/unit/ajax.js
@@ -1666,8 +1666,10 @@ module( "ajax", {
//----------- jQuery.getScript()
asyncTest( "jQuery.getScript( String, Function ) - with callback", 2, function() {
+ Globals.register("foobar");
+ window["foobar"] = undefined;
@jaubourg
jaubourg Dec 2, 2012 Member

Why? https://github.com/gibson042/jquery/blob/db2356ee61673ac4597d83d1228216031d27cc32/test/data/testrunner.js#L144

Is it in case globals are not checked for? I don't really get what we're trying to fix in the test here.

@gibson042
gibson042 Dec 2, 2012 Member

This is one of those failures I was talking about... when I checked, foobar was already "bar" before the getScript call, making it impossible to verify that test.js (re)assigned it.

@jaubourg
jaubourg Dec 2, 2012 Member

Fine, but why? This is most definitely a problem elsewhere (a test leaking or the global being defined some place outside of test) that has been shadowed here. If the global wasn't detected as leaking, I guess it means the latter?

@gibson042
gibson042 Dec 2, 2012 Member

Every invocation of test.js sets this variable, and our tests should always clear it so this kind of masking doesn't happen—but they don't. And I didn't want to mix those fixes with providing this hook.

@jaubourg
jaubourg Dec 2, 2012 Member

I don't get the problem (not following your answer I'm afraid :/) but I don't think we should have a hack hidding the actual issue and I'd rather we don't go around a problem in the globals leak detection code.

@jaubourg
jaubourg Dec 2, 2012 Member

Oh, I get it now, we should probably change how Globals.register and Globals.cleanup behave then.

@jaubourg jaubourg commented on the diff Dec 2, 2012
src/ajax.js
}
});
+jQuery.each( [ "get", "post" ], function( i, method ) {
+ jQuery[ method ] = function( url, data, callback, type ) {
+ // shift arguments if data argument was omitted
+ if ( jQuery.isFunction( data ) ) {
+ type = type || callback;
+ callback = data;
+ data = undefined;
+ }
+
+ return jQuery.ajax({
@jaubourg
jaubourg Dec 2, 2012 Member

Nice use of options to protect from custom converters.

@rwaldron rwaldron commented on an outdated diff Dec 14, 2012
getScript: function( url, callback ) {
return jQuery.get( url, undefined, callback, "script" );
},
- getJSON: function( url, data, callback ) {
- return jQuery.get( url, data, callback, "json" );
+ _evalSrc: function( url ) {
@rwaldron
rwaldron Dec 14, 2012 Member

As I said previously and still feel very strongly about: I'd like to avoid creating new API. "_" prefixing doesn't cut it.

@Krinkle Krinkle commented on the diff Jan 5, 2013
src/ajax.js
@@ -194,25 +194,6 @@ jQuery.each( [ "ajaxStart", "ajaxStop", "ajaxComplete", "ajaxError", "ajaxSucces
};
});
-jQuery.each( [ "get", "post" ], function( i, method ) {
@Krinkle
Krinkle Jan 5, 2013 Member

Why was this block moved?

@Krinkle Krinkle commented on an outdated diff Jan 5, 2013
getScript: function( url, callback ) {
return jQuery.get( url, undefined, callback, "script" );
},
- getJSON: function( url, data, callback ) {
@Krinkle
Krinkle Jan 5, 2013 Member

Why was this block moved?

@gibson042
Member

Updated for the new year! Also, @jaubourg was right... putting this alongside ajax methods with which it has little in common is just begging for bug reports. So manipulation it is!

   raw     gz Sizes                                                             
239589  71007 dist/jquery.js                                                    
 82978  29518 dist/jquery.min.js                                                

   raw     gz Compared to master @ 32b066d3805a48f8c8312562ed52a1b5910b1d85     
   +35     -3 dist/jquery.js                                                    
   +52    +17 dist/jquery.min.js
@rwaldron rwaldron commented on the diff Apr 4, 2013
src/manipulation.js
@@ -334,14 +334,7 @@ jQuery.fn.extend({
if ( node.src ) {
// Hope ajax is available...
- jQuery.ajax({
- url: node.src,
- type: "GET",
- dataType: "script",
- async: false,
- global: false,
- "throws": true
- });
+ jQuery._evalUrl( node.src );
@rwaldron
rwaldron Apr 4, 2013 Member

-1, No new API surface.

I'm sorry, but we need to find another way.

@jaubourg
jaubourg Apr 4, 2013 Member

It's not so much surface API as it is a simple hook. It's clearly marked as private (leading underscore).

@gibson042
gibson042 Apr 4, 2013 Member

It is literally impossible to satisfy #12838 without new API surface. We may opt not to do it, but we will not find "another way".

@dmethvin
dmethvin Apr 4, 2013 Member

The only other way I see is to have a global core_evalUrl variable and require anyone who wants to override the ajax module to do a custom build.

@jaubourg
jaubourg Apr 4, 2013 Member

The only other way I see is to have a global core_evalUrl variable and require anyone who wants to override the ajax module to do a custom build.

It's not practical. One would be expected to have a build without ajax and be able to use this build with whatever third party ajax lib (or none: $._evalURL = $.noop;) they see fit, not have to rebuild a custom version per third-party. Having to override a variable in the closure at build time is overkill when a private overridable hook on the jQuery object does the job perfectly.

I understand rick's reservations with this but I don't think we can offer a practical solution any other way. We can build jQuery without ajax now, so we should provide this hook.

@dmethvin
dmethvin Apr 4, 2013 Member

I tend to agree, just exploring the whole solution space as they say. Making someone do a custom build for a situation like this is a lot of pain. So let's just expose it the way it is here.

@rwaldron
rwaldron Apr 4, 2013 Member

It's not so much surface API

It's a new static method defined on the publicly accessible "surface" of the jQuery constructor function object.

It's not so much surface API as it is a simple hook. It's clearly marked as private (leading underscore).

An underscore is meaningless.

Having to override a variable in the closure at build time is overkill

Yes, I agree

when a private overridable hook on the jQuery object does the job perfectly.

This is contradictory.

...

When did jQuery get in the business of doing insane shit to support phantom third-party Ajax libs?

@rwaldron
rwaldron Apr 4, 2013 Member

There's 240 more we might as well just implement!

@dmethvin
dmethvin Apr 4, 2013 Member

HOW DARE YOU CHALLENGE THE POWER OF THE UNDERSCORE??!!?!

Looking out a few months I don't think it's unreasonable for us to come up with a lightweight ajax API, either as a proper subset of what we have or something else entirely.

We're talking about making scripts not be executed by default but there will still need to be some way to execute them so that older code can work. And we need some way that a custom build can override or stub out the functionality. We've discussed several approaches. If we're out of other options then it's just a case of choosing.

@jaubourg
jaubourg Apr 4, 2013 Member

It's not so much surface API
It's a new static method defined on the publicly accessible "surface" of the jQuery constructor function object.
It's not so much surface API as it is a simple hook. It's clearly marked as private (leading underscore).
An underscore is meaningless.

We don't have to document this as a prime citizen in the docs. We can just mention it in the custom build part of the docs. This is not public API, it is a hook.

And, yes, prefixing a method with an underscore is a known and accepted convention in javascript used to note it as private. If it's good enough for jQuery UI widgets, to take an example close to home, then it's good enough for jQuery core.

when a private overridable hook on the jQuery object does the job perfectly.
This is contradictory.

No, it is not. Please read above.

When did jQuery get in the business of doing insane shit to support phantom third-party Ajax libs?

Since we support building a version of jQuery without the default ajax implementation. Unless you're advocating we remove manipulation together with ajax because of this tiny bit of dependency.

@rwaldron
rwaldron Apr 4, 2013 Member

Why does injecting a script require the ajax module?

As for "_": it's a bogus inference that means nothing. jQuery UI uses it, jQuery uses it... but all it means is that we can close tickets as "Won't Fix", because it's not for user code to mess with... except when it is, like right now.

@jaubourg
jaubourg Apr 4, 2013 Member

Why does injecting a script require the ajax module?

Because it's not injecting a script, it's making a synchronous ajax request (at least for same-domain stuff).

Do you really want to duplicate all of this XMLHttpRequest logic in manipulation? It makes sense to re-use ajax here except we now support builds without ajax, so we need to provide a way to re-implement this for advanced users with their other lib of choice (or none like I stated in a previous comment).

As for "_": it's a bogus inference that means nothing. jQuery UI uses it, jQuery uses it... but all it means is that we can close tickets as "Won't Fix", because it's not for user code to mess with... except when it is, like right now.

It's code users shouldn't mess with but advanced users can.

Hell, our users can override ajax, css, whatever, marked as private or not, documented or not.

@gibson042
gibson042 Apr 4, 2013 Member

Because it's not injecting a script, it's making a synchronous ajax request (at least for same-domain stuff).

Precisely. And it's done so for basically forever.

@rwaldron
rwaldron Apr 4, 2013 Member

This looks like a script element being created and appended to the document.head: https://github.com/jquery/jquery/blob/master/src/ajax/script.js#L34-L48 which would be invoked as a result of this call site: https://github.com/jquery/jquery/blob/master/src/manipulation.js#L335-L345

Is that incorrect?

@jaubourg
jaubourg Apr 4, 2013 Member

This looks like a script element being created and appended to the document.head: https://github.com/jquery/jquery/blob/master/src/ajax/script.js#L34-L48 which would be invoked as a result of this call site: https://github.com/jquery/jquery/blob/master/src/manipulation.js#L335-L345
Is that incorrect?

Yes, it is incorrect. Transport selection is a bit more involved than a simple one-on-one correspondance with a dataType. You missed the condition here: https://github.com/jquery/jquery/blob/master/src/ajax/script.js#L30

If it's not a crossDomain request, then ajax will continue searching for a suitable transport, until it reaches the one in xhr.js. Effectively enabling synchronous same-domain requests. Executing scripts synchronously is mandatory for domManip. It is, and always has been, broken for scripts of another domain but that's beside the point.

@rwaldron
rwaldron Apr 4, 2013 Member

What happens if the custom ajax lib doesn't do any of this? Can it just use a script element?

@jaubourg
jaubourg Apr 4, 2013 Member

You can't load a script using script tag injection synchronously.

@rwaldron
rwaldron Apr 4, 2013 Member

Yes, I know—thank you—but that's not what I asked.

I concede, let's pile more ugly "_" junk onto jQuery. Can't wait for the bug reports.

@gibson042 gibson042 added a commit that closed this pull request Apr 17, 2013
@gibson042 gibson042 Fix #12838: hook point for non-jQuery.ajax synchronous script fetch/e…
…xecute in domManip. Close gh-1051.
03db1ad
@gibson042 gibson042 closed this in 03db1ad Apr 17, 2013
@gibson042 gibson042 added a commit that referenced this pull request Apr 17, 2013
@gibson042 gibson042 Fix #12838: hook point for non-jQuery.ajax synchronous script fetch/e…
…xecute in domManip. Close gh-1051.

(cherry picked from commit 03db1ad)
0100bec
@mescoda mescoda pushed a commit to mescoda/jquery that referenced this pull request Nov 4, 2014
@gibson042 gibson042 Fix #12838: hook point for non-jQuery.ajax synchronous script fetch/e…
…xecute in domManip. Close gh-1051.

(cherry picked from commit 03db1ad)
c72ce97
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment