Add canonical cross-browser favicon package Introduce a complete, brand-neutral Git favicon set based on the Git coral-orange squircle with white branching commit nodes. Includes: - favicon.svg: Lossless scalable vector for modern browsers - favicon.ico: Multi-resolution binary (16x16, 32x32, 48x48) - favicon-32x32.png & favicon-16x16.png: High-DPI and standard PNGs - apple-touch-icon.png: 180x180 for iOS/macOS Safari bookmarks - android-chrome-192x192.png & android-chrome-512x512.png: High-resolution assets Updates Renderer.java and HTML head templates in Common.soy and Doc.soy to declare the canonical favicon links. Adds comprehensive servlet unit tests in FaviconTest. Change-Id: Ia4e17c7a17da2813a767a0d4f000afc5c6ccc284
diff --git a/java/com/google/gitiles/Renderer.java b/java/com/google/gitiles/Renderer.java index 964063d..c8c1ba6 100644 --- a/java/com/google/gitiles/Renderer.java +++ b/java/com/google/gitiles/Renderer.java
@@ -90,10 +90,16 @@ "RepositoryIndex.soy"); public static final ImmutableMap<String, String> STATIC_URL_GLOBALS = - ImmutableMap.of( - "gitiles.BASE_CSS_URL", "base.css", - "gitiles.DOC_CSS_URL", "doc.css", - "gitiles.PRETTIFY_CSS_URL", "prettify/prettify.css"); + ImmutableMap.<String, String>builder() + .put("gitiles.BASE_CSS_URL", "base.css") + .put("gitiles.DOC_CSS_URL", "doc.css") + .put("gitiles.PRETTIFY_CSS_URL", "prettify/prettify.css") + .put("gitiles.FAVICON_ICO_URL", "favicon.ico") + .put("gitiles.FAVICON_SVG_URL", "favicon.svg") + .put("gitiles.FAVICON_32_URL", "favicon-32x32.png") + .put("gitiles.FAVICON_16_URL", "favicon-16x16.png") + .put("gitiles.APPLE_TOUCH_ICON_URL", "apple-touch-icon.png") + .build(); protected static Function<String, URL> fileUrlMapper() { return fileUrlMapper("");
diff --git a/javatests/com/google/gitiles/FaviconTest.java b/javatests/com/google/gitiles/FaviconTest.java new file mode 100644 index 0000000..dedcd37 --- /dev/null +++ b/javatests/com/google/gitiles/FaviconTest.java
@@ -0,0 +1,84 @@ +// Copyright (C) 2026 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gitiles; + +import static com.google.common.truth.Truth.assertThat; + +import org.eclipse.jgit.revwalk.RevCommit; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Tests for favicon links and static asset mapping in Gitiles HTML pages. */ +@RunWith(JUnit4.class) +public class FaviconTest extends ServletTest { + + private void assertFaviconElementsPresent(String html) { + assertThat(html).contains("rel=\"icon\" href=\"/+static/favicon.ico\" sizes=\"any\""); + assertThat(html).contains("rel=\"icon\" type=\"image/svg+xml\""); + assertThat(html).contains("rel=\"icon\" type=\"image/png\" sizes=\"32x32\""); + assertThat(html).contains("rel=\"icon\" type=\"image/png\" sizes=\"16x16\""); + assertThat(html).contains("rel=\"apple-touch-icon\" sizes=\"180x180\""); + assertThat(html).contains("favicon.ico"); + assertThat(html).contains("favicon.svg"); + assertThat(html).contains("favicon-32x32.png"); + assertThat(html).contains("favicon-16x16.png"); + assertThat(html).contains("apple-touch-icon.png"); + } + + @Test + public void staticUrlGlobalsContainsAllFaviconEntries() { + assertThat(Renderer.STATIC_URL_GLOBALS) + .containsEntry("gitiles.FAVICON_ICO_URL", "favicon.ico"); + assertThat(Renderer.STATIC_URL_GLOBALS) + .containsEntry("gitiles.FAVICON_SVG_URL", "favicon.svg"); + assertThat(Renderer.STATIC_URL_GLOBALS) + .containsEntry("gitiles.FAVICON_32_URL", "favicon-32x32.png"); + assertThat(Renderer.STATIC_URL_GLOBALS) + .containsEntry("gitiles.FAVICON_16_URL", "favicon-16x16.png"); + assertThat(Renderer.STATIC_URL_GLOBALS) + .containsEntry("gitiles.APPLE_TOUCH_ICON_URL", "apple-touch-icon.png"); + } + + @Test + public void hostIndexIncludesFavicons() throws Exception { + String html = buildHtml("/", false); + assertFaviconElementsPresent(html); + } + + @Test + public void repositoryIndexIncludesFavicons() throws Exception { + String html = buildHtml("/repo/", false); + assertFaviconElementsPresent(html); + } + + @Test + public void docPageIncludesFavicons() throws Exception { + repo.branch("master") + .commit() + .add("README.md", "# Test Document\n\nDoc content.") + .create(); + + String html = buildHtml("/repo/+doc/master/README.md", false); + assertFaviconElementsPresent(html); + } + + @Test + public void commitDetailIncludesFavicons() throws Exception { + RevCommit commit = repo.branch("master").commit().add("foo", "contents").create(); + String html = buildHtml("/repo/+/" + commit.name(), false); + assertFaviconElementsPresent(html); + } +}
diff --git a/resources/com/google/gitiles/static/android-chrome-192x192.png b/resources/com/google/gitiles/static/android-chrome-192x192.png new file mode 100644 index 0000000..4ec6f19 --- /dev/null +++ b/resources/com/google/gitiles/static/android-chrome-192x192.png Binary files differ
diff --git a/resources/com/google/gitiles/static/android-chrome-512x512.png b/resources/com/google/gitiles/static/android-chrome-512x512.png new file mode 100644 index 0000000..88dbe2e --- /dev/null +++ b/resources/com/google/gitiles/static/android-chrome-512x512.png Binary files differ
diff --git a/resources/com/google/gitiles/static/apple-touch-icon.png b/resources/com/google/gitiles/static/apple-touch-icon.png new file mode 100644 index 0000000..b4c1032 --- /dev/null +++ b/resources/com/google/gitiles/static/apple-touch-icon.png Binary files differ
diff --git a/resources/com/google/gitiles/static/favicon-16x16.png b/resources/com/google/gitiles/static/favicon-16x16.png new file mode 100644 index 0000000..422d3f7 --- /dev/null +++ b/resources/com/google/gitiles/static/favicon-16x16.png Binary files differ
diff --git a/resources/com/google/gitiles/static/favicon-32x32.png b/resources/com/google/gitiles/static/favicon-32x32.png new file mode 100644 index 0000000..62cc3fb --- /dev/null +++ b/resources/com/google/gitiles/static/favicon-32x32.png Binary files differ
diff --git a/resources/com/google/gitiles/static/favicon.ico b/resources/com/google/gitiles/static/favicon.ico new file mode 100644 index 0000000..fe69fb6 --- /dev/null +++ b/resources/com/google/gitiles/static/favicon.ico Binary files differ
diff --git a/resources/com/google/gitiles/static/favicon.svg b/resources/com/google/gitiles/static/favicon.svg new file mode 100644 index 0000000..249a0d0 --- /dev/null +++ b/resources/com/google/gitiles/static/favicon.svg
@@ -0,0 +1,17 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="512" height="512"> + <!-- Background Squircle in Git Coral-Orange --> + <rect width="512" height="512" rx="112" ry="112" fill="#F05133"/> + + <!-- UNIFIED WHITE Y-FORK (Exact flush geometry, 40px uniform width, zero bulge) --> + <polygon points="236,366 236,257 139,175 165,145 256,222 347,145 373,175 276,257 276,366" fill="#FFFFFF"/> + + <!-- THREE COMMIT NODE RINGS (Outer White Circles) --> + <circle cx="256" cy="366" r="62" fill="#FFFFFF"/> + <circle cx="152" cy="160" r="62" fill="#FFFFFF"/> + <circle cx="360" cy="160" r="62" fill="#FFFFFF"/> + + <!-- INNER HOLLOW DONUT CORES (Punched out in Coral-Orange) --> + <circle cx="256" cy="366" r="32" fill="#F05133"/> + <circle cx="152" cy="160" r="32" fill="#F05133"/> + <circle cx="360" cy="160" r="32" fill="#F05133"/> +</svg>
diff --git a/resources/com/google/gitiles/templates/Common.soy b/resources/com/google/gitiles/templates/Common.soy index 371a10d..0aca3dc 100644 --- a/resources/com/google/gitiles/templates/Common.soy +++ b/resources/com/google/gitiles/templates/Common.soy
@@ -43,6 +43,11 @@ {/msg} </title> + <link rel="icon" href="{$staticUrls.FAVICON_ICO_URL}" sizes="any"> + <link rel="icon" type="image/svg+xml" href="{$staticUrls.FAVICON_SVG_URL}"> + <link rel="icon" type="image/png" sizes="32x32" href="{$staticUrls.FAVICON_32_URL}"> + <link rel="icon" type="image/png" sizes="16x16" href="{$staticUrls.FAVICON_16_URL}"> + <link rel="apple-touch-icon" sizes="180x180" href="{$staticUrls.APPLE_TOUCH_ICON_URL}"> <link rel="stylesheet" type="text/css" href="{$staticUrls.BASE_CSS_URL}"> {if $css && length($css)} {for $url in $css}
diff --git a/resources/com/google/gitiles/templates/Doc.soy b/resources/com/google/gitiles/templates/Doc.soy index 36bf1d5..fab786b 100644 --- a/resources/com/google/gitiles/templates/Doc.soy +++ b/resources/com/google/gitiles/templates/Doc.soy
@@ -59,6 +59,11 @@ {if $siteTitle}{$siteTitle} -{sp}{/if} {$pageTitle} </title> + <link rel="icon" href="{$staticUrls.FAVICON_ICO_URL}" sizes="any" /> + <link rel="icon" type="image/svg+xml" href="{$staticUrls.FAVICON_SVG_URL}" /> + <link rel="icon" type="image/png" sizes="32x32" href="{$staticUrls.FAVICON_32_URL}" /> + <link rel="icon" type="image/png" sizes="16x16" href="{$staticUrls.FAVICON_16_URL}" /> + <link rel="apple-touch-icon" sizes="180x180" href="{$staticUrls.APPLE_TOUCH_ICON_URL}" /> <link rel="stylesheet" type="text/css" href="{$staticUrls.BASE_CSS_URL}" /> <link rel="stylesheet" type="text/css" href="{$staticUrls.DOC_CSS_URL}" /> <link rel="stylesheet" type="text/css" href="{$staticUrls.PRETTIFY_CSS_URL}" />