How to detect page navigation on YouTube and modify HTML before page is rendered?

I'm making a simple Chrome extension to add up the length of each video in a YouTube playlist and insert the total length in the page. I've succeeded at that, but my script only works after refreshing a page but not when the site is navigated. That's not very convenient though.

Is it possible to detect page navigation on YouTube and insert HTML into the page before it's rendered in the browser so that the added content is shown immediately without any delay and no page refresh is required?

Example link: https://www.youtube.com/playlist?list=PL_8APVyhfhpdgMJ3J80YQxWBMUhbwXw8B

P.S. My question is not the same as Modify elements immediately after they are displayed (not after page completely loads) in greasemonkey script? because I've tried MutationObserver and the problem is the same - a refresh is needed to show changes to the web page:

var observer = new MutationObserver(function(mutations) {
    for (var i=0; i<mutations.length; i++) {
        var mutationAddedNodes = mutations[i].addedNodes;
        for (var j=0; j<mutationAddedNodes.length; j++) {
            var node = mutationAddedNodes[j];
            if (node.classList && node.classList.contains("timestamp")) {
                var videoLength = node.firstElementChild.innerText;
                observer.disconnect();    

                var lengthNode = document.createElement("li");
                var lengthNodeText = document.createTextNode(videoLength);
                lengthNode.appendChild(lengthNodeText);
                document.getElementsByClassName("pl-header-details")[0].appendChild(lengthNode);

                return;
            }
        }
    }
});
observer.observe(document, {childList: true, subtree: true});

Answers:

Answer

Youtube site doesn't reload pages on navigation, it replaces the history state.

The extension's content scripts aren't reinjected when URL changes without a page being reloaded. Naturally when you reload a page manually the content script is executed.

There are several methods to detect page transitions on Youtube site:

  • using a background page script: webNavigation API, tabs API
  • using a content script: transitionend event for the progress meter on video pages
  • using a content script and a site-specific event triggered on video navigation:

    Run getEventListeners(window) in devtools console and inspect the output.

    enter image description here

    yt-navigate-start is what we need in this case.

    Notes:

    • for other tasks we might want yt-navigate-finish
    • the old youtube design was using spfdone event

manifest.json:

{
  "name": "YouTube Playlist Length",
  "version": "0.0.1",
  "manifest_version": 2,
  "description": ".............",
  "content_scripts": [{
      "matches": [ "*://*.youtube.com/*" ],
      "js": [ "content.js" ],
      "run_at": "document_start"
  }]
}

Note, when we load the content script at document_start it will make our DOMContentLoaded listener run slightly sooner compared to the default behavior when content scripts are injected slightly after DOMContentLoaded. With document_start the content script runs when there's nothing in the document body and even no head element. Attaching a listener to document is possible, though, just the same.

content.js:

window.addEventListener("spfdone", process); // old youtube design, just in case    
window.addEventListener("yt-navigate-start", process); // new youtube design

document.addEventListener("DOMContentLoaded", process); // one-time early processing
window.addEventListener("load", postProcess); // one-time late postprocessing 

The process function will alter the page.
Note, the specified element classes and structure will change in the future.

function process() {
  if (!location.pathname.startsWith('/playlist')) {
    return;
  }
  var seconds = [].reduce.call(
    document.getElementsByClassName('timestamp'),
    function (sum, ts) {
      var minsec = ts.textContent.split(':');
      return sum + minsec[0] * 60 + minsec[1] * 1;
    },
    0,
  );
  if (!seconds) {
    console.warn('Got no timestamps. Empty playlist?');
    return;
  }
  var timeHMS = new Date(seconds * 1000).toUTCString().split(' ')[4]
    .replace(/^[0:]+/, ''); // trim leading zeroes
  document.querySelector('.pl-header-details')
    .insertAdjacentHTML('beforeend', '<li>Length: ' + timeHMS + '</li>');
}

The postProcess function that we attached on load will run only one time when the site is opened. Use it for one-time processing of the page after all of its scripts are loaded.

Answer

2017 answer:

I use this for new Material Design version Youtube

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

and this for old Youtube

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

code come from 2 script I wrote call
"Youtube subtitle downloader" and "Youtube auto subtitle downloader".

both of them work, I tested.

if you are interested in my script and want know more detail:
https://github.com/1c7/Youtube-Auto-Subtitle-Download

Tags

Recent Questions

Top Questions

Home Tags Terms of Service Privacy Policy DMCA Contact Us Javascript

©2020 All rights reserved.