Support for Drupal 7 is ending on 5 January 2025—it’s time to migrate to Drupal 10! Learn about the many benefits of Drupal 10 and find migration tools in our resource center.
This is a performance concern.
Is there a specific reason why this is done?
I looked at the code and there isn't a comment to go with it.
======
It is because drupal_add_js()
does not support adding a script with both a src=""
attribute and body. So Gigya uses drupal_add_html_head()
, which gets rendered into <head>
before CSS and scripts in most themes. (drupal_add_html_head()
does not allow the "weight" to be set.)
Comment | File | Size | Author |
---|---|---|---|
#23 | interdiff-2020717-22-23.txt | 682 bytes | robcolburn |
#23 | gigya-socialize-js-footer-2020717-23.patch | 43.96 KB | robcolburn |
#16 | interdiff-2020717-14-16.txt | 649 bytes | Elijah Lynn |
#16 | gigya-socialize-js-footer-2020717-16.patch | 2.07 KB | Elijah Lynn |
#14 | interdiff-2020717-12-14.txt | 818 bytes | Elijah Lynn |
Comments
Comment #1
gambaweb CreditAttribution: gambaweb commentedThe only reason for that is the we need the gigya script to be loaded before we call its functions.
Could you please elaborate more way is this a concern for you?
Comment #2
torgosPizza@gambaweb, it's possible to use the 'weight' setting attribute (among others) to ensure the Gigya script is added in front of other scripts in the same scope. See the drupal_add_js() API documentation. Using the settings in this API function will allow the JS to get aggregated along with the rest of the site's Javascript.
I'd recommend the JS-addition functionality in the Gigya module(s) use this feature to help performance and cleanliness.
Comment #3
ruplTo elaborate on the performance concern, adding JS before CSS causes the web page to stop rendering, and it causes a massive degradation in the performance of a page load.
As was already mentioned, it is a much better practice to use the
group
and/orweight
settings insidedrupal_add_js()
. Even better: define your main library using the Libraries API 7.x-2.x andlibraries_load()
.Regardless of your choice, using
drupal_add_html_head()
to add JavaScript should be avoided in all situations.Take a look at the Modernizr module source code for an example. Modernizr has the need to be the first script (or really close to it), so that it can provide class names on the
<html>
tag which are used for presentation. The module uses a large negative number for bothgroup
andweight
(in addition to explicitly defining ascope
of 'header'), such that it normally appears first in a Drupal JS listing.I am the maintainer of the Modernizr module and am happy to assist you in re-configuring the JS for the Gigya module so that it is more performant.
Comment #4
Alex.Zabavsky CreditAttribution: Alex.Zabavsky commentedI am preparing a patch for you, Eric. Going to upload it shortly.
Comment #5
Alex.Zabavsky CreditAttribution: Alex.Zabavsky commentedComment #6
ruplThis is a good start (nice job using
hook_page_build()
), but it might be detrimental to move the script that far down. He still needs the script before the other code, so using something similar to the following would work fine (code untested):edit: I forgot the
'type' => 'inline'
on the second call to add the settings. It's there now.Comment #7
elliotttf CreditAttribution: elliotttf commentedThe patch in #5 causes issues with the media module – specifically the media upload popup – because the footer piece of the page is unset in
media_page_alter()
Updated patch that implements rupl's suggestions to follow.
Comment #8
elliotttf CreditAttribution: elliotttf commentedComment #9
elliotttf CreditAttribution: elliotttf commentedUpdating patch to use a relative protocol since protocol detection server side is not 100% reliable when reverse proxies are in play.
Comment #10
elliotttf CreditAttribution: elliotttf commentedComment #11
Bevan CreditAttribution: Bevan commentedThis doesn't work because Gigya needs the settings to be in the same
<script>
tag that thesrc=""
is loaded on. Otherwise it can not locate the JSON settings.I think a better architecture would be something along the lines of:
gigya_init()
, call the first module that implements a new hook, E.g.hook_gigya_add_js()
Any patch should also document why
drupal_add_html_head()
is used in the default case. I have added this reason to the issue description.However;
hook_page_build()
, instead ofhook_init()
. That way it won't run on pages that return AJAX requests or process image styles and be more performant for those pages. It will also give many more options about how and where to add the script, since the entire page is available as structured data inhook_page_build()
.<body>
, if the script is capable of executing successfully there. This would allow the page to render without being blocked by the DNS lookup, HTTP request, HTTP transfer (for unprimed browser caches) and JS execution of this JS file, which is much lower priority than the page's content. See also #784626: Default all JS to the footer, allow asset libraries to force their JS to the headerThese last two points would probably mitigate the performance concerns of the original poster.
Comment #12
robcolburn CreditAttribution: robcolburn commentedThis patch is not the best option ever, nor is it the most elegant. It just does the minimum to achieve the task and Bevan's comment.
We can debate semantics later. :)
Comment #13
Bevan CreditAttribution: Bevan commentedThis is a great start! There are a few style errors and maybe functional errors too. However my main concern is that it still does the heavy lifting on
hook_init()
. Is there any reason it can't be moved tohook_page_build()
?Comment #14
Elijah LynnRerolling #12 to be relative to module root (git diff --relative > patch) + a few docblock improvements.
Maybe it can be improved more but it does work so I will leave my RTBC in the comments here.
Comment #15
robcolburn CreditAttribution: robcolburn commented@Bevan,
Sorry, I stalled on answering your questions.
I totally agree with your comments. Getting out of hook_init() significantly helps backend performance, and getting scripts to footer significantly helps frontend performance. Unfortunately, this is a difficult to achieve goal. I tried for awhile, but kept hitting unexpected behavior.
* Some of the hook_init logic deals with user session/authentication, so it's difficult to move down to the page_build scope.
* The JS is very order dependent, so we need to be careful about it's execution order.
* It's difficult to piecemeal move things from hook_init to hook_page_build without breaking things.
Perhaps, we can think of this as a first step?
Comment #16
Elijah LynnI added a async and defer (for older browsers) to the socialize.js tag. It helps a bit on the performance end since jQuery waits for DOMContentLoaded to do it's stuff and we have a site that depends on that for bringing in images.
Comment #17
robcolburn CreditAttribution: robcolburn commented@Elijah,
Does it continue to work okay with defer (esp in IE8)?
In particular, can we be sure that gigya will be loaded before window.onload fires? Because once onload fires, we're ok a way ticket through jQuery.ready and Drupal.behaviors to run the "glue" scripts in this module [1], which assume the presence of the gigya global object.
I believe async should be fine, but defer might get us in to trouble, since IE8 doesn't guarantee execution order, and the gigya script take seconds to load.
[1] Example http://cgit.drupalcode.org/gigya/tree/js/gigya_sharebar.js
Aside
------------
If you really need to delay some JavaScript, like say you needed to have most of your JS wait for the "frame buffered" event of from your 3rd party video player. Then you might consider this module JS Defer module we wrote. You need to add custom code to define your deferred scripts, and the event to listen for. But, after that you get a bunch of stuff kicked to the curb.
https://www.drupal.org/project/js_defer
Comment #18
Elijah LynnHave no idea right now, it appeared to be fine in my testing. Our QA team is going through it and I will report back any findings.
Comment #19
msound CreditAttribution: msound commentedIn continuation of Elijah's last comment: Our QA team did find an issue with Patch #16. It breaks Gigya share bar. We are moving back to patch #14.
@robcolburn, you are right. the `gigya` global object is not available when its needed.
Comment #20
robcolburn CreditAttribution: robcolburn commentedI've got an idea here, but haven't had time to go back, but perhaps if someone else does.
Gigya provides a ready callback "onGigyaServiceReady" which you define, then they execute once.
http://developers.gigya.com/010_Developer_Guide/84_Using_the_Client_API/...
We could add a little js to gigya.js
/js/gigya.js
With that in place, we could update all the dependent parts of the script.
Before /js/gigya.behaviors.js
After /js/gigya.behaviors.js
Being careful of anything that makes execution order assumptions.
Comment #21
robcolburn CreditAttribution: robcolburn commentedOkay, here's an patch that essentially does what I just said, in particular:
* It provides `gigya` via promise rather than assumed synchronous global.
* Organizes JS in to hook_library to manage order.
* Updates related JS to use promise.
* Pushes much more logic into hook_page_build.
Before
After
Major Caveats:
* I've only coded this, and haven't tested it at all yet.
* The new async logic is behind a variable `gigya_async`, which you can set in the admin. It's behind a variable because you may have custom code on your site that depends on the synchronous behavior (ex: https://www.drupal.org/node/2387495 ).
Comment #22
robcolburn CreditAttribution: robcolburn commentedFixing typos.
Comment #23
robcolburn CreditAttribution: robcolburn commentedFix typo
Comment #24
Gigya CreditAttribution: Gigya commentedPatches provided that may not suit all sites.
Comment #25
Elijah Lynn@Gigya Could you please respond more appropriately? This is a performance issue. Performance affects all sites. Maybe the patches don't work right now but they could with some more work.
Please be more specific when closing this issue.
Comment #26
Elijah Lynn@Gigya Also, just a friendly heads up that shared accounts are not permitted to comment on nodes/issues. Not sure if this is a shared account but seems that it may be possible given that your username is named after the Gigya product.
See the Drupal Terms of Service here https://www.drupal.org/terms
Comment #27
Elijah LynnComment #28
sokrplare CreditAttribution: sokrplare commentedBumping this issue up as it is an important one for our sites. We've had an internal ticket open since the start of this year waiting on this patch to get wrapped up.
@Gigya - what can we do to help get this into the module?
Comment #29
luciodiri CreditAttribution: luciodiri commentedGigya minor version 4.8 now includes the module scripts via the more appropriate drupal_add_js script.
(instead of drupal_add_html_head)
See in 7.x-4.8 release on gigya.module line 74-96:
Comment #30
robcolburn CreditAttribution: robcolburn commented@luciodiri,
I'm glad Gigya is supporting more industry-standard ways of passing in variables (inline script vs embedded in external script tag.
That doesn't really address the core issue raised here. The Gigya module currently requires that the third-party Gigya script be placed at the HEAD of the document. However, this is considered best practice that all scripts (especially, third party scripts) be placed at the bottom of the document and/or setting the async attribute. This allows the browser to defer loading and processing of the script till after the DOM is loaded (when the user has control of the page again). Placing scripts in the HEAD, causes the browser to lock and stop downloading & rendering the remaining document until it downloads and processes the script. If the Gigya takes even 300ms to load, then that cuts down 300ms performance at the most critical time - initial page load.
So, the first thing that needs to be addressed is pushing the external Gigya script to the bottom of the page and/or setting the async attribute. Afterward, all of the associated Gigya behaviors can be pushed to the bottom of the page. However, if the external Gigya script is asynchronous this will cause a cascading issue on all the Gigya behaviors - whereby they expect the
gigya
variable to be available. To accommodate for that behavior, I re-factored the Gigya behaviors to know that thegigya
variable will be supplied via a JS Promise (technically, jQuery.Deferred object because D7 behaviors are rooted in jQuery).Further, the practice of promising the 3rd-party supplied
gigya
variable acts a safe-guard if the Gigya script becomes unavailable for any reason (Gigya outage, blocked by browser plugin, blocked by ISP/country firewall, network latency, etc…). Protecting the Drupal site and Drupal Behaviors sub-system by isolating down the Gigya functionality.If the maintainers of the Gigya module believe that these concerns are important (front-end performance, front-end application security). Then the previously provided patch can be re-rolled on top of the changes to the module.
Personally, while I wrote a substantial portion of the currently proposed patch, it is not likely that I will assist in re-rolling the patch. The brand that I performed the patching for, was later burned by aforementioned Gigya security issues, and became wary of these performance issues as well. So, there is no business value for me to continue contributing to the Gigya module. Further, the module maintainers seem to ignore and/or reject the issue which is pretty de-motivating for a patch author.
Comment #31
luciodiri CreditAttribution: luciodiri commented@robcolburn,
My comment referred to the fix in version 4.8 of the module, which gives the solution to the original topic raised by @ericduran. this fix has other implications which are important to the module functionality.
The further discussion and suggested patches regarding performance optimization, is a different and more extensive line of discussion. that is the reason that although the original topic was answered, I did not close the issue.
We very much appreciate any contribution and keep track of continuing this discussion and implementing when and what's possible.