If page is interactive, fire pageLoaded() immediately #274
Conversation
When using Turbolinks with the `async` script attribute, Turbolinks may execute after DOMContentLoaded. To fix this, we only attach a handler to DOMContentLoaded if the readystate is not "loading" (that is to say, it is "interactive" or "complete"). Otherwise, we fire the pageLoaded event immediately without installing the handler. This changes Turbolinks interaction with `defer` scripts. After this change, an `async` Turbolinks script may execute *before* `defer`ed scripts. This is because the browser executes all `defer` scripts *before* the DOMContentLoaded event. Previously, Turbolinks would always execute after these scripts, but may now execute before them in some cases depending on when Turbolinks has finished downloading.
Not sure the whole caveat about "now Turbolinks can execute before deferred scripts" even matters, but figured I'd mention it. Closes #266 |
I've been thinking more on this and now I'm not sure what the right thing to do is. |
This could be easily fixed with just tracking the state somewhere to make sure it only goes off once, right?
This is interesting. Is this really a bad thing, though? If someone's just using JQuery and $(document).ready hooks, adding |
The difference is |
It depends on what you think
To be clear, it can fire any time after the document has finished parsing (readystate |
+1. Agree the initial I don’t think apps should use |
From the README:
This change assumes that turbolinks will always be loaded in the initial page's js (synchronously, // <script src="main.js">
setTimeout(function() {
lazyLoad("turbolinks.js")
}, 10000) Would you expect a Here's one way Turbolinks could handle it: --- a/src/turbolinks/controller.coffee
+++ b/src/turbolinks/controller.coffee
@@ -17,9 +17,18 @@ class Turbolinks.Controller
start: ->
if Turbolinks.supported and not @started
addEventListener("click", @clickCaptured, true)
- addEventListener("DOMContentLoaded", @pageLoaded, false)
+ @startHistory() # Start history to set initial `@location`
+ switch document.readyState
+ when "loading"
+ # Page is still loading, fire `turbolinks:load` when it's interactive
+ addEventListener("DOMContentLoaded", @pageLoaded, false)
+ when "interactive"
+ # Page is interactive, fire `turbolinks:load` now
+ @pageLoaded()
+ else
+ # Page is loaded, don't fire `turbolinks:load`
+ @lastRenderedLocation = @location
@scrollManager.start()
- @startHistory()
@started = true
@enabled = true |
I guess you're counting on the fact that the
So, a Turbolinks script with |
My answer to that is "yeah, probably". I mean, what are the odds someone is inserting Turbolinks onto a page and counting on turbolinks:load not firing? |
Cool, yeah, I'm just being difficult. 😬 |
src/turbolinks/controller.coffee
Outdated
if document.readyState is "loading" | ||
addEventListener("DOMContentLoaded", @pageLoaded, false) | ||
else | ||
@pageLoaded() |
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 think @startHistory()
needs to be called before this condition so @location
is set for @pageLoaded()
to use.
👍 moved startHistory |
Proposing a README change as well to be more specific about when turbolinks:load fires, happy to workshop the phrasing. |
👍 to the README change, but it's conflicting with master somehow. Mind fixing it up? |
Removed the whitespace removal and it looks good now. |
❤️ |
Turbolinks 5.0.1 is out! |
Turbolinks 5.0.1 introduced a major breaking change in a patch version. This is a hotfix. For more information, see: turbolinks/turbolinks#274
Turbolinks 5.0.1 introduced a major breaking change in a patch version. This is a hotfix. For more information, see: turbolinks/turbolinks#274
The odds are "definitely" because |
@glebm That comment was in regard to somebody injecting Turbolinks into a page long after initial load (i.e. not async or defer). I still don't think we actually broke anything for you. |
@nateberkopec What do you mean? It was working and now it's not (well I've patched it on master but then people will run If you believe that it wasn't working before either you can check that very easily: https://github.com/thredded/thredded#development PS It's 3:30AM here so I'm logging off for a while |
@nateberkopec I'm also having issues with the new bahavior, and am unsure how to fix it. Until now I've loaded certain page elements that take longer to load due to db queries via ajax - I added Now as If anyone has a suggestion how to properly load page elements automatically via ujs once the page itself is loaded, I'd be thankful. Using So the question is: which event can we rely on that will trigger after all javascript has been loaded async, even on the initial page load? Until turbolinks 5.0.0 it was |
@maia Upgrade to >= 5.0.2 |
@nateberkopec latest published version as of today is 5.0.1 ??? is this still a not fixed bug? |
@nateberkopec great, thx! |
When using Turbolinks with the
async
script attribute, Turbolinks mayexecute after DOMContentLoaded. To fix this, we only attach a handler
to DOMContentLoaded if the readystate is not "loading" (that is to say,
it is "interactive" or "complete"). Otherwise, we fire the pageLoaded
event immediately without installing the handler.
This changes Turbolinks interaction with
defer
scripts. After thischange, an
async
Turbolinks script may execute beforedefer
edscripts. This is because the browser executes all
defer
scripts beforethe DOMContentLoaded event. Previously, Turbolinks would always execute
after these scripts, but may now execute before them in some cases
depending on when Turbolinks has finished downloading.