Simplify favicons and other web icons (#31000)

* Simplify favicons and other web icons

browserconfig.xml seems to have died with Internet Explorer
`apple-touch-icon` is awfully documented but seems like larger sizes are now preferred
Use PNG for the favicon as things now support it across the board, we could even consider moving to SVG favicons in the future

Optimised using oxipng, this is to simplify icon replacement in `element-web-bin`

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Fix paths

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Update tests

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Remove border around favicons

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Add test

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Add test

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
Michael Telatynski 2025-10-14 14:08:52 +01:00 committed by GitHub
parent 5dc8edcae0
commit b3188b47be
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
35 changed files with 123 additions and 133 deletions

View File

@ -5,69 +5,39 @@
"theme_color": "#76CFA6",
"start_url": "index.html",
"icons": [
{
"src": "/vector-icons/44.png",
"sizes": "44x44",
"type": "image/png"
},
{
"src": "/vector-icons/1240x600.png",
"sizes": "1240x600",
"type": "image/png"
},
{
"src": "/vector-icons/300.png",
"sizes": "300x300",
"type": "image/png"
},
{
"src": "/vector-icons/150.png",
"sizes": "150x150",
"type": "image/png"
},
{
"src": "/vector-icons/88.png",
"sizes": "88x88",
"type": "image/png"
},
{
"src": "/vector-icons/24.png",
"sizes": "24x24",
"type": "image/png"
},
{
"src": "/vector-icons/50.png",
"sizes": "50x50",
"type": "image/png"
},
{
"src": "/vector-icons/620x300.png",
"sizes": "620x300",
"type": "image/png"
},
{
"src": "/vector-icons/1024.png",
"sizes": "1024x1024",
"type": "image/png"
},
{
"src": "/vector-icons/180.png",
"sizes": "180x180",
"type": "image/png"
},
{
"src": "/vector-icons/152.png",
"sizes": "152x152",
"type": "image/png"
},
{
"src": "/vector-icons/120.png",
"sizes": "120x120",
"type": "image/png"
},
{
"src": "/vector-icons/76.png",
"sizes": "76x76",
"src": "/vector-icons/144.png",
"sizes": "144x144",
"type": "image/png"
},
{
"src": "/vector-icons/152png",
"sizes": "152x152",
"type": "image/png"
},
{
"src": "/vector-icons/180.png",
"sizes": "180x180",
"type": "image/png"
},
{
"src": "/vector-icons/512.png",
"sizes": "512x512",
"type": "image/png"
},
{
"src": "/vector-icons/1024.png",
"sizes": "1024x1024",
"type": "image/png"
}
],

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

BIN
res/vector-icons/144.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 881 B

After

Width:  |  Height:  |  Size: 829 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

BIN
res/vector-icons/512.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

View File

@ -1,12 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<browserconfig>
<msapplication>
<tile>
<square70x70logo src="/vector-icons/mstile-70.png"/>
<square150x150logo src="/vector-icons/mstile-150.png"/>
<square310x310logo src="/vector-icons/mstile-310.png"/>
<wide310x150logo src="/vector-icons/mstile-310x150.png"/>
<TileColor>#da532c</TileColor>
</tile>
</msapplication>
</browserconfig>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

View File

@ -192,6 +192,10 @@ export class BadgeOverlayRenderer extends IconRenderer {
}
}
// multiple of 48 as per Google guidelines
// multiple of 16 for higher quality scaling for small display
const DEFAULT_FAVICON_SIZE = 144;
// Allows dynamic rendering of a circular badge atop the loaded favicon
// supports colour, font and basic positioning parameters.
// Based upon https://github.com/ejci/favico.js/blob/master/favico.js [MIT license]
@ -218,14 +222,14 @@ export default class Favicon extends IconRenderer {
baseImage.setAttribute("crossOrigin", "anonymous");
baseImage.onload = (): void => {
// get height and width of the favicon
this.canvas.height = baseImage.height > 0 ? baseImage.height : 32;
this.canvas.width = baseImage.width > 0 ? baseImage.width : 32;
this.canvas.height = baseImage.height > 0 ? baseImage.height : DEFAULT_FAVICON_SIZE;
this.canvas.width = baseImage.width > 0 ? baseImage.width : DEFAULT_FAVICON_SIZE;
this.ready();
};
baseImage.setAttribute("src", lastIcon.getAttribute("href")!);
} else {
this.canvas.height = baseImage.height = 32;
this.canvas.width = baseImage.width = 32;
this.canvas.height = baseImage.height = DEFAULT_FAVICON_SIZE;
this.canvas.width = baseImage.width = DEFAULT_FAVICON_SIZE;
this.ready();
}
}
@ -302,10 +306,6 @@ export default class Favicon extends IconRenderer {
elms[0].setAttribute("rel", "icon");
window.document.getElementsByTagName("head")[0].appendChild(elms[0]);
}
elms.forEach((item) => {
item.setAttribute("type", "image/png");
});
return elms;
}
}

View File

@ -3,24 +3,18 @@
<head>
<meta charset="utf-8">
<title>Element</title>
<link rel="apple-touch-icon" sizes="57x57" href="<%= require('../../res/vector-icons/apple-touch-icon-57.png') %>">
<link rel="apple-touch-icon" sizes="60x60" href="<%= require('../../res/vector-icons/apple-touch-icon-60.png') %>">
<link rel="apple-touch-icon" sizes="72x72" href="<%= require('../../res/vector-icons/apple-touch-icon-72.png') %>">
<link rel="apple-touch-icon" sizes="76x76" href="<%= require('../../res/vector-icons/apple-touch-icon-76.png') %>">
<link rel="apple-touch-icon" sizes="114x114" href="<%= require('../../res/vector-icons/apple-touch-icon-114.png') %>">
<link rel="apple-touch-icon" sizes="120x120" href="<%= require('../../res/vector-icons/apple-touch-icon-120.png') %>">
<link rel="apple-touch-icon" sizes="144x144" href="<%= require('../../res/vector-icons/apple-touch-icon-144.png') %>">
<link rel="apple-touch-icon" sizes="152x152" href="<%= require('../../res/vector-icons/apple-touch-icon-152.png') %>">
<link rel="apple-touch-icon" sizes="180x180" href="<%= require('../../res/vector-icons/apple-touch-icon-180.png') %>">
<link rel="apple-touch-icon" sizes="120x120" href="<%= require('../../res/vector-icons/120.png') %>">
<link rel="apple-touch-icon" sizes="144x144" href="<%= require('../../res/vector-icons/144.png') %>">
<link rel="apple-touch-icon" sizes="152x152" href="<%= require('../../res/vector-icons/152.png') %>">
<link rel="apple-touch-icon" sizes="180x180" href="<%= require('../../res/vector-icons/180.png') %>">
<link rel="manifest" href="manifest.json">
<meta name="referrer" content="no-referrer">
<link rel="shortcut icon" href="<%= require('../../res/vector-icons/favicon.ico') %>">
<link rel="icon" type="image/png" sizes="24x24" href="<%= require('../../res/vector-icons/24.png') %>">
<link rel="icon" type="image/png" sizes="144x144" href="<%= require('../../res/vector-icons/144.png') %>">
<link rel="icon" type="image/png" sizes="512x512" href="<%= require('../../res/vector-icons/512.png') %>">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="apple-mobile-web-app-title" content="Element">
<meta name="application-name" content="Element">
<meta name="msapplication-TileColor" content="#da532c">
<meta name="msapplication-TileImage" content="<%= require('../../res/vector-icons/mstile-150.png') %>">
<meta name="msapplication-config" content="<%= require('../../res/vector-icons/browserconfig.xml') %>">
<meta name="theme-color" content="#ffffff">
<meta property="og:image" content="<%= og_image_url %>" />
<meta http-equiv="Content-Security-Policy" content="

View File

@ -4,8 +4,8 @@ exports[`Favicon should clear a badge if called with a zero value 1`] = `
[
{
"props": {
"height": 32,
"width": 32,
"height": 144,
"width": 144,
"x": 0,
"y": 0,
},
@ -21,16 +21,16 @@ exports[`Favicon should clear a badge if called with a zero value 1`] = `
},
{
"props": {
"dHeight": 32,
"dWidth": 32,
"dHeight": 144,
"dWidth": 144,
"dx": 0,
"dy": 0,
"img": <img
height="32"
width="32"
height="144"
width="144"
/>,
"sHeight": 32,
"sWidth": 32,
"sHeight": 144,
"sWidth": 144,
"sx": 0,
"sy": 0,
},
@ -62,8 +62,8 @@ exports[`Favicon should clear a badge if called with a zero value 1`] = `
},
{
"props": {
"x": 16.159999999999997,
"y": 12.8,
"x": 72.72,
"y": 57.6,
},
"transform": [
1,
@ -77,8 +77,8 @@ exports[`Favicon should clear a badge if called with a zero value 1`] = `
},
{
"props": {
"x": 22.4,
"y": 12.8,
"x": 100.79999999999998,
"y": 57.6,
},
"transform": [
1,
@ -92,8 +92,8 @@ exports[`Favicon should clear a badge if called with a zero value 1`] = `
},
{
"props": {
"x": 31.999999999999996,
"y": 22.4,
"x": 143.99999999999997,
"y": 100.80000000000001,
},
"transform": [
1,
@ -107,8 +107,8 @@ exports[`Favicon should clear a badge if called with a zero value 1`] = `
},
{
"props": {
"x": 9.92,
"y": 32,
"x": 44.64,
"y": 144,
},
"transform": [
1,
@ -122,8 +122,8 @@ exports[`Favicon should clear a badge if called with a zero value 1`] = `
},
{
"props": {
"x": 0.3200000000000003,
"y": 22.4,
"x": 1.4400000000000048,
"y": 100.8,
},
"transform": [
1,
@ -178,8 +178,8 @@ exports[`Favicon should clear a badge if called with a zero value 1`] = `
"props": {
"maxWidth": null,
"text": "123",
"x": 16,
"y": 29,
"x": 72,
"y": 131,
},
"transform": [
1,
@ -193,8 +193,8 @@ exports[`Favicon should clear a badge if called with a zero value 1`] = `
},
{
"props": {
"height": 32,
"width": 32,
"height": 144,
"width": 144,
"x": 0,
"y": 0,
},
@ -210,16 +210,16 @@ exports[`Favicon should clear a badge if called with a zero value 1`] = `
},
{
"props": {
"dHeight": 32,
"dWidth": 32,
"dHeight": 144,
"dWidth": 144,
"dx": 0,
"dy": 0,
"img": <img
height="32"
width="32"
height="144"
width="144"
/>,
"sHeight": 32,
"sWidth": 32,
"sHeight": 144,
"sWidth": 144,
"sx": 0,
"sy": 0,
},
@ -240,8 +240,8 @@ exports[`Favicon should draw a badge if called with a non-zero value 1`] = `
[
{
"props": {
"height": 32,
"width": 32,
"height": 144,
"width": 144,
"x": 0,
"y": 0,
},
@ -257,16 +257,16 @@ exports[`Favicon should draw a badge if called with a non-zero value 1`] = `
},
{
"props": {
"dHeight": 32,
"dWidth": 32,
"dHeight": 144,
"dWidth": 144,
"dx": 0,
"dy": 0,
"img": <img
height="32"
width="32"
height="144"
width="144"
/>,
"sHeight": 32,
"sWidth": 32,
"sHeight": 144,
"sWidth": 144,
"sx": 0,
"sy": 0,
},
@ -298,8 +298,8 @@ exports[`Favicon should draw a badge if called with a non-zero value 1`] = `
},
{
"props": {
"x": 16.159999999999997,
"y": 12.8,
"x": 72.72,
"y": 57.6,
},
"transform": [
1,
@ -313,8 +313,8 @@ exports[`Favicon should draw a badge if called with a non-zero value 1`] = `
},
{
"props": {
"x": 22.4,
"y": 12.8,
"x": 100.79999999999998,
"y": 57.6,
},
"transform": [
1,
@ -328,8 +328,8 @@ exports[`Favicon should draw a badge if called with a non-zero value 1`] = `
},
{
"props": {
"x": 31.999999999999996,
"y": 22.4,
"x": 143.99999999999997,
"y": 100.80000000000001,
},
"transform": [
1,
@ -343,8 +343,8 @@ exports[`Favicon should draw a badge if called with a non-zero value 1`] = `
},
{
"props": {
"x": 9.92,
"y": 32,
"x": 44.64,
"y": 144,
},
"transform": [
1,
@ -358,8 +358,8 @@ exports[`Favicon should draw a badge if called with a non-zero value 1`] = `
},
{
"props": {
"x": 0.3200000000000003,
"y": 22.4,
"x": 1.4400000000000048,
"y": 100.8,
},
"transform": [
1,
@ -414,8 +414,8 @@ exports[`Favicon should draw a badge if called with a non-zero value 1`] = `
"props": {
"maxWidth": null,
"text": "123",
"x": 16,
"y": 29,
"x": 72,
"y": 131,
},
"transform": [
1,

View File

@ -14,6 +14,8 @@ jest.useFakeTimers();
describe("Favicon", () => {
beforeEach(() => {
jest.restoreAllMocks();
document.getElementsByTagName("head")[0]?.remove();
const head = document.createElement("head");
window.document.documentElement.prepend(head);
});
@ -50,4 +52,40 @@ describe("Favicon", () => {
const newLink = window.document.querySelector("link");
expect(originalLink).not.toStrictEqual(newLink);
});
it("should default to 144px", () => {
const head = document.getElementsByTagName("head")[0];
const link = document.createElement("link");
link.rel = "icon";
link.href = "favicon.png";
head.appendChild(link);
const spy = jest.spyOn(document, "createElement");
const favicon = new Favicon();
jest.runAllTimers();
const img = spy.mock.results[0].value;
img.onload();
expect(favicon["canvas"].height).toBe(144);
});
it("should use the size of the last favicon", () => {
const head = document.getElementsByTagName("head")[0];
const link = document.createElement("link");
link.rel = "icon";
link.sizes = "512x512";
link.href = "favicon.png";
head.appendChild(link);
const spy = jest.spyOn(document, "createElement");
const favicon = new Favicon();
jest.runAllTimers();
const img = spy.mock.results[0].value;
img.height = 512;
img.onload();
expect(favicon["canvas"].height).toBe(512);
});
});