TamperMonkey Script to replace the YouTube favicon with the channel logo I felt that having a plain red favicon for every youtube tab is a little boring. So, I wrote a TamperMonkey Script with the new ES6 promises that replaces the favicon with the channel's logo. Link to the script here.

The script works by first extracting the channel logo's URL from the window.ytInitialData object. Then, a mutation listener is attached to the title, and whenever the title of the page changes, the script makes a GET request using the window.location.href and then extracts the logo's URL from the responseText.

Since this is my first time writing a userscript, I wanted to confirm that I am following best practices and not breaking anything on the site. Also, is there a better way of achieving the same result?

Thank you for your time.

My code:

// ==UserScript==
// @name        YouTube Favicon to Channel logo
// @version     1.1
// @description Changes the YouTube Favicon to the Channel logo
// @author      Kartik Soneji
// @updateURL   https://openuserjs.org/meta/KartikSoneji/YouTube_Favicon_to_Channel_logo.meta.js
// @match       https://www.youtube.com/*
// @exclude     https://www.youtube.com/
// @exclude     https://www.youtube.com/tv*
// @exclude     https://www.youtube.com/embed/*
// @exclude     https://www.youtube.com/live_chat*
// @grant       none
// @run-at      document-end
// @license     MIT
// ==/UserScript==

(function(){
    'use strict';

    const urlRegex = /https:\/\/yt3.ggpht.com\/a[/-]{1,2}[A-Za-z\d-_]+/;

    function isInIframe(){
        try{
            return window.self !== window.top;
        }
        catch(e){
            return true;
        }
    }
    if(isInIframe()) return;

    console.log("Started");

    roundImageToDataUrl(JSON.stringify(window.ytInitialData).match(urlRegex)[0]).then(setFavicon);

    function run(){ 
        getChannelLogo(window.location.href).then(roundImageToDataUrl).then(setFavicon);
    }

    async function getChannelLogo(url){
        return new Promise((resolve, reject) =&gt; {
            let xhr = new XMLHttpRequest();
            xhr.open("GET", url);
            xhr.onload = () =&gt;{
                let u = xhr.responseText.match(urlRegex)[0];
                console.log(u);
                resolve(u);
            }
            xhr.send();
        });
    }

    let observer = new MutationObserver(run);

    function registerUrlChangeListener(){
        let e = document.querySelector("title");
        if(e){
            //observer.observe(e, {attributes: true});
            observer.observe(document.querySelector("title"), {attributes: true, characterData: true, childList: true});
            return;
        }

        setTimeout(registerUrlChangeListener, 250);
    }
    registerUrlChangeListener();

    async function roundImageToDataUrl(url){
        return new Promise((resolve, reject) =&gt; {
            if(url.src) url = url.src;

            let img = document.createElement("img");
            img.crossOrigin = "anonymous";
            img.src = url;
            img.style = "position: absolute; top: -100000px; left: -100000px;";

            img.onload = function(){
                let canvas = document.createElement("canvas"), g = canvas.getContext("2d");

                canvas.height = img.naturalHeight;
                canvas.width = img.naturalWidth;

                g.beginPath();
                g.arc(canvas.width/2, canvas.height/2, canvas.width/2, 0, Math.PI * 2);
                g.clip();

                g.drawImage(img, 0, 0);

                img.parentElement.removeChild(img);

                resolve(canvas.toDataURL());
            }

            document.body.appendChild(img);
        });
    }

    function setFavicon(url){
        let a = document.querySelectorAll("link[rel *= 'icon']");

        if(a.length == 0){
            let link = document.createElement('link');
            link.type = 'image/x-icon';
            link.rel = 'icon';
            document.head.appendChild(link);
            a = [link];
        }

        for(let i of a) i.href = url;
    }
})();
img.style = "position: absolute; top: -100000px; left: -100000px;";
document.body.appendChild(img);

img.parentElement.removeChild(img);



This seems weird.  The canvas can be drawn from an unattached image and these lines can be removed.


  if(url.src) url = url.src;

  img.src = url;



A more idiomatic phrasing is img.src = url.src || url.


 .querySelectorAll("link[rel *= 'icon']");



Unless you have something specific in mind, I'd use [rel='icon'] here.  It's faster, and not clear that matching an exotic future variant like &lt;link rel="BiscottiConfig"&gt; is going to be desirable.


let



Except for the final loop, every use of let in your script can be replaced by const.  It's a good habit, even if the aesthetics are inferior to let or var.


const urlRegex = /https:\/\/yt3.ggpht.com\/a[/-]{1,2}[A-Za-z\d-_]+/;

roundImageToDataUrl(JSON.stringify(window.ytInitialData).match(urlRegex)[0]).then(setFavicon);



This looks like a misstep.  Youtube avatars have id=img; why not just roundImageToDataUrl( document.getElementById("img").src )?  

Dealing with future changes to the host page is a constant challenge with userscripts.  There are many things that have to remain unchanged for this to work (the image host, the URL format, the name, content and encoding of ytInitialData) and I'll predict that some of them won't.  Selecting the avatar itself with semantic markup is likely to be less brittle, and easier to fix if the markup changes (assuming there will always be an avatar and it will always have some semantics attached to it).