Merge "Bump JGit to 5ae8d28"
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/Documentation/rest-api-changes.txt b/Documentation/rest-api-changes.txt
index a35f508..bf51252 100644
--- a/Documentation/rest-api-changes.txt
+++ b/Documentation/rest-api-changes.txt
@@ -6736,7 +6736,7 @@
 |`rebase:chain`|`rebase`|
 Present if the user can rebase the chain. +
 This is the case if the calling user can rebase each change in the
-chain.+
+chain.
 Rebasing a change is allowed for the change owner and users with the
 link:access-control.html#category_submit[Submit] or
 link:access-control.html#category_rebase[Rebase] permission if they
@@ -6744,7 +6744,7 @@
 |`rebase:chain`|`rebase_on_behalf_of_uploader`|
 Present if the user can rebase the chain on behalf of the uploader. +
 This is the case if the calling user can rebase each change in the
-chain on behalf of the uploader.+
+chain on behalf of the uploader.
 Rebasing a change on behalf of the uploader is allowed for the change
 owner and users with the link:access-control.html#category_submit[Submit]
 or link:access-control.html#category_rebase[Rebase] permission.
diff --git a/java/com/google/gerrit/server/restapi/change/Rebase.java b/java/com/google/gerrit/server/restapi/change/Rebase.java
index 3cb1870..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 =
@@ -142,9 +143,7 @@
     UiAction.Description description =
         new UiAction.Description()
             .setLabel("Rebase")
-            .setTitle(
-                "Rebase onto tip of branch or parent change. Makes you the uploader of this "
-                    + "change which can affect validity of approvals.")
+            .setTitle("Rebase onto tip of branch or parent change.")
             .setVisible(false);
 
     Change change = rsrc.getChange();
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/api/diff.ts b/polygerrit-ui/app/api/diff.ts
index d8f4942..4bf253d 100644
--- a/polygerrit-ui/app/api/diff.ts
+++ b/polygerrit-ui/app/api/diff.ts
@@ -243,12 +243,6 @@
   image_diff_prefs?: ImageDiffPreferences;
   responsive_mode?: DiffResponsiveMode;
   num_lines_rendered_at_once?: number;
-  /**
-   * If enabled, then a new (experimental) diff rendering is used that is
-   * based on Lit components and multiple rendering passes. This is planned to
-   * be a temporary setting until the experiment is concluded.
-   */
-  use_lit_components?: boolean;
   show_sign_col?: boolean;
   /**
    * The default view mode is SIDE_BY_SIDE.
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/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog.ts b/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog.ts
index 27fde97..166d895 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog.ts
+++ b/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog.ts
@@ -3,6 +3,7 @@
  * Copyright 2016 Google LLC
  * SPDX-License-Identifier: Apache-2.0
  */
+import '../../shared/gr-account-chip/gr-account-chip';
 import {css, html, LitElement, PropertyValues} from 'lit';
 import {customElement, property, query, state} from 'lit/decorators.js';
 import {when} from 'lit/directives/when.js';
@@ -10,6 +11,8 @@
   NumericChangeId,
   BranchName,
   ChangeActionDialog,
+  AccountDetailInfo,
+  AccountInfo,
 } from '../../../types/common';
 import '../../shared/gr-dialog/gr-dialog';
 import '../../shared/gr-autocomplete/gr-autocomplete';
@@ -26,6 +29,7 @@
 import {resolve} from '../../../models/dependency';
 import {changeModelToken} from '../../../models/change/change-model';
 import {subscribe} from '../../lit/subscription-controller';
+import {userModelToken} from '../../../models/user/user-model';
 
 export interface RebaseChange {
   name: string;
@@ -86,9 +90,6 @@
   @state()
   allowConflicts = false;
 
-  @state()
-  isOwner = false;
-
   @query('#rebaseOnParentInput')
   private rebaseOnParentInput!: HTMLInputElement;
 
@@ -107,17 +108,30 @@
   @query('#parentInput')
   parentInput!: GrAutocomplete;
 
+  @state()
+  account?: AccountDetailInfo;
+
+  @state()
+  uploader?: AccountInfo;
+
   private readonly restApiService = getAppContext().restApiService;
 
   private readonly getChangeModel = resolve(this, changeModelToken);
 
+  private readonly getUserModel = resolve(this, userModelToken);
+
   constructor() {
     super();
     this.query = input => this.getChangeSuggestions(input);
     subscribe(
       this,
-      () => this.getChangeModel().isOwner$,
-      x => (this.isOwner = x)
+      () => this.getUserModel().account$,
+      x => (this.account = x)
+    );
+    subscribe(
+      this,
+      () => this.getChangeModel().latestUploader$,
+      x => (this.uploader = x)
     );
   }
 
@@ -152,12 +166,15 @@
         display: block;
         width: 100%;
       }
-      .rebaseCheckbox:first-of-type {
+      .rebaseCheckbox {
         margin-top: var(--spacing-m);
       }
       .rebaseOption {
         margin: var(--spacing-m) 0;
       }
+      .rebaseOnBehalfMsg {
+        margin-top: var(--spacing-m);
+      }
     `,
   ];
 
@@ -255,7 +272,7 @@
             >
           </div>
           ${when(
-            !this.isOwner && this.allowConflicts,
+            !this.isCurrentUserEqualToLatestUploader() && this.allowConflicts,
             () =>
               html`<span class="message"
                 >Rebase cannot be done on behalf of the uploader when allowing
@@ -276,6 +293,16 @@
                 <label for="rebaseChain">Rebase all ancestors</label>
               </div>`
           )}
+          ${when(
+            !this.isCurrentUserEqualToLatestUploader(),
+            () => html`<div class="rebaseOnBehalfMsg">Rebase will be done on behalf of${
+              !this.allowConflicts ? ' the uploader:' : ''
+            } <gr-account-chip
+                .account=${this.allowConflicts ? this.account : this.uploader}
+                .hideHovercard=${true}
+              ></gr-account-chip
+              ><span></div>`
+          )}
         </div>
       </gr-dialog>
     `;
@@ -310,6 +337,11 @@
       });
   }
 
+  isCurrentUserEqualToLatestUploader() {
+    if (!this.account || !this.uploader) return true;
+    return this.account._account_id === this.uploader._account_id;
+  }
+
   getRecentChanges() {
     if (this.recentChanges) {
       return Promise.resolve(this.recentChanges);
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog_test.ts b/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog_test.ts
index 3064014..8d907c9 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog_test.ts
+++ b/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog_test.ts
@@ -9,21 +9,34 @@
 import {
   pressKey,
   queryAndAssert,
-  stubFlags,
   stubRestApi,
   waitUntil,
 } from '../../../test/test-utils';
-import {NumericChangeId, BranchName} from '../../../types/common';
-import {createChangeViewChange} from '../../../test/test-data-generators';
+import {NumericChangeId, BranchName, Timestamp} from '../../../types/common';
+import {
+  createAccountWithEmail,
+  createChangeViewChange,
+} from '../../../test/test-data-generators';
 import {fixture, html, assert} from '@open-wc/testing';
 import {Key} from '../../../utils/dom-util';
 import {GrDialog} from '../../shared/gr-dialog/gr-dialog';
+import {testResolver} from '../../../test/common-test-setup';
+import {userModelToken} from '../../../models/user/user-model';
+import {
+  changeModelToken,
+  LoadingStatus,
+} from '../../../models/change/change-model';
+import {GrAccountChip} from '../../shared/gr-account-chip/gr-account-chip';
 
 suite('gr-confirm-rebase-dialog tests', () => {
   let element: GrConfirmRebaseDialog;
 
   setup(async () => {
-    stubFlags('isEnabled').returns(true);
+    const userModel = testResolver(userModelToken);
+    userModel.setAccount({
+      ...createAccountWithEmail('abc@def.com'),
+      registered_on: '2015-03-12 18:32:08.000000000' as Timestamp,
+    });
     element = await fixture(
       html`<gr-confirm-rebase-dialog></gr-confirm-rebase-dialog>`
     );
@@ -91,6 +104,57 @@
     );
   });
 
+  suite('on behalf of uploader', () => {
+    let changeModel;
+    const change = {
+      ...createChangeViewChange(),
+    };
+    setup(async () => {
+      element.branch = 'test' as BranchName;
+      await element.updateComplete;
+      changeModel = testResolver(changeModelToken);
+      changeModel.setState({
+        loadingStatus: LoadingStatus.LOADED,
+        change,
+      });
+    });
+    test('for reviewer it shows message about on behalf', () => {
+      const rebaseOnBehalfMsg = queryAndAssert(element, '.rebaseOnBehalfMsg');
+      assert.dom.equal(
+        rebaseOnBehalfMsg,
+        /* HTML */ `<div class="rebaseOnBehalfMsg">
+          Rebase will be done on behalf of the uploader:
+          <gr-account-chip> </gr-account-chip> <span> </span>
+        </div>`
+      );
+      const accountChip: GrAccountChip = queryAndAssert(
+        rebaseOnBehalfMsg,
+        'gr-account-chip'
+      );
+      assert.equal(
+        accountChip.account!,
+        change?.revisions[change.current_revision]?.uploader
+      );
+    });
+    test('allowConflicts', async () => {
+      element.allowConflicts = true;
+      await element.updateComplete;
+      const rebaseOnBehalfMsg = queryAndAssert(element, '.rebaseOnBehalfMsg');
+      assert.dom.equal(
+        rebaseOnBehalfMsg,
+        /* HTML */ `<div class="rebaseOnBehalfMsg">
+          Rebase will be done on behalf of
+          <gr-account-chip> </gr-account-chip> <span> </span>
+        </div>`
+      );
+      const accountChip: GrAccountChip = queryAndAssert(
+        rebaseOnBehalfMsg,
+        'gr-account-chip'
+      );
+      assert.equal(accountChip.account, element.account);
+    });
+  });
+
   test('disableActions property disables dialog confirm', async () => {
     element.disableActions = false;
     await element.updateComplete;
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 8b0a5a2..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});
@@ -342,10 +342,6 @@
       resolve(this, highlightServiceToken),
       () => getAppContext().reportingService
     );
-    this.renderPrefs = {
-      ...this.renderPrefs,
-      use_lit_components: true,
-    };
     this.addEventListener(
       // These are named inconsistently for a reason:
       // The create-comment event is fired to indicate that we should
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/embed/diff/gr-diff-builder/gr-diff-builder-element.ts b/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-builder-element.ts
index 71a0637..7fa6d72 100644
--- a/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-builder-element.ts
+++ b/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-builder-element.ts
@@ -13,9 +13,7 @@
   DiffContextExpandedEventDetail,
   isImageDiffBuilder,
 } from './gr-diff-builder';
-import {GrDiffBuilderSideBySide} from './gr-diff-builder-side-by-side';
 import {GrDiffBuilderImage} from './gr-diff-builder-image';
-import {GrDiffBuilderUnified} from './gr-diff-builder-unified';
 import {GrDiffBuilderBinary} from './gr-diff-builder-binary';
 import {GrDiffBuilderLit} from './gr-diff-builder-lit';
 import {CancelablePromise, makeCancelable} from '../../../utils/async-util';
@@ -428,7 +426,6 @@
     }
 
     let builder = null;
-    const useLit = this.renderPrefs?.use_lit_components ?? false;
     if (this.isImageDiff) {
       builder = new GrDiffBuilderImage(
         this.diff,
@@ -447,45 +444,25 @@
         ...this.renderPrefs,
         view_mode: DiffViewMode.SIDE_BY_SIDE,
       };
-      if (useLit) {
-        builder = new GrDiffBuilderLit(
-          this.diff,
-          localPrefs,
-          this.diffElement,
-          this.layersInternal,
-          this.renderPrefs
-        );
-      } else {
-        builder = new GrDiffBuilderSideBySide(
-          this.diff,
-          localPrefs,
-          this.diffElement,
-          this.layersInternal,
-          this.renderPrefs
-        );
-      }
+      builder = new GrDiffBuilderLit(
+        this.diff,
+        localPrefs,
+        this.diffElement,
+        this.layersInternal,
+        this.renderPrefs
+      );
     } else if (this.viewMode === DiffViewMode.UNIFIED) {
       this.renderPrefs = {
         ...this.renderPrefs,
         view_mode: DiffViewMode.UNIFIED,
       };
-      if (useLit) {
-        builder = new GrDiffBuilderLit(
-          this.diff,
-          localPrefs,
-          this.diffElement,
-          this.layersInternal,
-          this.renderPrefs
-        );
-      } else {
-        builder = new GrDiffBuilderUnified(
-          this.diff,
-          localPrefs,
-          this.diffElement,
-          this.layersInternal,
-          this.renderPrefs
-        );
-      }
+      builder = new GrDiffBuilderLit(
+        this.diff,
+        localPrefs,
+        this.diffElement,
+        this.layersInternal,
+        this.renderPrefs
+      );
     }
     if (!builder) {
       throw Error(`Unsupported diff view mode: ${this.viewMode}`);
diff --git a/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-builder-element_test.ts b/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-builder-element_test.ts
index 9cf9bae..ba6acfd 100644
--- a/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-builder-element_test.ts
+++ b/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-builder-element_test.ts
@@ -6,17 +6,15 @@
 import '../../../test/common-test-setup';
 import {
   createConfig,
-  createDiff,
   createEmptyDiff,
 } from '../../../test/test-data-generators';
 import './gr-diff-builder-element';
-import {queryAndAssert, stubBaseUrl, waitUntil} from '../../../test/test-utils';
+import {stubBaseUrl, waitUntil} from '../../../test/test-utils';
 import {GrAnnotation} from '../gr-diff-highlight/gr-annotation';
 import {GrDiffLine, GrDiffLineType} from '../gr-diff/gr-diff-line';
 import {GrDiffBuilderSideBySide} from './gr-diff-builder-side-by-side';
 import {
   DiffContent,
-  DiffInfo,
   DiffLayer,
   DiffPreferencesInfo,
   DiffViewMode,
@@ -30,6 +28,7 @@
 import {KeyLocations} from '../gr-diff-processor/gr-diff-processor';
 import {BlameInfo} from '../../../types/common';
 import {fixture, html, assert} from '@open-wc/testing';
+import {GrDiffRow} from './gr-diff-row';
 
 const DEFAULT_PREFS = createDefaultDiffPrefs();
 
@@ -39,7 +38,6 @@
   let diffTable: HTMLTableElement;
 
   const LINE_BREAK_HTML = '<span class="gr-diff br"></span>';
-  const WBR_HTML = '<wbr class="gr-diff">';
 
   const setBuilderPrefs = (prefs: Partial<DiffPreferencesInfo>) => {
     builder = new GrDiffBuilderSideBySide(
@@ -65,15 +63,6 @@
     setBuilderPrefs({});
   });
 
-  test('line_length applied with <wbr> if line_wrapping is true', () => {
-    setBuilderPrefs({line_wrapping: true, tab_size: 4, line_length: 50});
-    const text = 'a'.repeat(51);
-    const expected = 'a'.repeat(50) + WBR_HTML + 'a';
-    const result = builder.createTextEl(null, line(text)).firstElementChild
-      ?.firstElementChild?.innerHTML;
-    assert.equal(result, expected);
-  });
-
   test('line_length applied with line break if line_wrapping is false', () => {
     setBuilderPrefs({line_wrapping: false, tab_size: 4, line_length: 50});
     const text = 'a'.repeat(51);
@@ -691,7 +680,7 @@
       assert.include(diffRows[4].textContent, 'unchanged 11');
     });
 
-    test('clicking +x common lines expands those lines', () => {
+    test('clicking +x common lines expands those lines', async () => {
       const contextControls = diffTable.querySelectorAll('gr-context-controls');
       const topExpandCommonButton =
         contextControls[0].shadowRoot?.querySelectorAll<HTMLElement>(
@@ -699,10 +688,19 @@
         )[0];
       assert.isOk(topExpandCommonButton);
       assert.include(topExpandCommonButton!.textContent, '+9 common lines');
+      let diffRows = diffTable.querySelectorAll('.diff-row');
+      // 5 lines:
+      // FILE, LOST, the changed line plus one line of context in each direction
+      assert.equal(diffRows.length, 5);
+
       topExpandCommonButton!.click();
-      const diffRows = diffTable.querySelectorAll('.diff-row');
-      // The first two are LOST and FILE line
-      assert.equal(diffRows.length, 2 + 10 + 1 + 1);
+
+      await waitUntil(() => {
+        diffRows = diffTable.querySelectorAll<GrDiffRow>('.diff-row');
+        return diffRows.length === 14;
+      });
+      // 14 lines: The 5 above plus the 9 unchanged lines that were expanded
+      assert.equal(diffRows.length, 14);
       assert.include(diffRows[2].textContent, 'unchanged 1');
       assert.include(diffRows[3].textContent, 'unchanged 2');
       assert.include(diffRows[4].textContent, 'unchanged 3');
@@ -722,6 +720,11 @@
       dispatchStub.reset();
       element.unhideLine(4, Side.LEFT);
 
+      await waitUntil(() => {
+        const rows = diffTable.querySelectorAll<GrDiffRow>('.diff-row');
+        return rows.length === 2 + 5 + 1 + 1 + 1;
+      });
+
       const diffRows = diffTable.querySelectorAll('.diff-row');
       // The first two are LOST and FILE line
       // Lines 3-5 (Line 4 plus 1 context in each direction) will be expanded
@@ -745,332 +748,6 @@
     });
   });
 
-  [DiffViewMode.UNIFIED, DiffViewMode.SIDE_BY_SIDE].forEach(mode => {
-    suite(`mock-diff mode:${mode}`, () => {
-      let builder: GrDiffBuilderSideBySide;
-      let diff: DiffInfo;
-      let keyLocations: KeyLocations;
-
-      setup(() => {
-        element.viewMode = mode;
-        diff = createDiff();
-        element.diff = diff;
-
-        keyLocations = {left: {}, right: {}};
-
-        element.prefs = {
-          ...createDefaultDiffPrefs(),
-          line_length: 80,
-          show_tabs: true,
-          tab_size: 4,
-        };
-        element.render(keyLocations);
-        builder = element.builder as GrDiffBuilderSideBySide;
-      });
-
-      test('aria-labels on added line numbers', () => {
-        const deltaLineNumberButton = diffTable.querySelectorAll(
-          '.lineNumButton.right'
-        )[5];
-
-        assert.isOk(deltaLineNumberButton);
-        assert.equal(
-          deltaLineNumberButton.getAttribute('aria-label'),
-          '5 added'
-        );
-      });
-
-      test('aria-labels on removed line numbers', () => {
-        const deltaLineNumberButton = diffTable.querySelectorAll(
-          '.lineNumButton.left'
-        )[10];
-
-        assert.isOk(deltaLineNumberButton);
-        assert.equal(
-          deltaLineNumberButton.getAttribute('aria-label'),
-          '10 removed'
-        );
-      });
-
-      test('getContentByLine', () => {
-        let actual: HTMLElement | null;
-
-        actual = builder.getContentByLine(2, Side.LEFT);
-        assert.equal(actual?.textContent, diff.content[0].ab?.[1]);
-
-        actual = builder.getContentByLine(2, Side.RIGHT);
-        assert.equal(actual?.textContent, diff.content[0].ab?.[1]);
-
-        actual = builder.getContentByLine(5, Side.LEFT);
-        assert.equal(actual?.textContent, diff.content[2].ab?.[0]);
-
-        actual = builder.getContentByLine(5, Side.RIGHT);
-        assert.equal(actual?.textContent, diff.content[1].b?.[0]);
-      });
-
-      test('getContentTdByLineEl works both with button and td', () => {
-        const diffRow = diffTable.querySelectorAll('tr.diff-row')[2];
-
-        const lineNumTdLeft = queryAndAssert(diffRow, 'td.lineNum.left');
-        const lineNumButtonLeft = queryAndAssert(lineNumTdLeft, 'button');
-        const contentTdLeft = diffRow.querySelectorAll('.content')[0];
-
-        const lineNumTdRight = queryAndAssert(diffRow, 'td.lineNum.right');
-        const lineNumButtonRight = queryAndAssert(lineNumTdRight, 'button');
-        const contentTdRight =
-          mode === DiffViewMode.SIDE_BY_SIDE
-            ? diffRow.querySelectorAll('.content')[1]
-            : contentTdLeft;
-
-        assert.equal(
-          element.getContentTdByLineEl(lineNumTdLeft),
-          contentTdLeft
-        );
-        assert.equal(
-          element.getContentTdByLineEl(lineNumButtonLeft),
-          contentTdLeft
-        );
-        assert.equal(
-          element.getContentTdByLineEl(lineNumTdRight),
-          contentTdRight
-        );
-        assert.equal(
-          element.getContentTdByLineEl(lineNumButtonRight),
-          contentTdRight
-        );
-      });
-
-      test('findLinesByRange LEFT', () => {
-        const lines: GrDiffLine[] = [];
-        const elems: HTMLElement[] = [];
-        const start = 1;
-        const end = 44;
-
-        // lines 26-29 are collapsed, so minus 4
-        let count = end - start + 1 - 4;
-        // Lines 14+15 are part of a 'common' chunk. And we have a bug in
-        // unified diff that results in not rendering these lines for the LEFT
-        // side. TODO: Fix that bug!
-        if (mode === DiffViewMode.UNIFIED) count -= 2;
-
-        builder.findLinesByRange(start, end, Side.LEFT, lines, elems);
-
-        assert.equal(lines.length, count);
-        assert.equal(elems.length, count);
-
-        for (let i = 0; i < count; i++) {
-          assert.instanceOf(lines[i], GrDiffLine);
-          assert.instanceOf(elems[i], HTMLElement);
-          assert.equal(lines[i].text, elems[i].textContent);
-        }
-      });
-
-      test('findLinesByRange RIGHT', () => {
-        const lines: GrDiffLine[] = [];
-        const elems: HTMLElement[] = [];
-        const start = 1;
-        const end = 48;
-
-        // lines 26-29 are collapsed, so minus 4
-        const count = end - start + 1 - 4;
-
-        builder.findLinesByRange(start, end, Side.RIGHT, lines, elems);
-
-        assert.equal(lines.length, count);
-        assert.equal(elems.length, count);
-
-        for (let i = 0; i < count; i++) {
-          assert.instanceOf(lines[i], GrDiffLine);
-          assert.instanceOf(elems[i], HTMLElement);
-          assert.equal(lines[i].text, elems[i].textContent);
-        }
-      });
-
-      test('renderContentByRange', () => {
-        const spy = sinon.spy(builder, 'createTextEl');
-        const start = 9;
-        const end = 14;
-        let count = end - start + 1;
-        // Lines 14+15 are part of a 'common' chunk. And we have a bug in
-        // unified diff that results in not rendering these lines for the LEFT
-        // side. TODO: Fix that bug!
-        if (mode === DiffViewMode.UNIFIED) count -= 1;
-
-        builder.renderContentByRange(start, end, Side.LEFT);
-
-        assert.equal(spy.callCount, count);
-        spy.getCalls().forEach((call, i: number) => {
-          assert.equal(call.args[1].beforeNumber, start + i);
-        });
-      });
-
-      test('renderContentByRange non-existent elements', () => {
-        const spy = sinon.spy(builder, 'createTextEl');
-
-        sinon
-          .stub(builder, 'getLineNumberEl')
-          .returns(document.createElement('div'));
-        sinon
-          .stub(builder, 'findLinesByRange')
-          .callsFake((_1, _2, _3, lines, elements) => {
-            // Add a line and a corresponding element.
-            lines?.push(new GrDiffLine(GrDiffLineType.BOTH));
-            const tr = document.createElement('tr');
-            const td = document.createElement('td');
-            const el = document.createElement('div');
-            tr.appendChild(td);
-            td.appendChild(el);
-            elements?.push(el);
-
-            // Add 2 lines without corresponding elements.
-            lines?.push(new GrDiffLine(GrDiffLineType.BOTH));
-            lines?.push(new GrDiffLine(GrDiffLineType.BOTH));
-          });
-
-        builder.renderContentByRange(1, 10, Side.LEFT);
-        // Should be called only once because only one line had a corresponding
-        // element.
-        assert.equal(spy.callCount, 1);
-      });
-
-      test('getLineNumberEl side-by-side left', () => {
-        const contentEl = builder.getContentByLine(
-          5,
-          Side.LEFT,
-          element.diffElement as HTMLTableElement
-        );
-        assert.isOk(contentEl);
-        const lineNumberEl = builder.getLineNumberEl(contentEl!, Side.LEFT);
-        assert.isOk(lineNumberEl);
-        assert.isTrue(lineNumberEl!.classList.contains('lineNum'));
-        assert.isTrue(lineNumberEl!.classList.contains(Side.LEFT));
-      });
-
-      test('getLineNumberEl side-by-side right', () => {
-        const contentEl = builder.getContentByLine(
-          5,
-          Side.RIGHT,
-          element.diffElement as HTMLTableElement
-        );
-        assert.isOk(contentEl);
-        const lineNumberEl = builder.getLineNumberEl(contentEl!, Side.RIGHT);
-        assert.isOk(lineNumberEl);
-        assert.isTrue(lineNumberEl!.classList.contains('lineNum'));
-        assert.isTrue(lineNumberEl!.classList.contains(Side.RIGHT));
-      });
-
-      test('getLineNumberEl unified left', async () => {
-        // Re-render as unified:
-        element.viewMode = 'UNIFIED_DIFF';
-        element.render(keyLocations);
-        builder = element.builder as GrDiffBuilderSideBySide;
-
-        const contentEl = builder.getContentByLine(
-          5,
-          Side.LEFT,
-          element.diffElement as HTMLTableElement
-        );
-        assert.isOk(contentEl);
-        const lineNumberEl = builder.getLineNumberEl(contentEl!, Side.LEFT);
-        assert.isOk(lineNumberEl);
-        assert.isTrue(lineNumberEl!.classList.contains('lineNum'));
-        assert.isTrue(lineNumberEl!.classList.contains(Side.LEFT));
-      });
-
-      test('getLineNumberEl unified right', async () => {
-        // Re-render as unified:
-        element.viewMode = 'UNIFIED_DIFF';
-        element.render(keyLocations);
-        builder = element.builder as GrDiffBuilderSideBySide;
-
-        const contentEl = builder.getContentByLine(
-          5,
-          Side.RIGHT,
-          element.diffElement as HTMLTableElement
-        );
-        assert.isOk(contentEl);
-        const lineNumberEl = builder.getLineNumberEl(contentEl!, Side.RIGHT);
-        assert.isOk(lineNumberEl);
-        assert.isTrue(lineNumberEl!.classList.contains('lineNum'));
-        assert.isTrue(lineNumberEl!.classList.contains(Side.RIGHT));
-      });
-
-      test('getNextContentOnSide side-by-side left', () => {
-        const startElem = builder.getContentByLine(
-          5,
-          Side.LEFT,
-          element.diffElement as HTMLTableElement
-        );
-        assert.isOk(startElem);
-        const expectedStartString = diff.content[2].ab?.[0];
-        const expectedNextString = diff.content[2].ab?.[1];
-        assert.equal(startElem!.textContent, expectedStartString);
-
-        const nextElem = builder.getNextContentOnSide(startElem!, Side.LEFT);
-        assert.isOk(nextElem);
-        assert.equal(nextElem!.textContent, expectedNextString);
-      });
-
-      test('getNextContentOnSide side-by-side right', () => {
-        const startElem = builder.getContentByLine(
-          5,
-          Side.RIGHT,
-          element.diffElement as HTMLTableElement
-        );
-        const expectedStartString = diff.content[1].b?.[0];
-        const expectedNextString = diff.content[1].b?.[1];
-        assert.isOk(startElem);
-        assert.equal(startElem!.textContent, expectedStartString);
-
-        const nextElem = builder.getNextContentOnSide(startElem!, Side.RIGHT);
-        assert.isOk(nextElem);
-        assert.equal(nextElem!.textContent, expectedNextString);
-      });
-
-      test('getNextContentOnSide unified left', async () => {
-        // Re-render as unified:
-        element.viewMode = 'UNIFIED_DIFF';
-        element.render(keyLocations);
-        builder = element.builder as GrDiffBuilderSideBySide;
-
-        const startElem = builder.getContentByLine(
-          5,
-          Side.LEFT,
-          element.diffElement as HTMLTableElement
-        );
-        const expectedStartString = diff.content[2].ab?.[0];
-        const expectedNextString = diff.content[2].ab?.[1];
-        assert.isOk(startElem);
-        assert.equal(startElem!.textContent, expectedStartString);
-
-        const nextElem = builder.getNextContentOnSide(startElem!, Side.LEFT);
-        assert.isOk(nextElem);
-        assert.equal(nextElem!.textContent, expectedNextString);
-      });
-
-      test('getNextContentOnSide unified right', async () => {
-        // Re-render as unified:
-        element.viewMode = 'UNIFIED_DIFF';
-        element.render(keyLocations);
-        builder = element.builder as GrDiffBuilderSideBySide;
-
-        const startElem = builder.getContentByLine(
-          5,
-          Side.RIGHT,
-          element.diffElement as HTMLTableElement
-        );
-        const expectedStartString = diff.content[1].b?.[0];
-        const expectedNextString = diff.content[1].b?.[1];
-        assert.isOk(startElem);
-        assert.equal(startElem!.textContent, expectedStartString);
-
-        const nextElem = builder.getNextContentOnSide(startElem!, Side.RIGHT);
-        assert.isOk(nextElem);
-        assert.equal(nextElem!.textContent, expectedNextString);
-      });
-    });
-  });
-
   suite('blame', () => {
     let mockBlame: BlameInfo[];
 
diff --git a/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-text_test.ts b/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-text_test.ts
index a0e7840..3858bed 100644
--- a/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-text_test.ts
+++ b/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-text_test.ts
@@ -10,6 +10,8 @@
 
 const LINE_BREAK = '<span class="gr-diff br"></span>';
 
+const LINE_BREAK_WBR = '<wbr class="gr-diff"></wbr>';
+
 const TAB = '<span class="" style=""></span>';
 
 const TAB_IGNORE = ['class', 'style'];
@@ -39,6 +41,12 @@
       await check('a'.repeat(20), `aaaaaaaaaa${LINE_BREAK}aaaaaaaaaa`);
     });
 
+    test('renderText newlines 1 responsive', async () => {
+      element.isResponsive = true;
+      await check('abcdef', 'abcdef');
+      await check('a'.repeat(20), `aaaaaaaaaa${LINE_BREAK_WBR}aaaaaaaaaa`);
+    });
+
     test('renderText newlines 2', async () => {
       await check(
         '<span class="thumbsup">👍</span>',
diff --git a/polygerrit-ui/app/embed/diff/gr-diff-cursor/gr-diff-cursor_test.ts b/polygerrit-ui/app/embed/diff/gr-diff-cursor/gr-diff-cursor_test.ts
index b9db280..61f8551 100644
--- a/polygerrit-ui/app/embed/diff/gr-diff-cursor/gr-diff-cursor_test.ts
+++ b/polygerrit-ui/app/embed/diff/gr-diff-cursor/gr-diff-cursor_test.ts
@@ -46,7 +46,6 @@
 
     diff = createDiff();
     diffElement.prefs = createDefaultDiffPrefs();
-    diffElement.renderPrefs = {use_lit_components: true};
     diffElement.diff = diff;
     await promise;
   });
@@ -661,7 +660,7 @@
       // Goto second last line of the first diff
       cursor.moveToLineNumber(lastLine - 1, Side.RIGHT);
       assert.equal(
-        cursor.getTargetLineElement()!.textContent,
+        cursor.getTargetLineElement()!.textContent?.trim(),
         `${lastLine - 1}`
       );
 
@@ -669,7 +668,7 @@
       cursor.moveDown();
       assert.equal(getTargetDiffIndex(), 0);
       assert.equal(
-        cursor.getTargetLineElement()!.textContent,
+        cursor.getTargetLineElement()!.textContent?.trim(),
         lastLine.toString()
       );
 
@@ -677,7 +676,7 @@
       cursor.moveDown();
       assert.equal(getTargetDiffIndex(), 0);
       assert.equal(
-        cursor.getTargetLineElement()!.textContent,
+        cursor.getTargetLineElement()!.textContent?.trim(),
         lastLine.toString()
       );
 
@@ -686,9 +685,10 @@
       await waitForEventOnce(diffElements[1], 'render');
 
       // Now we can go down
-      cursor.moveDown();
+      cursor.moveDown(); // LOST
+      cursor.moveDown(); // FILE
       assert.equal(getTargetDiffIndex(), 1);
-      assert.equal(cursor.getTargetLineElement()!.textContent, 'File');
+      assert.equal(cursor.getTargetLineElement()!.textContent?.trim(), 'File');
     });
   });
 });
diff --git a/polygerrit-ui/app/embed/diff/gr-diff-selection/gr-diff-selection_test.ts b/polygerrit-ui/app/embed/diff/gr-diff-selection/gr-diff-selection_test.ts
index 9e3d288..f216e04 100644
--- a/polygerrit-ui/app/embed/diff/gr-diff-selection/gr-diff-selection_test.ts
+++ b/polygerrit-ui/app/embed/diff/gr-diff-selection/gr-diff-selection_test.ts
@@ -62,7 +62,6 @@
       ],
     };
     grDiff.prefs = createDefaultDiffPrefs();
-    grDiff.renderPrefs = {use_lit_components: true};
     grDiff.diff = diff;
     await waitForEventOnce(grDiff, 'render');
     assert.isOk(element.diffTable);
diff --git a/polygerrit-ui/app/embed/diff/gr-diff/gr-diff_test.ts b/polygerrit-ui/app/embed/diff/gr-diff/gr-diff_test.ts
index 4adb1cf..227ac2d 100644
--- a/polygerrit-ui/app/embed/diff/gr-diff/gr-diff_test.ts
+++ b/polygerrit-ui/app/embed/diff/gr-diff/gr-diff_test.ts
@@ -68,18 +68,8 @@
       );
     });
 
-    test('a unified diff legacy', async () => {
-      element.viewMode = DiffViewMode.UNIFIED;
-      await testUnified();
-    });
-
     test('a unified diff lit', async () => {
       element.viewMode = DiffViewMode.UNIFIED;
-      element.renderPrefs = {...element.renderPrefs, use_lit_components: true};
-      await testUnified();
-    });
-
-    const testUnified = async () => {
       element.prefs = {...MINIMAL_PREFS};
       element.diff = createDiff();
       await element.updateComplete;
@@ -1343,18 +1333,9 @@
           ],
         }
       );
-    };
-
-    test('a normal diff legacy', async () => {
-      await testNormal();
     });
 
     test('a normal diff lit', async () => {
-      element.renderPrefs = {...element.renderPrefs, use_lit_components: true};
-      await testNormal();
-    });
-
-    const testNormal = async () => {
       element.prefs = {...MINIMAL_PREFS};
       element.diff = createDiff();
       await element.updateComplete;
@@ -3008,7 +2989,7 @@
           ],
         }
       );
-    };
+    });
   });
 
   suite('selectionchange event handling', () => {
@@ -3548,7 +3529,11 @@
         await element.updateComplete;
         const ROWS = 48;
         const FILE_ROW = 1;
-        assert.equal(element.getCursorStops().length, ROWS + FILE_ROW);
+        const LOST_ROW = 1;
+        assert.equal(
+          element.getCursorStops().length,
+          ROWS + FILE_ROW + LOST_ROW
+        );
       });
 
       test('returns an additional AbortStop when still loading', async () => {
@@ -3557,8 +3542,9 @@
         await element.updateComplete;
         const ROWS = 48;
         const FILE_ROW = 1;
+        const LOST_ROW = 1;
         const actual = element.getCursorStops();
-        assert.equal(actual.length, ROWS + FILE_ROW + 1);
+        assert.equal(actual.length, ROWS + FILE_ROW + LOST_ROW + 1);
         assert.isTrue(actual[actual.length - 1] instanceof AbortStop);
       });
     });
@@ -4050,13 +4036,13 @@
         b: ['Non eram nescius, Brute, cum, quae summis ingeniis '],
       },
     ];
-    function assertDiffTableWithContent() {
+    function diffTableHasContent() {
       assertIsDefined(element.diffTable);
       const diffTable = element.diffTable;
-      assert.isTrue(diffTable.innerText.includes(content[0].a?.[0] ?? ''));
+      return diffTable.innerText.includes(content[0].a?.[0] ?? '');
     }
     await setupSampleDiff({content});
-    assertDiffTableWithContent();
+    await waitUntil(diffTableHasContent);
     element.diff = {...element.diff!};
     await element.updateComplete;
     // immediately cleaned up
@@ -4066,7 +4052,7 @@
     element.renderDiffTable();
     await element.updateComplete;
     // rendered again
-    assertDiffTableWithContent();
+    await waitUntil(diffTableHasContent);
   });
 
   suite('selection test', () => {
diff --git a/polygerrit-ui/app/models/change/change-model.ts b/polygerrit-ui/app/models/change/change-model.ts
index ad5b217..b6b9de7 100644
--- a/polygerrit-ui/app/models/change/change-model.ts
+++ b/polygerrit-ui/app/models/change/change-model.ts
@@ -196,6 +196,11 @@
     computeLatestPatchNumWithEdit(patchsets)
   );
 
+  public readonly latestUploader$ = select(
+    this.change$,
+    change => change?.revisions[change.current_revision]?.uploader
+  );
+
   /**
    * Emits the current patchset number. If the route does not define the current
    * patchset num, then this selector waits for the change to be defined and
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'}
     );
   });