Merge changes I7ae81b6e,Ia648d04c

* changes:
  Documentation/user-review-ui: remove no difference screenshot
  Documentation/user-review-ui: update patchset screenshot. 
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index c2a2fa4..2204c65 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -5099,6 +5099,16 @@
 +
 By default, all supported MACs are available.
 
+[[sshd.enableDeprecatedKexAlgorithms]]sshd.enableDeprecatedKexAlgorithms::
++
+Enable deprecated kex algorithms:
++
+* `diffie-hellman-group1-sha1`
+* `diffie-hellman-group14-sha1`
+* `diffie-hellman-group-exchange-sha1`
+
+By default, the deprecated kex algorithms are disabled.
+
 [[sshd.kex]]sshd.kex::
 +
 --
@@ -5109,24 +5119,20 @@
 algorithms, key exchange algorithm names starting with `-` are
 removed from the default key exchange algorithms.
 
-In the following example configuration, support for the 1024-bit
-`diffie-hellman-group1-sha1` key exchange is disabled while leaving
-all of the other default algorithms enabled:
-
-----
-[sshd]
-  kex = -diffie-hellman-group1-sha1
-----
-
 Supported key exchange algorithms:
 
 * `ecdh-sha2-nistp521`
 * `ecdh-sha2-nistp384`
 * `ecdh-sha2-nistp256`
 * `diffie-hellman-group-exchange-sha256`
-* `diffie-hellman-group-exchange-sha1`
-* `diffie-hellman-group14-sha1`
-* `diffie-hellman-group1-sha1`
+* `diffie-hellman-group18-sha512`
+* `diffie-hellman-group17-sha512`
+* `diffie-hellman-group16-sha512`
+* `diffie-hellman-group15-sha512`
+* `diffie-hellman-group14-sha256`
+
+See link:#sshd.enableDeprecatedKexAlgorithms[sshd.enableDeprecatedKexAlgorithms]
+for deprecated key algorithms and how to enable them.
 
 By default, all supported key exchange algorithms are available.
 
diff --git a/Documentation/glossary.txt b/Documentation/glossary.txt
index 2b40b5b..83362ab 100644
--- a/Documentation/glossary.txt
+++ b/Documentation/glossary.txt
@@ -1,6 +1,12 @@
 :linkattrs:
 = Glossary
 
+[[cluster]]
+== Cluster
+A Gerrit Cluster is a set of Gerrit processes sharing the same
+link:config-gerrit.html#gerrit.serverId[ServerId] and associated to the same
+set of repositories, accounts, and groups.
+
 [[event]]
 == Event
 
@@ -32,6 +38,45 @@
 API for listening to Gerrit events from plugins, without having any
 visibility restrictions.
 
+[[multi-primary]]
+== Multi-primary
+Multi-primary typically refers to configurations where multiple Gerrit primary
+processes are running in one or more xref:cluster[clusters] together.
+
+=== Single cluster multi-primary with shared storage
+A variation of multi-primary (a.k.a. HA or high-availability) that shares a file
+storage volume for the git repositories. These configurations can use the
+link:https://gerrit.googlesource.com/plugins/high-availability[high-availability plugin]
+to synchronize or share caches, indexes, events, and web sessions. The
+replication plugin also
+link:https://gerrit.googlesource.com/plugins/replication/+/refs/heads/master/src/main/resources/Documentation/config.md#configuring-cluster-replication[supports]
+synchronizing events using a shared file storage volume.
+
+[[multi-cluster-multi-primary]]
+=== Multiple clusters multi-primary
+Multi-cluster (aka multi-site) primaries typically refers to configurations
+where multiple Gerrit primary processes are running in different (likely
+geographically distributed) clusters (sites). This also typically makes use of
+a multi-primary configuration within each cluster. Synchronization across sites
+is necessary to detect and prevent split-brain scenarios. These configurations
+can use the link:https://gerrit.googlesource.com/plugins/multi-site[multi-site plugin]
+to facilitate synchronization.
+
+[[primary]]
+== Primary
+A Gerrit primary is the link:pgm-daemon.html[main Gerrit process] permitting
+write operations by clients. Most installations of Gerrit have only a single
+Gerrit primary running at a time for their service.
+
+[[replica]]
+== Replica
+A Gerrit process running with the link:pgm-daemon.html[--replica switch]
+provided. This permits read-only git operations by clients. There is no REST
+API, WebUI, or search operation available. Replicas can be run in
+the same cluster with primaries (likely sharing the storage volume) or in other
+clusters/sites (likely facilitated by the
+link:https://gerrit.googlesource.com/plugins/replication[replication plugin]).
+
 [[stream-events]]
 == Stream events
 
diff --git a/Documentation/js_licenses.txt b/Documentation/js_licenses.txt
index 9ac438a..0d8da89 100644
--- a/Documentation/js_licenses.txt
+++ b/Documentation/js_licenses.txt
@@ -250,6 +250,7 @@
 [[DefinitelyTyped]]
 DefinitelyTyped
 
+* @types/resemblejs
 * @types/resize-observer-browser
 
 [[DefinitelyTyped_license]]
@@ -1251,6 +1252,35 @@
 ----
 
 
+[[resemblejs]]
+resemblejs
+
+* resemblejs
+
+[[resemblejs_license]]
+----
+The MIT License (MIT) Copyright © 2013 Huddle

+

+Permission is hereby granted, free of charge, to any person obtaining a copy of

+this software and associated documentation files (the “Software”), to deal in

+the Software without restriction, including without limitation the rights to

+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of

+the Software, and to permit persons to whom the Software is furnished to do so,

+subject to the following conditions:

+

+The above copyright notice and this permission notice shall be included in all

+copies or substantial portions of the Software.

+

+THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR

+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS

+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR

+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER

+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN

+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+----
+
+
 [[rxjs]]
 rxjs
 
diff --git a/Documentation/licenses.txt b/Documentation/licenses.txt
index 1bff25c..ed1a336 100644
--- a/Documentation/licenses.txt
+++ b/Documentation/licenses.txt
@@ -3209,6 +3209,7 @@
 [[DefinitelyTyped]]
 DefinitelyTyped
 
+* @types/resemblejs
 * @types/resize-observer-browser
 
 [[DefinitelyTyped_license]]
@@ -4210,6 +4211,35 @@
 ----
 
 
+[[resemblejs]]
+resemblejs
+
+* resemblejs
+
+[[resemblejs_license]]
+----
+The MIT License (MIT) Copyright © 2013 Huddle

+

+Permission is hereby granted, free of charge, to any person obtaining a copy of

+this software and associated documentation files (the “Software”), to deal in

+the Software without restriction, including without limitation the rights to

+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of

+the Software, and to permit persons to whom the Software is furnished to do so,

+subject to the following conditions:

+

+The above copyright notice and this permission notice shall be included in all

+copies or substantial portions of the Software.

+

+THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR

+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS

+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR

+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER

+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN

+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+----
+
+
 [[rxjs]]
 rxjs
 
diff --git a/WORKSPACE b/WORKSPACE
index 428a6a4..5d5c69d 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -948,7 +948,17 @@
 
 yarn_install(
     name = "ui_npm",
-    args = ["--prod"],
+    args = [
+        "--prod",
+        # By default, yarn install all optional dependencies.
+        # In some cases, it installs a lot of additional dependencies which
+        # are not required (for example, "resemblejs" has one optional
+        # dependencies "canvas" that leads to tens of additional dependencies).
+        # Each additional dependency requires a license even if it is not used
+        # in our code.  We want to ensure that all optional dependencies are
+        # explicitly added to package.json.
+        "--ignore-optional",
+    ],
     frozen_lockfile = False,
     package_json = "//:polygerrit-ui/app/package.json",
     yarn_lock = "//:polygerrit-ui/app/yarn.lock",
diff --git a/java/com/google/gerrit/elasticsearch/ElasticVersion.java b/java/com/google/gerrit/elasticsearch/ElasticVersion.java
index 6ca51fa..c6400df 100644
--- a/java/com/google/gerrit/elasticsearch/ElasticVersion.java
+++ b/java/com/google/gerrit/elasticsearch/ElasticVersion.java
@@ -18,7 +18,6 @@
 import java.util.regex.Pattern;
 
 public enum ElasticVersion {
-  V7_5("7.5.*"),
   V7_6("7.6.*"),
   V7_7("7.7.*"),
   V7_8("7.8.*");
diff --git a/java/com/google/gerrit/server/git/receive/ReceiveCommits.java b/java/com/google/gerrit/server/git/receive/ReceiveCommits.java
index 6d6c19d2..005aebb 100644
--- a/java/com/google/gerrit/server/git/receive/ReceiveCommits.java
+++ b/java/com/google/gerrit/server/git/receive/ReceiveCommits.java
@@ -100,6 +100,9 @@
 import com.google.gerrit.extensions.validators.CommentValidationContext;
 import com.google.gerrit.extensions.validators.CommentValidationFailure;
 import com.google.gerrit.extensions.validators.CommentValidator;
+import com.google.gerrit.metrics.Counter0;
+import com.google.gerrit.metrics.Description;
+import com.google.gerrit.metrics.MetricMaker;
 import com.google.gerrit.server.ChangeUtil;
 import com.google.gerrit.server.CommentsUtil;
 import com.google.gerrit.server.CreateGroupPermissionSyncer;
@@ -188,6 +191,7 @@
 import com.google.gerrit.util.cli.CmdLineParser;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
+import com.google.inject.Singleton;
 import com.google.inject.assistedinject.Assisted;
 import com.google.inject.util.Providers;
 import java.io.IOException;
@@ -310,6 +314,19 @@
     return RestApiException.wrap("Error inserting change/patchset", e);
   }
 
+  @Singleton
+  private static class Metrics {
+    private final Counter0 psRevisionMissing;
+
+    @Inject
+    Metrics(MetricMaker metricMaker) {
+      psRevisionMissing =
+          metricMaker.newCounter(
+              "receivecommits/ps_revision_missing",
+              new Description("errors due to patch set revision missing"));
+    }
+  }
+
   // ReceiveCommits has a lot of fields, sorry. Here and in the constructor they are split up
   // somewhat, and kept sorted lexicographically within sections, except where later assignments
   // depend on previous ones.
@@ -334,6 +351,7 @@
   private final DynamicSet<PluginPushOption> pluginPushOptions;
   private final PluginSetContext<ReceivePackInitializer> initializers;
   private final MergedByPushOp.Factory mergedByPushOpFactory;
+  private final Metrics metrics;
   private final PatchSetInfoFactory patchSetInfoFactory;
   private final PatchSetUtil psUtil;
   private final DynamicSet<PerformanceLogger> performanceLoggers;
@@ -419,6 +437,7 @@
       PluginSetContext<ReceivePackInitializer> initializers,
       PluginSetContext<CommentValidator> commentValidators,
       MergedByPushOp.Factory mergedByPushOpFactory,
+      Metrics metrics,
       PatchSetInfoFactory patchSetInfoFactory,
       PatchSetUtil psUtil,
       DynamicSet<PerformanceLogger> performanceLoggers,
@@ -473,6 +492,7 @@
     this.notesFactory = notesFactory;
     this.optionParserFactory = optionParserFactory;
     this.ormProvider = ormProvider;
+    this.metrics = metrics;
     this.patchSetInfoFactory = patchSetInfoFactory;
     this.permissionBackend = permissionBackend;
     this.pluginConfigEntries = pluginConfigEntries;
@@ -2862,6 +2882,7 @@
         Change change = notes.getChange();
         priorPatchSet = change.currentPatchSetId();
         if (!revisions.containsValue(priorPatchSet)) {
+          metrics.psRevisionMissing.increment();
           logger.atWarning().log(
               "Change %d is missing revision for patch set %s"
                   + " (it has revisions for these patch sets: %s)",
diff --git a/java/com/google/gerrit/server/notedb/ChangeNotes.java b/java/com/google/gerrit/server/notedb/ChangeNotes.java
index 77d7bc0..5daf28c 100644
--- a/java/com/google/gerrit/server/notedb/ChangeNotes.java
+++ b/java/com/google/gerrit/server/notedb/ChangeNotes.java
@@ -19,7 +19,6 @@
 import static com.google.common.collect.ImmutableSet.toImmutableSet;
 import static com.google.gerrit.entities.RefNames.changeMetaRef;
 import static java.util.Comparator.comparing;
-import static java.util.Objects.requireNonNull;
 
 import com.google.auto.value.AutoValue;
 import com.google.common.annotations.VisibleForTesting;
@@ -554,8 +553,20 @@
 
   public PatchSet getCurrentPatchSet() {
     PatchSet.Id psId = change.currentPatchSetId();
-    return requireNonNull(
-        getPatchSets().get(psId), () -> String.format("missing current patch set %s", psId.get()));
+    if (psId == null || getPatchSets().get(psId) == null) {
+      // In some cases, the current patch-set doesn't exist yet as it's being created during the
+      // operation (e.g rebase).
+      PatchSet currentPatchset =
+          getPatchSets().values().stream()
+              .max((p1, p2) -> p1.id().get() - p2.id().get())
+              .orElseThrow(
+                  () ->
+                      new IllegalStateException(
+                          String.format(
+                              "change %s can't load any patchset", getChangeId().toString())));
+      return currentPatchset;
+    }
+    return getPatchSets().get(psId);
   }
 
   @Override
diff --git a/java/com/google/gerrit/server/patch/SubmitWithStickyApprovalDiff.java b/java/com/google/gerrit/server/patch/SubmitWithStickyApprovalDiff.java
index f33493e..e33b261 100644
--- a/java/com/google/gerrit/server/patch/SubmitWithStickyApprovalDiff.java
+++ b/java/com/google/gerrit/server/patch/SubmitWithStickyApprovalDiff.java
@@ -84,16 +84,7 @@
   public String apply(ChangeNotes notes, CurrentUser currentUser)
       throws AuthException, IOException, PermissionBackendException,
           InvalidChangeOperationException {
-    // In some submit strategies, the current patch-set doesn't exist yet as it's being created
-    // during the submit. Hence, we assign the current patch-set to be the last existing patch-set.
-    PatchSet currentPatchset =
-        notes.getPatchSets().values().stream()
-            .max((p1, p2) -> p1.id().get() - p2.id().get())
-            .orElseThrow(
-                () ->
-                    new IllegalStateException(
-                        String.format(
-                            "change %s can't load any patchset", notes.getChangeId().toString())));
+    PatchSet currentPatchset = notes.getCurrentPatchSet();
 
     PatchSet.Id latestApprovedPatchsetId = getLatestApprovedPatchsetId(notes);
     if (latestApprovedPatchsetId.get() == currentPatchset.id().get()) {
diff --git a/java/com/google/gerrit/sshd/HostKeyProvider.java b/java/com/google/gerrit/sshd/HostKeyProvider.java
index 3578fb9..0e9b46b 100644
--- a/java/com/google/gerrit/sshd/HostKeyProvider.java
+++ b/java/com/google/gerrit/sshd/HostKeyProvider.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.sshd;
 
+import com.google.common.flogger.FluentLogger;
 import com.google.gerrit.server.config.SitePaths;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
@@ -22,11 +23,13 @@
 import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.List;
+import org.apache.sshd.common.config.keys.KeyUtils;
 import org.apache.sshd.common.keyprovider.FileKeyPairProvider;
 import org.apache.sshd.common.keyprovider.KeyPairProvider;
 import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider;
 
 class HostKeyProvider implements Provider<KeyPairProvider> {
+  private static final FluentLogger logger = FluentLogger.forEnclosingClass();
   private final SitePaths site;
 
   @Inject
@@ -63,7 +66,11 @@
     if (Files.exists(objKey)) {
       if (stdKeys.isEmpty()) {
         SimpleGeneratorHostKeyProvider p = new SimpleGeneratorHostKeyProvider();
+        p.setAlgorithm(KeyUtils.RSA_ALGORITHM);
         p.setPath(objKey.toAbsolutePath());
+        logger.atWarning().log(
+            "Defaulting to RSA algorithm for SSH key exchange."
+                + "This is a weak security setting, consider changing it (see 'sshd.kex' documentation section).");
         return p;
       }
       // Both formats of host key exist, we don't know which format
diff --git a/java/com/google/gerrit/sshd/SshDaemon.java b/java/com/google/gerrit/sshd/SshDaemon.java
index fa20b9c..553287ec 100644
--- a/java/com/google/gerrit/sshd/SshDaemon.java
+++ b/java/com/google/gerrit/sshd/SshDaemon.java
@@ -83,6 +83,7 @@
 import org.apache.sshd.common.io.IoServiceFactoryFactory;
 import org.apache.sshd.common.io.IoSession;
 import org.apache.sshd.common.io.nio2.Nio2ServiceFactoryFactory;
+import org.apache.sshd.common.kex.BuiltinDHFactories;
 import org.apache.sshd.common.kex.KeyExchangeFactory;
 import org.apache.sshd.common.keyprovider.KeyPairProvider;
 import org.apache.sshd.common.mac.Mac;
@@ -479,7 +480,13 @@
   }
 
   private void initKeyExchanges(Config cfg) {
-    List<KeyExchangeFactory> a = ServerBuilder.setUpDefaultKeyExchanges(true);
+    List<KeyExchangeFactory> a =
+        NamedFactory.setUpTransformedFactories(
+            true,
+            cfg.getBoolean("sshd", null, "enableDeprecatedKexAlgorithms", false)
+                ? BuiltinDHFactories.VALUES
+                : BaseBuilder.DEFAULT_KEX_PREFERENCE,
+            ServerBuilder.DH2KEX);
     setKeyExchangeFactories(filter(cfg, "kex", a.toArray(new KeyExchangeFactory[a.size()])));
   }
 
diff --git a/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java b/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java
index bab9640..42354ca 100644
--- a/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java
@@ -77,6 +77,7 @@
 import com.google.gerrit.acceptance.GitUtil;
 import com.google.gerrit.acceptance.NoHttpd;
 import com.google.gerrit.acceptance.PushOneCommit;
+import com.google.gerrit.acceptance.TestAccount;
 import com.google.gerrit.acceptance.TestProjectInput;
 import com.google.gerrit.acceptance.UseClockStep;
 import com.google.gerrit.acceptance.UseTimezone;
@@ -106,6 +107,7 @@
 import com.google.gerrit.exceptions.StorageException;
 import com.google.gerrit.extensions.annotations.Exports;
 import com.google.gerrit.extensions.api.accounts.DeleteDraftCommentsInput;
+import com.google.gerrit.extensions.api.changes.AttentionSetInput;
 import com.google.gerrit.extensions.api.changes.DeleteReviewerInput;
 import com.google.gerrit.extensions.api.changes.DeleteVoteInput;
 import com.google.gerrit.extensions.api.changes.DraftApi;
@@ -808,6 +810,28 @@
   }
 
   @Test
+  public void rebaseAsUploaderInAttentionSet() throws Exception {
+    // Create two changes both with the same parent
+    PushOneCommit.Result r = createChange();
+    testRepo.reset("HEAD~1");
+    PushOneCommit.Result r2 = createChange();
+
+    // Approve and submit the first change
+    RevisionApi revision = gApi.changes().id(r.getChangeId()).current();
+    revision.review(ReviewInput.approve());
+    revision.submit();
+
+    TestAccount admin2 = accountCreator.admin2();
+    requestScopeOperations.setApiUser(admin2.id());
+    amendChangeWithUploader(r2, project, admin2);
+    gApi.changes()
+        .id(r2.getChangeId())
+        .addToAttentionSet(new AttentionSetInput(admin2.id().toString(), "manual update"));
+
+    gApi.changes().id(r2.getChangeId()).rebase();
+  }
+
+  @Test
   public void rebaseOnChangeNumber() throws Exception {
     String branchTip = testRepo.getRepository().exactRef("HEAD").getObjectId().name();
     PushOneCommit.Result r1 = createChange();
diff --git a/javatests/com/google/gerrit/elasticsearch/ElasticContainer.java b/javatests/com/google/gerrit/elasticsearch/ElasticContainer.java
index 4d7468f..c330961 100644
--- a/javatests/com/google/gerrit/elasticsearch/ElasticContainer.java
+++ b/javatests/com/google/gerrit/elasticsearch/ElasticContainer.java
@@ -39,8 +39,6 @@
 
   private static String getImageName(ElasticVersion version) {
     switch (version) {
-      case V7_5:
-        return "blacktop/elasticsearch:7.5.2";
       case V7_6:
         return "blacktop/elasticsearch:7.6.2";
       case V7_7:
diff --git a/javatests/com/google/gerrit/elasticsearch/ElasticVersionTest.java b/javatests/com/google/gerrit/elasticsearch/ElasticVersionTest.java
index 75e9636..2ce3a2c 100644
--- a/javatests/com/google/gerrit/elasticsearch/ElasticVersionTest.java
+++ b/javatests/com/google/gerrit/elasticsearch/ElasticVersionTest.java
@@ -22,9 +22,6 @@
 public class ElasticVersionTest {
   @Test
   public void supportedVersion() throws Exception {
-    assertThat(ElasticVersion.forVersion("7.5.0")).isEqualTo(ElasticVersion.V7_5);
-    assertThat(ElasticVersion.forVersion("7.5.1")).isEqualTo(ElasticVersion.V7_5);
-
     assertThat(ElasticVersion.forVersion("7.6.0")).isEqualTo(ElasticVersion.V7_6);
     assertThat(ElasticVersion.forVersion("7.6.1")).isEqualTo(ElasticVersion.V7_6);
 
diff --git a/plugins/plugin-manager b/plugins/plugin-manager
index 00e5794..5b87f63 160000
--- a/plugins/plugin-manager
+++ b/plugins/plugin-manager
@@ -1 +1 @@
-Subproject commit 00e57948f4f112c226028bc5c8d8fe60f770038f
+Subproject commit 5b87f63f3e9c5817bcddf008c0b4005494059368
diff --git a/polygerrit-ui/app/api/diff.ts b/polygerrit-ui/app/api/diff.ts
index 3f51cf0..015f11d 100644
--- a/polygerrit-ui/app/api/diff.ts
+++ b/polygerrit-ui/app/api/diff.ts
@@ -208,12 +208,17 @@
   show_file_comment_button?: boolean;
 }
 
+export declare interface ImageDiffPreferences {
+  automatic_blink?: boolean;
+}
+
 export declare interface RenderPreferences {
   hide_left_side?: boolean;
   disable_context_control_buttons?: boolean;
   show_file_comment_button?: boolean;
   hide_line_length_indicator?: boolean;
   use_block_expansion?: boolean;
+  image_diff_prefs?: ImageDiffPreferences;
 }
 
 /**
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_html.ts b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_html.ts
index 693406c..a9743b4 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_html.ts
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_html.ts
@@ -570,6 +570,7 @@
           change="[[_change]]"
           change-num="[[_changeNum]]"
           logged-in="[[_loggedIn]]"
+          account="[[_account]]"
           comment-tab-state="[[_tabState.commentTab]]"
           only-show-robot-comments-with-human-reply=""
           unresolved-only="[[unresolvedOnly]]"
diff --git a/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header_html.ts b/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header_html.ts
index be3a0be..bceee27 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header_html.ts
+++ b/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header_html.ts
@@ -189,8 +189,8 @@
             ><iron-icon icon="gr-icons:settings"></iron-icon
           ></gr-button>
         </span>
+        <span class="separator"></span>
       </div>
-      <span class="separator"></span>
       <span class="downloadContainer desktop">
         <gr-button
           link=""
diff --git a/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list.ts b/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list.ts
index 2e8e4ba..aa1096d 100644
--- a/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list.ts
+++ b/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list.ts
@@ -30,7 +30,11 @@
   PolymerSpliceChange,
   PolymerDeepPropertyChange,
 } from '@polymer/polymer/interfaces';
-import {AccountInfo, ChangeInfo} from '../../../types/common';
+import {
+  AccountDetailInfo,
+  AccountInfo,
+  ChangeInfo,
+} from '../../../types/common';
 import {
   CommentThread,
   isDraft,
@@ -120,6 +124,9 @@
   @property({type: Array, notify: true})
   selectedAuthors: AccountInfo[] = [];
 
+  @property({type: Object})
+  account?: AccountDetailInfo;
+
   @computed('unresolvedOnly', '_draftsOnly')
   get commentsDropdownValue() {
     // set initial value and triggered when comment summary chips are clicked
@@ -199,8 +206,8 @@
     return items;
   }
 
-  getCommentAuthors(threads?: CommentThread[]) {
-    return getCommentAuthors(threads);
+  getCommentAuthors(threads?: CommentThread[], account?: AccountDetailInfo) {
+    return getCommentAuthors(threads, account);
   }
 
   handleAccountClicked(e: MouseEvent) {
diff --git a/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list_html.ts b/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list_html.ts
index aa14b11..aef34d8 100644
--- a/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list_html.ts
+++ b/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list_html.ts
@@ -105,7 +105,7 @@
       </gr-dropdown-list>
       <template is="dom-if" if="[[threads.length]]">
         <span class="author-text">From:</span>
-        <template is="dom-repeat" items="[[getCommentAuthors(threads)]]">
+        <template is="dom-repeat" items="[[getCommentAuthors(threads, account)]]">
           <gr-account-label
             account="[[item]]"
             on-click="handleAccountClicked"
diff --git a/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list_test.js b/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list_test.js
index 5c87c4b..11d07dc 100644
--- a/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list_test.js
+++ b/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list_test.js
@@ -24,6 +24,7 @@
 import {queryAll} from '../../../test/test-utils.js';
 import {accountOrGroupKey} from '../../../utils/account-util.js';
 import {tap} from '@polymer/iron-test-helpers/mock-interactions';
+import {createAccountDetailWithId} from '../../../test/test-data-generators.js';
 
 const basicFixture = fixtureFromElement('gr-thread-list');
 
@@ -499,11 +500,13 @@
   });
 
   test('tapping single author chips', () => {
+    element.account = createAccountDetailWithId(1);
+    flush();
     const chips = queryAll(element, 'gr-account-label');
     const authors = Array.from(chips).map(
         chip => accountOrGroupKey(chip.account))
         .sort();
-    assert.deepEqual(authors, [1000000, 1000001, 1000002, 1000003]);
+    assert.deepEqual(authors, [1, 1000000, 1000001, 1000002, 1000003]);
     assert.equal(element.threads.length, 9);
     assert.equal(element._displayedThreads.length, 9);
 
@@ -522,10 +525,12 @@
   });
 
   test('tapping multiple author chips', () => {
+    element.account = createAccountDetailWithId(1);
+    flush();
     const chips = queryAll(element, 'gr-account-label');
 
     tap(chips[0]); // accountId 1000001
-    tap(chips[1]); // accountId 1000002
+    tap(chips[2]); // accountId 1000002
     flush();
 
     assert.equal(element.threads.length, 9);
diff --git a/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header_html.ts b/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header_html.ts
index 7f3970f..4bd048c 100644
--- a/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header_html.ts
+++ b/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header_html.ts
@@ -215,6 +215,7 @@
           href$="[[_feedbackURL]]"
           title="File a bug"
           aria-label="File a bug"
+          target="_blank"
           role="button"
         >
           <iron-icon icon="gr-icons:bug"></iron-icon>
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-binary.ts b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-binary.ts
index c66ea4f..51fc207 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-binary.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-binary.ts
@@ -19,6 +19,7 @@
 import {DiffInfo, DiffPreferencesInfo} from '../../../types/diff';
 import {GrDiffLine, GrDiffLineType} from '../gr-diff/gr-diff-line';
 import {queryAndAssert} from '../../../utils/common-util';
+import {RenderPreferences} from '../../../api/diff';
 
 export class GrDiffBuilderBinary extends GrDiffBuilderUnified {
   constructor(
@@ -38,4 +39,7 @@
     section.appendChild(fileRow);
     return section;
   }
+
+  /** @override */
+  updateRenderPrefs(_renderPrefs: RenderPreferences) {}
 }
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-element.ts b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-element.ts
index d65deb6..733c940 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-element.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-element.ts
@@ -522,6 +522,11 @@
     if (!this._builder) return;
     this._builder.setBlame(blame);
   }
+
+  updateRenderPrefs(renderPrefs: RenderPreferences) {
+    if (!this._builder) return;
+    this._builder.updateRenderPrefs(renderPrefs);
+  }
 }
 
 declare global {
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-image.ts b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-image.ts
index 650a9e7..1243e8f 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-image.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-image.ts
@@ -96,6 +96,8 @@
 
     imageViewer.baseUrl = this._getImageSrc(this._baseImage);
     imageViewer.revisionUrl = this._getImageSrc(this._revisionImage);
+    imageViewer.automaticBlink = !!this._renderPrefs?.image_diff_prefs
+      ?.automatic_blink;
 
     td.appendChild(imageViewer);
     tr.appendChild(td);
@@ -213,6 +215,17 @@
 
     section.appendChild(tr);
   }
+
+  /** @override */
+  updateRenderPrefs(renderPrefs: RenderPreferences) {
+    const imageViewer = this._outputEl.querySelector(
+      'gr-image-viewer'
+    ) as GrImageViewer;
+    if (this._useNewImageDiffUi && imageViewer) {
+      imageViewer.automaticBlink = !!renderPrefs?.image_diff_prefs
+        ?.automatic_blink;
+    }
+  }
 }
 
 function _getImageLabel(image: ImageInfo | null) {
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-side-by-side.ts b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-side-by-side.ts
index f44e006..da1d971 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-side-by-side.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-side-by-side.ts
@@ -137,4 +137,7 @@
     }
     return null;
   }
+
+  /** @override */
+  updateRenderPrefs(_renderPrefs: RenderPreferences) {}
 }
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-unified.ts b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-unified.ts
index 2067455..4ecfcbf 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-unified.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-unified.ts
@@ -146,4 +146,7 @@
     }
     return null;
   }
+
+  /** @override */
+  updateRenderPrefs(_renderPrefs: RenderPreferences) {}
 }
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.ts b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.ts
index f70974f..a50f1fc 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.ts
@@ -78,7 +78,7 @@
 
   private readonly _prefs: DiffPreferencesInfo;
 
-  private readonly _renderPrefs?: RenderPreferences;
+  protected readonly _renderPrefs?: RenderPreferences;
 
   protected readonly _outputEl: HTMLElement;
 
@@ -895,4 +895,6 @@
     while (row && !row.classList.contains('diff-row')) row = row.parentElement;
     return row ? (row.querySelector('.lineNum.' + side) as HTMLElement) : null;
   }
+
+  updateRenderPrefs(_renderPrefs: RenderPreferences) {}
 }
diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.ts b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.ts
index 0b42f9f..3d9bba7 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.ts
@@ -766,6 +766,7 @@
     if (renderPrefs.hide_line_length_indicator) {
       this.classList.add('hide-line-length-indicator');
     }
+    this.$.diffBuilder.updateRenderPrefs(renderPrefs);
   }
 
   _diffChanged(newValue?: DiffInfo) {
diff --git a/polygerrit-ui/app/elements/shared/gr-lib-loader/resemble-types.d.ts b/polygerrit-ui/app/elements/shared/gr-lib-loader/resemble-types.d.ts
new file mode 100644
index 0000000..79ce81c
--- /dev/null
+++ b/polygerrit-ui/app/elements/shared/gr-lib-loader/resemble-types.d.ts
@@ -0,0 +1,28 @@
+/**
+ * @license
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import Resemble from 'resemblejs';
+
+// @types/resemblejs does not expose a global variable resemble and instead
+// exposes the namespace Resemble. Because Resemble.js should remain an
+// optional dependency, we define a global variable in a separate .d.ts file;
+// otherwise, the TS compiler tries to import the JS library too and fails.
+declare global {
+  interface Window {
+    resemble?: typeof Resemble;
+  }
+}
diff --git a/polygerrit-ui/app/elements/shared/gr-lib-loader/resemblejs_config.ts b/polygerrit-ui/app/elements/shared/gr-lib-loader/resemblejs_config.ts
new file mode 100644
index 0000000..872d01c
--- /dev/null
+++ b/polygerrit-ui/app/elements/shared/gr-lib-loader/resemblejs_config.ts
@@ -0,0 +1,33 @@
+/**
+ * @license
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import {LibraryConfig} from './gr-lib-loader';
+
+export const RESEMBLEJS_LIBRARY_CONFIG: LibraryConfig = {
+  src: 'bower_components/resemblejs/resemble.js',
+  checkPresent: () => window.resemble !== undefined,
+  configureCallback: () => {
+    window.resemble!.outputSettings({
+      errorColor: {red: 255, green: 0, blue: 255},
+      errorType: 'flat',
+      transparency: 0,
+      // Disable large image threshold; by default this otherwise skips pixels
+      // if width or height exceed 1200 pixels.
+      largeImageThreshold: 0,
+    });
+    return window.resemble;
+  },
+};
diff --git a/polygerrit-ui/app/node_modules_licenses/licenses.ts b/polygerrit-ui/app/node_modules_licenses/licenses.ts
index ac49388..f04e224 100644
--- a/polygerrit-ui/app/node_modules_licenses/licenses.ts
+++ b/polygerrit-ui/app/node_modules_licenses/licenses.ts
@@ -288,6 +288,14 @@
     license: SharedLicenses.Polymer2017
   },
   {
+    name: "@types/resemblejs",
+    license: {
+      name: 'DefinitelyTyped',
+      type: LicenseTypes.Mit,
+      packageLicenseFile: "LICENSE"
+    }
+  },
+  {
     name: "@types/resize-observer-browser",
     license: {
       name: 'DefinitelyTyped',
@@ -389,6 +397,14 @@
     },
     nonPackages: ["modules", "test/validateModuleExportsMatchCommonJS"],
   },
+  {
+    name: "resemblejs",
+    license: {
+      name: "resemblejs",
+      type: LicenseTypes.Mit,
+      packageLicenseFile: "LICENSE",
+    }
+  },
 ];
 
 export default packages;
diff --git a/polygerrit-ui/app/package.json b/polygerrit-ui/app/package.json
index 4f5acf5..4e18e47 100644
--- a/polygerrit-ui/app/package.json
+++ b/polygerrit-ui/app/package.json
@@ -31,15 +31,17 @@
     "@polymer/paper-toggle-button": "^3.0.1",
     "@polymer/paper-tooltip": "^3.0.1",
     "@polymer/polymer": "^3.4.1",
+    "@types/resemblejs": "^3.2.0",
     "@types/resize-observer-browser": "^0.1.5",
     "@webcomponents/shadycss": "^1.10.2",
     "@webcomponents/webcomponentsjs": "^1.3.3",
     "ba-linkify": "file:../../lib/ba-linkify/src/",
-    "codemirror-minified": "^5.60.0",
+    "codemirror-minified": "^5.62.0",
     "lit-element": "^2.5.1",
     "page": "^1.11.6",
     "polymer-bridges": "file:../../polymer-bridges/",
     "polymer-resin": "^2.0.1",
+    "resemblejs": "^4.0.0",
     "rxjs": "^6.6.7"
   },
   "license": "Apache-2.0",
diff --git a/polygerrit-ui/app/rules.bzl b/polygerrit-ui/app/rules.bzl
index feb1a82..4e8f9f6 100644
--- a/polygerrit-ui/app/rules.bzl
+++ b/polygerrit-ui/app/rules.bzl
@@ -142,19 +142,21 @@
             "//lib/fonts:robotofonts",
             "//lib/js:highlightjs__files",
             "@ui_npm//:node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js",
+            "@ui_npm//:node_modules/resemblejs/resemble.js",
             "@ui_npm//@polymer/font-roboto-local",
             "@ui_npm//:node_modules/@polymer/font-roboto-local/package.json",
         ],
         outs = outs,
         cmd = " && ".join([
             "FONT_DIR=$$(dirname $(location @ui_npm//:node_modules/@polymer/font-roboto-local/package.json))/fonts",
-            "mkdir -p $$TMP/polygerrit_ui/{styles/themes,fonts/{roboto,robotomono},bower_components/{highlightjs,webcomponentsjs},elements}",
+            "mkdir -p $$TMP/polygerrit_ui/{styles/themes,fonts/{roboto,robotomono},bower_components/{highlightjs,webcomponentsjs,resemblejs},elements}",
             "for f in $(locations " + name + "_app_sources); do ext=$${f##*.}; cp -p $$f $$TMP/polygerrit_ui/elements/" + app_name + ".$$ext; done",
             "cp $(locations //lib/fonts:robotofonts) $$TMP/polygerrit_ui/fonts/",
             "for f in $(locations " + name + "_top_sources); do cp $$f $$TMP/polygerrit_ui/; done",
             "for f in $(locations " + name + "_css_sources); do cp $$f $$TMP/polygerrit_ui/styles; done",
             "for f in $(locations //lib/js:highlightjs__files); do cp $$f $$TMP/polygerrit_ui/bower_components/highlightjs/ ; done",
             "cp $(location @ui_npm//:node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js) $$TMP/polygerrit_ui/bower_components/webcomponentsjs/webcomponents-lite.js",
+            "cp $(location @ui_npm//:node_modules/resemblejs/resemble.js) $$TMP/polygerrit_ui/bower_components/resemblejs/resemble.js",
             "cp $$FONT_DIR/roboto/*.ttf $$TMP/polygerrit_ui/fonts/roboto/",
             "cp $$FONT_DIR/robotomono/*.ttf $$TMP/polygerrit_ui/fonts/robotomono/",
             "cd $$TMP",
diff --git a/polygerrit-ui/app/utils/comment-util.ts b/polygerrit-ui/app/utils/comment-util.ts
index fd276c3..70ba38e 100644
--- a/polygerrit-ui/app/utils/comment-util.ts
+++ b/polygerrit-ui/app/utils/comment-util.ts
@@ -28,6 +28,7 @@
   BasePatchSetNum,
   RevisionPatchSetNum,
   AccountInfo,
+  AccountDetailInfo,
 } from '../types/common';
 import {CommentSide, Side, SpecialFilePath} from '../constants/constants';
 import {parseDate} from './date-util';
@@ -332,12 +333,20 @@
   return diff;
 }
 
-export function getCommentAuthors(threads?: CommentThread[]) {
-  if (!threads) return [];
+export function getCommentAuthors(
+  threads?: CommentThread[],
+  user?: AccountDetailInfo
+) {
+  if (!threads || !user) return [];
   const ids = new Set();
   const authors: AccountInfo[] = [];
   threads.forEach(t =>
     t.comments.forEach(c => {
+      if (isDraft(c) && !ids.has(user._account_id)) {
+        ids.add(user._account_id);
+        authors.push(user);
+        return;
+      }
       if (c.author && !ids.has(c.author._account_id)) {
         ids.add(c.author._account_id);
         authors.push(c.author);
diff --git a/polygerrit-ui/app/yarn.lock b/polygerrit-ui/app/yarn.lock
index fed42a4..d8657f7 100644
--- a/polygerrit-ui/app/yarn.lock
+++ b/polygerrit-ui/app/yarn.lock
@@ -2,6 +2,21 @@
 # yarn lockfile v1
 
 
+"@mapbox/node-pre-gyp@^1.0.0":
+  version "1.0.5"
+  resolved "https://registry.yarnpkg.com/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.5.tgz#2a0b32fcb416fb3f2250fd24cb2a81421a4f5950"
+  integrity sha512-4srsKPXWlIxp5Vbqz5uLfBN+du2fJChBoYn/f2h991WLdk7jUvcSk/McVLSv/X+xQIPI8eGD5GjrnygdyHnhPA==
+  dependencies:
+    detect-libc "^1.0.3"
+    https-proxy-agent "^5.0.0"
+    make-dir "^3.1.0"
+    node-fetch "^2.6.1"
+    nopt "^5.0.0"
+    npmlog "^4.1.2"
+    rimraf "^3.0.2"
+    semver "^7.3.4"
+    tar "^6.1.0"
+
 "@polymer/decorators@^3.0.0":
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/@polymer/decorators/-/decorators-3.0.0.tgz#e4212ac976d9abd1210f560b6e1be4165c1c0183"
@@ -395,6 +410,11 @@
   dependencies:
     "@webcomponents/shadycss" "^1.9.1"
 
+"@types/resemblejs@^3.2.0":
+  version "3.2.0"
+  resolved "https://registry.yarnpkg.com/@types/resemblejs/-/resemblejs-3.2.0.tgz#a2093fd6ae027d39b56ae279f362a4d83e00788f"
+  integrity sha512-YUBCCipw3DG0/FUswHAiamZcs+JBZlRr1aNs1T19AkfLZNtzV4VphmRLy6wJ3m1i9QxIfiBe3RnzVjHbjRqLaA==
+
 "@types/resize-observer-browser@^0.1.5":
   version "0.1.5"
   resolved "https://registry.yarnpkg.com/@types/resize-observer-browser/-/resize-observer-browser-0.1.5.tgz#36d897708172ac2380cd486da7a3daf1161c1e23"
@@ -415,19 +435,206 @@
   resolved "https://registry.yarnpkg.com/@webcomponents/webcomponentsjs/-/webcomponentsjs-2.5.0.tgz#61b27785a6ad5bfd68fa018201fe418b118cb38d"
   integrity sha512-C0l51MWQZ9kLzcxOZtniOMohpIFdCLZum7/TEHv3XWFc1Fvt5HCpbSX84x8ltka/JuNKcuiDnxXFkiB2gaePcg==
 
+abbrev@1:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8"
+  integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==
+
+agent-base@6:
+  version "6.0.2"
+  resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77"
+  integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==
+  dependencies:
+    debug "4"
+
+ansi-regex@^2.0.0:
+  version "2.1.1"
+  resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df"
+  integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8=
+
+ansi-regex@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998"
+  integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=
+
+aproba@^1.0.3:
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a"
+  integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==
+
+are-we-there-yet@~1.1.2:
+  version "1.1.5"
+  resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz#4b35c2944f062a8bfcda66410760350fe9ddfc21"
+  integrity sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==
+  dependencies:
+    delegates "^1.0.0"
+    readable-stream "^2.0.6"
+
 "ba-linkify@file:../../lib/ba-linkify/src":
   version "1.0.0"
 
-codemirror-minified@^5.60.0:
-  version "5.60.0"
-  resolved "https://registry.yarnpkg.com/codemirror-minified/-/codemirror-minified-5.60.0.tgz#d0d8b62ab6d50864903f812cd203b97193b1fb75"
-  integrity sha512-Ru9aChh07DwYrUEfI+LznD3l8GxOPlYKAqcG8qxIlxRZTyw4BPfZsV5m1oUz1y6knxaPxa23FwM/R5rWggRnKg==
+balanced-match@^1.0.0:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
+  integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
+
+brace-expansion@^1.1.7:
+  version "1.1.11"
+  resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
+  integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==
+  dependencies:
+    balanced-match "^1.0.0"
+    concat-map "0.0.1"
+
+canvas@2.8.0:
+  version "2.8.0"
+  resolved "https://registry.yarnpkg.com/canvas/-/canvas-2.8.0.tgz#f99ca7f25e6e26686661ffa4fec1239bbef74461"
+  integrity sha512-gLTi17X8WY9Cf5GZ2Yns8T5lfBOcGgFehDFb+JQwDqdOoBOcECS9ZWMEAqMSVcMYwXD659J8NyzjRY/2aE+C2Q==
+  dependencies:
+    "@mapbox/node-pre-gyp" "^1.0.0"
+    nan "^2.14.0"
+    simple-get "^3.0.3"
+
+chownr@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece"
+  integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==
+
+code-point-at@^1.0.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77"
+  integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=
+
+codemirror-minified@^5.62.0:
+  version "5.62.0"
+  resolved "https://registry.yarnpkg.com/codemirror-minified/-/codemirror-minified-5.62.0.tgz#1d5bc5fc2c2baddebe54afefc90371d462be5f05"
+  integrity sha512-p4ALY/Lz5y4ftS5Q34rCBwLcupeATA5h4nBP2CZQgMWr+kQGnVDJxOCtC5KAYNk6Yo0jyKBvrsvr0ZxzuEuDow==
+
+concat-map@0.0.1:
+  version "0.0.1"
+  resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
+  integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=
+
+console-control-strings@^1.0.0, console-control-strings@~1.1.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e"
+  integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=
+
+core-util-is@~1.0.0:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
+  integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=
+
+debug@4:
+  version "4.3.1"
+  resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee"
+  integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==
+  dependencies:
+    ms "2.1.2"
+
+decompress-response@^4.2.0:
+  version "4.2.1"
+  resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-4.2.1.tgz#414023cc7a302da25ce2ec82d0d5238ccafd8986"
+  integrity sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==
+  dependencies:
+    mimic-response "^2.0.0"
+
+delegates@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a"
+  integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=
+
+detect-libc@^1.0.3:
+  version "1.0.3"
+  resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b"
+  integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=
+
+fs-minipass@^2.0.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb"
+  integrity sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==
+  dependencies:
+    minipass "^3.0.0"
+
+fs.realpath@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
+  integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8=
+
+gauge@~2.7.3:
+  version "2.7.4"
+  resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7"
+  integrity sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=
+  dependencies:
+    aproba "^1.0.3"
+    console-control-strings "^1.0.0"
+    has-unicode "^2.0.0"
+    object-assign "^4.1.0"
+    signal-exit "^3.0.0"
+    string-width "^1.0.1"
+    strip-ansi "^3.0.1"
+    wide-align "^1.1.0"
+
+glob@^7.1.3:
+  version "7.1.7"
+  resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.7.tgz#3b193e9233f01d42d0b3f78294bbeeb418f94a90"
+  integrity sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==
+  dependencies:
+    fs.realpath "^1.0.0"
+    inflight "^1.0.4"
+    inherits "2"
+    minimatch "^3.0.4"
+    once "^1.3.0"
+    path-is-absolute "^1.0.0"
+
+has-unicode@^2.0.0:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9"
+  integrity sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=
+
+https-proxy-agent@^5.0.0:
+  version "5.0.0"
+  resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz#e2a90542abb68a762e0a0850f6c9edadfd8506b2"
+  integrity sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==
+  dependencies:
+    agent-base "6"
+    debug "4"
+
+inflight@^1.0.4:
+  version "1.0.6"
+  resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
+  integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=
+  dependencies:
+    once "^1.3.0"
+    wrappy "1"
+
+inherits@2, inherits@~2.0.3:
+  version "2.0.4"
+  resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
+  integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
+
+is-fullwidth-code-point@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb"
+  integrity sha1-754xOG8DGn8NZDr4L95QxFfvAMs=
+  dependencies:
+    number-is-nan "^1.0.0"
+
+is-fullwidth-code-point@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f"
+  integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=
 
 isarray@0.0.1:
   version "0.0.1"
   resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf"
   integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=
 
+isarray@~1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
+  integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=
+
 lit-element@^2.5.1:
   version "2.5.1"
   resolved "https://registry.yarnpkg.com/lit-element/-/lit-element-2.5.1.tgz#3fa74b121a6cd22902409ae3859b7847d01aa6b6"
@@ -440,6 +647,101 @@
   resolved "https://registry.yarnpkg.com/lit-html/-/lit-html-1.4.1.tgz#0c6f3ee4ad4eb610a49831787f0478ad8e9ae5e0"
   integrity sha512-B9btcSgPYb1q4oSOb/PrOT6Z/H+r6xuNzfH4lFli/AWhYwdtrgQkQWBbIc6mdnf6E2IL3gDXdkkqNktpU0OZQA==
 
+lru-cache@^6.0.0:
+  version "6.0.0"
+  resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94"
+  integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==
+  dependencies:
+    yallist "^4.0.0"
+
+make-dir@^3.1.0:
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f"
+  integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==
+  dependencies:
+    semver "^6.0.0"
+
+mimic-response@^2.0.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-2.1.0.tgz#d13763d35f613d09ec37ebb30bac0469c0ee8f43"
+  integrity sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==
+
+minimatch@^3.0.4:
+  version "3.0.4"
+  resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
+  integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==
+  dependencies:
+    brace-expansion "^1.1.7"
+
+minipass@^3.0.0:
+  version "3.1.3"
+  resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.1.3.tgz#7d42ff1f39635482e15f9cdb53184deebd5815fd"
+  integrity sha512-Mgd2GdMVzY+x3IJ+oHnVM+KG3lA5c8tnabyJKmHSaG2kAGpudxuOf8ToDkhumF7UzME7DecbQE9uOZhNm7PuJg==
+  dependencies:
+    yallist "^4.0.0"
+
+minizlib@^2.1.1:
+  version "2.1.2"
+  resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931"
+  integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==
+  dependencies:
+    minipass "^3.0.0"
+    yallist "^4.0.0"
+
+mkdirp@^1.0.3:
+  version "1.0.4"
+  resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
+  integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
+
+ms@2.1.2:
+  version "2.1.2"
+  resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
+  integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
+
+nan@^2.14.0:
+  version "2.14.2"
+  resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.2.tgz#f5376400695168f4cc694ac9393d0c9585eeea19"
+  integrity sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==
+
+node-fetch@^2.6.1:
+  version "2.6.1"
+  resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052"
+  integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==
+
+nopt@^5.0.0:
+  version "5.0.0"
+  resolved "https://registry.yarnpkg.com/nopt/-/nopt-5.0.0.tgz#530942bb58a512fccafe53fe210f13a25355dc88"
+  integrity sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==
+  dependencies:
+    abbrev "1"
+
+npmlog@^4.1.2:
+  version "4.1.2"
+  resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b"
+  integrity sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==
+  dependencies:
+    are-we-there-yet "~1.1.2"
+    console-control-strings "~1.1.0"
+    gauge "~2.7.3"
+    set-blocking "~2.0.0"
+
+number-is-nan@^1.0.0:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d"
+  integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=
+
+object-assign@^4.1.0:
+  version "4.1.1"
+  resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
+  integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=
+
+once@^1.3.0, once@^1.3.1:
+  version "1.4.0"
+  resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
+  integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E=
+  dependencies:
+    wrappy "1"
+
 page@^1.11.6:
   version "1.11.6"
   resolved "https://registry.yarnpkg.com/page/-/page-1.11.6.tgz#5ef4efc7073749b8085ccdaa0dcd7c9e0de12fe3"
@@ -447,6 +749,11 @@
   dependencies:
     path-to-regexp "~1.2.1"
 
+path-is-absolute@^1.0.0:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
+  integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18=
+
 path-to-regexp@~1.2.1:
   version "1.2.1"
   resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.2.1.tgz#b33705c140234d873c8721c7b9fd8b541ed3aff9"
@@ -465,6 +772,38 @@
     "@polymer/polymer" "^3.0.2"
     "@webcomponents/webcomponentsjs" "^2.0.3"
 
+process-nextick-args@~2.0.0:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2"
+  integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==
+
+readable-stream@^2.0.6:
+  version "2.3.7"
+  resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57"
+  integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==
+  dependencies:
+    core-util-is "~1.0.0"
+    inherits "~2.0.3"
+    isarray "~1.0.0"
+    process-nextick-args "~2.0.0"
+    safe-buffer "~5.1.1"
+    string_decoder "~1.1.1"
+    util-deprecate "~1.0.1"
+
+resemblejs@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/resemblejs/-/resemblejs-4.0.0.tgz#5382f0484430d826ed293433833b9fc4e06e5496"
+  integrity sha512-vaGs/hFVx/941+RS4UJtd8DQvx5RuB61tPLOQCxPso3JpmjfDb6odH5HViT17S0d8DaZsexD01nRJI12giCz/A==
+  optionalDependencies:
+    canvas "2.8.0"
+
+rimraf@^3.0.2:
+  version "3.0.2"
+  resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a"
+  integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==
+  dependencies:
+    glob "^7.1.3"
+
 rxjs@^6.6.7:
   version "6.6.7"
   resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.7.tgz#90ac018acabf491bf65044235d5863c4dab804c9"
@@ -472,7 +811,120 @@
   dependencies:
     tslib "^1.9.0"
 
+safe-buffer@~5.1.0, safe-buffer@~5.1.1:
+  version "5.1.2"
+  resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
+  integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
+
+semver@^6.0.0:
+  version "6.3.0"
+  resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
+  integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
+
+semver@^7.3.4:
+  version "7.3.5"
+  resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7"
+  integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==
+  dependencies:
+    lru-cache "^6.0.0"
+
+set-blocking@~2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
+  integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc=
+
+signal-exit@^3.0.0:
+  version "3.0.3"
+  resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c"
+  integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==
+
+simple-concat@^1.0.0:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.1.tgz#f46976082ba35c2263f1c8ab5edfe26c41c9552f"
+  integrity sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==
+
+simple-get@^3.0.3:
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-3.1.0.tgz#b45be062435e50d159540b576202ceec40b9c6b3"
+  integrity sha512-bCR6cP+aTdScaQCnQKbPKtJOKDp/hj9EDLJo3Nw4y1QksqaovlW/bnptB6/c1e+qmNIDHRK+oXFDdEqBT8WzUA==
+  dependencies:
+    decompress-response "^4.2.0"
+    once "^1.3.1"
+    simple-concat "^1.0.0"
+
+string-width@^1.0.1:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3"
+  integrity sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=
+  dependencies:
+    code-point-at "^1.0.0"
+    is-fullwidth-code-point "^1.0.0"
+    strip-ansi "^3.0.0"
+
+"string-width@^1.0.2 || 2":
+  version "2.1.1"
+  resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e"
+  integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==
+  dependencies:
+    is-fullwidth-code-point "^2.0.0"
+    strip-ansi "^4.0.0"
+
+string_decoder@~1.1.1:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8"
+  integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==
+  dependencies:
+    safe-buffer "~5.1.0"
+
+strip-ansi@^3.0.0, strip-ansi@^3.0.1:
+  version "3.0.1"
+  resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf"
+  integrity sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=
+  dependencies:
+    ansi-regex "^2.0.0"
+
+strip-ansi@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f"
+  integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8=
+  dependencies:
+    ansi-regex "^3.0.0"
+
+tar@^6.1.0:
+  version "6.1.0"
+  resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.0.tgz#d1724e9bcc04b977b18d5c573b333a2207229a83"
+  integrity sha512-DUCttfhsnLCjwoDoFcI+B2iJgYa93vBnDUATYEeRx6sntCTdN01VnqsIuTlALXla/LWooNg0yEGeB+Y8WdFxGA==
+  dependencies:
+    chownr "^2.0.0"
+    fs-minipass "^2.0.0"
+    minipass "^3.0.0"
+    minizlib "^2.1.1"
+    mkdirp "^1.0.3"
+    yallist "^4.0.0"
+
 tslib@^1.9.0:
   version "1.14.1"
   resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
   integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
+
+util-deprecate@~1.0.1:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
+  integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=
+
+wide-align@^1.1.0:
+  version "1.1.3"
+  resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457"
+  integrity sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==
+  dependencies:
+    string-width "^1.0.2 || 2"
+
+wrappy@1:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
+  integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
+
+yallist@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
+  integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
diff --git a/resources/com/google/gerrit/pgm/init/gerrit.sh b/resources/com/google/gerrit/pgm/init/gerrit.sh
index 87a6c05..0f99202 100755
--- a/resources/com/google/gerrit/pgm/init/gerrit.sh
+++ b/resources/com/google/gerrit/pgm/init/gerrit.sh
@@ -96,6 +96,36 @@
   fi
 }
 
+# Limited support for Gerrit's getTimeUnit() limited from seconds to days
+# because having gerrit startup/shutdown that wait for weeks or years would
+# not make so much sense.
+get_time_unit_sec() {
+  TIME_LC=`echo $1 | tr '[:upper:]' '[:lower:]'`
+  if [[ "$TIME_LC" =~ ^(0|[1-9][0-9]*)$ ]]
+  then
+    echo $TIME_LC
+  elif [[ "$TIME_LC" =~ ^[1-9][0-9]*\ *(s|sec|second|seconds)$ ]]
+  then
+    echo "$TIME_LC" | tr -d -c 0-9
+  elif [[ "$TIME_LC" =~ ^[1-9][0-9]*\ *(m|min|minute|minutes)$ ]]
+  then
+    expr `echo "$TIME_LC" | tr -d -c 0-9` '*' 60
+  elif [[ "$TIME_LC" =~ ^[1-9][0-9]*\ *(h|hr|hour|hours)$ ]]
+  then
+    expr `echo "$TIME_LC" | tr -d -c 0-9` '*' 3600
+  elif [[ "$TIME_LC" =~ ^[1-9][0-9]*\ *(d|day|days)$ ]]
+  then
+    expr `echo "$TIME_LC" | tr -d -c 0-9` '*' 86400
+  else
+    >&2 echo "Unsupported time format $1"
+    exit 1
+  fi
+}
+
+max() {
+  echo $(( $1 > $2 ? $1 : $2 ))
+}
+
 ##################################################
 # Get the action and options
 ##################################################
@@ -321,6 +351,15 @@
 ulimit -x >/dev/null 2>&1 && ulimit -x unlimited  ; # file locks
 
 #####################################################
+# Configure the maximum wait time for shutdown
+#####################################################
+EXTRA_STOP_TIMEOUT=30
+HTTPD_STOP_TIMEOUT=$(get_time_unit_sec "$(get_config --get httpd.gracefulStopTimeout || echo 0)")
+SSHD_STOP_TIMEOUT=$(get_time_unit_sec "$(get_config --get sshd.gracefulStopTimeout || echo 0)")
+
+STOP_TIMEOUT=`expr $(max $HTTPD_STOP_TIMEOUT $SSHD_STOP_TIMEOUT) '+' $EXTRA_STOP_TIMEOUT`
+
+#####################################################
 # This is how the Gerrit server will be started
 #####################################################
 
@@ -482,7 +521,7 @@
       if running "$GERRIT_PID" ; then
         sleep 3
         if running "$GERRIT_PID" ; then
-          sleep 30
+          sleep $STOP_TIMEOUT
           if running "$GERRIT_PID" ; then
             start-stop-daemon -K -p "$GERRIT_PID" -s KILL
           fi
@@ -492,7 +531,7 @@
       echo OK
     else
       PID=`cat "$GERRIT_PID" 2>/dev/null`
-      TIMEOUT=30
+      TIMEOUT=$STOP_TIMEOUT
       while running "$GERRIT_PID" && test $TIMEOUT -gt 0 ; do
         kill $PID 2>/dev/null
         sleep 1
diff --git a/tools/bzl/license-map.py b/tools/bzl/license-map.py
index 555aa17..3e4fc92 100644
--- a/tools/bzl/license-map.py
+++ b/tools/bzl/license-map.py
@@ -24,7 +24,7 @@
 
 def read_file(filename):
     "Reads file and returns its content"
-    with open(filename) as fd:
+    with open(filename, encoding='utf-8') as fd:
         return fd.read()
 
 # List of files in package to which license is applied.
@@ -56,6 +56,8 @@
 LicenseMapItem = namedtuple("LicenseMapItem",
                             ["name", "safename", "packages", "license_text"])
 
+def print_utf8(str=""):
+  stdout.buffer.write((str + "\n").encode('utf-8'))
 
 def load_xmls(xml_filenames):
     """Load xml files produced by bazel query
@@ -134,7 +136,7 @@
 
     if args.asciidoctor:
         # We don't want any blank line before "= Gerrit Code Review - Licenses"
-        print("""= Gerrit Code Review - Licenses
+        print_utf8("""= Gerrit Code Review - Licenses
 
 // DO NOT EDIT - GENERATED AUTOMATICALLY.
 
@@ -178,10 +180,10 @@
     for data in xml_data + json_map_data:
         name = data.name
         safename = data.safename
-        print()
-        print("[[%s]]" % safename)
-        print(name)
-        print()
+        print_utf8()
+        print_utf8("[[%s]]" % safename)
+        print_utf8(name)
+        print_utf8()
         for p in data.packages:
             package_notice = ""
             if p.licensed_files.kind == "OnlySpecificFiles":
@@ -189,20 +191,20 @@
             elif p.licensed_files.kind == "AllFilesExceptSpecific":
                 package_notice = " - except the following file(s):"
 
-            print("* " + get_package_display_name(p) + package_notice)
+            print_utf8("* " + get_package_display_name(p) + package_notice)
             for file in p.licensed_files.files:
-                print("** " + file)
-        print()
-        print("[[%s_license]]" % safename)
-        print("----")
+                print_utf8("** " + file)
+        print_utf8()
+        print_utf8("[[%s_license]]" % safename)
+        print_utf8("----")
         license_text = data.license_text
-        print(data.license_text.rstrip("\r\n"))
-        print()
-        print("----")
-        print()
+        print_utf8(data.license_text.rstrip("\r\n"))
+        print_utf8()
+        print_utf8("----")
+        print_utf8()
 
     if args.asciidoctor:
-        print("""
+        print_utf8("""
 GERRIT
 ------
 Part of link:index.html[Gerrit Code Review]
@@ -219,7 +221,7 @@
     """
     result = []
     for json_map in json_filenames:
-        with open(json_map, 'r') as f:
+        with open(json_map, 'r', encoding='utf-8') as f:
             licenses_list = json.load(f)
         for license_id, license in licenses_list.items():
             name = license["licenseName"]