Erik Rothoff
Erik Rothoff

Reputation: 5123

Reliably getting favicons in Chrome extensions, chrome://favicon?

I'm using the chrome://favicon/ in my Google Chrome extension to get the favicon for RSS feeds. What I do is get the base path of linked page, and append it to chrome://favicon/http://<domainpath>.

It's working really unreliably. A lot of the time it's reporting the standard "no-favicon"-icon, even when the page really has a favicon. There is almost 0 documentation regarding the chrome://favicon mechanism, so it's difficult to understand how it actually works. Is it just a cache of links that have been visited? Is it possible to detect if there was an icon or not?

From some simple testing it's just a cache of favicons for pages you have visited. So if I subscribe to dribbble.com's RSS feed, it won't show a favicon in my extension. Then if I visit chrome://favicon/http://dribbble.com/ it won't return right icon. Then I open dribbble.com in another tab, it shows its icon in the tab, then when I reload the chrome://favicon/http://dribbble.com/-tab, it will return the correct favicon. Then I open my extensions popup and it still shows the standard icon. But if I then restart Chrome it will get the correct icon everywhere.

Now that's just from some basic research, and doesn't get me any closer to a solution. So my question is: Is the chrome://favicon/ a correct use-case for what I'm doing. Is there any documentation for it? And what is this its intended behavior?

Upvotes: 43

Views: 12995

Answers (7)

Luke Vo
Luke Vo

Reputation: 20668

There is now the official documentation for Favicon Permission - Fetching favicons.

Add favicon to your permission

{
  "name": "Favicon API in a popup",
  "manifest_version": 3,
  ...
  "permissions": ["favicon"],
  ...
}

Access the favicon URL

For my simple case:

const favIconUrl = `chrome-extension://${chrome.runtime.id}/_favicon/?pageUrl=${encodeURIComponent(url)}&size=32`;

From their example:

function faviconURL(u) {
  const url = new URL(chrome.runtime.getURL("/_favicon/"));
  url.searchParams.set("pageUrl", u);
  url.searchParams.set("size", "32");
  return url.toString();
}

const img = document.createElement('img');
img.src = faviconURL("https://www.google.com") 
document.body.appendChild(img);

This also works on Chromium Edge, it automatically transform chrome-extension:// into extension://

Note: when fetching favicons in content scripts, the "_favicon/*" folder must be declared as a web accessible resource. For example:

  "web_accessible_resources": [
    {
      "resources": ["_favicon/*"],
      "matches": ["<all_urls>"],
      "extension_ids": ["*"]
    }
  ]

Upvotes: 3

previous_developer
previous_developer

Reputation: 10988

chrome://favicon url is deprecated in favor of new favicon API with manifest v3.

// manifest.json
{
  "permissions": ["favicon"]
}
// utils.js
function getFaviconUrl(url) {
  return `chrome-extension://${chrome.runtime.id}/_favicon/?pageUrl=${encodeURIComponent(url)}&size=32`;
}

Source: https://groups.google.com/a/chromium.org/g/chromium-extensions/c/qS1rVpQVl8o/m/qmg1M13wBAAJ

Upvotes: 6

Mason
Mason

Reputation: 71

As of Oct 2020, it appears chrome extensions using manifest version 3 are no longer able to access chrome://favicon/* urls. I haven't found the 'dedicated API' the message refers to.

Manifest v3 and higher extensions will not have access to the chrome://favicon host; instead, we'll provide a dedicated API permission and different URL. This results in being able to tighten our permissions around the chrome:-scheme.

Upvotes: 7

user9586876
user9586876

Reputation: 81

I inspected the website-icon in Chrome history page and found this simpler method. You can get the favicon url by --

favIconURL = "chrome://favicon/size/16@1x/" + tab.url;

Don't forget to add "permissions" and "content_security_policy" to Chrome. (https://stackoverflow.com/a/48304708/9586876)

Upvotes: 4

Danziger
Danziger

Reputation: 21161

In the latest version of Chrome, Version 78.0.3904.87 (Official Build) (64-bit)) when tested, adding just img-src chrome://favicon; as content_security_policy will still show 2 warnings:

'content_security_policy': CSP directive 'script-src' must be specified (either explicitly, or implicitly via 'default-src') and must whitelist only secure resources.

And:

'content_security_policy': CSP directive 'object-src' must be specified (either explicitly, or implicitly via 'default-src') and must whitelist only secure resources.

To get rid of them use:

"permissions": ["chrome://favicon/"],
"content_security_policy": "script-src 'self'; object-src 'self'; img-src chrome://favicon;"

Now you can use chrome://favicon/http://example.com without getting any errors or warnings.

Upvotes: 3

Jiacai Liu
Jiacai Liu

Reputation: 2743

In order to use chrome://favicon/some-site in extension. manifest.json need to be updated:

"permissions": ["chrome://favicon/"],
"content_security_policy": "img-src chrome://favicon;"

Test on Version 63.0.3239.132 (Official Build) (64-bit)

Upvotes: 6

joelpt
joelpt

Reputation: 5031

I've seen this problem as well and it's really obnoxious.

From what I can tell, Chrome populates the chrome://favicon/ cache after you visit a URL (omitting the #hash part of the URL if any). It appears to usually populate this cache sometime after a page is completely loaded. If you try to access chrome://favicon/http://yoururl.com before the associated page is completely loaded you will often get back the default 'globe icon'. Subsequently refreshing the page you're displaying the icon(s) on will then fix them.

So, if you can, possibly just refreshing the page you're displaying the icons on just prior to displaying it to the user may serve as a fix.

In my use case, I am actually opening tabs which I want to obtain the favicons from. So far the most reliable approach I have found to obtain them looks roughly like this:

chrome.webNavigation.onCompleted.addListener(onCompleted);

function onCompleted(details)
{
    if (details.frameId > 0)
    {
        // we don't care about activity occurring within a subframe of a tab
        return;
    }

    chrome.tabs.get(details.tabId, function(tab) {
        var url = tab.url ? tab.url.replace(/#.*$/, '') : ''; // drop #hash
        var favicon;
        var delay;

        if (tab.favIconUrl && tab.favIconUrl != '' 
            && tab.favIconUrl.indexOf('chrome://favicon/') == -1) {
            // favicon appears to be a normal url
            favicon = tab.favIconUrl;
            delay = 0;
        }
        else {
            // couldn't obtain favicon as a normal url, try chrome://favicon/url
            favicon = 'chrome://favicon/' + url;
            delay = 100; // larger values will probably be more reliable
        }

        setTimeout(function() {
            /// set favicon wherever it needs to be set here
            console.log('delay', delay, 'tabId', tab.id, 'favicon', favicon);
        }, delay);
    });
}

This approach returns the correct favicon about 95% of the time for new URLs, using delay=100. Increasing the delay if you can accept it will increase the reliability (I'm using 1500ms for my use case and it misses <1% of the time on new URLs; this reliability worsens when many tabs are being opened simultaneously). Obviously this is a pretty imprecise way of making it work but it is the best method I've figured out so far.

Another possible approach is to instead pull favicons from http://www.google.com/s2/favicons?domain=somedomain.com. I don't like this approach very much as it requires accessing the external network, relies on a service that has no guarantee of being up, and is itself somewhat unreliable; I have seen it inconsistently return the "globe" icon for a www.domain.com URL yet return the proper icon for just domain.com.

Hope this helps in some way.

Upvotes: 24

Related Questions