Merge "Add canonical cross-browser favicon package"
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}" />