Merge "Merge branch 'stable-3.5'"
diff --git a/Documentation/config-labels.txt b/Documentation/config-labels.txt
index 55aa9bd..c62b626 100644
--- a/Documentation/config-labels.txt
+++ b/Documentation/config-labels.txt
@@ -304,25 +304,17 @@
 
 Matches votes that are equal to the minimal or maximal voting range. Or any votes.
 
-==== approverin:link:rest-api-groups.html#group-id[\{group-id\}]
+[[approverin]]
+==== approverin:link:#group-id[\{group-id\}]
 
 Matches votes granted by a user who is a member of
-link:rest-api-groups.html#group-id[\{group-id\}].
+link:#group-id[\{group-id\}].
 
-Avoid using a group name with spaces (if it has spaces, use the group uuid).
-Although supported for convenience, it's better to use group uuid than group
-name since using names only works as long as the names are unique (and future
-groups with the same name will break the query).
-
-==== uploaderin:link:rest-api-groups.html#group-id[\{group-id\}]
+[[uploaderin]]
+==== uploaderin:link:#group-id[\{group-id\}]
 
 Matches votes where the new patch set was uploaded by a member of
-link:rest-api-groups.html#group-id[\{group-id\}].
-
-Avoid using a group name with spaces (if it has spaces, use the group uuid).
-Although supported for convenience, it's better to use group uuid than group
-name since using names only works as long as the names are unique (and future
-groups with the same name will break the query).
+link:#group-id[\{group-id\}].
 
 ==== has:unchanged-files
 
@@ -330,6 +322,29 @@
 
 Only 'unchanged-files' is supported for 'has'.
 
+[[group-id]]
+==== Group ID
+
+Some predicates (link:#approverin[approverin], link:#uploaderin[uploaderin])
+expect a group ID as value. This group ID can be any of the
+link:rest-api-groups.html#group-id[group identifiers] that are supported in the
+REST API: group UUID, group ID (for Gerrit internal groups only) and group name
+
+It's preferred to reference groups by UUID, rather than name. Referencing
+groups by name is not recommended because:
+
+* Groups may be renamed and then the group reference can no longer be resolved.
+  If this happens another group with different members can take over the group
+  name, so that exemptions which have been granted by this predicate apply to
+  the other group. This is a security concern.
+* Group names that contain spaces are not supported.
+* Ambiguous group names cannot be resolved. This means if another group with
+  the same name gets created at a later point in time, the group name can no
+  longer be resolved and the predicate breaks.
+
+Using the group UUID has a small drawback though, since it makes the condition
+less human-readable.
+
 ==== Example
 
 ----
diff --git a/Documentation/config-project-config.txt b/Documentation/config-project-config.txt
index 0a6e945e..ea8f6d2 100644
--- a/Documentation/config-project-config.txt
+++ b/Documentation/config-project-config.txt
@@ -532,7 +532,7 @@
 A boolean indicating if reviewers and CCs that do not currently have a Gerrit
 account can be added to a change by providing their email address.
 +
-This setting only takes affect for changes that are readable by anonymous users.
+This setting only takes effect for changes that are readable by anonymous users.
 +
 Default is `INHERIT`, which means that this property is inherited from
 the parent project. If the property is not set in any parent project, the
diff --git a/Documentation/dev-processes.txt b/Documentation/dev-processes.txt
index e43e021..f0fe1e5 100644
--- a/Documentation/dev-processes.txt
+++ b/Documentation/dev-processes.txt
@@ -378,6 +378,12 @@
 that they are vetted long enough before they go into a release and we can be sure
 that the update doesn't introduce a regression.
 
+[[escalation-channel-to-google]]
+== Escalation channel to Google
+
+If anything urgent is blocking that requires the attention of a Googler you may
+escalate this by writing an email to Han-Wen Nienhuys: hanwen@google.com
+
 [[deprecating-features]]
 == Deprecating features
 
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item_test.ts b/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item_test.ts
index cd43151..43196a0 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item_test.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item_test.ts
@@ -26,6 +26,7 @@
   createSubmitRequirementExpressionInfo,
   createSubmitRequirementResultInfo,
   createNonApplicableSubmitRequirementResultInfo,
+  createServerInfo,
 } from '../../../test/test-data-generators';
 import {
   query,
@@ -485,6 +486,42 @@
     assert.equal(element.computeTruncatedRepoDisplay(), '…/test/repo');
   });
 
+  test('renders', async () => {
+    element.showStar = true;
+    element.showNumber = true;
+    element.account = createAccountWithId(1);
+    element.config = createServerInfo();
+    element.change = createChange();
+    await element.updateComplete;
+    expect(element).shadowDom.to.equal(`
+      <gr-change-star></gr-change-star>
+      <a href="">42</a>
+      <a href="" title="Test subject">
+        <div class="container">
+          <div class="content"> Test subject </div>
+          <div class="spacer"> Test subject </div>
+          <span></span>
+        </div>
+      </a>
+      <span class="placeholder"> -- </span>
+      <gr-account-link highlightattention=""></gr-account-link>
+      <div></div>
+      <span></span>
+      <a class="fullRepo" href=""> test-project </a>
+      <a class="truncatedRepo" href="" title="test-project"> test-project </a>
+      <a href=""> test-branch </a>
+      <gr-date-formatter withtooltip=""></gr-date-formatter>
+      <gr-date-formatter withtooltip=""></gr-date-formatter>
+      <gr-date-formatter forcerelative="" relativeoptionnoago="" withtooltip="">
+      </gr-date-formatter>
+      <gr-tooltip-content has-tooltip="" title="Size unknown">
+        <span class="placeholder"> -- </span>
+      </gr-tooltip-content>
+      <gr-change-list-column-requirements-summary>
+      </gr-change-list-column-requirements-summary>
+    `);
+  });
+
   test('renders requirement with new submit requirements', async () => {
     sinon.stub(getAppContext().flagsService, 'isEnabled').returns(true);
     const submitRequirement: SubmitRequirementResultInfo = {
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list_test.ts b/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list_test.ts
index c5861b7..88b1357 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list_test.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list_test.ts
@@ -43,6 +43,79 @@
     element = basicFixture.instantiate();
   });
 
+  test('renders', async () => {
+    element.preferences = {
+      legacycid_in_change_table: true,
+      time_format: TimeFormat.HHMM_12,
+      change_table: [],
+    };
+    element.account = {_account_id: 1001 as AccountId};
+    element.config = createServerInfo();
+    element.sections = [{results: new Array(1)}, {results: new Array(2)}];
+    element.selectedIndex = 0;
+    element.changes = [
+      {...createChange(), _number: 0 as NumericChangeId},
+      {...createChange(), _number: 1 as NumericChangeId},
+      {...createChange(), _number: 2 as NumericChangeId},
+    ];
+    await element.updateComplete;
+    expect(element).shadowDom.to.equal(`
+      <gr-change-list-item
+        aria-label="Test subject"
+        selected=""
+      >
+      </gr-change-list-item>
+      <gr-change-list-item aria-label="Test subject">
+      </gr-change-list-item>
+      <gr-change-list-item aria-label="Test subject">
+      </gr-change-list-item>
+      <table id="changeList">
+        <tbody class="groupContent">
+          <tr class="groupTitle">
+            <td
+              aria-hidden="true"
+              class="leftPadding"
+            >
+            </td>
+            <td
+              aria-label="Star status column"
+              class="star"
+              hidden=""
+            >
+            </td>
+            <td class="number">
+              #
+            </td>
+            <td class="subject">
+              Subject
+            </td>
+            <td class="status">
+              Status
+            </td>
+            <td class="owner">
+              Owner
+            </td>
+            <td class="reviewers">
+              Reviewers
+            </td>
+            <td class="repo">
+              Repo
+            </td>
+            <td class="branch">
+              Branch
+            </td>
+            <td class="updated">
+              Updated
+            </td>
+            <td class="size">
+              Size
+            </td>
+          </tr>
+        </tbody>
+      </table>
+    `);
+  });
+
   suite('test show change number not logged in', () => {
     setup(async () => {
       element = basicFixture.instantiate();
diff --git a/polygerrit-ui/app/elements/shared/gr-download-commands/gr-download-commands.ts b/polygerrit-ui/app/elements/shared/gr-download-commands/gr-download-commands.ts
index cfbb942..210041a 100644
--- a/polygerrit-ui/app/elements/shared/gr-download-commands/gr-download-commands.ts
+++ b/polygerrit-ui/app/elements/shared/gr-download-commands/gr-download-commands.ts
@@ -140,13 +140,12 @@
   }
 
   private renderDownloadTabs() {
-    if (this.schemes.length <= 1) return;
-
     const selectedIndex =
       this.schemes.findIndex(scheme => scheme === this.selectedScheme) || 0;
     return html`
       <paper-tabs
         id="downloadTabs"
+        class="${this.computeShowTabs()}"
         .selected=${selectedIndex}
         @selected-changed=${this.handleTabChange}
       >
@@ -205,6 +204,10 @@
       : '';
   }
 
+  private computeShowTabs() {
+    return this.schemes.length > 1 ? '' : 'hidden';
+  }
+
   // TODO: maybe unify with strToClassName from dom-util
   private computeClass(title: string) {
     // Only retain [a-z] chars, so "Cherry Pick" becomes "cherrypick".
diff --git a/polygerrit-ui/app/elements/shared/gr-download-commands/gr-download-commands_test.ts b/polygerrit-ui/app/elements/shared/gr-download-commands/gr-download-commands_test.ts
index eb9490f..0e8cf82 100644
--- a/polygerrit-ui/app/elements/shared/gr-download-commands/gr-download-commands_test.ts
+++ b/polygerrit-ui/app/elements/shared/gr-download-commands/gr-download-commands_test.ts
@@ -18,7 +18,12 @@
 import '../../../test/common-test-setup-karma';
 import './gr-download-commands';
 import {GrDownloadCommands} from './gr-download-commands';
-import {query, queryAndAssert, stubRestApi} from '../../../test/test-utils';
+import {
+  isHidden,
+  query,
+  queryAndAssert,
+  stubRestApi,
+} from '../../../test/test-utils';
 import {createPreferences} from '../../../test/test-data-generators';
 import * as MockInteractions from '@polymer/iron-test-helpers/mock-interactions';
 import {GrShellCommand} from '../gr-shell-command/gr-shell-command';
@@ -77,13 +82,17 @@
     });
 
     test('element visibility', async () => {
-      assert.isTrue(Boolean(query(element, 'paper-tabs')));
-      assert.isTrue(Boolean(query(element, '.commands')));
+      assert.isFalse(isHidden(queryAndAssert(element, 'paper-tabs')));
+      assert.isFalse(isHidden(queryAndAssert(element, '.commands')));
+      assert.isTrue(Boolean(query(element, '#downloadTabs')));
 
       element.schemes = [];
       await element.updateComplete;
-      assert.isFalse(Boolean(query(element, 'paper-tabs')));
+      assert.isTrue(isHidden(queryAndAssert(element, 'paper-tabs')));
       assert.isFalse(Boolean(query(element, '.commands')));
+      // Should still be present but hidden
+      assert.isTrue(Boolean(query(element, '#downloadTabs')));
+      assert.isTrue(isHidden(queryAndAssert(element, '#downloadTabs')));
     });
 
     test('tab selection', async () => {
diff --git a/polygerrit-ui/app/tsconfig.json b/polygerrit-ui/app/tsconfig.json
index ac26c6d..2e51f6e 100644
--- a/polygerrit-ui/app/tsconfig.json
+++ b/polygerrit-ui/app/tsconfig.json
@@ -42,6 +42,14 @@
 
     "allowUmdGlobalAccess": true,
 
+    /* We cannot just use the defaults, because of "webworker". */
+    "lib": [
+      "dom",
+      "dom.iterable",
+      "es2019",
+      "webworker"
+    ],
+
     /* typeRoots for IDE (see tsconfig_bazel.json for Bazel) */
     "typeRoots": [
       "node_modules/@types",
diff --git a/polygerrit-ui/app/utils/worker-util.ts b/polygerrit-ui/app/utils/worker-util.ts
index e4f5d2e..933744f 100644
--- a/polygerrit-ui/app/utils/worker-util.ts
+++ b/polygerrit-ui/app/utils/worker-util.ts
@@ -18,3 +18,7 @@
 export function createWorker(workerUrl: string): Worker {
   return new Worker(wrapUrl(workerUrl));
 }
+
+export function importScript(scope: WorkerGlobalScope, url: string): void {
+  scope.importScripts(url);
+}
diff --git a/polygerrit-ui/app/workers/syntax-worker.ts b/polygerrit-ui/app/workers/syntax-worker.ts
index 85a0fb4..1ddb9c6 100644
--- a/polygerrit-ui/app/workers/syntax-worker.ts
+++ b/polygerrit-ui/app/workers/syntax-worker.ts
@@ -11,6 +11,7 @@
   isInit,
 } from '../types/syntax-worker-api';
 import {highlightedStringToRanges} from '../utils/syntax-util';
+import {importScript} from '../utils/worker-util';
 
 // This is an entry point file of a bundle. Keep free of exports!
 
@@ -33,8 +34,7 @@
  * Once imported the HighlightJS lib exposes its functionality via the global
  * `hljs` variable.
  */
-type Context = Worker & {hljs?: HighlightJS};
-const ctx: Context = self as unknown as Context;
+const ctx = self as DedicatedWorkerGlobalScope & {hljs?: HighlightJS};
 
 /**
  * We are encapsulating the web worker API here, so this is the only place
@@ -49,7 +49,7 @@
       ctx.postMessage(result);
     }
     if (isRequest(message)) {
-      const ranges = worker.highlight(message.language, message.code);
+      const ranges = worker.highlightCode(message.language, message.code);
       const result: SyntaxWorkerResult = {ranges};
       ctx.postMessage(result);
     }
@@ -65,7 +65,7 @@
   private highlightJsLib?: HighlightJS;
 
   init(highlightJsLibUrl: string) {
-    importScripts(highlightJsLibUrl);
+    importScript(ctx, highlightJsLibUrl);
     if (!ctx.hljs) {
       throw new Error('HighlightJS lib not available after import');
     }
@@ -73,10 +73,10 @@
     this.highlightJsLib.configure({classPrefix: ''});
   }
 
-  highlight(language: string, code: string) {
+  highlightCode(language: string, code: string) {
     if (!this.highlightJsLib) throw new Error('worker not initialized');
-    const highlight = this.highlightJsLib.highlight(language, code, true);
-    return highlightedStringToRanges(highlight.value);
+    const highlighted = this.highlightJsLib.highlight(language, code, true);
+    return highlightedStringToRanges(highlighted.value);
   }
 }
 
diff --git a/tools/deps.bzl b/tools/deps.bzl
index 460ca55..62049e7 100644
--- a/tools/deps.bzl
+++ b/tools/deps.bzl
@@ -1,4 +1,5 @@
 load("//tools/bzl:maven_jar.bzl", "GERRIT", "maven_jar")
+load("@bazel_tools//tools/build_defs/repo:java.bzl", "java_import_external")
 
 CAFFEINE_VERS = "2.9.2"
 ANTLR_VERS = "3.5.2"
@@ -8,7 +9,7 @@
 GREENMAIL_VERS = "1.5.5"
 MAIL_VERS = "1.6.0"
 MIME4J_VERS = "0.8.1"
-OW2_VERS = "9.0"
+OW2_VERS = "9.2"
 AUTO_VALUE_VERSION = "1.7.4"
 AUTO_VALUE_GSON_VERSION = "1.3.1"
 PROLOG_VERS = "1.4.4"
@@ -80,10 +81,16 @@
         sha1 = "9feecc2b24d6bc9ff865af8d082f192238a293eb",
     )
 
-    maven_jar(
+    # TODO(davido): Switch to official release once available.
+    # Use custom release that fixed compatibility with JDK 17:
+    # https://github.com/google/gson/issues/1875
+    java_import_external(
         name = "gson",
-        artifact = "com.google.code.gson:gson:2.8.7",
-        sha1 = "69d9503ea0a40ee16f0bcdac7e3eaf83d0fa914a",
+        jar_sha256 = "d68e2a0f4b97143988f2ceef593947acc3f9d9e9618569c26264e63179887d49",
+        jar_urls = [
+            "https://github.com/davido/gson/releases/download/v2.9.0/gson-2.9.0.jar",
+        ],
+        licenses = ["unencumbered"],  # public domain
     )
 
     maven_jar(
@@ -412,31 +419,31 @@
     maven_jar(
         name = "ow2-asm",
         artifact = "org.ow2.asm:asm:" + OW2_VERS,
-        sha1 = "af582ff60bc567c42d931500c3fdc20e0141ddf9",
+        sha1 = "81a03f76019c67362299c40e0ba13405f5467bff",
     )
 
     maven_jar(
         name = "ow2-asm-analysis",
         artifact = "org.ow2.asm:asm-analysis:" + OW2_VERS,
-        sha1 = "4630afefbb43939c739445dde0af1a5729a0fb4e",
+        sha1 = "7487dd756daf96cab9986e44b9d7bcb796a61c10",
     )
 
     maven_jar(
         name = "ow2-asm-commons",
         artifact = "org.ow2.asm:asm-commons:" + OW2_VERS,
-        sha1 = "5a34a3a9ac44f362f35d1b27932380b0031a3334",
+        sha1 = "f4d7f0fc9054386f2893b602454d48e07d4fbead",
     )
 
     maven_jar(
         name = "ow2-asm-tree",
         artifact = "org.ow2.asm:asm-tree:" + OW2_VERS,
-        sha1 = "9df939f25c556b0c7efe00701d47e77a49837f24",
+        sha1 = "d96c99a30f5e1a19b0e609dbb19a44d8518ac01e",
     )
 
     maven_jar(
         name = "ow2-asm-util",
         artifact = "org.ow2.asm:asm-util:" + OW2_VERS,
-        sha1 = "7c059a94ab5eed3347bf954e27fab58e52968848",
+        sha1 = "fbc178fc5ba3dab50fd7e8a5317b8b647c8e8946",
     )
 
     maven_jar(