Merge "Fix formatting issues in action options documentation"
diff --git a/Documentation/metrics.txt b/Documentation/metrics.txt
index e6494e6..974e897 100644
--- a/Documentation/metrics.txt
+++ b/Documentation/metrics.txt
@@ -202,8 +202,13 @@
 * `change/count_rebases`: Total number of rebases
 ** `on_behalf_of_uploader`:
    Whether the rebase was done on behalf of the uploader.
+   If the uploader does a rebase with '`on_behalf_of_uploader = true`', the flag
+   is ignored and a normal rebase is done, hence such rebases are recorded as
+   '`on_behalf_of_uploader` = false`'.
 ** `rebase_chain`:
    Whether a chain was rebased.
+** `allow_conflicts`:
+   Whether the rebase was done with allowing conflicts.
 * `change/submitted_with_rebaser_approval`: Number of rebased changes that were
   submitted with a Code-Review approval of the rebaser that would not have been
   submittable if the rebase was not done on behalf of the uploader.
diff --git a/java/com/google/gerrit/server/restapi/change/Rebase.java b/java/com/google/gerrit/server/restapi/change/Rebase.java
index 7bddd61..167f784 100644
--- a/java/com/google/gerrit/server/restapi/change/Rebase.java
+++ b/java/com/google/gerrit/server/restapi/change/Rebase.java
@@ -97,6 +97,7 @@
       rsrc.permissions().check(ChangePermission.REBASE_ON_BEHALF_OF_UPLOADER);
       rsrc = rebaseUtil.onBehalfOf(rsrc, input);
     } else {
+      input.onBehalfOfUploader = false;
       rsrc.permissions().check(ChangePermission.REBASE);
     }
 
@@ -127,7 +128,7 @@
         bu.addOp(change.getId(), rebaseOp);
         bu.execute();
 
-        rebaseMetrics.countRebase(input.onBehalfOfUploader);
+        rebaseMetrics.countRebase(input.onBehalfOfUploader, input.allowConflicts);
 
         ChangeInfo changeInfo = json.create(OPTIONS).format(change.getProject(), change.getId());
         changeInfo.containsGitConflicts =
diff --git a/java/com/google/gerrit/server/restapi/change/RebaseChain.java b/java/com/google/gerrit/server/restapi/change/RebaseChain.java
index 32b5b11..b8afcb7 100644
--- a/java/com/google/gerrit/server/restapi/change/RebaseChain.java
+++ b/java/com/google/gerrit/server/restapi/change/RebaseChain.java
@@ -136,6 +136,7 @@
 
     CurrentUser user = tipRsrc.getUser();
 
+    boolean anyRebaseOnBehalfOfUploader = false;
     List<Change.Id> upToDateAncestors = new ArrayList<>();
     Map<Change.Id, RebaseChangeOp> rebaseOps = new LinkedHashMap<>();
     try (RefUpdateContext ctx = RefUpdateContext.open(CHANGE_MODIFICATION)) {
@@ -161,6 +162,7 @@
               && !revRsrc.getPatchSet().uploader().equals(revRsrc.getAccountId())) {
             revRsrc = rebaseUtil.onBehalfOf(revRsrc, input);
             revRsrc.permissions().check(ChangePermission.REBASE_ON_BEHALF_OF_UPLOADER);
+            anyRebaseOnBehalfOfUploader = true;
           } else {
             revRsrc.permissions().check(ChangePermission.REBASE);
           }
@@ -208,7 +210,7 @@
       }
     }
 
-    rebaseMetrics.countRebaseChain(input.onBehalfOfUploader);
+    rebaseMetrics.countRebaseChain(anyRebaseOnBehalfOfUploader, input.allowConflicts);
 
     RebaseChainInfo res = new RebaseChainInfo();
     res.rebasedChanges = new ArrayList<>();
diff --git a/java/com/google/gerrit/server/restapi/change/RebaseMetrics.java b/java/com/google/gerrit/server/restapi/change/RebaseMetrics.java
index 114a112..d6577ea 100644
--- a/java/com/google/gerrit/server/restapi/change/RebaseMetrics.java
+++ b/java/com/google/gerrit/server/restapi/change/RebaseMetrics.java
@@ -14,7 +14,7 @@
 
 package com.google.gerrit.server.restapi.change;
 
-import com.google.gerrit.metrics.Counter2;
+import com.google.gerrit.metrics.Counter3;
 import com.google.gerrit.metrics.Description;
 import com.google.gerrit.metrics.Field;
 import com.google.gerrit.metrics.MetricMaker;
@@ -24,7 +24,7 @@
 /** Metrics for the rebase REST endpoints ({@link Rebase} and {@link RebaseChain}). */
 @Singleton
 public class RebaseMetrics {
-  private final Counter2<Boolean, Boolean> countRebases;
+  private final Counter3<Boolean, Boolean, Boolean> countRebases;
 
   @Inject
   public RebaseMetrics(MetricMaker metricMaker) {
@@ -37,18 +37,25 @@
                 .build(),
             Field.ofBoolean("rebase_chain", (metadataBuilder, isRebaseChain) -> {})
                 .description("Whether a chain was rebased.")
+                .build(),
+            Field.ofBoolean("allow_conflicts", (metadataBuilder, allow_conflicts) -> {})
+                .description("Whether the rebase was done with allowing conflicts.")
                 .build());
   }
 
-  public void countRebase(boolean isOnBehalfOfUploader) {
-    countRebase(isOnBehalfOfUploader, /* isRebaseChain= */ false);
+  public void countRebase(boolean isOnBehalfOfUploader, boolean allowConflicts) {
+    countRebase(isOnBehalfOfUploader, /* isRebaseChain= */ false, allowConflicts);
   }
 
-  public void countRebaseChain(boolean isOnBehalfOfUploader) {
-    countRebase(isOnBehalfOfUploader, /* isRebaseChain= */ true);
+  public void countRebaseChain(boolean isOnBehalfOfUploader, boolean allowConflicts) {
+    countRebase(isOnBehalfOfUploader, /* isRebaseChain= */ true, allowConflicts);
   }
 
-  private void countRebase(boolean isOnBehalfOfUploader, boolean isRebaseChain) {
-    countRebases.increment(/* field1= */ isOnBehalfOfUploader, /* field2= */ isRebaseChain);
+  private void countRebase(
+      boolean isOnBehalfOfUploader, boolean isRebaseChain, boolean allowConflicts) {
+    countRebases.increment(
+        /* field1= */ isOnBehalfOfUploader,
+        /* field2= */ isRebaseChain,
+        /* field3= */ allowConflicts);
   }
 }
diff --git a/javatests/com/google/gerrit/acceptance/api/change/RebaseChainOnBehalfOfUploaderIT.java b/javatests/com/google/gerrit/acceptance/api/change/RebaseChainOnBehalfOfUploaderIT.java
index 13f9904..785186d 100644
--- a/javatests/com/google/gerrit/acceptance/api/change/RebaseChainOnBehalfOfUploaderIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/change/RebaseChainOnBehalfOfUploaderIT.java
@@ -1234,11 +1234,30 @@
     RebaseInput rebaseInput = new RebaseInput();
     rebaseInput.onBehalfOfUploader = true;
     gApi.changes().id(changeToBeRebased2.get()).rebaseChain(rebaseInput);
-    // field1 is on_behalf_of_uploader, field2 is rebase_chain
-    assertThat(testMetricMaker.getCount("change/count_rebases", true, true)).isEqualTo(1);
-    assertThat(testMetricMaker.getCount("change/count_rebases", true, false)).isEqualTo(0);
-    assertThat(testMetricMaker.getCount("change/count_rebases", false, false)).isEqualTo(0);
-    assertThat(testMetricMaker.getCount("change/count_rebases", false, true)).isEqualTo(0);
+    // field1 is on_behalf_of_uploader, field2 is rebase_chain, field3 is allow_conflicts
+    assertThat(testMetricMaker.getCount("change/count_rebases", true, true, false)).isEqualTo(1);
+    assertThat(testMetricMaker.getCount("change/count_rebases", true, false, false)).isEqualTo(0);
+    assertThat(testMetricMaker.getCount("change/count_rebases", false, false, false)).isEqualTo(0);
+    assertThat(testMetricMaker.getCount("change/count_rebases", false, true, false)).isEqualTo(0);
+
+    // Create and submit another change so that we can rebase the change once again.
+    requestScopeOperations.setApiUser(approver);
+    Change.Id changeToBeTheNewBase2 =
+        changeOperations.newChange().project(project).owner(uploader).create();
+    gApi.changes().id(changeToBeTheNewBase2.get()).current().review(ReviewInput.approve());
+    gApi.changes().id(changeToBeTheNewBase2.get()).current().submit();
+
+    // Rebase the change once again, this time as the uploader.
+    // If the uploader sets on_behalf_of_uploader = true, the flag is ignored and a normal rebase is
+    // done, hence the metric should count this as a a rebase with on_behalf_of_uploader = false.
+    requestScopeOperations.setApiUser(uploader);
+    testMetricMaker.reset();
+    gApi.changes().id(changeToBeRebased2.get()).rebaseChain(rebaseInput);
+    // field1 is on_behalf_of_uploader, field2 is rebase_chain, field3 is allow_conflicts
+    assertThat(testMetricMaker.getCount("change/count_rebases", false, true, false)).isEqualTo(1);
+    assertThat(testMetricMaker.getCount("change/count_rebases", true, true, false)).isEqualTo(0);
+    assertThat(testMetricMaker.getCount("change/count_rebases", false, false, false)).isEqualTo(0);
+    assertThat(testMetricMaker.getCount("change/count_rebases", true, false, false)).isEqualTo(0);
   }
 
   private void assertRebase(
diff --git a/javatests/com/google/gerrit/acceptance/api/change/RebaseIT.java b/javatests/com/google/gerrit/acceptance/api/change/RebaseIT.java
index dee8d1f..4e95032 100644
--- a/javatests/com/google/gerrit/acceptance/api/change/RebaseIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/change/RebaseIT.java
@@ -665,10 +665,15 @@
           extensionRegistry.newRegistration().add(wipStateChangedListener)) {
         RebaseInput rebaseInput = new RebaseInput();
         rebaseInput.allowConflicts = true;
+        testMetricMaker.reset();
         ChangeInfo changeInfo =
             gApi.changes().id(changeId).revision(patchSet.name()).rebaseAsInfo(rebaseInput);
         assertThat(changeInfo.containsGitConflicts).isTrue();
         assertThat(changeInfo.workInProgress).isTrue();
+
+        // field1 is on_behalf_of_uploader, field2 is rebase_chain, field3 is allow_conflicts
+        assertThat(testMetricMaker.getCount("change/count_rebases", false, false, true))
+            .isEqualTo(1);
       }
       assertThat(wipStateChangedListener.invoked).isTrue();
       assertThat(wipStateChangedListener.wip).isTrue();
@@ -790,11 +795,12 @@
       // Rebase the second change
       testMetricMaker.reset();
       rebaseCallWithInput.call(r2.getChangeId(), new RebaseInput());
-      // field1 is on_behalf_of_uploader, field2 is rebase_chain
-      assertThat(testMetricMaker.getCount("change/count_rebases", false, false)).isEqualTo(1);
-      assertThat(testMetricMaker.getCount("change/count_rebases", true, false)).isEqualTo(0);
-      assertThat(testMetricMaker.getCount("change/count_rebases", true, true)).isEqualTo(0);
-      assertThat(testMetricMaker.getCount("change/count_rebases", false, true)).isEqualTo(0);
+      // field1 is on_behalf_of_uploader, field2 is rebase_chain, field3 is allow_conflicts
+      assertThat(testMetricMaker.getCount("change/count_rebases", false, false, false))
+          .isEqualTo(1);
+      assertThat(testMetricMaker.getCount("change/count_rebases", true, false, false)).isEqualTo(0);
+      assertThat(testMetricMaker.getCount("change/count_rebases", true, true, false)).isEqualTo(0);
+      assertThat(testMetricMaker.getCount("change/count_rebases", false, true, false)).isEqualTo(0);
     }
 
     @Test
@@ -1249,11 +1255,12 @@
       testMetricMaker.reset();
       verifyRebaseChainResponse(
           gApi.changes().id(r4.getChangeId()).rebaseChain(), false, r2, r3, r4);
-      // field1 is on_behalf_of_uploader, field2 is rebase_chain
-      assertThat(testMetricMaker.getCount("change/count_rebases", false, true)).isEqualTo(1);
-      assertThat(testMetricMaker.getCount("change/count_rebases", false, false)).isEqualTo(0);
-      assertThat(testMetricMaker.getCount("change/count_rebases", true, true)).isEqualTo(0);
-      assertThat(testMetricMaker.getCount("change/count_rebases", true, false)).isEqualTo(0);
+      // field1 is on_behalf_of_uploader, field2 is rebase_chain, field3 is allow_conflicts
+      assertThat(testMetricMaker.getCount("change/count_rebases", false, true, false)).isEqualTo(1);
+      assertThat(testMetricMaker.getCount("change/count_rebases", false, false, false))
+          .isEqualTo(0);
+      assertThat(testMetricMaker.getCount("change/count_rebases", true, true, false)).isEqualTo(0);
+      assertThat(testMetricMaker.getCount("change/count_rebases", true, false, false)).isEqualTo(0);
     }
 
     private void verifyRebaseChainResponse(
diff --git a/javatests/com/google/gerrit/acceptance/api/change/RebaseOnBehalfOfUploaderIT.java b/javatests/com/google/gerrit/acceptance/api/change/RebaseOnBehalfOfUploaderIT.java
index 1b6956c..96e3e8e 100644
--- a/javatests/com/google/gerrit/acceptance/api/change/RebaseOnBehalfOfUploaderIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/change/RebaseOnBehalfOfUploaderIT.java
@@ -1128,11 +1128,30 @@
     RebaseInput rebaseInput = new RebaseInput();
     rebaseInput.onBehalfOfUploader = true;
     gApi.changes().id(changeToBeRebased.get()).rebase(rebaseInput);
-    // field1 is on_behalf_of_uploader, field2 is rebase_chain
-    assertThat(testMetricMaker.getCount("change/count_rebases", true, false)).isEqualTo(1);
-    assertThat(testMetricMaker.getCount("change/count_rebases", false, false)).isEqualTo(0);
-    assertThat(testMetricMaker.getCount("change/count_rebases", true, true)).isEqualTo(0);
-    assertThat(testMetricMaker.getCount("change/count_rebases", false, true)).isEqualTo(0);
+    // field1 is on_behalf_of_uploader, field2 is rebase_chain, field3 is allow_conflicts
+    assertThat(testMetricMaker.getCount("change/count_rebases", true, false, false)).isEqualTo(1);
+    assertThat(testMetricMaker.getCount("change/count_rebases", false, false, false)).isEqualTo(0);
+    assertThat(testMetricMaker.getCount("change/count_rebases", true, true, false)).isEqualTo(0);
+    assertThat(testMetricMaker.getCount("change/count_rebases", false, true, false)).isEqualTo(0);
+
+    // Create and submit another change so that we can rebase the change once again.
+    requestScopeOperations.setApiUser(approver);
+    Change.Id changeToBeTheNewBase2 =
+        changeOperations.newChange().project(project).owner(uploader).create();
+    gApi.changes().id(changeToBeTheNewBase2.get()).current().review(ReviewInput.approve());
+    gApi.changes().id(changeToBeTheNewBase2.get()).current().submit();
+
+    // Rebase the change once again, this time as the uploader.
+    // If the uploader sets on_behalf_of_uploader = true, the flag is ignored and a normal rebase is
+    // done, hence the metric should count this as a a rebase with on_behalf_of_uploader = false.
+    requestScopeOperations.setApiUser(uploader);
+    testMetricMaker.reset();
+    gApi.changes().id(changeToBeRebased.get()).rebase(rebaseInput);
+    // field1 is on_behalf_of_uploader, field2 is rebase_chain, field3 is allow_conflicts
+    assertThat(testMetricMaker.getCount("change/count_rebases", false, false, false)).isEqualTo(1);
+    assertThat(testMetricMaker.getCount("change/count_rebases", true, false, false)).isEqualTo(0);
+    assertThat(testMetricMaker.getCount("change/count_rebases", false, true, false)).isEqualTo(0);
+    assertThat(testMetricMaker.getCount("change/count_rebases", true, true, false)).isEqualTo(0);
   }
 
   private void allowPermissionToAllUsers(String permission) {
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access.ts b/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access.ts
index 048933b..2809d6e 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access.ts
+++ b/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access.ts
@@ -47,6 +47,7 @@
 import {createChangeUrl} from '../../../models/views/change';
 import {throwingErrorCallback} from '../../shared/gr-rest-api-interface/gr-rest-apis/gr-rest-api-helper';
 import {createRepoUrl, RepoDetailView} from '../../../models/views/repo';
+import '../../shared/gr-weblink/gr-weblink';
 
 const NOTHING_TO_SAVE = 'No changes to save.';
 
@@ -145,7 +146,7 @@
           min-height: 2em;
           align-items: center;
         }
-        .weblink {
+        gr-weblink {
           margin-right: var(--spacing-xs);
         }
         gr-access-section {
@@ -209,7 +210,9 @@
           </h3>
           <div class="weblinks ${this.weblinks?.length ? 'show' : ''}">
             History:
-            ${this.weblinks?.map(webLink => this.renderWebLinks(webLink))}
+            ${this.weblinks?.map(
+              info => html`<gr-weblink .info=${info}></gr-weblink>`
+            )}
           </div>
           ${this.sections?.map((section, index) =>
             this.renderPermissionSections(section, index)
@@ -253,14 +256,6 @@
     `;
   }
 
-  private renderWebLinks(webLink: WebLinkInfo) {
-    return html`
-      <a class="weblink" href=${webLink.url} rel="noopener" target="_blank">
-        ${webLink.name}
-      </a>
-    `;
-  }
-
   private renderPermissionSections(
     section: PermissionAccessSection,
     index: number
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-detail-list/gr-repo-detail-list.ts b/polygerrit-ui/app/elements/admin/gr-repo-detail-list/gr-repo-detail-list.ts
index a11951d..2841598 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-detail-list/gr-repo-detail-list.ts
+++ b/polygerrit-ui/app/elements/admin/gr-repo-detail-list/gr-repo-detail-list.ts
@@ -9,6 +9,7 @@
 import '../../shared/gr-date-formatter/gr-date-formatter';
 import '../../shared/gr-dialog/gr-dialog';
 import '../../shared/gr-list-view/gr-list-view';
+import '../../shared/gr-weblink/gr-weblink';
 import '../gr-create-pointer-dialog/gr-create-pointer-dialog';
 import '../gr-confirm-delete-item-dialog/gr-confirm-delete-item-dialog';
 import {GrCreatePointerDialog} from '../gr-create-pointer-dialog/gr-create-pointer-dialog';
@@ -335,12 +336,8 @@
     `;
   }
 
-  private renderWeblink(link: WebLinkInfo) {
-    return html`
-      <a href=${link.url} class="webLink" rel="noopener" target="_blank">
-        (${link.name})
-      </a>
-    `;
+  private renderWeblink(info: WebLinkInfo) {
+    return html`<gr-weblink .info=${info}></gr-weblink>`;
   }
 
   override willUpdate(changedProperties: PropertyValues) {
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-detail-list/gr-repo-detail-list_test.ts b/polygerrit-ui/app/elements/admin/gr-repo-detail-list/gr-repo-detail-list_test.ts
index a2b23b4..262c7d0 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-detail-list/gr-repo-detail-list_test.ts
+++ b/polygerrit-ui/app/elements/admin/gr-repo-detail-list/gr-repo-detail-list_test.ts
@@ -253,14 +253,7 @@
                     <td class="hideItem message"></td>
                     <td class="hideItem tagger"></td>
                     <td class="repositoryBrowser">
-                      <a
-                        class="webLink"
-                        href="https://git.example.org/branch/test;refs/heads/test0"
-                        rel="noopener"
-                        target="_blank"
-                      >
-                        (diffusion)
-                      </a>
+                      <gr-weblink></gr-weblink>
                     </td>
                     <td class="delete">
                       <gr-button
@@ -329,14 +322,7 @@
                     <td class="hideItem message"></td>
                     <td class="hideItem tagger"></td>
                     <td class="repositoryBrowser">
-                      <a
-                        class="webLink"
-                        href="https://git.example.org/branch/test;refs/heads/test1"
-                        rel="noopener"
-                        target="_blank"
-                      >
-                        (diffusion)
-                      </a>
+                      <gr-weblink></gr-weblink>
                     </td>
                     <td class="delete">
                       <gr-button
@@ -405,14 +391,7 @@
                     <td class="hideItem message"></td>
                     <td class="hideItem tagger"></td>
                     <td class="repositoryBrowser">
-                      <a
-                        class="webLink"
-                        href="https://git.example.org/branch/test;refs/heads/test2"
-                        rel="noopener"
-                        target="_blank"
-                      >
-                        (diffusion)
-                      </a>
+                      <gr-weblink></gr-weblink>
                     </td>
                     <td class="delete">
                       <gr-button
@@ -481,14 +460,7 @@
                     <td class="hideItem message"></td>
                     <td class="hideItem tagger"></td>
                     <td class="repositoryBrowser">
-                      <a
-                        class="webLink"
-                        href="https://git.example.org/branch/test;refs/heads/test3"
-                        rel="noopener"
-                        target="_blank"
-                      >
-                        (diffusion)
-                      </a>
+                      <gr-weblink></gr-weblink>
                     </td>
                     <td class="delete">
                       <gr-button
@@ -557,14 +529,7 @@
                     <td class="hideItem message"></td>
                     <td class="hideItem tagger"></td>
                     <td class="repositoryBrowser">
-                      <a
-                        class="webLink"
-                        href="https://git.example.org/branch/test;refs/heads/test4"
-                        rel="noopener"
-                        target="_blank"
-                      >
-                        (diffusion)
-                      </a>
+                      <gr-weblink></gr-weblink>
                     </td>
                     <td class="delete">
                       <gr-button
@@ -633,14 +598,7 @@
                     <td class="hideItem message"></td>
                     <td class="hideItem tagger"></td>
                     <td class="repositoryBrowser">
-                      <a
-                        class="webLink"
-                        href="https://git.example.org/branch/test;refs/heads/test5"
-                        rel="noopener"
-                        target="_blank"
-                      >
-                        (diffusion)
-                      </a>
+                      <gr-weblink></gr-weblink>
                     </td>
                     <td class="delete">
                       <gr-button
@@ -709,14 +667,7 @@
                     <td class="hideItem message"></td>
                     <td class="hideItem tagger"></td>
                     <td class="repositoryBrowser">
-                      <a
-                        class="webLink"
-                        href="https://git.example.org/branch/test;refs/heads/test6"
-                        rel="noopener"
-                        target="_blank"
-                      >
-                        (diffusion)
-                      </a>
+                      <gr-weblink></gr-weblink>
                     </td>
                     <td class="delete">
                       <gr-button
@@ -785,14 +736,7 @@
                     <td class="hideItem message"></td>
                     <td class="hideItem tagger"></td>
                     <td class="repositoryBrowser">
-                      <a
-                        class="webLink"
-                        href="https://git.example.org/branch/test;refs/heads/test7"
-                        rel="noopener"
-                        target="_blank"
-                      >
-                        (diffusion)
-                      </a>
+                      <gr-weblink></gr-weblink>
                     </td>
                     <td class="delete">
                       <gr-button
@@ -861,14 +805,7 @@
                     <td class="hideItem message"></td>
                     <td class="hideItem tagger"></td>
                     <td class="repositoryBrowser">
-                      <a
-                        class="webLink"
-                        href="https://git.example.org/branch/test;refs/heads/test8"
-                        rel="noopener"
-                        target="_blank"
-                      >
-                        (diffusion)
-                      </a>
+                      <gr-weblink></gr-weblink>
                     </td>
                     <td class="delete">
                       <gr-button
@@ -937,14 +874,7 @@
                     <td class="hideItem message"></td>
                     <td class="hideItem tagger"></td>
                     <td class="repositoryBrowser">
-                      <a
-                        class="webLink"
-                        href="https://git.example.org/branch/test;refs/heads/test9"
-                        rel="noopener"
-                        target="_blank"
-                      >
-                        (diffusion)
-                      </a>
+                      <gr-weblink></gr-weblink>
                     </td>
                     <td class="delete">
                       <gr-button
@@ -1013,14 +943,7 @@
                     <td class="hideItem message"></td>
                     <td class="hideItem tagger"></td>
                     <td class="repositoryBrowser">
-                      <a
-                        class="webLink"
-                        href="https://git.example.org/branch/test;refs/heads/test10"
-                        rel="noopener"
-                        target="_blank"
-                      >
-                        (diffusion)
-                      </a>
+                      <gr-weblink></gr-weblink>
                     </td>
                     <td class="delete">
                       <gr-button
@@ -1089,14 +1012,7 @@
                     <td class="hideItem message"></td>
                     <td class="hideItem tagger"></td>
                     <td class="repositoryBrowser">
-                      <a
-                        class="webLink"
-                        href="https://git.example.org/branch/test;refs/heads/test11"
-                        rel="noopener"
-                        target="_blank"
-                      >
-                        (diffusion)
-                      </a>
+                      <gr-weblink></gr-weblink>
                     </td>
                     <td class="delete">
                       <gr-button
@@ -1165,14 +1081,7 @@
                     <td class="hideItem message"></td>
                     <td class="hideItem tagger"></td>
                     <td class="repositoryBrowser">
-                      <a
-                        class="webLink"
-                        href="https://git.example.org/branch/test;refs/heads/test12"
-                        rel="noopener"
-                        target="_blank"
-                      >
-                        (diffusion)
-                      </a>
+                      <gr-weblink></gr-weblink>
                     </td>
                     <td class="delete">
                       <gr-button
@@ -1241,14 +1150,7 @@
                     <td class="hideItem message"></td>
                     <td class="hideItem tagger"></td>
                     <td class="repositoryBrowser">
-                      <a
-                        class="webLink"
-                        href="https://git.example.org/branch/test;refs/heads/test13"
-                        rel="noopener"
-                        target="_blank"
-                      >
-                        (diffusion)
-                      </a>
+                      <gr-weblink></gr-weblink>
                     </td>
                     <td class="delete">
                       <gr-button
@@ -1317,14 +1219,7 @@
                     <td class="hideItem message"></td>
                     <td class="hideItem tagger"></td>
                     <td class="repositoryBrowser">
-                      <a
-                        class="webLink"
-                        href="https://git.example.org/branch/test;refs/heads/test14"
-                        rel="noopener"
-                        target="_blank"
-                      >
-                        (diffusion)
-                      </a>
+                      <gr-weblink></gr-weblink>
                     </td>
                     <td class="delete">
                       <gr-button
@@ -1393,14 +1288,7 @@
                     <td class="hideItem message"></td>
                     <td class="hideItem tagger"></td>
                     <td class="repositoryBrowser">
-                      <a
-                        class="webLink"
-                        href="https://git.example.org/branch/test;refs/heads/test15"
-                        rel="noopener"
-                        target="_blank"
-                      >
-                        (diffusion)
-                      </a>
+                      <gr-weblink></gr-weblink>
                     </td>
                     <td class="delete">
                       <gr-button
@@ -1469,14 +1357,7 @@
                     <td class="hideItem message"></td>
                     <td class="hideItem tagger"></td>
                     <td class="repositoryBrowser">
-                      <a
-                        class="webLink"
-                        href="https://git.example.org/branch/test;refs/heads/test16"
-                        rel="noopener"
-                        target="_blank"
-                      >
-                        (diffusion)
-                      </a>
+                      <gr-weblink></gr-weblink>
                     </td>
                     <td class="delete">
                       <gr-button
@@ -1545,14 +1426,7 @@
                     <td class="hideItem message"></td>
                     <td class="hideItem tagger"></td>
                     <td class="repositoryBrowser">
-                      <a
-                        class="webLink"
-                        href="https://git.example.org/branch/test;refs/heads/test17"
-                        rel="noopener"
-                        target="_blank"
-                      >
-                        (diffusion)
-                      </a>
+                      <gr-weblink></gr-weblink>
                     </td>
                     <td class="delete">
                       <gr-button
@@ -1621,14 +1495,7 @@
                     <td class="hideItem message"></td>
                     <td class="hideItem tagger"></td>
                     <td class="repositoryBrowser">
-                      <a
-                        class="webLink"
-                        href="https://git.example.org/branch/test;refs/heads/test18"
-                        rel="noopener"
-                        target="_blank"
-                      >
-                        (diffusion)
-                      </a>
+                      <gr-weblink></gr-weblink>
                     </td>
                     <td class="delete">
                       <gr-button
@@ -1697,14 +1564,7 @@
                     <td class="hideItem message"></td>
                     <td class="hideItem tagger"></td>
                     <td class="repositoryBrowser">
-                      <a
-                        class="webLink"
-                        href="https://git.example.org/branch/test;refs/heads/test19"
-                        rel="noopener"
-                        target="_blank"
-                      >
-                        (diffusion)
-                      </a>
+                      <gr-weblink></gr-weblink>
                     </td>
                     <td class="delete">
                       <gr-button
@@ -1773,14 +1633,7 @@
                     <td class="hideItem message"></td>
                     <td class="hideItem tagger"></td>
                     <td class="repositoryBrowser">
-                      <a
-                        class="webLink"
-                        href="https://git.example.org/branch/test;refs/heads/test20"
-                        rel="noopener"
-                        target="_blank"
-                      >
-                        (diffusion)
-                      </a>
+                      <gr-weblink></gr-weblink>
                     </td>
                     <td class="delete">
                       <gr-button
@@ -1849,14 +1702,7 @@
                     <td class="hideItem message"></td>
                     <td class="hideItem tagger"></td>
                     <td class="repositoryBrowser">
-                      <a
-                        class="webLink"
-                        href="https://git.example.org/branch/test;refs/heads/test21"
-                        rel="noopener"
-                        target="_blank"
-                      >
-                        (diffusion)
-                      </a>
+                      <gr-weblink></gr-weblink>
                     </td>
                     <td class="delete">
                       <gr-button
@@ -1925,14 +1771,7 @@
                     <td class="hideItem message"></td>
                     <td class="hideItem tagger"></td>
                     <td class="repositoryBrowser">
-                      <a
-                        class="webLink"
-                        href="https://git.example.org/branch/test;refs/heads/test22"
-                        rel="noopener"
-                        target="_blank"
-                      >
-                        (diffusion)
-                      </a>
+                      <gr-weblink></gr-weblink>
                     </td>
                     <td class="delete">
                       <gr-button
@@ -2001,14 +1840,7 @@
                     <td class="hideItem message"></td>
                     <td class="hideItem tagger"></td>
                     <td class="repositoryBrowser">
-                      <a
-                        class="webLink"
-                        href="https://git.example.org/branch/test;refs/heads/test23"
-                        rel="noopener"
-                        target="_blank"
-                      >
-                        (diffusion)
-                      </a>
+                      <gr-weblink></gr-weblink>
                     </td>
                     <td class="delete">
                       <gr-button
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-list/gr-repo-list.ts b/polygerrit-ui/app/elements/admin/gr-repo-list/gr-repo-list.ts
index 055cb30..7c8d1ce 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-list/gr-repo-list.ts
+++ b/polygerrit-ui/app/elements/admin/gr-repo-list/gr-repo-list.ts
@@ -5,6 +5,7 @@
  */
 import '../../shared/gr-dialog/gr-dialog';
 import '../../shared/gr-list-view/gr-list-view';
+import '../../shared/gr-weblink/gr-weblink';
 import '../gr-create-repo-dialog/gr-create-repo-dialog';
 import {ProjectInfoWithName, WebLinkInfo} from '../../../types/common';
 import {GrCreateRepoDialog} from '../gr-create-repo-dialog/gr-create-repo-dialog';
@@ -169,12 +170,8 @@
     return webLinks.map(link => this.renderWebLink(link));
   }
 
-  private renderWebLink(link: WebLinkInfo) {
-    return html`
-      <a href=${link.url} class="webLink" rel="noopener" target="_blank">
-        ${link.name}
-      </a>
-    `;
+  private renderWebLink(info: WebLinkInfo) {
+    return html`<gr-weblink .info=${info}></gr-weblink>`;
   }
 
   override willUpdate(changedProperties: PropertyValues) {
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-list/gr-repo-list_test.ts b/polygerrit-ui/app/elements/admin/gr-repo-list/gr-repo-list_test.ts
index 906b733..b63d1c5 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-list/gr-repo-list_test.ts
+++ b/polygerrit-ui/app/elements/admin/gr-repo-list/gr-repo-list_test.ts
@@ -90,14 +90,7 @@
                     <a href="/admin/repos/test"> test </a>
                   </td>
                   <td class="repositoryBrowser">
-                    <a
-                      class="webLink"
-                      href="https://phabricator.example.org/r/project/test0"
-                      rel="noopener"
-                      target="_blank"
-                    >
-                      diffusion
-                    </a>
+                    <gr-weblink></gr-weblink>
                   </td>
                   <td class="changesLink">
                     <a href="/q/project:test"> view all </a>
@@ -110,14 +103,7 @@
                     <a href="/admin/repos/test"> test </a>
                   </td>
                   <td class="repositoryBrowser">
-                    <a
-                      class="webLink"
-                      href="https://phabricator.example.org/r/project/test1"
-                      rel="noopener"
-                      target="_blank"
-                    >
-                      diffusion
-                    </a>
+                    <gr-weblink></gr-weblink>
                   </td>
                   <td class="changesLink">
                     <a href="/q/project:test"> view all </a>
@@ -130,14 +116,7 @@
                     <a href="/admin/repos/test"> test </a>
                   </td>
                   <td class="repositoryBrowser">
-                    <a
-                      class="webLink"
-                      href="https://phabricator.example.org/r/project/test2"
-                      rel="noopener"
-                      target="_blank"
-                    >
-                      diffusion
-                    </a>
+                    <gr-weblink></gr-weblink>
                   </td>
                   <td class="changesLink">
                     <a href="/q/project:test"> view all </a>
@@ -150,14 +129,7 @@
                     <a href="/admin/repos/test"> test </a>
                   </td>
                   <td class="repositoryBrowser">
-                    <a
-                      class="webLink"
-                      href="https://phabricator.example.org/r/project/test3"
-                      rel="noopener"
-                      target="_blank"
-                    >
-                      diffusion
-                    </a>
+                    <gr-weblink></gr-weblink>
                   </td>
                   <td class="changesLink">
                     <a href="/q/project:test"> view all </a>
@@ -170,14 +142,7 @@
                     <a href="/admin/repos/test"> test </a>
                   </td>
                   <td class="repositoryBrowser">
-                    <a
-                      class="webLink"
-                      href="https://phabricator.example.org/r/project/test4"
-                      rel="noopener"
-                      target="_blank"
-                    >
-                      diffusion
-                    </a>
+                    <gr-weblink></gr-weblink>
                   </td>
                   <td class="changesLink">
                     <a href="/q/project:test"> view all </a>
@@ -190,14 +155,7 @@
                     <a href="/admin/repos/test"> test </a>
                   </td>
                   <td class="repositoryBrowser">
-                    <a
-                      class="webLink"
-                      href="https://phabricator.example.org/r/project/test5"
-                      rel="noopener"
-                      target="_blank"
-                    >
-                      diffusion
-                    </a>
+                    <gr-weblink></gr-weblink>
                   </td>
                   <td class="changesLink">
                     <a href="/q/project:test"> view all </a>
@@ -210,14 +168,7 @@
                     <a href="/admin/repos/test"> test </a>
                   </td>
                   <td class="repositoryBrowser">
-                    <a
-                      class="webLink"
-                      href="https://phabricator.example.org/r/project/test6"
-                      rel="noopener"
-                      target="_blank"
-                    >
-                      diffusion
-                    </a>
+                    <gr-weblink></gr-weblink>
                   </td>
                   <td class="changesLink">
                     <a href="/q/project:test"> view all </a>
@@ -230,14 +181,7 @@
                     <a href="/admin/repos/test"> test </a>
                   </td>
                   <td class="repositoryBrowser">
-                    <a
-                      class="webLink"
-                      href="https://phabricator.example.org/r/project/test7"
-                      rel="noopener"
-                      target="_blank"
-                    >
-                      diffusion
-                    </a>
+                    <gr-weblink></gr-weblink>
                   </td>
                   <td class="changesLink">
                     <a href="/q/project:test"> view all </a>
@@ -250,14 +194,7 @@
                     <a href="/admin/repos/test"> test </a>
                   </td>
                   <td class="repositoryBrowser">
-                    <a
-                      class="webLink"
-                      href="https://phabricator.example.org/r/project/test8"
-                      rel="noopener"
-                      target="_blank"
-                    >
-                      diffusion
-                    </a>
+                    <gr-weblink></gr-weblink>
                   </td>
                   <td class="changesLink">
                     <a href="/q/project:test"> view all </a>
@@ -270,14 +207,7 @@
                     <a href="/admin/repos/test"> test </a>
                   </td>
                   <td class="repositoryBrowser">
-                    <a
-                      class="webLink"
-                      href="https://phabricator.example.org/r/project/test9"
-                      rel="noopener"
-                      target="_blank"
-                    >
-                      diffusion
-                    </a>
+                    <gr-weblink></gr-weblink>
                   </td>
                   <td class="changesLink">
                     <a href="/q/project:test"> view all </a>
@@ -290,14 +220,7 @@
                     <a href="/admin/repos/test"> test </a>
                   </td>
                   <td class="repositoryBrowser">
-                    <a
-                      class="webLink"
-                      href="https://phabricator.example.org/r/project/test10"
-                      rel="noopener"
-                      target="_blank"
-                    >
-                      diffusion
-                    </a>
+                    <gr-weblink></gr-weblink>
                   </td>
                   <td class="changesLink">
                     <a href="/q/project:test"> view all </a>
@@ -310,14 +233,7 @@
                     <a href="/admin/repos/test"> test </a>
                   </td>
                   <td class="repositoryBrowser">
-                    <a
-                      class="webLink"
-                      href="https://phabricator.example.org/r/project/test11"
-                      rel="noopener"
-                      target="_blank"
-                    >
-                      diffusion
-                    </a>
+                    <gr-weblink></gr-weblink>
                   </td>
                   <td class="changesLink">
                     <a href="/q/project:test"> view all </a>
@@ -330,14 +246,7 @@
                     <a href="/admin/repos/test"> test </a>
                   </td>
                   <td class="repositoryBrowser">
-                    <a
-                      class="webLink"
-                      href="https://phabricator.example.org/r/project/test12"
-                      rel="noopener"
-                      target="_blank"
-                    >
-                      diffusion
-                    </a>
+                    <gr-weblink></gr-weblink>
                   </td>
                   <td class="changesLink">
                     <a href="/q/project:test"> view all </a>
@@ -350,14 +259,7 @@
                     <a href="/admin/repos/test"> test </a>
                   </td>
                   <td class="repositoryBrowser">
-                    <a
-                      class="webLink"
-                      href="https://phabricator.example.org/r/project/test13"
-                      rel="noopener"
-                      target="_blank"
-                    >
-                      diffusion
-                    </a>
+                    <gr-weblink></gr-weblink>
                   </td>
                   <td class="changesLink">
                     <a href="/q/project:test"> view all </a>
@@ -370,14 +272,7 @@
                     <a href="/admin/repos/test"> test </a>
                   </td>
                   <td class="repositoryBrowser">
-                    <a
-                      class="webLink"
-                      href="https://phabricator.example.org/r/project/test14"
-                      rel="noopener"
-                      target="_blank"
-                    >
-                      diffusion
-                    </a>
+                    <gr-weblink></gr-weblink>
                   </td>
                   <td class="changesLink">
                     <a href="/q/project:test"> view all </a>
@@ -390,14 +285,7 @@
                     <a href="/admin/repos/test"> test </a>
                   </td>
                   <td class="repositoryBrowser">
-                    <a
-                      class="webLink"
-                      href="https://phabricator.example.org/r/project/test15"
-                      rel="noopener"
-                      target="_blank"
-                    >
-                      diffusion
-                    </a>
+                    <gr-weblink></gr-weblink>
                   </td>
                   <td class="changesLink">
                     <a href="/q/project:test"> view all </a>
@@ -410,14 +298,7 @@
                     <a href="/admin/repos/test"> test </a>
                   </td>
                   <td class="repositoryBrowser">
-                    <a
-                      class="webLink"
-                      href="https://phabricator.example.org/r/project/test16"
-                      rel="noopener"
-                      target="_blank"
-                    >
-                      diffusion
-                    </a>
+                    <gr-weblink></gr-weblink>
                   </td>
                   <td class="changesLink">
                     <a href="/q/project:test"> view all </a>
@@ -430,14 +311,7 @@
                     <a href="/admin/repos/test"> test </a>
                   </td>
                   <td class="repositoryBrowser">
-                    <a
-                      class="webLink"
-                      href="https://phabricator.example.org/r/project/test17"
-                      rel="noopener"
-                      target="_blank"
-                    >
-                      diffusion
-                    </a>
+                    <gr-weblink></gr-weblink>
                   </td>
                   <td class="changesLink">
                     <a href="/q/project:test"> view all </a>
@@ -450,14 +324,7 @@
                     <a href="/admin/repos/test"> test </a>
                   </td>
                   <td class="repositoryBrowser">
-                    <a
-                      class="webLink"
-                      href="https://phabricator.example.org/r/project/test18"
-                      rel="noopener"
-                      target="_blank"
-                    >
-                      diffusion
-                    </a>
+                    <gr-weblink></gr-weblink>
                   </td>
                   <td class="changesLink">
                     <a href="/q/project:test"> view all </a>
@@ -470,14 +337,7 @@
                     <a href="/admin/repos/test"> test </a>
                   </td>
                   <td class="repositoryBrowser">
-                    <a
-                      class="webLink"
-                      href="https://phabricator.example.org/r/project/test19"
-                      rel="noopener"
-                      target="_blank"
-                    >
-                      diffusion
-                    </a>
+                    <gr-weblink></gr-weblink>
                   </td>
                   <td class="changesLink">
                     <a href="/q/project:test"> view all </a>
@@ -490,14 +350,7 @@
                     <a href="/admin/repos/test"> test </a>
                   </td>
                   <td class="repositoryBrowser">
-                    <a
-                      class="webLink"
-                      href="https://phabricator.example.org/r/project/test20"
-                      rel="noopener"
-                      target="_blank"
-                    >
-                      diffusion
-                    </a>
+                    <gr-weblink></gr-weblink>
                   </td>
                   <td class="changesLink">
                     <a href="/q/project:test"> view all </a>
@@ -510,14 +363,7 @@
                     <a href="/admin/repos/test"> test </a>
                   </td>
                   <td class="repositoryBrowser">
-                    <a
-                      class="webLink"
-                      href="https://phabricator.example.org/r/project/test21"
-                      rel="noopener"
-                      target="_blank"
-                    >
-                      diffusion
-                    </a>
+                    <gr-weblink></gr-weblink>
                   </td>
                   <td class="changesLink">
                     <a href="/q/project:test"> view all </a>
@@ -530,14 +376,7 @@
                     <a href="/admin/repos/test"> test </a>
                   </td>
                   <td class="repositoryBrowser">
-                    <a
-                      class="webLink"
-                      href="https://phabricator.example.org/r/project/test22"
-                      rel="noopener"
-                      target="_blank"
-                    >
-                      diffusion
-                    </a>
+                    <gr-weblink></gr-weblink>
                   </td>
                   <td class="changesLink">
                     <a href="/q/project:test"> view all </a>
@@ -550,14 +389,7 @@
                     <a href="/admin/repos/test"> test </a>
                   </td>
                   <td class="repositoryBrowser">
-                    <a
-                      class="webLink"
-                      href="https://phabricator.example.org/r/project/test23"
-                      rel="noopener"
-                      target="_blank"
-                    >
-                      diffusion
-                    </a>
+                    <gr-weblink></gr-weblink>
                   </td>
                   <td class="changesLink">
                     <a href="/q/project:test"> view all </a>
@@ -570,14 +402,7 @@
                     <a href="/admin/repos/test"> test </a>
                   </td>
                   <td class="repositoryBrowser">
-                    <a
-                      class="webLink"
-                      href="https://phabricator.example.org/r/project/test24"
-                      rel="noopener"
-                      target="_blank"
-                    >
-                      diffusion
-                    </a>
+                    <gr-weblink></gr-weblink>
                   </td>
                   <td class="changesLink">
                     <a href="/q/project:test"> view all </a>
diff --git a/polygerrit-ui/app/elements/change-list/gr-repo-header/gr-repo-header.ts b/polygerrit-ui/app/elements/change-list/gr-repo-header/gr-repo-header.ts
index e27274b..9c951fb 100644
--- a/polygerrit-ui/app/elements/change-list/gr-repo-header/gr-repo-header.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-repo-header/gr-repo-header.ts
@@ -12,6 +12,7 @@
 import {LitElement, css, html, PropertyValues} from 'lit';
 import {customElement, property} from 'lit/decorators.js';
 import {createRepoUrl} from '../../../models/views/repo';
+import '../../shared/gr-weblink/gr-weblink';
 
 @customElement('gr-repo-header')
 export class GrRepoHeader extends LitElement {
@@ -49,9 +50,7 @@
     if (!webLinks) return;
     return html`<div>
       <span class="browse">Browse:</span>
-      ${webLinks.map(
-        link => html`<a target="_blank" href=${link.url}>${link.name}</a> `
-      )}
+      ${webLinks.map(info => html`<gr-weblink .info=${info}></gr-weblink>`)}
     </div> `;
   }
 
diff --git a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.ts b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.ts
index 0e71c1f..54752b0 100644
--- a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.ts
+++ b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.ts
@@ -16,6 +16,7 @@
 import '../../shared/gr-limited-text/gr-limited-text';
 import '../../shared/gr-linked-chip/gr-linked-chip';
 import '../../shared/gr-tooltip-content/gr-tooltip-content';
+import '../../shared/gr-weblink/gr-weblink';
 import '../gr-submit-requirements/gr-submit-requirements';
 import '../gr-commit-info/gr-commit-info';
 import '../gr-reviewer-list/gr-reviewer-list';
@@ -47,6 +48,7 @@
   RepoName,
   RevisionInfo,
   ServerInfo,
+  WebLinkInfo,
 } from '../../../types/common';
 import {assertIsDefined, assertNever, unique} from '../../../utils/common-util';
 import {GrEditableLabel} from '../../shared/gr-editable-label/gr-editable-label';
@@ -76,10 +78,9 @@
 import {fontStyles} from '../../../styles/gr-font-styles';
 import {changeMetadataStyles} from '../../../styles/gr-change-metadata-shared-styles';
 import {when} from 'lit/directives/when.js';
-import {ifDefined} from 'lit/directives/if-defined.js';
 import {createSearchUrl} from '../../../models/views/search';
 import {createChangeUrl} from '../../../models/views/change';
-import {GeneratedWebLink, getChangeWeblinks} from '../../../utils/weblink-util';
+import {getChangeWeblinks} from '../../../utils/weblink-util';
 import {throwingErrorCallback} from '../../shared/gr-rest-api-interface/gr-rest-apis/gr-rest-api-helper';
 
 const HASHTAG_ADD_MESSAGE = 'Add Hashtag';
@@ -182,7 +183,7 @@
       gr-editable-label {
         max-width: 9em;
       }
-      .webLink {
+      gr-weblink {
         display: block;
       }
       gr-account-chip[disabled],
@@ -703,16 +704,7 @@
     return html`<section id="webLinks">
       <span class="title">Links</span>
       <span class="value">
-        ${webLinks.map(
-          link => html`<a
-            href=${ifDefined(link.url)}
-            class="webLink"
-            rel="noopener"
-            target="_blank"
-          >
-            ${link.name}
-          </a>`
-        )}
+        ${webLinks.map(info => html`<gr-weblink .info=${info}></gr-weblink>`)}
       </span>
     </section>`;
   }
@@ -741,7 +733,7 @@
   }
 
   // private but used in test
-  computeWebLinks(): GeneratedWebLink[] {
+  computeWebLinks(): WebLinkInfo[] {
     return getChangeWeblinks(this.commitInfo?.web_links, this.serverConfig);
   }
 
diff --git a/polygerrit-ui/app/elements/change/gr-change-summary/gr-summary-chip.ts b/polygerrit-ui/app/elements/change/gr-change-summary/gr-summary-chip.ts
index 146ab61..5588f40 100644
--- a/polygerrit-ui/app/elements/change/gr-change-summary/gr-summary-chip.ts
+++ b/polygerrit-ui/app/elements/change/gr-change-summary/gr-summary-chip.ts
@@ -66,7 +66,7 @@
           border-color: var(--info-foreground);
           background: var(--info-background);
         }
-        .summaryChip.info:hover {
+        button.summaryChip.info:hover {
           background: var(--info-background-hover);
           box-shadow: var(--elevation-level-1);
         }
@@ -80,7 +80,7 @@
           border-color: var(--warning-foreground);
           background: var(--warning-background);
         }
-        .summaryChip.warning:hover {
+        button.summaryChip.warning:hover {
           background: var(--warning-background-hover);
           box-shadow: var(--elevation-level-1);
         }
@@ -94,7 +94,7 @@
           border-color: var(--gray-foreground);
           background: var(--gray-background);
         }
-        .summaryChip.check:hover {
+        button.summaryChip.check:hover {
           background: var(--gray-background-hover);
           box-shadow: var(--elevation-level-1);
         }
diff --git a/polygerrit-ui/app/elements/change/gr-comments-summary/gr-comments-summary.ts b/polygerrit-ui/app/elements/change/gr-comments-summary/gr-comments-summary.ts
index 3d5533c..6f83190 100644
--- a/polygerrit-ui/app/elements/change/gr-comments-summary/gr-comments-summary.ts
+++ b/polygerrit-ui/app/elements/change/gr-comments-summary/gr-comments-summary.ts
@@ -179,6 +179,7 @@
         styleType=${SummaryChipStyles.CHECK}
         category=${CommentTabState.SHOW_ALL}
         .clickable=${this.clickableChips}
+        icon="mark_chat_read"
         ><gr-avatar-stack .accounts=${resolvedAuthors} imageSize="32">
           <gr-icon
             slot="fallback"
diff --git a/polygerrit-ui/app/elements/change/gr-commit-info/gr-commit-info.ts b/polygerrit-ui/app/elements/change/gr-commit-info/gr-commit-info.ts
index d6a5327..673f260 100644
--- a/polygerrit-ui/app/elements/change/gr-commit-info/gr-commit-info.ts
+++ b/polygerrit-ui/app/elements/change/gr-commit-info/gr-commit-info.ts
@@ -4,7 +4,13 @@
  * SPDX-License-Identifier: Apache-2.0
  */
 import '../../shared/gr-copy-clipboard/gr-copy-clipboard';
-import {CommitInfo, ServerInfo} from '../../../types/common';
+import '../../shared/gr-weblink/gr-weblink';
+import {
+  CommitId,
+  CommitInfo,
+  ServerInfo,
+  WebLinkInfo,
+} from '../../../types/common';
 import {sharedStyles} from '../../../styles/shared-styles';
 import {LitElement, css, html, nothing} from 'lit';
 import {customElement, property, state} from 'lit/decorators.js';
@@ -12,7 +18,7 @@
 import {resolve} from '../../../models/dependency';
 import {configModelToken} from '../../../models/config/config-model';
 import {createSearchUrl} from '../../../models/views/search';
-import {getPatchSetWeblink} from '../../../utils/weblink-util';
+import {getBrowseCommitWeblink} from '../../../utils/weblink-util';
 
 declare global {
   interface HTMLElementTagNameMap {
@@ -55,9 +61,7 @@
     const commit = this.commitInfo?.commit;
     if (!commit) return nothing;
     return html` <div class="container">
-      <a target="_blank" rel="noopener" href=${this.computeCommitLink()}
-        >${this.getWeblink()?.name ?? ''}</a
-      >
+      <gr-weblink .info=${this.getWeblink(commit)}></gr-weblink>
       <gr-copy-clipboard
         hastooltip
         .buttonTitle=${'Copy full SHA to clipboard'}
@@ -68,19 +72,18 @@
     </div>`;
   }
 
-  getWeblink() {
-    return getPatchSetWeblink(
-      this.commitInfo?.commit,
+  /**
+   * Looks up the primary patchset weblink, but replaces its name by the
+   * shortened commit hash. And falls back to a search query, if no weblink
+   * is configured.
+   */
+  getWeblink(commit: CommitId): WebLinkInfo | undefined {
+    if (!commit) return undefined;
+    const name = commit.slice(0, 7);
+    const primaryLink = getBrowseCommitWeblink(
       this.commitInfo?.web_links,
       this.serverConfig
     );
-  }
-
-  computeCommitLink() {
-    const weblink = this.getWeblink();
-    if (weblink?.url) return weblink.url;
-
-    const hash = weblink?.name;
-    return hash ? createSearchUrl({query: hash}) : '';
+    return {name, url: primaryLink?.url ?? createSearchUrl({query: name})};
   }
 }
diff --git a/polygerrit-ui/app/elements/change/gr-commit-info/gr-commit-info_test.ts b/polygerrit-ui/app/elements/change/gr-commit-info/gr-commit-info_test.ts
index d992ffe..41615a0 100644
--- a/polygerrit-ui/app/elements/change/gr-commit-info/gr-commit-info_test.ts
+++ b/polygerrit-ui/app/elements/change/gr-commit-info/gr-commit-info_test.ts
@@ -12,6 +12,7 @@
 } from '../../../test/test-data-generators';
 import {CommitId} from '../../../types/common';
 import {fixture, html, assert} from '@open-wc/testing';
+import {queryAndAssert} from '../../../utils/common-util';
 
 suite('gr-commit-info tests', () => {
   let element: GrCommitInfo;
@@ -36,13 +37,15 @@
     };
     await element.updateComplete;
 
+    const weblink = queryAndAssert(element, 'gr-weblink');
     assert.shadowDom.equal(
-      element,
+      weblink,
       /* HTML */ `
-        <div class="container">
-          <a href="link-url" rel="noopener" target="_blank">sha4567</a>
-          <gr-copy-clipboard hastooltip="" hideinput=""> </gr-copy-clipboard>
-        </div>
+        <a href="link-url" rel="noopener" target="_blank">
+          <gr-tooltip-content>
+            <span> sha4567 </span>
+          </gr-tooltip-content>
+        </a>
       `
     );
   });
@@ -54,13 +57,15 @@
     };
     await element.updateComplete;
 
+    const weblink = queryAndAssert(element, 'gr-weblink');
     assert.shadowDom.equal(
-      element,
+      weblink,
       /* HTML */ `
-        <div class="container">
-          <a href="/q/sha4567" rel="noopener" target="_blank">sha4567</a>
-          <gr-copy-clipboard hastooltip="" hideinput=""> </gr-copy-clipboard>
-        </div>
+        <a href="/q/sha4567" rel="noopener" target="_blank">
+          <gr-tooltip-content>
+            <span> sha4567 </span>
+          </gr-tooltip-content>
+        </a>
       `
     );
   });
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host.ts b/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host.ts
index 2827c9b..9bead95 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host.ts
@@ -44,6 +44,7 @@
   DiffInfo,
   DiffPreferencesInfo,
   IgnoreWhitespaceType,
+  WebLinkInfo,
 } from '../../../types/diff';
 import {
   CreateCommentEventDetail,
@@ -95,7 +96,6 @@
   noAwait,
 } from '../../../utils/async-util';
 import {subscribe} from '../../lit/subscription-controller';
-import {GeneratedWebLink} from '../../../utils/weblink-util';
 import {userModelToken} from '../../../models/user/user-model';
 import {pluginLoaderToken} from '../../shared/gr-js-api-interface/gr-plugin-loader';
 
@@ -128,7 +128,7 @@
     'create-comment': CustomEvent<CreateCommentEventDetail>;
     'is-blame-loaded-changed': ValueChangedEvent<boolean>;
     'diff-changed': ValueChangedEvent<DiffInfo | undefined>;
-    'edit-weblinks-changed': ValueChangedEvent<GeneratedWebLink[] | undefined>;
+    'edit-weblinks-changed': ValueChangedEvent<WebLinkInfo[] | undefined>;
     'files-weblinks-changed': ValueChangedEvent<FilesWebLinks | undefined>;
     'is-image-diff-changed': ValueChangedEvent<boolean>;
     // Fired when the user selects a line (See gr-diff).
@@ -188,13 +188,13 @@
   }
 
   @state()
-  private _editWeblinks?: GeneratedWebLink[];
+  private _editWeblinks?: WebLinkInfo[];
 
   get editWeblinks() {
     return this._editWeblinks;
   }
 
-  set editWeblinks(editWeblinks: GeneratedWebLink[] | undefined) {
+  set editWeblinks(editWeblinks: WebLinkInfo[] | undefined) {
     if (this._editWeblinks === editWeblinks) return;
     this._editWeblinks = editWeblinks;
     fire(this, 'edit-weblinks-changed', {value: editWeblinks});
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.ts b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.ts
index 4207c2b..4687fc1 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.ts
@@ -12,6 +12,7 @@
 import '../../shared/gr-dropdown-list/gr-dropdown-list';
 import '../../shared/gr-icon/gr-icon';
 import '../../shared/gr-select/gr-select';
+import '../../shared/gr-weblink/gr-weblink';
 import '../../shared/revision-info/revision-info';
 import '../../../embed/diff/gr-diff-cursor/gr-diff-cursor';
 import '../gr-apply-fix-dialog/gr-apply-fix-dialog';
@@ -48,7 +49,7 @@
   ServerInfo,
   CommentMap,
 } from '../../../types/common';
-import {DiffInfo, DiffPreferencesInfo} from '../../../types/diff';
+import {DiffInfo, DiffPreferencesInfo, WebLinkInfo} from '../../../types/diff';
 import {FileRange, ParsedChangeInfo} from '../../../types/types';
 import {
   FilesWebLinks,
@@ -89,7 +90,6 @@
   ChangeChildView,
   changeViewModelToken,
 } from '../../../models/views/change';
-import {GeneratedWebLink} from '../../../utils/weblink-util';
 import {userModelToken} from '../../../models/user/user-model';
 import {modalStyles} from '../../../styles/gr-modal-styles';
 import {PaperTabsElement} from '@polymer/paper-tabs/paper-tabs';
@@ -225,7 +225,7 @@
   private isImageDiff?: boolean;
 
   @state()
-  private editWeblinks?: GeneratedWebLink[];
+  private editWeblinks?: WebLinkInfo[];
 
   @state()
   private filesWeblinks?: FilesWebLinks;
@@ -935,11 +935,7 @@
         () => html`
           <span class="separator"></span>
           ${this.editWeblinks!.map(
-            weblink => html`
-              <a target="_blank" href=${ifDefined(weblink.url)}
-                >${weblink.name}</a
-              >
-            `
+            weblink => html`<gr-weblink .info=${weblink}></gr-weblink>`
           )}
         `
       )}
@@ -1084,7 +1080,7 @@
   }
 
   private onEditWeblinksChanged(
-    e: ValueChangedEvent<GeneratedWebLink[] | undefined>
+    e: ValueChangedEvent<WebLinkInfo[] | undefined>
   ) {
     this.editWeblinks = e.detail.value;
   }
diff --git a/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select.ts b/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select.ts
index e33c48a..e1dfd1f 100644
--- a/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select.ts
+++ b/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select.ts
@@ -5,6 +5,7 @@
  */
 import '../../shared/gr-dropdown-list/gr-dropdown-list';
 import '../../shared/gr-select/gr-select';
+import '../../shared/gr-weblink/gr-weblink';
 import {convertToString, pluralize} from '../../../utils/string-util';
 import {getAppContext} from '../../../services/app-context';
 import {
@@ -27,6 +28,7 @@
   RevisionInfo,
   RevisionPatchSetNum,
   Timestamp,
+  WebLinkInfo,
 } from '../../../types/common';
 import {RevisionInfo as RevisionInfoClass} from '../../shared/revision-info/revision-info';
 import {ChangeComments} from '../gr-comment-api/gr-comment-api';
@@ -42,9 +44,7 @@
 import {subscribe} from '../../lit/subscription-controller';
 import {commentsModelToken} from '../../../models/comments/comments-model';
 import {resolve} from '../../../models/dependency';
-import {ifDefined} from 'lit/directives/if-defined.js';
 import {ValueChangedEvent} from '../../../types/events';
-import {GeneratedWebLink} from '../../../utils/weblink-util';
 import {changeModelToken} from '../../../models/change/change-model';
 import {changeViewModelToken} from '../../../models/views/change';
 import {fireNoBubbleNoCompose} from '../../../utils/event-util';
@@ -65,8 +65,8 @@
 export type PatchRangeChangeEvent = CustomEvent<PatchRangeChangeDetail>;
 
 export interface FilesWebLinks {
-  meta_a: GeneratedWebLink[];
-  meta_b: GeneratedWebLink[];
+  meta_a: WebLinkInfo[];
+  meta_b: WebLinkInfo[];
 }
 
 declare global {
@@ -188,6 +188,9 @@
           --trigger-style-text-color: var(--deemphasized-text-color);
           --trigger-style-font-family: var(--font-family);
         }
+        .filesWeblinks gr-weblink {
+          vertical-align: baseline;
+        }
         @media screen and (max-width: 50em) {
           .filesWeblinks {
             display: none;
@@ -234,15 +237,11 @@
     `;
   }
 
-  private renderWeblinks(fileLinks?: GeneratedWebLink[]) {
+  private renderWeblinks(fileLinks?: WebLinkInfo[]) {
     if (!fileLinks) return;
     return html`<span class="filesWeblinks">
       ${fileLinks.map(
-        weblink => html`
-          <a target="_blank" rel="noopener" href=${ifDefined(weblink.url)}>
-            ${weblink.name}
-          </a>
-        `
+        weblink => html`<gr-weblink .info=${weblink}></gr-weblink>`
       )}</span
     > `;
   }
diff --git a/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select_test.ts b/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select_test.ts
index 3813946..20d2549 100644
--- a/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select_test.ts
+++ b/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select_test.ts
@@ -9,7 +9,7 @@
 import {GrPatchRangeSelect} from './gr-patch-range-select';
 import {RevisionInfo as RevisionInfoClass} from '../../shared/revision-info/revision-info';
 import {ChangeComments} from '../gr-comment-api/gr-comment-api';
-import {stubReporting} from '../../../test/test-utils';
+import {queryAll, stubReporting} from '../../../test/test-utils';
 import {
   BasePatchSetNum,
   EDIT,
@@ -322,28 +322,11 @@
 
   test('filesWeblinks', async () => {
     element.filesWeblinks = {
-      meta_a: [
-        {
-          name: 'foo',
-          url: 'f.oo',
-        },
-      ],
-      meta_b: [
-        {
-          name: 'bar',
-          url: 'ba.r',
-        },
-      ],
+      meta_a: [{name: 'foo', url: 'f.oo'}],
+      meta_b: [{name: 'bar', url: 'ba.r'}],
     };
     await element.updateComplete;
-    assert.equal(
-      queryAndAssert(element, 'a[href="f.oo"]').textContent!.trim(),
-      'foo'
-    );
-    assert.equal(
-      queryAndAssert(element, 'a[href="ba.r"]').textContent!.trim(),
-      'bar'
-    );
+    assert.equal(queryAll(element, 'gr-weblink').length, 2);
   });
 
   test('computePatchSetCommentsString', () => {
diff --git a/polygerrit-ui/app/elements/shared/gr-change-status/gr-change-status.ts b/polygerrit-ui/app/elements/shared/gr-change-status/gr-change-status.ts
index 269bbea..c93cc97 100644
--- a/polygerrit-ui/app/elements/shared/gr-change-status/gr-change-status.ts
+++ b/polygerrit-ui/app/elements/shared/gr-change-status/gr-change-status.ts
@@ -6,12 +6,11 @@
 import '../gr-icon/gr-icon';
 import '../gr-tooltip-content/gr-tooltip-content';
 import '../../../styles/shared-styles';
-import {ChangeInfo, ChangeStates} from '../../../types/common';
+import {ChangeInfo, ChangeStates, WebLinkInfo} from '../../../types/common';
 import {sharedStyles} from '../../../styles/shared-styles';
 import {LitElement, PropertyValues, html, css} from 'lit';
 import {customElement, property, state} from 'lit/decorators.js';
 import {createSearchUrl} from '../../../models/views/search';
-import {GeneratedWebLink} from '../../../utils/weblink-util';
 
 export const WIP_TOOLTIP =
   "This change isn't ready to be reviewed or submitted. " +
@@ -47,7 +46,7 @@
   revertedChange?: ChangeInfo;
 
   @property({type: Object})
-  resolveWeblinks?: GeneratedWebLink[] = [];
+  resolveWeblinks?: WebLinkInfo[] = [];
 
   static override get styles() {
     return [
diff --git a/polygerrit-ui/app/elements/shared/gr-change-status/gr-change-status_test.ts b/polygerrit-ui/app/elements/shared/gr-change-status/gr-change-status_test.ts
index 36615c0..89d30ee 100644
--- a/polygerrit-ui/app/elements/shared/gr-change-status/gr-change-status_test.ts
+++ b/polygerrit-ui/app/elements/shared/gr-change-status/gr-change-status_test.ts
@@ -80,7 +80,7 @@
     );
     assert.equal(element.tooltipText, '');
     assert.isTrue(element.classList.contains('merged'));
-    element.resolveWeblinks = [{url: 'http://google.com'}];
+    element.resolveWeblinks = [{name: 'browse', url: 'http://google.com'}];
     element.status = ChangeStates.MERGED;
     assert.isFalse(element.showResolveIcon());
   });
@@ -117,7 +117,7 @@
   test('merge conflict with resolve link', () => {
     const status = ChangeStates.MERGE_CONFLICT;
     const url = 'http://google.com';
-    const weblinks = [{url}];
+    const weblinks = [{name: 'browse', url}];
 
     element.revertedChange = undefined;
     element.resolveWeblinks = weblinks;
diff --git a/polygerrit-ui/app/elements/shared/gr-dropdown-list/gr-dropdown-list.ts b/polygerrit-ui/app/elements/shared/gr-dropdown-list/gr-dropdown-list.ts
index a083526..9b74e24 100644
--- a/polygerrit-ui/app/elements/shared/gr-dropdown-list/gr-dropdown-list.ts
+++ b/polygerrit-ui/app/elements/shared/gr-dropdown-list/gr-dropdown-list.ts
@@ -166,6 +166,9 @@
             --selection-background-color
           );
         }
+        gr-comments-summary {
+          padding-left: var(--spacing-s);
+        }
         @media only screen and (max-width: 50em) {
           gr-select {
             display: var(--gr-select-style-display, inline);
@@ -252,15 +255,17 @@
     return html`
       <paper-item ?disabled=${item.disabled} data-value=${item.value}>
         <div class="topContent">
-          <div>${item.text}</div>
-          ${when(
-            item.commentThreads,
-            () => html`<gr-comments-summary
-              .commentThreads=${item.commentThreads}
-              emptyWhenNoComments
-              showAvatarForResolved
-            ></gr-comments-summary>`
-          )}
+          <div>
+            <span>${item.text}</span>
+            ${when(
+              item.commentThreads,
+              () => html`<gr-comments-summary
+                .commentThreads=${item.commentThreads}
+                emptyWhenNoComments
+                showAvatarForResolved
+              ></gr-comments-summary>`
+            )}
+          </div>
           ${when(
             item.date,
             () => html`
diff --git a/polygerrit-ui/app/elements/shared/gr-dropdown-list/gr-dropdown-list_test.ts b/polygerrit-ui/app/elements/shared/gr-dropdown-list/gr-dropdown-list_test.ts
index b9380cb..c148a1b 100644
--- a/polygerrit-ui/app/elements/shared/gr-dropdown-list/gr-dropdown-list_test.ts
+++ b/polygerrit-ui/app/elements/shared/gr-dropdown-list/gr-dropdown-list_test.ts
@@ -91,7 +91,7 @@
               tabindex="-1"
             >
               <div class="topContent">
-                <div>Top Text 1</div>
+                <div><span>Top Text 1</span></div>
               </div>
             </paper-item>
             <paper-item
@@ -103,7 +103,7 @@
               tabindex="0"
             >
               <div class="topContent">
-                <div>Top Text 2</div>
+                <div><span>Top Text 2</span></div>
               </div>
               <div class="bottomContent">
                 <div>Bottom Text 2</div>
@@ -119,7 +119,7 @@
               tabindex="-1"
             >
               <div class="topContent">
-                <div>Top Text 3</div>
+                <div><span>Top Text 3</span></div>
                 <gr-date-formatter> </gr-date-formatter>
               </div>
               <div class="bottomContent">
@@ -231,7 +231,8 @@
     assert.equal(items[0].dataset.value, element.items[0].value as any);
     assert.equal(mobileItems[0].value, element.items[0].value);
     assert.equal(
-      queryAndAssert<HTMLDivElement>(items[0], '.topContent div').innerText,
+      queryAndAssert<HTMLDivElement>(items[0], '.topContent div span')
+        .innerText,
       element.items[0].text
     );
 
@@ -250,7 +251,8 @@
     assert.equal(items[1].dataset.value, element.items[1].value as any);
     assert.equal(mobileItems[1].value, element.items[1].value);
     assert.equal(
-      queryAndAssert<HTMLDivElement>(items[1], '.topContent div').innerText,
+      queryAndAssert<HTMLDivElement>(items[1], '.topContent div span')
+        .textContent,
       element.items[1].text
     );
 
@@ -273,7 +275,8 @@
     assert.equal(items[2].dataset.value, element.items[2].value as any);
     assert.equal(mobileItems[2].value, element.items[2].value);
     assert.equal(
-      queryAndAssert<HTMLDivElement>(items[2], '.topContent div').innerText,
+      queryAndAssert<HTMLDivElement>(items[2], '.topContent div span')
+        .innerText,
       element.items[2].text
     );
 
diff --git a/polygerrit-ui/app/elements/shared/gr-weblink/gr-weblink.ts b/polygerrit-ui/app/elements/shared/gr-weblink/gr-weblink.ts
new file mode 100644
index 0000000..6d14957
--- /dev/null
+++ b/polygerrit-ui/app/elements/shared/gr-weblink/gr-weblink.ts
@@ -0,0 +1,61 @@
+/**
+ * @license
+ * Copyright 2023 Google LLC
+ * SPDX-License-Identifier: Apache-2.0
+ */
+import '../gr-tooltip-content/gr-tooltip-content';
+import {LitElement, css, html, nothing} from 'lit';
+import {customElement, property} from 'lit/decorators.js';
+import {WebLinkInfo} from '../../../api/rest-api';
+import {ifDefined} from 'lit/directives/if-defined.js';
+import {when} from 'lit/directives/when.js';
+
+declare global {
+  interface HTMLElementTagNameMap {
+    'gr-weblink': GrWeblink;
+  }
+}
+@customElement('gr-weblink')
+export class GrWeblink extends LitElement {
+  @property({type: Object})
+  info?: WebLinkInfo;
+
+  static override get styles() {
+    return [
+      css`
+        :host {
+          display: inline-block;
+          vertical-align: top;
+          line-height: var(--line-height-normal);
+        }
+        a {
+          color: var(--link-color);
+        }
+        img {
+          width: var(--line-height-normal);
+          height: var(--line-height-normal);
+        }
+      `,
+    ];
+  }
+
+  override render() {
+    if (!this.info?.url) return nothing;
+    if (!this.info?.name) return nothing;
+
+    return html`
+      <a href=${this.info.url} rel="noopener" target="_blank">
+        <gr-tooltip-content
+          title=${ifDefined(this.info.tooltip)}
+          ?has-tooltip=${this.info.tooltip !== undefined}
+        >
+          ${when(
+            this.info.image_url,
+            () => html`<img src=${this.info!.image_url!} />`,
+            () => html`<span>${this.info!.name}</span>`
+          )}
+        </gr-tooltip-content>
+      </a>
+    `;
+  }
+}
diff --git a/polygerrit-ui/app/elements/shared/gr-weblink/gr-weblink_test.ts b/polygerrit-ui/app/elements/shared/gr-weblink/gr-weblink_test.ts
new file mode 100644
index 0000000..acb309f
--- /dev/null
+++ b/polygerrit-ui/app/elements/shared/gr-weblink/gr-weblink_test.ts
@@ -0,0 +1,56 @@
+/**
+ * @license
+ * Copyright 2023 Google LLC
+ * SPDX-License-Identifier: Apache-2.0
+ */
+import '../../../test/common-test-setup';
+import {fixture, assert} from '@open-wc/testing';
+import {html} from 'lit';
+import './gr-weblink';
+import {GrWeblink} from './gr-weblink';
+import {WebLinkInfo} from '../../../api/rest-api';
+
+suite('gr-weblink tests', () => {
+  test('renders with image', async () => {
+    const info: WebLinkInfo = {
+      name: 'gitiles',
+      url: 'https://www.google.com',
+      image_url: 'https://www.google.com/favicon.ico',
+      tooltip: 'Open in Gitiles',
+    };
+    const element = await fixture<GrWeblink>(
+      html`<gr-weblink .info=${info}></gr-weblink>`
+    );
+    assert.shadowDom.equal(
+      element,
+      /* HTML */ `
+        <a href="https://www.google.com" rel="noopener" target="_blank">
+          <gr-tooltip-content title="Open in Gitiles" has-tooltip>
+            <img src="https://www.google.com/favicon.ico" />
+          </gr-tooltip-content>
+        </a>
+      `
+    );
+  });
+
+  test('renders with text', async () => {
+    const info: WebLinkInfo = {
+      name: 'gitiles',
+      url: 'https://www.google.com',
+      tooltip: 'Open in Gitiles',
+    };
+    const element = await fixture<GrWeblink>(
+      html`<gr-weblink .info=${info}></gr-weblink>`
+    );
+    assert.shadowDom.equal(
+      element,
+      /* HTML */ `
+        <a href="https://www.google.com" rel="noopener" target="_blank">
+          <gr-tooltip-content title="Open in Gitiles" has-tooltip>
+            <span>gitiles</span>
+          </gr-tooltip-content>
+        </a>
+      `
+    );
+  });
+});
diff --git a/polygerrit-ui/app/utils/weblink-util.ts b/polygerrit-ui/app/utils/weblink-util.ts
index 1e9315c..17ad44b 100644
--- a/polygerrit-ui/app/utils/weblink-util.ts
+++ b/polygerrit-ui/app/utils/weblink-util.ts
@@ -3,51 +3,26 @@
  * Copyright 2022 Google LLC
  * SPDX-License-Identifier: Apache-2.0
  */
-import {CommitId, ServerInfo} from '../api/rest-api';
-
-export interface WebLink {
-  name?: string;
-  label: string;
-  url: string;
-}
-
-export interface GeneratedWebLink {
-  name?: string;
-  label?: string;
-  url?: string;
-}
-
-export function getPatchSetWeblink(
-  commit?: CommitId,
-  weblinks?: GeneratedWebLink[],
-  config?: ServerInfo
-): GeneratedWebLink | undefined {
-  if (!commit) return undefined;
-  const name = commit.slice(0, 7);
-  const weblink = getBrowseCommitWeblink(weblinks, config);
-  if (!weblink?.url) return {name};
-  return {name, url: weblink.url};
-}
+import {ServerInfo, WebLinkInfo} from '../api/rest-api';
 
 // visible for testing
-export function getCodeBrowserWeblink(weblinks: GeneratedWebLink[]) {
+export function getCodeBrowserWeblink(weblinks: WebLinkInfo[]) {
   // is an ordered allowed list of web link types that provide direct
   // links to the commit in the url property.
-  const codeBrowserLinks = ['gitiles', 'browse', 'gitweb'];
+  const codeBrowserLinks = ['gitiles', 'browse', 'gitweb', 'code search'];
   for (let i = 0; i < codeBrowserLinks.length; i++) {
     const weblink = weblinks.find(
-      weblink => weblink.name === codeBrowserLinks[i]
+      weblink => weblink.name?.toLowerCase() === codeBrowserLinks[i]
     );
     if (weblink) return weblink;
   }
   return undefined;
 }
 
-// visible for testing
 export function getBrowseCommitWeblink(
-  weblinks?: GeneratedWebLink[],
+  weblinks?: WebLinkInfo[],
   config?: ServerInfo
-): GeneratedWebLink | undefined {
+): WebLinkInfo | undefined {
   if (!weblinks) return undefined;
 
   // Use primary weblink if configured and exists.
@@ -61,9 +36,9 @@
 }
 
 export function getChangeWeblinks(
-  weblinks?: GeneratedWebLink[],
+  weblinks?: WebLinkInfo[],
   config?: ServerInfo
-): GeneratedWebLink[] {
+): WebLinkInfo[] {
   if (!weblinks?.length) return [];
   const commitWeblink = getBrowseCommitWeblink(weblinks, config);
   return weblinks.filter(
diff --git a/polygerrit-ui/app/utils/weblink-util_test.ts b/polygerrit-ui/app/utils/weblink-util_test.ts
index be97cfd..63842e2 100644
--- a/polygerrit-ui/app/utils/weblink-util_test.ts
+++ b/polygerrit-ui/app/utils/weblink-util_test.ts
@@ -16,17 +16,20 @@
   test('getCodeBrowserWeblink', () => {
     assert.deepEqual(
       getCodeBrowserWeblink([
-        {name: 'gitweb'},
-        {name: 'gitiles'},
-        {name: 'browse'},
-        {name: 'test'},
+        {name: 'gitweb', url: 'http://www.test.com'},
+        {name: 'gitiles', url: 'http://www.test.com'},
+        {name: 'browse', url: 'http://www.test.com'},
+        {name: 'test', url: 'http://www.test.com'},
       ]),
-      {name: 'gitiles'}
+      {name: 'gitiles', url: 'http://www.test.com'}
     );
 
     assert.deepEqual(
-      getCodeBrowserWeblink([{name: 'gitweb'}, {name: 'test'}]),
-      {name: 'gitweb'}
+      getCodeBrowserWeblink([
+        {name: 'gitweb', url: 'http://www.test.com'},
+        {name: 'test', url: 'http://www.test.com'},
+      ]),
+      {name: 'gitweb', url: 'http://www.test.com'}
     );
   });