{"id":542,"date":"2018-08-17T18:00:02","date_gmt":"2018-08-18T01:00:02","guid":{"rendered":"https:\/\/eligrey.com\/blog\/?p=542"},"modified":"2025-06-20T01:08:05","modified_gmt":"2025-06-20T08:08:05","slug":"favioli","status":"publish","type":"post","link":"https:\/\/eligrey.com\/blog\/favioli\/","title":{"rendered":"Favioli"},"content":{"rendered":"<p><a href=\"https:\/\/favioli.com\">\ud83e\udd2f Favioli<\/a> is a productivity extension that makes it easier to recognize tabs within Chrome.<\/p>\n<p>Favioli was originally inspired by two things. The first thing that spurred the idea was Eli Grey&#8217;s personal site and its use of <a href=\"https:\/\/github.com\/eligrey\/emoji-favicon-toolkit\">Emoji Favicon Toolkit<\/a> to make randomized emoji favicons. The favicon of their site shows different emoji on-load that persist within a session. It&#8217;s pretty creative and fun! This was the creative inspiration for Favioli.<\/p>\n<p>There is an <a href=\"https:\/\/eligrey.com\/blog\/wp-content\/themes\/eligrey.com\/js\/favicon.js\">Emoji Favicon Toolkit usage example<\/a> here on eligrey.com.<\/p>\n<p>The practical inspiration for Favioli came from my day job. We have a lot of internal tools and sites, and they tend to either not have favicons, or have the standard Sony logo. For me this was a bit of a pain, because I love to pin my tabs. I couldn&#8217;t tell these sites apart.<\/p>\n<p>I could use a Chrome extension that lets me set custom favicons, but then I&#8217;d have to go through and specify each one. If I were to use emojis, I wouldn&#8217;t have to deal with finding art for each individual site, and I could make something that could automatically make all of these pages recognizable at a glance.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"wp-image-600 aligncenter\" src=\"https:\/\/eligrey.com\/blog\/wp-content\/uploads\/2018\/08\/comparison-300x188.png\" alt=\"\" width=\"673\" height=\"422\" srcset=\"https:\/\/eligrey.com\/blog\/wp-content\/uploads\/2018\/08\/comparison-300x188.png 300w, https:\/\/eligrey.com\/blog\/wp-content\/uploads\/2018\/08\/comparison-768x480.png 768w, https:\/\/eligrey.com\/blog\/wp-content\/uploads\/2018\/08\/comparison-1024x640.png 1024w\" sizes=\"auto, (max-width: 673px) 100vw, 673px\" \/><\/p>\n<p>This project has been a fun exploration of javascript strings, chrome extensions, and browser\/os string support.<\/p>\n<p>As we look through everything, feel free to follow along by looking at Favioli&#8217;s <a href=\"https:\/\/github.com\/ivebencrazy\/favioli\">source code<\/a>! All the Favioli code that is my own is licensed with the <a href=\"https:\/\/unlicense.org\/\">Unlicense<\/a>, so feel free to go crazy with it.<\/p>\n<p><!--more--><\/p>\n<h2>Structure of a Browser Extension<\/h2>\n<p>There are essentially 4 pieces of any fully-local web extension that modifies a web page:<\/p>\n<ul>\n<li>Options: The internal website that extensions use for full-screen setting updates<\/li>\n<li>Popup: The mini-website that pops up when you click the extension icon<\/li>\n<li>Background: The background extension process that does stuff&#8230; in the background&#8230;<\/li>\n<li>ContentScript: The script that gets added to websites you visit in your web browser.<\/li>\n<\/ul>\n<p>We use all of these except popup for Favioli.&nbsp; The general structure of our extension looks like this:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\" aligncenter\" src=\"https:\/\/eligrey.com\/blog\/wp-content\/favioli\/favioli-structure.png\" width=\"649\" height=\"458\"><\/p>\n<h3>Settings: Options and Popup Pages<\/h3>\n<p>This is the simplest piece of Favioli, and for a good reason; it doesn&#8217;t really do much. All we do here is run a basic website, &#8220;options.html&#8221;, which saves data to a browser-provided storage mechanism. Similar to using localStorage, key differences being that it can sync between different browsers on multiple computers, and is available to other areas of our Chrome Extension that don&#8217;t interact with a webpage. What this boils down to in Favioli is simply saving custom overrides and, in the future, things like icon packs and other settings.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\" wp-image-597 aligncenter\" src=\"https:\/\/eligrey.com\/blog\/wp-content\/uploads\/2018\/08\/Options-300x204.png\" alt=\"\" width=\"643\" height=\"437\" srcset=\"https:\/\/eligrey.com\/blog\/wp-content\/uploads\/2018\/08\/Options-300x204.png 300w, https:\/\/eligrey.com\/blog\/wp-content\/uploads\/2018\/08\/Options-768x522.png 768w, https:\/\/eligrey.com\/blog\/wp-content\/uploads\/2018\/08\/Options-1024x696.png 1024w, https:\/\/eligrey.com\/blog\/wp-content\/uploads\/2018\/08\/Options.png 1980w\" sizes=\"auto, (max-width: 643px) 100vw, 643px\" \/><\/p>\n<p>The most complicated piece of the options page is the emoji selector, adapted from Dominic Valenicana&#8217;s <a href=\"https:\/\/github.com\/Kiricon\/emoji-selector\">Emoji Selector<\/a>. We use this to define a custom HTML element that we can use for the options page.<\/p>\n<p>One thing we haven&#8217;t implemented in Favioli is a popup page. In Chrome Extension land, the &#8220;popup&#8221; is the mini-site that shows up when you click the extenstion icon. We currently don&#8217;t use this for Favioli, but it would be extremely useful for quick-pinning specific emojis to sites we are currently visiting. It would essentially follow the same formula as our Options page, though; featuring a user interface that simply feeds information to our background process.<\/p>\n<h3>Background: The Decision Engine<\/h3>\n<p>The Background process is Favioli&#8217;s primary decision engine. It takes information from our content script and background process, and spits the correct emoji to each page. Our decision is a simple priority list: we just check each item and use the first rule to apply:<\/p>\n<table style=\"font-size: medium;\">\n<thead>\n<tr>\n<th>Priority<\/th>\n<th>Rule<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>1<\/td>\n<td><strong>User-set Overrides<\/strong>: If a user has specified a favicon match via the options page, then the site will use that favicon.<\/td>\n<\/tr>\n<tr>\n<td>2<\/td>\n<td><strong>Site Default<\/strong>: If the site has a favicon, then use that.<\/td>\n<\/tr>\n<tr>\n<td>3<\/td>\n<td><strong>Random Emoji<\/strong>: If the site has not natural favicon, we generate a random one via a hashing algorithm.<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>The user-set overrides and site defaults are pretty straightforward; we can match pieces of a url, or match a regex. If it doesn&#8217;t use those, then fall back to the site&#8217;s default favicon. The more interesting case is if a site doesn&#8217;t have a native Favicon.<\/p>\n<h3>Random Emoji Hash<\/h3>\n<p>The reasoning for using a non-cryptographic hash to determine emojis is based on one idea: there&#8217;s no way in hell we&#8217;re storing the settings of each site a person visits. THAT would be a pain in the ass. We use a hash of the website&#8217;s host to map to an emoji with a custom set of char codes (we don&#8217;t really want to randomly apply flag and symbol emojis to all the sites. It&#8217;s just not as fun). This creates a function that creates a random emoji that is always the same for an individual website host, without storing any data.<\/p>\n<h3>Background: Applying the Favicon<\/h3>\n<p>Our decision process is run in 3 cases, which are in the background.js:<\/p>\n<pre lang=\"javascript\">\/\/ After we fetch our settings, start listening for url updates\ninit().then(function() {\n  \/\/ If a tab updates, check to see whether we should set a favicon\n  chrome.tabs.onUpdated.addListener(function(tabId, opts, tab) {\n    tryToSetFavicon(tabId, tab)\n  })\n})\n\n\/\/ Manually sent Chrome messages\nchrome.runtime.onMessage.addListener(function(message, details) {\n  \/\/ If we manually say a tab has been updated, try to set favicon\n  \/\/ This happens when contentScript loads before settings are ready\n  if (message === \"updated:tab\") tryToSetFavicon(details.tab.id, details.tab)\n\n  \/\/ If our settings change, re-run init to fetch new settings\n  if (message === \"updated:settings\") init()\n})<\/pre>\n<p><code>tryToSetFavicon<\/code> decides what emoji we want to use and sends it to our content script as an emoji message string, to render our emoji as a favicon. Our content script has a few additional checks for whether we should show our favicon, because there are some checks that can only be done on each individual site.<\/p>\n<p>We can think of our bakground process as determining <strong>WHEN<\/strong> to set a favicon, and <strong>WHAT<\/strong> to set it as. The content script will determine <strong>IF<\/strong> a favicon truly gets set.<\/p>\n<h3>Content Script: Building Text Favicons<\/h3>\n<p>Our content script is the script that runs on each website we visit, appending or replacing the favicon when our background process tells it to. This scripts could be quite a bit simpler than we made it. The reason? Essentially, this boils down to one thing:<\/p>\n<blockquote><p>Favicons are images. Emojis are not images.<\/p><\/blockquote>\n<p>Unfortunately, favicons still must be images, so we have to do a bit of hackery magic in order to show our native text emojis show up as favicons. This was the clever bit of code Eli wrote that I borrowed to make Favioli an image-less experience.<\/p>\n<p>I should probably mention that there&#8217;s definitely some over-engineering in Favioli. Practically, using the same clever method as Eli for creating legitimate emoji text favicons is not reeeeally necessary, and makes for some complications when it comes to multi-platform support. So this script coooould be &#8220;use a pre-rendered emoji image and add it as a favicon.&#8221; That script would be easy, but WHAT FUN WOULD THAT BE?!?!<\/p>\n<p>Let&#8217;s jump into a condensed version of <a href=\"https:\/\/github.com\/ivebencrazy\/favioli\/blob\/master\/source\/utilities\/faviconHelpers.js#L60\">the code we use<\/a>&nbsp;to create our favicons:<\/p>\n<pre lang=\"javascript\">\/\/ Initialize canvas and context to render emojis\n\nconst PIXEL_GRID = 16 \/\/ Standard favIcon size 16x16\nconst EMOJI_SIZE = 256 \/\/ 16 x 16\n\nconst canvas = document.createElement(\"canvas\")\ncanvas.width = canvas.height = EMOJI_SIZE\n\nconst context = canvas.getContext(\"2d\")\ncontext.font = `normal normal normal ${EMOJI_SIZE}px\/${EMOJI_SIZE}px sans-serif`\ncontext.textAlign = \"center\"\ncontext.textBaseline = \"middle\"\n\nfunction createEmojiUrl(char) {\nconst { width } = context.measureText(char)\n\n\/\/ Bottom and Left of the emoji (where we start drawing on canvas)\n\/\/ Since favicons are a square, we can use the same number for both\nconst center = (EMOJI_SIZE + EMOJI_SIZE \/ PIXEL_GRID) \/ 2\nconst scale = Math.min(EMOJI_SIZE \/ width, 1) \/\/ Adjust canvas to fit\nconst center_scaled = center \/ scale\n\n\/\/ Scale and resize the canvas to adjust for width of emoji\ncontext.clearRect(0, 0, EMOJI_SIZE, EMOJI_SIZE)\ncontext.save()\ncontext.scale(scale, scale)\n\n\/\/ context.fillText(char, bottom, left)\ncontext.fillText(char, center_scaled, center_scaled)\ncontext.restore()\n\n\/\/ We need it to be an image\nreturn canvas.toDataURL(\"image\/png\")<\/pre>\n<p>We make a canvas, draw a centered piece of favicon text, then convert that canvas drawing into a png <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTTP\/Basics_of_HTTP\/Data_URIs\">data url<\/a>. We can set favicons with data urls, so at this point, we just need to add our favicon to the site!<\/p>\n<h3>Content Script: Appending Favicons<\/h3>\n<p>The last step of Favioli is appending a favicon to the site we visit. Appending a favicon to an existing site can have a few complications, mainly stemming from the fact that different sites apply their favicons in different ways. We have a few different cases that affect how Favioli adds favicons:<\/p>\n<h4>1. Custom Favicon Path<\/h4>\n<p>This is the easiest to deal with; when a site has a custom favicon path, we can be assured that it has a favicon, and we can either override it or leave it be, depending on our settings.<\/p>\n<h4>2. Weird path changes<\/h4>\n<p>Sometimes a site changes path and expects the favicon to persist through the site. To maintain consistency, and to avoid unnecessary work, favioli memoizes the decision of whether a site has a custom favicon within the context of a session.<\/p>\n<h4>3. No Favicon in the HTML<\/h4>\n<p>When there is no specified favicon, there are two things it could mean. It could mean that a website is either using the default <code>favicon.ico<\/code>, or it doesn&#8217;t have a favicon. It could be confusing if we try to determine which of these is happening, though. So instead, Favioli simply appends a <code>favicon.ico<\/code> link after it appends our emoji favicon in cases where we shouldn&#8217;t override the default emoji. This way, our emoji favicon gets overridden by the default one.<\/p>\n<pre lang=\"javascript\">const href = memoizedEmojiUrl(name);\n\nif (existingFavicon) {\n  existingFavicon.setAttribute(\"href\", href);\n} else {\n  const link = createLink(href, EMOJI_SIZE, \"image\/png\");\n  existingFavicon = documentHead.appendChild(link);\n\n  if (!shouldOverride) {\n    const defaultLink = createLink(\"\/favicon.ico\");\n    documentHead.appendChild(defaultLink);\n  }\n}<\/pre>\n<h2>Now we have done it!<\/h2>\n<p>At this point, we have appended a text emoji as a favicon, so we&#8217;ve successfully completed our mission to emoji-fy the universe! With Favioli, we no longer need to worry about the dreaded favicon-less existence that some people still somehow call life.<\/p>\n<h2>Where do we go from here?<\/h2>\n<p>There are a myriad of ways we can extend Favioli in the future. To give you an idea, here are some ideas we have been thinking about:<\/p>\n<h3>Custom sets for Randomly Selected Emojis<\/h3>\n<p>This would be the easiest way to deal with cross-platform compatibility; just change the set that we randomly select from. This will let people better customize their experience.<\/p>\n<h3>Custom pngs as Favicons<\/h3>\n<p>This would create more utility for Favioli. I feel our default offering is better than most favicon replacement extensions. But not being able to set custom pngs is a real killer from being the best one out there. Also, though&#8230; gif favicons. Imagine how hilarious (and technically dumb) that could be. Using pngs as a fallback could also be used for browsers, os that are insufficient for life and don&#8217;t have the new emojis.<\/p>\n<h3>Custom Application Overrides<\/h3>\n<p>It would be cool to be able to override some aspects of page load in a smarter and more fun way. Image \ud83d\ude2d emojis for 404\/500 page responses, \ud83d\udc40 for non-https sites or something like that. These would be configured in settings, but could be a fun way to interact with the web.<\/p>\n<h3>Popup Page<\/h3>\n<p>This is a pretty obvious one; A useful UI update<\/p>\n<h3>Stats for nerds<\/h3>\n<p>Being able to apply Favioli to browser history would be fun. We&#8217;d have to play with permission settings so that Favioli is only enabled on this page, but could be interesting&#8230;<\/p>\n<p>Anywhoooooo<\/p>\n<p><a href=\"https:\/\/favioli.com\">This is Favioli<\/a>! Check it out! Edit the <a href=\"https:\/\/github.com\/ivebencrazy\/favioli\">source code<\/a> and send a PR if you want to help, or just use it!<\/p>\n<p>\ud83d\ude18 \u2764\ufe0f\ud83e\udd2f,<br \/>\n<a href=\"https:\/\/bpev.me\">Ben Pevsner<\/a><!--\n\n<img loading=\"lazy\" decoding=\"async\" class=\" wp-image-599 aligncenter\" src=\"https:\/\/eligrey.com\/blog\/wp-content\/uploads\/2018\/08\/29526742_10215362185271619_136158773_o-300x225.png\" alt=\"\" width=\"563\" height=\"422\" srcset=\"https:\/\/eligrey.com\/blog\/wp-content\/uploads\/2018\/08\/29526742_10215362185271619_136158773_o-300x225.png 300w, https:\/\/eligrey.com\/blog\/wp-content\/uploads\/2018\/08\/29526742_10215362185271619_136158773_o-768x577.png 768w, https:\/\/eligrey.com\/blog\/wp-content\/uploads\/2018\/08\/29526742_10215362185271619_136158773_o-1024x769.png 1024w, https:\/\/eligrey.com\/blog\/wp-content\/uploads\/2018\/08\/29526742_10215362185271619_136158773_o.png 1065w\" sizes=\"auto, (max-width: 563px) 100vw, 563px\" \/>--><\/p>\n","protected":false},"excerpt":{"rendered":"<p>\ud83e\udd2f Favioli is a productivity extension that makes it easier to recognize tabs within Chrome. Favioli was originally inspired by two things. The first thing that spurred the idea was Eli Grey&#8217;s personal site and its use of Emoji Favicon Toolkit to make randomized emoji favicons. The favicon of their site shows different emoji on-load [&hellip;]<\/p>\n","protected":false},"author":3,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[1],"tags":[],"class_list":["post-542","post","type-post","status-publish","format-standard","hentry","category-uncategorized"],"jetpack_featured_media_url":"","jetpack_shortlink":"https:\/\/wp.me\/sfpUD-favioli","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/eligrey.com\/blog\/wp-json\/wp\/v2\/posts\/542","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/eligrey.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/eligrey.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/eligrey.com\/blog\/wp-json\/wp\/v2\/users\/3"}],"replies":[{"embeddable":true,"href":"https:\/\/eligrey.com\/blog\/wp-json\/wp\/v2\/comments?post=542"}],"version-history":[{"count":0,"href":"https:\/\/eligrey.com\/blog\/wp-json\/wp\/v2\/posts\/542\/revisions"}],"wp:attachment":[{"href":"https:\/\/eligrey.com\/blog\/wp-json\/wp\/v2\/media?parent=542"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/eligrey.com\/blog\/wp-json\/wp\/v2\/categories?post=542"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/eligrey.com\/blog\/wp-json\/wp\/v2\/tags?post=542"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}