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});
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:
transitionend
event for the progress meter on video pagesusing a content script and a site-specific event triggered on video navigation:
Run getEventListeners(window)
in devtools console and inspect the output.
yt-navigate-start is what we need in this case.
Notes:
spfdone
eventmanifest.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.
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
©2020 All rights reserved.