7

Since some time last year, YouTube made it so that every page is not actually loading an entirely new page, but primarily just re-loading the contents in div#content. You can notice this when you click on a link in YouTube and see the red loading bar at the top of the page.

I have a Greasemonkey script that modified elements on YouTube, but now that YouTube doesn't reload the entire page, the Greasemonkey script no longer fires on every "new" page. How can I make the Greasemonkey script fire on every "new" page that I load on YouTube?

I'm using jQuery in this Greasemonkey script. I tried using functions like .on() with DOMNodeInserted but I can't find the right combination to make it work properly. With the event listeners that I've been using, I end up running my script hundreds of times for each page load, such as with the following:

$('div#page').on('DOMNodeInserted', 'div#content', function() { });

Another solution I was thinking of was making all links on YouTube load pages like normal, without the new way that they are doing it.

Gary
  • 2,835
  • 5
  • 28
  • 51
  • 1
    attach an eventlistener to the div#content. – Mike Jan 14 '14 at 20:13
  • @Mike: That's a decent answer, if you provide a short code sample – Robert Harvey Jan 14 '14 at 20:13
  • Yeah, I'm looking for some sample code. I know I need an event listener, but I can't find the right "combination" to get it working properly on YouTube specifically. I've done it before on other sites, but YouTube is a bit different it seems. – Gary Jan 14 '14 at 20:14
  • There is no change event for div's, so your best solution seems to be using Intervals to check to see if the content has changed. [Here's some code someone else wrote, I'm not reinventing the wheel on this one.](http://stackoverflow.com/a/3234646/3116761) – Mike Jan 14 '14 at 20:19
  • Would it also be possible to just watch for a url change? possibly something like this http://stackoverflow.com/questions/2161906/handle-url-anchor-change-event-in-js – Dan Jan 14 '14 at 20:55
  • possible duplicate of [Why doesn't this script work with successive page clicks?](http://stackoverflow.com/questions/18949888/why-doesnt-this-script-work-with-successive-page-clicks) – Brock Adams Jan 15 '14 at 04:00
  • I don't think it's a duplicate. I've found a solution to my own question, and posted it. I think my solution is actually applicable to a wider range of problems related to writing Greasemonkey (and similar) YouTube scripts. – Gary Jan 15 '14 at 19:34

3 Answers3

7

I figured it out myself after some research. First off, I don't like solutions that use setTimeout. This is often one method suggested in favor over the deprecated DOMNodeInserted for instance (which I use in some of my scripts, but try to avoid as much as possible), but if possible, I always prefer a solution where the script actually executes after a specific event. I've posted the solution I initially used in the first section below, then the final solution I used in the second section. All code below requires jQuery.

Decent solution, but not the best

At first, I had a solution where I added a click event to all A elements, which would run a timer that ran my script after 2 seconds. This isn't elegant, because if the page loads quickly, then there's a split second where the script hasn't run. And if the page loads for more than two seconds, then the script doesn't run at all. Script below:

$('a').click(function()
{
    setTimeout(youtubeFunction, 2000);
});

Much better solution

So I began looking for a solution that was related to what I wanted to accomplish. I eventually found other people with a similar problem to mine (such as people wanting to create a Chrome script that modifies YouTube pages). This led me to this particular Stack Overflow solution, which basically says that the red loading bar at the top of YouTube pages was a CSS transition element, and that it created a transitionend (case sensitive) event when it was finished. The code in the linked solution wasn't complete (for me anyway), but it did explain how to achieve a working solution. The code I have runs only once per page, which is perfect. So here's what I have now:

function youtubePageChange()
{
    youtubeFunction();
    $('body').on('transitionend', function(event)
    {
        if (event.target.id != 'progress') return false;
        youtubeFunction();
    });
}

$(youtubePageChange);

To explain the code above, basically I run the code once for when you first load a YouTube page (such as by typing the URL in the address bar). Then for every subsequent click that requires the progress bar, the code runs again.

Red progress bar code

Oh, and for future reference, when the red progress bar appears at the top of YouTube pages, the site temporarily adds a new DIV to the end of BODY, with the following code:

<div id="progress" class="waiting" style="transition-duration: 400ms; width: 99%;"><dt></dt><dd></dd></div>
Community
  • 1
  • 1
Gary
  • 2,835
  • 5
  • 28
  • 51
  • 1
    I'm having problems with this now. It only works half the time. YouTube must've changed something and I can't pinpoint what it is at the moment. I've just reverted back to `setInterval` for now, as a fallback. – Gary Apr 07 '15 at 18:17
  • Same here. Works most of the time, but not all the time – huyz Apr 09 '15 at 22:21
0

Hooking into the popstate might be an option, but i was unable to make that work correctly for some reason (youtube may be preventing it from propagating), so i came up with this that shows the concept:

var currentState = "";
setInterval(function(){
    if (currentState != history.state["spf-referer"]) {
        currentState = history.state["spf-referer"];
        console.log("Do Stuff!");
    }
},250)

Just watches for the history.state to change, at which point it will log. The state should change any time the url changes, even if it wasn't due to a page reload.

Kevin B
  • 92,700
  • 15
  • 158
  • 170
  • Could I do something like use the `hashchange` event? Although I just tried it and it doesn't seem to work, but perhaps something like that might do the trick. – Gary Jan 14 '14 at 20:42
  • No, because the hash isn't changing. The correct event is the popState event, but like i said i can't get that to work with youtube. They're likely stopping it. – Kevin B Jan 14 '14 at 20:43
  • This is heavy on memory, when the user watches a long video. A video for an hour would produce 14400 loop waste checks. – mehulmpt Sep 27 '15 at 16:06
0

You can set a listener which gets called when the page has finished loading.

This is for the new YouTube material design:

body.addEventListener("yt-navigate-finish", function() {
    //your code
});

And this for the old:

window.addEventListener("spfdone", function() {
    //your code
});

(if you are using *monkey, you'll need to use unsafeWindow)

Keep in mind that the old design will be discontinued, so your script may not work after that.

redbeam_
  • 157
  • 11