How to Detect Page Navigation on Youtube and Modify Its Appearance Seamlessly

How to detect page navigation on YouTube and modify its appearance seamlessly?

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(document) in devtools console and inspect the output.

    Sample Image

    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: the matches key encompasses the entire youtube.com domain so that the content script runs when the user first opens the home youtube page then navigates to a watch page.

content.js:

document.addEventListener('yt-navigate-start', process);

if (document.body) process();
else document.addEventListener('DOMContentLoaded', process);

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>');
}

How to get the new youtube title when yt-navigate-finish runs?

Read it from document.querySelector('a.ytp-title-link').textContent

Getting the actual HTML after a transition accures in a youtube page (chrome extension)

If you debug your script you will see that url_encoded_fmt_stream_map isn't added anywhere in the document after in-site navigation. Hacking the site JS shows that ytplayer.config variable is updated directly in such cases.

We'll have to inject our script into the page itself.

Declare a content script that runs on all of youtube in manifest.json:

"content_scripts": [{
"matches": [ "*://*.youtube.com/*" ],
"js": [ "content.js" ],
"run_at": "document_start"
}]

content.js:

function injectedCode() {
document.addEventListener("spfdone", process);
document.addEventListener("DOMContentLoaded", process);

function process() {
function getTags(fmts_info) {
var tags = [];
r = /itag=(\d+)/;
for(var i = 0; i < fmts_info.length; i++) {
var matches = fmts_info[i].match(r);
if (matches)
tags.push(matches[1]);
}
return tags;
}
if (location.href.indexOf('watch?') < 0) {
return;
}
var tags = getTags(ytplayer.config.args.url_encoded_fmt_stream_map.split(','));
console.log(tags);
}
}

function getFunctionText(f) {
return f.toString().match(/\{[\s\S]*\}$/)[0];
}

document.documentElement.appendChild(document.createElement("script")).text =
getFunctionText(injectedCode)

To pass the results back to content script use custom events, or externally_connectable to send data directly to extension's background page script.

Is there a JavaScript / jQuery DOM change listener?

For a long time, DOM3 mutation events were the best available solution, but they have been deprecated for performance reasons. DOM4 Mutation Observers are the replacement for deprecated DOM3 mutation events. They are currently implemented in modern browsers as MutationObserver (or as the vendor-prefixed WebKitMutationObserver in old versions of Chrome):

MutationObserver = window.MutationObserver || window.WebKitMutationObserver;

var observer = new MutationObserver(function(mutations, observer) {
// fired when a mutation occurs
console.log(mutations, observer);
// ...
});

// define what element should be observed by the observer
// and what types of mutations trigger the callback
observer.observe(document, {
subtree: true,
attributes: true
//...
});

This example listens for DOM changes on document and its entire subtree, and it will fire on changes to element attributes as well as structural changes. The draft spec has a full list of valid mutation listener properties:

childList

  • Set to true if mutations to target's children are to be observed.

attributes

  • Set to true if mutations to target's attributes are to be observed.

characterData

  • Set to true if mutations to target's data are to be observed.

subtree

  • Set to true if mutations to not just target, but also target's descendants are to be observed.

attributeOldValue

  • Set to true if attributes is set to true and target's attribute value before the mutation needs to be recorded.

characterDataOldValue

  • Set to true if characterData is set to true and target's data before the mutation needs to be recorded.

attributeFilter

  • Set to a list of attribute local names (without namespace) if not all attribute mutations need to be observed.

(This list is current as of April 2014; you may check the specification for any changes.)



Related Topics



Leave a reply



Submit