Merge branch 'stable-3.8'

* stable-3.8:
  Add in memory change data cache by project

Change-Id: I84a278b0d3f5bf081143a891009c8be15849ee7b
Release-Notes: skip
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index d91590e..7f25509 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -991,6 +991,13 @@
 The cache should be flushed whenever NoteDb change metadata in a repository is
 modified outside of Gerrit.
 
+cache `"changes_by_project"`::
++
+Ideally, the memorylimit of this cache is large enough to cover all projects.
+This should significantly speed up change ref advertisements and git pushes,
+especially for projects with lots of changes, and particularly on replicas
+where there is no index.
+
 cache `"git_modified_files"`::
 +
 Each item caches the list of git modified files between two git trees
diff --git a/java/com/google/gerrit/httpd/init/WebAppInitializer.java b/java/com/google/gerrit/httpd/init/WebAppInitializer.java
index 3556593..e1abcb1 100644
--- a/java/com/google/gerrit/httpd/init/WebAppInitializer.java
+++ b/java/com/google/gerrit/httpd/init/WebAppInitializer.java
@@ -78,9 +78,9 @@
 import com.google.gerrit.server.config.SysExecutorModule;
 import com.google.gerrit.server.events.EventBroker.EventBrokerModule;
 import com.google.gerrit.server.events.StreamEventsApiListener.StreamEventsApiListenerModule;
+import com.google.gerrit.server.git.ChangesByProjectCache;
 import com.google.gerrit.server.git.GarbageCollectionModule;
 import com.google.gerrit.server.git.GitRepositoryManagerModule;
-import com.google.gerrit.server.git.SearchingChangeCacheImpl.SearchingChangeCacheImplModule;
 import com.google.gerrit.server.git.SystemReaderInstaller;
 import com.google.gerrit.server.git.WorkQueue.WorkQueueModule;
 import com.google.gerrit.server.index.IndexModule;
@@ -317,7 +317,7 @@
     modules.add(new ProjectQueryBuilderModule());
     modules.add(new DefaultRefLogIdentityProvider.Module());
     modules.add(new PluginApiModule());
-    modules.add(new SearchingChangeCacheImplModule());
+    modules.add(new ChangesByProjectCache.Module(ChangesByProjectCache.UseIndex.TRUE, config));
     modules.add(new InternalAccountDirectoryModule());
     modules.add(new DefaultPermissionBackendModule());
     modules.add(new DefaultMemoryCacheModule());
diff --git a/java/com/google/gerrit/pgm/Daemon.java b/java/com/google/gerrit/pgm/Daemon.java
index ba518b7..d56955f 100644
--- a/java/com/google/gerrit/pgm/Daemon.java
+++ b/java/com/google/gerrit/pgm/Daemon.java
@@ -89,8 +89,8 @@
 import com.google.gerrit.server.config.SysExecutorModule;
 import com.google.gerrit.server.events.EventBroker.EventBrokerModule;
 import com.google.gerrit.server.events.StreamEventsApiListener.StreamEventsApiListenerModule;
+import com.google.gerrit.server.git.ChangesByProjectCache;
 import com.google.gerrit.server.git.GarbageCollectionModule;
-import com.google.gerrit.server.git.SearchingChangeCacheImpl.SearchingChangeCacheImplModule;
 import com.google.gerrit.server.git.WorkQueue.WorkQueueModule;
 import com.google.gerrit.server.group.PeriodicGroupIndexer.PeriodicGroupIndexerModule;
 import com.google.gerrit.server.index.AbstractIndexModule;
@@ -476,7 +476,10 @@
     modules.add(new DefaultRefLogIdentityProvider.Module());
     modules.add(new PluginApiModule());
 
-    modules.add(new SearchingChangeCacheImplModule(replica));
+    modules.add(
+        new ChangesByProjectCache.Module(
+            replica ? ChangesByProjectCache.UseIndex.FALSE : ChangesByProjectCache.UseIndex.TRUE,
+            config));
     modules.add(new InternalAccountDirectoryModule());
     modules.add(new DefaultPermissionBackendModule());
     modules.add(new DefaultMemoryCacheModule());
diff --git a/java/com/google/gerrit/pgm/util/BatchProgramModule.java b/java/com/google/gerrit/pgm/util/BatchProgramModule.java
index f42de84..82d1dda 100644
--- a/java/com/google/gerrit/pgm/util/BatchProgramModule.java
+++ b/java/com/google/gerrit/pgm/util/BatchProgramModule.java
@@ -69,8 +69,8 @@
 import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
 import com.google.gerrit.server.extensions.events.RevisionCreated;
 import com.google.gerrit.server.extensions.events.WorkInProgressStateChanged;
+import com.google.gerrit.server.git.ChangesByProjectCache;
 import com.google.gerrit.server.git.PureRevertCache;
-import com.google.gerrit.server.git.SearchingChangeCacheImpl;
 import com.google.gerrit.server.git.TagCache;
 import com.google.gerrit.server.notedb.ChangeDraftNotesUpdate;
 import com.google.gerrit.server.notedb.NoteDbModule;
@@ -164,10 +164,6 @@
     factory(PatchSetInserter.Factory.class);
     factory(RebaseChangeOp.Factory.class);
 
-    // As Reindex is a batch program, don't assume the index is available for
-    // the change cache.
-    bind(SearchingChangeCacheImpl.class).toProvider(Providers.of(null));
-
     bind(new TypeLiteral<ImmutableSet<GroupReference>>() {})
         .annotatedWith(AdministrateServerGroups.class)
         .toInstance(ImmutableSet.of());
@@ -179,6 +175,8 @@
         .toInstance(Collections.emptySet());
 
     modules.add(new BatchGitModule());
+    modules.add(
+        new ChangesByProjectCache.Module(ChangesByProjectCache.UseIndex.FALSE, getConfig()));
     modules.add(new DefaultPermissionBackendModule());
     modules.add(new DefaultMemoryCacheModule());
     modules.add(new H2CacheModule());
diff --git a/java/com/google/gerrit/server/git/ChangesByProjectCache.java b/java/com/google/gerrit/server/git/ChangesByProjectCache.java
new file mode 100644
index 0000000..df91891
--- /dev/null
+++ b/java/com/google/gerrit/server/git/ChangesByProjectCache.java
@@ -0,0 +1,63 @@
+// Copyright (C) 2023 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.
+
+package com.google.gerrit.server.git;
+
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.gerrit.server.query.change.ChangeData;
+import com.google.inject.AbstractModule;
+import java.io.IOException;
+import java.util.stream.Stream;
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.Repository;
+
+public interface ChangesByProjectCache {
+  public enum UseIndex {
+    TRUE,
+    FALSE;
+  }
+
+  public static class Module extends AbstractModule {
+    private UseIndex useIndex;
+    private @GerritServerConfig Config config;
+
+    public Module(UseIndex useIndex, @GerritServerConfig Config config) {
+      this.useIndex = useIndex;
+      this.config = config;
+    }
+
+    @Override
+    protected void configure() {
+      boolean searchingCacheEnabled =
+          config.getLong("cache", SearchingChangeCacheImpl.ID_CACHE, "memoryLimit", 0) > 0;
+      if (searchingCacheEnabled && UseIndex.TRUE.equals(useIndex)) {
+        install(new SearchingChangeCacheImpl.SearchingChangeCacheImplModule());
+      } else {
+        bind(UseIndex.class).toInstance(useIndex);
+        install(new ChangesByProjectCacheImpl.Module());
+      }
+    }
+  }
+
+  /**
+   * Stream changeDatas for the project
+   *
+   * @param project project to read.
+   * @param repository repository for the project to read.
+   * @return Stream of known changes; empty if no changes.
+   */
+  Stream<ChangeData> streamChangeDatas(Project.NameKey project, Repository repository)
+      throws IOException;
+}
diff --git a/java/com/google/gerrit/server/git/ChangesByProjectCacheImpl.java b/java/com/google/gerrit/server/git/ChangesByProjectCacheImpl.java
new file mode 100644
index 0000000..094287b
--- /dev/null
+++ b/java/com/google/gerrit/server/git/ChangesByProjectCacheImpl.java
@@ -0,0 +1,360 @@
+// Copyright (C) 2023 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.
+
+package com.google.gerrit.server.git;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.cache.Cache;
+import com.google.common.cache.Weigher;
+import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.common.Nullable;
+import com.google.gerrit.entities.BranchNameKey;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.server.ReviewerSet;
+import com.google.gerrit.server.cache.CacheModule;
+import com.google.gerrit.server.git.ChangesByProjectCache.UseIndex;
+import com.google.gerrit.server.index.change.ChangeField;
+import com.google.gerrit.server.logging.Metadata;
+import com.google.gerrit.server.logging.TraceContext;
+import com.google.gerrit.server.logging.TraceContext.TraceTimer;
+import com.google.gerrit.server.notedb.ChangeNotes;
+import com.google.gerrit.server.query.change.ChangeData;
+import com.google.gerrit.server.query.change.InternalChangeQuery;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+import com.google.inject.name.Named;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutionException;
+import java.util.stream.Stream;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Repository;
+
+/**
+ * Lightweight cache of changes in each project.
+ *
+ * <p>This cache is intended to be used when filtering references and stores only the minimal fields
+ * required for a read permission check.
+ */
+@Singleton
+public class ChangesByProjectCacheImpl implements ChangesByProjectCache {
+  private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+
+  private static final String CACHE_NAME = "changes_by_project";
+
+  public static class Module extends CacheModule {
+    @Override
+    protected void configure() {
+      cache(CACHE_NAME, Project.NameKey.class, CachedProjectChanges.class)
+          .weigher(ChangesByProjetCacheWeigher.class);
+      bind(ChangesByProjectCache.class).to(ChangesByProjectCacheImpl.class);
+    }
+  }
+
+  private final Cache<Project.NameKey, CachedProjectChanges> cache;
+  private final ChangeData.Factory cdFactory;
+  private final UseIndex useIndex;
+  private final Provider<InternalChangeQuery> queryProvider;
+
+  @Inject
+  ChangesByProjectCacheImpl(
+      @Named(CACHE_NAME) Cache<Project.NameKey, CachedProjectChanges> cache,
+      ChangeData.Factory cdFactory,
+      UseIndex useIndex,
+      Provider<InternalChangeQuery> queryProvider) {
+    this.cache = cache;
+    this.cdFactory = cdFactory;
+    this.useIndex = useIndex;
+    this.queryProvider = queryProvider;
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  public Stream<ChangeData> streamChangeDatas(Project.NameKey project, Repository repo)
+      throws IOException {
+    CachedProjectChanges projectChanges = cache.getIfPresent(project);
+    if (projectChanges != null) {
+      return projectChanges
+          .getUpdatedChangeDatas(
+              project, repo, cdFactory, ChangeNotes.Factory.scanChangeIds(repo), "Updating")
+          .stream();
+    }
+    if (UseIndex.TRUE.equals(useIndex)) {
+      return queryChangeDatasAndLoad(project).stream();
+    }
+    return scanChangeDatasAndLoad(project, repo).stream();
+  }
+
+  private Collection<ChangeData> scanChangeDatasAndLoad(Project.NameKey project, Repository repo)
+      throws IOException {
+    CachedProjectChanges ours = new CachedProjectChanges();
+    CachedProjectChanges projectChanges = ours;
+    try {
+      projectChanges = cache.get(project, () -> ours);
+    } catch (ExecutionException e) {
+      logger.atWarning().withCause(e).log("Cannot load %s for %s", CACHE_NAME, project.get());
+    }
+    return projectChanges.getUpdatedChangeDatas(
+        project,
+        repo,
+        cdFactory,
+        ChangeNotes.Factory.scanChangeIds(repo),
+        ours == projectChanges ? "Scanning" : "Updating");
+  }
+
+  private Collection<ChangeData> queryChangeDatasAndLoad(Project.NameKey project) {
+    Collection<ChangeData> cds = queryChangeDatas(project);
+    cache.put(project, new CachedProjectChanges(cds));
+    return cds;
+  }
+
+  private Collection<ChangeData> queryChangeDatas(Project.NameKey project) {
+    try (TraceTimer timer =
+        TraceContext.newTimer(
+            "Querying changes of project", Metadata.builder().projectName(project.get()).build())) {
+      return queryProvider
+          .get()
+          .setRequestedFields(
+              ChangeField.CHANGE_SPEC, ChangeField.REVIEWER_SPEC, ChangeField.REF_STATE_SPEC)
+          .byProject(project);
+    }
+  }
+
+  private static class CachedProjectChanges {
+    Map<String, Map<Change.Id, ObjectId>> metaObjectIdByNonPrivateChangeByBranch =
+        new ConcurrentHashMap<>(); // BranchNameKey "normalized" to a String to dedup project
+    Map<Change.Id, PrivateChange> privateChangeById = new ConcurrentHashMap<>();
+
+    public CachedProjectChanges() {}
+
+    public CachedProjectChanges(Collection<ChangeData> cds) {
+      cds.stream().forEach(cd -> insert(cd));
+    }
+
+    public Collection<ChangeData> getUpdatedChangeDatas(
+        Project.NameKey project,
+        Repository repo,
+        ChangeData.Factory cdFactory,
+        Map<Change.Id, ObjectId> metaObjectIdByChange,
+        String operation) {
+      try (TraceTimer timer =
+          TraceContext.newTimer(
+              operation + " changes of project",
+              Metadata.builder().projectName(project.get()).build())) {
+        Map<Change.Id, ChangeData> cachedCdByChange = getChangeDataByChange(project, cdFactory);
+        List<ChangeData> cds = new ArrayList<>();
+        for (Map.Entry<Change.Id, ObjectId> e : metaObjectIdByChange.entrySet()) {
+          Change.Id id = e.getKey();
+          ChangeData cached = cachedCdByChange.get(id);
+          ChangeData cd = cached;
+          try {
+            if (cd == null || !cached.metaRevisionOrThrow().equals(e.getValue())) {
+              cd = cdFactory.create(project, id);
+              update(cached, cd);
+            }
+          } catch (Exception ex) {
+            // Do not let a bad change prevent other changes from being available.
+            logger.atFinest().withCause(ex).log("Can't load changeData for %s", id);
+          }
+          cds.add(cd);
+        }
+        return cds;
+      }
+    }
+
+    public CachedProjectChanges update(ChangeData old, ChangeData updated) {
+      if (old != null) {
+        if (old.isPrivateOrThrow()) {
+          privateChangeById.remove(old.getId());
+        } else {
+          Map<Change.Id, ObjectId> metaObjectIdByNonPrivateChange =
+              metaObjectIdByNonPrivateChangeByBranch.get(old.branchOrThrow().branch());
+          if (metaObjectIdByNonPrivateChange != null) {
+            metaObjectIdByNonPrivateChange.remove(old.getId());
+          }
+        }
+      }
+      return insert(updated);
+    }
+
+    public CachedProjectChanges insert(ChangeData cd) {
+      if (cd.isPrivateOrThrow()) {
+        privateChangeById.put(
+            cd.getId(),
+            new AutoValue_ChangesByProjectCacheImpl_PrivateChange(
+                cd.change(), cd.reviewers(), cd.metaRevisionOrThrow()));
+      } else {
+        metaObjectIdByNonPrivateChangeByBranch
+            .computeIfAbsent(cd.branchOrThrow().branch(), b -> new ConcurrentHashMap<>())
+            .put(cd.getId(), cd.metaRevisionOrThrow());
+      }
+      return this;
+    }
+
+    public Map<Change.Id, ChangeData> getChangeDataByChange(
+        Project.NameKey project, ChangeData.Factory cdFactory) {
+      Map<Change.Id, ChangeData> cdByChange = new HashMap<>(privateChangeById.size());
+      for (PrivateChange pc : privateChangeById.values()) {
+        try {
+          ChangeData cd = cdFactory.create(pc.change());
+          cd.setReviewers(pc.reviewers());
+          cd.setMetaRevision(pc.metaRevision());
+          cdByChange.put(cd.getId(), cd);
+        } catch (Exception ex) {
+          // Do not let a bad change prevent other changes from being available.
+          logger.atFinest().withCause(ex).log("Can't load changeData for %s", pc.change().getId());
+        }
+      }
+
+      for (Map.Entry<String, Map<Change.Id, ObjectId>> e :
+          metaObjectIdByNonPrivateChangeByBranch.entrySet()) {
+        BranchNameKey branch = BranchNameKey.create(project, e.getKey());
+        for (Map.Entry<Change.Id, ObjectId> e2 : e.getValue().entrySet()) {
+          Change.Id id = e2.getKey();
+          try {
+            cdByChange.put(id, cdFactory.createNonPrivate(branch, id, e2.getValue()));
+          } catch (Exception ex) {
+            // Do not let a bad change prevent other changes from being available.
+            logger.atFinest().withCause(ex).log("Can't load changeData for %s", id);
+          }
+        }
+      }
+      return cdByChange;
+    }
+
+    public int weigh() {
+      int size = 0;
+      size += 24 * 2; // guess at basic ConcurrentHashMap overhead * 2
+      for (Map.Entry<String, Map<Change.Id, ObjectId>> e :
+          metaObjectIdByNonPrivateChangeByBranch.entrySet()) {
+        size += JavaWeights.REFERENCE + e.getKey().length();
+        size +=
+            e.getValue().size()
+                * (JavaWeights.REFERENCE
+                    + JavaWeights.OBJECT // Map.Entry
+                    + JavaWeights.REFERENCE
+                    + GerritWeights.CHANGE_NUM
+                    + JavaWeights.REFERENCE
+                    + GerritWeights.OBJECTID);
+      }
+      for (Map.Entry<Change.Id, PrivateChange> e : privateChangeById.entrySet()) {
+        size += JavaWeights.REFERENCE + GerritWeights.CHANGE_NUM;
+        size += JavaWeights.REFERENCE + e.getValue().weigh();
+      }
+      return size;
+    }
+  }
+
+  @AutoValue
+  abstract static class PrivateChange {
+    // Fields needed to serve permission checks on private Changes
+    abstract Change change();
+
+    @Nullable
+    abstract ReviewerSet reviewers();
+
+    abstract ObjectId metaRevision(); // Needed to confirm whether up-to-date
+
+    public int weigh() {
+      int size = 0;
+      size += JavaWeights.OBJECT; // this
+      size += JavaWeights.REFERENCE + weigh(change());
+      size += JavaWeights.REFERENCE + weigh(reviewers());
+      size += JavaWeights.REFERENCE + GerritWeights.OBJECTID; // metaRevision
+      return size;
+    }
+
+    private static int weigh(Change c) {
+      int size = 0;
+      size += JavaWeights.OBJECT; // change
+      size += JavaWeights.REFERENCE + GerritWeights.KEY_INT; // changeId
+      size += JavaWeights.REFERENCE + JavaWeights.OBJECT + 40; // changeKey;
+      size += JavaWeights.REFERENCE + GerritWeights.TIMESTAMP; // createdOn;
+      size += JavaWeights.REFERENCE + GerritWeights.TIMESTAMP; // lastUpdatedOn;
+      size += JavaWeights.REFERENCE + GerritWeights.ACCOUNT_ID; // owner;
+      size +=
+          JavaWeights.REFERENCE
+              + c.getDest().project().get().length()
+              + c.getDest().branch().length();
+      size += JavaWeights.CHAR; // status;
+      size += JavaWeights.INT; // currentPatchSetId;
+      size += JavaWeights.REFERENCE + c.getSubject().length();
+      size += JavaWeights.REFERENCE + (c.getTopic() == null ? 0 : c.getTopic().length());
+      size +=
+          JavaWeights.REFERENCE
+              + (c.getOriginalSubject().equals(c.getSubject()) ? 0 : c.getSubject().length());
+      size +=
+          JavaWeights.REFERENCE + (c.getSubmissionId() == null ? 0 : c.getSubmissionId().length());
+      size += JavaWeights.REFERENCE + GerritWeights.ACCOUNT_ID; // assignee;
+      size += JavaWeights.REFERENCE + JavaWeights.BOOLEAN; // isPrivate;
+      size += JavaWeights.REFERENCE + JavaWeights.BOOLEAN; // workInProgress;
+      size += JavaWeights.REFERENCE + JavaWeights.BOOLEAN; // reviewStarted;
+      size += JavaWeights.REFERENCE + GerritWeights.CHANGE_NUM; // revertOf;
+      size += JavaWeights.REFERENCE + GerritWeights.PACTCH_SET_ID; // cherryPickOf;
+      return size;
+    }
+
+    private static int weigh(ReviewerSet rs) {
+      int size = 0;
+      size += JavaWeights.OBJECT; // ReviewerSet
+      size += JavaWeights.REFERENCE; // table
+      size +=
+          rs.asTable().cellSet().size()
+              * (JavaWeights.OBJECT // cell (at least one object)
+                  + JavaWeights.REFERENCE // ReviewerStateInternal
+                  + (JavaWeights.REFERENCE + GerritWeights.ACCOUNT_ID)
+                  + (JavaWeights.REFERENCE + GerritWeights.TIMESTAMP));
+      size += JavaWeights.REFERENCE; // accounts
+      return size;
+    }
+  }
+
+  private static class ChangesByProjetCacheWeigher
+      implements Weigher<Project.NameKey, CachedProjectChanges> {
+    @Override
+    public int weigh(Project.NameKey project, CachedProjectChanges changes) {
+      int size = 0;
+      size += project.get().length();
+      size += changes.weigh();
+      return size;
+    }
+  }
+
+  private static class GerritWeights {
+    public static final int KEY_INT = JavaWeights.OBJECT + JavaWeights.INT; // IntKey
+    public static final int CHANGE_NUM = KEY_INT;
+    public static final int ACCOUNT_ID = KEY_INT;
+    public static final int PACTCH_SET_ID =
+        JavaWeights.OBJECT
+            + (JavaWeights.REFERENCE + GerritWeights.CHANGE_NUM) // PatchSet.Id.changeId
+            + JavaWeights.INT; // PatchSet.Id patch_num;
+    public static final int TIMESTAMP = JavaWeights.OBJECT + 8; // Timestamp
+    public static final int OBJECTID = JavaWeights.OBJECT + (5 * JavaWeights.INT); // (w1-w5)
+  }
+
+  private static class JavaWeights {
+    public static final int BOOLEAN = 1;
+    public static final int CHAR = 1;
+    public static final int INT = 4;
+    public static final int OBJECT = 16;
+    public static final int REFERENCE = 8;
+  }
+}
diff --git a/java/com/google/gerrit/server/git/SearchingChangeCacheImpl.java b/java/com/google/gerrit/server/git/SearchingChangeCacheImpl.java
index cfeec70..83024e3 100644
--- a/java/com/google/gerrit/server/git/SearchingChangeCacheImpl.java
+++ b/java/com/google/gerrit/server/git/SearchingChangeCacheImpl.java
@@ -40,12 +40,12 @@
 import com.google.inject.Singleton;
 import com.google.inject.TypeLiteral;
 import com.google.inject.name.Named;
-import com.google.inject.util.Providers;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.ExecutionException;
 import java.util.stream.Stream;
+import org.eclipse.jgit.lib.Repository;
 
 /**
  * Cache based on an index query of the most recent changes. The number of cached items depends on
@@ -55,35 +55,22 @@
  * fraction of all changes. These are the changes that were modified last.
  */
 @Singleton
-public class SearchingChangeCacheImpl implements GitReferenceUpdatedListener {
+public class SearchingChangeCacheImpl
+    implements ChangesByProjectCache, GitReferenceUpdatedListener {
   private static final FluentLogger logger = FluentLogger.forEnclosingClass();
 
   static final String ID_CACHE = "changes";
 
   public static class SearchingChangeCacheImplModule extends CacheModule {
-    private final boolean slave;
-
-    public SearchingChangeCacheImplModule() {
-      this(false);
-    }
-
-    public SearchingChangeCacheImplModule(boolean slave) {
-      this.slave = slave;
-    }
-
     @Override
     protected void configure() {
-      if (slave) {
-        bind(SearchingChangeCacheImpl.class).toProvider(Providers.of(null));
-      } else {
-        cache(ID_CACHE, Project.NameKey.class, new TypeLiteral<List<CachedChange>>() {})
-            .maximumWeight(0)
-            .loader(Loader.class);
+      cache(ID_CACHE, Project.NameKey.class, new TypeLiteral<List<CachedChange>>() {})
+          .maximumWeight(0)
+          .loader(Loader.class);
 
-        bind(SearchingChangeCacheImpl.class);
-        DynamicSet.bind(binder(), GitReferenceUpdatedListener.class)
-            .to(SearchingChangeCacheImpl.class);
-      }
+      bind(ChangesByProjectCache.class).to(SearchingChangeCacheImpl.class);
+      DynamicSet.bind(binder(), GitReferenceUpdatedListener.class)
+          .to(SearchingChangeCacheImpl.class);
     }
   }
 
@@ -117,9 +104,11 @@
    * Additional stored fields are not loaded from the index.
    *
    * @param project project to read.
+   * @param unusedrepo repository for the project to read.
    * @return stream of known changes; empty if no changes.
    */
-  public Stream<ChangeData> getChangeData(Project.NameKey project) {
+  @Override
+  public Stream<ChangeData> streamChangeDatas(Project.NameKey project, Repository unusedrepo) {
     List<CachedChange> cached;
     try {
       cached = cache.get(project);
diff --git a/java/com/google/gerrit/server/notedb/ChangeNotes.java b/java/com/google/gerrit/server/notedb/ChangeNotes.java
index f11e043..0d7c841 100644
--- a/java/com/google/gerrit/server/notedb/ChangeNotes.java
+++ b/java/com/google/gerrit/server/notedb/ChangeNotes.java
@@ -166,6 +166,12 @@
       return new ChangeNotes(args, newChange(project, changeId), true, null).load();
     }
 
+    public ChangeNotes create(
+        Project.NameKey project, Change.Id changeId, @Nullable ObjectId metaRevId) {
+      checkArgument(project != null, "project is required");
+      return new ChangeNotes(args, newChange(project, changeId), true, null, metaRevId).load();
+    }
+
     public ChangeNotes create(Repository repository, Project.NameKey project, Change.Id changeId) {
       checkArgument(project != null, "project is required");
       return new ChangeNotes(args, newChange(project, changeId), true, null).load(repository);
diff --git a/java/com/google/gerrit/server/permissions/ChangeControl.java b/java/com/google/gerrit/server/permissions/ChangeControl.java
index 3d35321..664ffa2 100644
--- a/java/com/google/gerrit/server/permissions/ChangeControl.java
+++ b/java/com/google/gerrit/server/permissions/ChangeControl.java
@@ -66,7 +66,7 @@
 
   /** Can this user see this change? */
   boolean isVisible() {
-    if (getChange().isPrivate() && !isPrivateVisible(changeData)) {
+    if (changeData.isPrivateOrThrow() && !isPrivateVisible(changeData)) {
       return false;
     }
     // Does the user have READ permission on the destination?
diff --git a/java/com/google/gerrit/server/permissions/DefaultRefFilter.java b/java/com/google/gerrit/server/permissions/DefaultRefFilter.java
index a706951..f179045 100644
--- a/java/com/google/gerrit/server/permissions/DefaultRefFilter.java
+++ b/java/com/google/gerrit/server/permissions/DefaultRefFilter.java
@@ -27,7 +27,6 @@
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.flogger.FluentLogger;
-import com.google.gerrit.common.Nullable;
 import com.google.gerrit.entities.BranchNameKey;
 import com.google.gerrit.entities.Change;
 import com.google.gerrit.entities.RefNames;
@@ -36,12 +35,11 @@
 import com.google.gerrit.metrics.MetricMaker;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.config.GerritServerConfig;
-import com.google.gerrit.server.git.SearchingChangeCacheImpl;
+import com.google.gerrit.server.git.ChangesByProjectCache;
 import com.google.gerrit.server.git.TagCache;
 import com.google.gerrit.server.git.TagMatcher;
 import com.google.gerrit.server.logging.TraceContext;
 import com.google.gerrit.server.logging.TraceContext.TraceTimer;
-import com.google.gerrit.server.notedb.ChangeNotes;
 import com.google.gerrit.server.permissions.PermissionBackend.RefFilterOptions;
 import com.google.gerrit.server.project.ProjectState;
 import com.google.gerrit.server.query.change.ChangeData;
@@ -94,9 +92,8 @@
   private final CurrentUser user;
   private final ProjectState projectState;
   private final PermissionBackend.ForProject permissionBackendForProject;
-  private final @Nullable SearchingChangeCacheImpl searchingChangeDataProvider;
+  private final ChangesByProjectCache changesByProjectCache;
   private final ChangeData.Factory changeDataFactory;
-  private final ChangeNotes.Factory changeNotesFactory;
   private final Metrics metrics;
   private final boolean skipFullRefEvaluationIfAllRefsAreVisible;
 
@@ -107,16 +104,14 @@
       RefVisibilityControl refVisibilityControl,
       @GerritServerConfig Config config,
       Metrics metrics,
-      @Nullable SearchingChangeCacheImpl searchingChangeDataProvider,
+      ChangesByProjectCache changesByProjectCache,
       ChangeData.Factory changeDataFactory,
-      ChangeNotes.Factory changeNotesFactory,
       @Assisted ProjectControl projectControl) {
     this.tagCache = tagCache;
     this.permissionBackend = permissionBackend;
     this.refVisibilityControl = refVisibilityControl;
-    this.searchingChangeDataProvider = searchingChangeDataProvider;
+    this.changesByProjectCache = changesByProjectCache;
     this.changeDataFactory = changeDataFactory;
-    this.changeNotesFactory = changeNotesFactory;
     this.skipFullRefEvaluationIfAllRefsAreVisible =
         config.getBoolean("auth", "skipFullRefEvaluationIfAllRefsAreVisible", true);
     this.projectControl = projectControl;
@@ -150,8 +145,7 @@
         Suppliers.memoize(
             () ->
                 GitVisibleChangeFilter.getVisibleChanges(
-                    searchingChangeDataProvider,
-                    changeNotesFactory,
+                    changesByProjectCache,
                     changeDataFactory,
                     projectState.getNameKey(),
                     permissionBackendForProject,
diff --git a/java/com/google/gerrit/server/permissions/GitVisibleChangeFilter.java b/java/com/google/gerrit/server/permissions/GitVisibleChangeFilter.java
index ae72b06..640ea9a 100644
--- a/java/com/google/gerrit/server/permissions/GitVisibleChangeFilter.java
+++ b/java/com/google/gerrit/server/permissions/GitVisibleChangeFilter.java
@@ -17,11 +17,9 @@
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.flogger.FluentLogger;
-import com.google.gerrit.common.Nullable;
 import com.google.gerrit.entities.Change;
 import com.google.gerrit.entities.Project;
-import com.google.gerrit.server.git.SearchingChangeCacheImpl;
-import com.google.gerrit.server.notedb.ChangeNotes;
+import com.google.gerrit.server.git.ChangesByProjectCache;
 import com.google.gerrit.server.query.change.ChangeData;
 import java.io.IOException;
 import java.util.HashMap;
@@ -39,11 +37,7 @@
  *
  * <ul>
  *   <li>For a low number of expected checks, we check visibility one-by-one.
- *   <li>For a high number of expected checks and settings where the change index is available, we
- *       load the N most recent changes from the index and filter them by visibility. This is fast,
- *       but comes with the caveat that older changes are pretended to be invisible.
- *   <li>For a high number of expected checks and settings where the change index is unavailable, we
- *       scan the repo and determine visibility one-by-one. This is *very* expensive.
+ *   <li>For a high number of expected checks we use the ChangesByProjectCache.
  * </ul>
  *
  * <p>Changes that fail to load are pretended to be invisible. This is important on the Git paths as
@@ -60,24 +54,23 @@
 
   /** Returns a map of all visible changes. Might pretend old changes are invisible. */
   static ImmutableMap<Change.Id, ChangeData> getVisibleChanges(
-      @Nullable SearchingChangeCacheImpl searchingChangeCache,
-      ChangeNotes.Factory changeNotesFactory,
+      ChangesByProjectCache changesByProjectCache,
       ChangeData.Factory changeDataFactory,
       Project.NameKey projectName,
       PermissionBackend.ForProject forProject,
       Repository repository,
       ImmutableSet<Change.Id> changes) {
-    Stream<ChangeData> changeDatas;
+    Stream<ChangeData> changeDatas = Stream.empty();
     if (changes.size() < CHANGE_LIMIT_FOR_DIRECT_FILTERING) {
       logger.atFine().log("Loading changes one by one for project %s", projectName);
       changeDatas = loadChangeDatasOneByOne(changes, changeDataFactory, projectName);
-    } else if (searchingChangeCache != null) {
-      logger.atFine().log("Loading changes from SearchingChangeCache for project %s", projectName);
-      changeDatas = searchingChangeCache.getChangeData(projectName);
     } else {
-      logger.atFine().log("Loading changes from all refs for project %s", projectName);
-      changeDatas =
-          scanRepoForChangeDatas(changeNotesFactory, changeDataFactory, repository, projectName);
+      logger.atFine().log("Loading changes from ChangesByProjectCache for project %s", projectName);
+      try {
+        changeDatas = changesByProjectCache.streamChangeDatas(projectName, repository);
+      } catch (IOException e) {
+        logger.atWarning().withCause(e).log("Unable to streamChangeDatas for %s", projectName);
+      }
     }
     HashMap<Change.Id, ChangeData> result = new HashMap<>();
     changeDatas
@@ -127,32 +120,4 @@
             })
         .filter(Objects::nonNull);
   }
-
-  /** Get a stream of all changes by scanning the repo. This is extremely slow. */
-  private static Stream<ChangeData> scanRepoForChangeDatas(
-      ChangeNotes.Factory changeNotesFactory,
-      ChangeData.Factory changeDataFactory,
-      Repository repository,
-      Project.NameKey projectName) {
-    Stream<ChangeData> cds;
-    try {
-      cds =
-          changeNotesFactory
-              .scan(repository, projectName)
-              .map(
-                  notesResult -> {
-                    if (!notesResult.error().isPresent()) {
-                      return changeDataFactory.create(notesResult.notes());
-                    }
-                    logger.atWarning().withCause(notesResult.error().get()).log(
-                        "Unable to load ChangeNotes for %s", notesResult.id());
-                    return null;
-                  })
-              .filter(Objects::nonNull);
-    } catch (IOException e) {
-      logger.atWarning().withCause(e).log("Unable to scanChangeIds for %s", projectName);
-      return Stream.empty();
-    }
-    return cds;
-  }
 }
diff --git a/java/com/google/gerrit/server/permissions/PermissionBackend.java b/java/com/google/gerrit/server/permissions/PermissionBackend.java
index eb5e053..ac9ac98 100644
--- a/java/com/google/gerrit/server/permissions/PermissionBackend.java
+++ b/java/com/google/gerrit/server/permissions/PermissionBackend.java
@@ -274,7 +274,7 @@
     /** Returns an instance scoped for the change, and its destination ref and project. */
     public ForChange change(ChangeData cd) {
       try {
-        return ref(cd.change().getDest().branch()).change(cd);
+        return ref(cd.branchOrThrow().branch()).change(cd);
       } catch (StorageException e) {
         return FailedPermissionBackend.change("unavailable", e);
       }
diff --git a/java/com/google/gerrit/server/permissions/ProjectControl.java b/java/com/google/gerrit/server/permissions/ProjectControl.java
index c235012..fab894e 100644
--- a/java/com/google/gerrit/server/permissions/ProjectControl.java
+++ b/java/com/google/gerrit/server/permissions/ProjectControl.java
@@ -115,7 +115,7 @@
   }
 
   ChangeControl controlFor(ChangeData cd) {
-    return new ChangeControl(controlForRef(cd.change().getDest()), cd);
+    return new ChangeControl(controlForRef(cd.branchOrThrow()), cd);
   }
 
   RefControl controlForRef(BranchNameKey ref) {
@@ -366,7 +366,7 @@
     @Override
     public ForChange change(ChangeData cd) {
       try {
-        checkProject(cd.change());
+        checkProject(cd);
         return super.change(cd);
       } catch (StorageException e) {
         return FailedPermissionBackend.change("unavailable", e);
@@ -379,13 +379,21 @@
       return super.change(notes);
     }
 
+    private void checkProject(ChangeData cd) {
+      checkProject(cd.project());
+    }
+
     private void checkProject(Change change) {
+      checkProject(change.getProject());
+    }
+
+    private void checkProject(Project.NameKey changeProject) {
       Project.NameKey project = getProject().getNameKey();
       checkArgument(
-          project.equals(change.getProject()),
+          project.equals(changeProject),
           "expected change in project %s, not %s",
           project,
-          change.getProject());
+          changeProject);
     }
 
     @Override
diff --git a/java/com/google/gerrit/server/query/change/ChangeData.java b/java/com/google/gerrit/server/query/change/ChangeData.java
index c5a8794..392d00a 100644
--- a/java/com/google/gerrit/server/query/change/ChangeData.java
+++ b/java/com/google/gerrit/server/query/change/ChangeData.java
@@ -39,6 +39,7 @@
 import com.google.gerrit.common.Nullable;
 import com.google.gerrit.entities.Account;
 import com.google.gerrit.entities.AttentionSetUpdate;
+import com.google.gerrit.entities.BranchNameKey;
 import com.google.gerrit.entities.Change;
 import com.google.gerrit.entities.ChangeMessage;
 import com.google.gerrit.entities.Comment;
@@ -241,6 +242,19 @@
       return assistedFactory.create(project, id, null, null);
     }
 
+    public ChangeData create(Project.NameKey project, Change.Id id, ObjectId metaRevision) {
+      ChangeData cd = assistedFactory.create(project, id, null, null);
+      cd.setMetaRevision(metaRevision);
+      return cd;
+    }
+
+    public ChangeData createNonPrivate(BranchNameKey branch, Change.Id id, ObjectId metaRevision) {
+      ChangeData cd = create(branch.project(), id, metaRevision);
+      cd.branch = branch.branch();
+      cd.isPrivate = false;
+      return cd;
+    }
+
     public ChangeData create(Change change) {
       return assistedFactory.create(change.getProject(), change.getId(), change, null);
     }
@@ -390,7 +404,10 @@
   private List<ChangeMessage> messages;
   private Optional<ChangedLines> changedLines;
   private SubmitTypeRecord submitTypeRecord;
+  private String branch;
+  private Boolean isPrivate;
   private Boolean mergeable;
+  private ObjectId metaRevision;
   private Set<String> hashtags;
   private ImmutableMap<String, String> customKeyedValues;
   /**
@@ -619,6 +636,55 @@
     return project;
   }
 
+  public BranchNameKey branchOrThrow() {
+    if (change == null) {
+      if (branch != null) {
+        return BranchNameKey.create(project, branch);
+      }
+      throwIfNotLazyLoad("branch");
+      change();
+    }
+    return change.getDest();
+  }
+
+  public boolean isPrivateOrThrow() {
+    if (change == null) {
+      if (isPrivate != null) {
+        return isPrivate;
+      }
+      throwIfNotLazyLoad("isPrivate");
+      change();
+    }
+    return change.isPrivate();
+  }
+
+  public ChangeData setMetaRevision(ObjectId metaRevision) {
+    this.metaRevision = metaRevision;
+    return this;
+  }
+
+  public ObjectId metaRevisionOrThrow() {
+    if (notes == null) {
+      if (metaRevision != null) {
+        return metaRevision;
+      }
+      if (refStates != null) {
+        Set<RefState> refs = refStates.get(project);
+        if (refs != null) {
+          String metaRef = RefNames.changeMetaRef(getId());
+          for (RefState r : refs) {
+            if (r.ref().equals(metaRef)) {
+              return r.id();
+            }
+          }
+        }
+      }
+      throwIfNotLazyLoad("metaRevision");
+      notes();
+    }
+    return notes.getRevision();
+  }
+
   boolean fastIsVisibleTo(CurrentUser user) {
     return visibleTo == user;
   }
@@ -629,7 +695,7 @@
 
   public Change change() {
     if (change == null && lazyload()) {
-      reloadChange();
+      loadChange();
     }
     return change;
   }
@@ -639,13 +705,19 @@
   }
 
   public Change reloadChange() {
+    metaRevision = null;
+    return loadChange();
+  }
+
+  private Change loadChange() {
     try {
-      notes = notesFactory.createChecked(project, legacyId);
+      notes = notesFactory.createChecked(project, legacyId, metaRevision);
     } catch (NoSuchChangeException e) {
       throw new StorageException("Unable to load change " + legacyId, e);
     }
     change = notes.getChange();
     changeServerId = notes.getServerId();
+    metaRevision = null;
     setPatchSets(null);
     return change;
   }
@@ -663,7 +735,8 @@
       if (!lazyload()) {
         throw new StorageException("ChangeNotes not available, lazyLoad = false");
       }
-      notes = notesFactory.create(project(), legacyId);
+      notes = notesFactory.create(project(), legacyId, metaRevision);
+      change = notes.getChange();
     }
     return notes;
   }
@@ -889,11 +962,7 @@
 
   public ReviewerSet reviewers() {
     if (reviewers == null) {
-      if (!lazyload()) {
-        // We are not allowed to load values from NoteDb. Reviewers were not populated with values
-        // from the index. However, we need these values for permission checks.
-        throw new IllegalStateException("reviewers not populated");
-      }
+      throwIfNotLazyLoad("reviewers");
       reviewers = approvalsUtil.getReviewers(notes());
     }
     return reviewers;
@@ -1447,6 +1516,14 @@
     this.refStatePatterns = ImmutableList.copyOf(refStatePatterns);
   }
 
+  private void throwIfNotLazyLoad(String field) {
+    if (!lazyload()) {
+      // We are not allowed to load values from NoteDb. 'field' was not populated, however,
+      // we need this value for permission checks.
+      throw new IllegalStateException("'" + field + "' field not populated");
+    }
+  }
+
   @AutoValue
   abstract static class ReviewedByEvent {
     private static ReviewedByEvent create(ChangeMessage msg) {
diff --git a/java/com/google/gerrit/testing/InMemoryModule.java b/java/com/google/gerrit/testing/InMemoryModule.java
index 56fcff5..fa879d6 100644
--- a/java/com/google/gerrit/testing/InMemoryModule.java
+++ b/java/com/google/gerrit/testing/InMemoryModule.java
@@ -84,10 +84,10 @@
 import com.google.gerrit.server.config.TrackingFooters;
 import com.google.gerrit.server.config.TrackingFootersProvider;
 import com.google.gerrit.server.experiments.ConfigExperimentFeatures.ConfigExperimentFeaturesModule;
+import com.google.gerrit.server.git.ChangesByProjectCache;
 import com.google.gerrit.server.git.GarbageCollection;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.git.PerThreadRequestScope;
-import com.google.gerrit.server.git.SearchingChangeCacheImpl.SearchingChangeCacheImplModule;
 import com.google.gerrit.server.git.WorkQueue.WorkQueueModule;
 import com.google.gerrit.server.group.testing.TestGroupBackend;
 import com.google.gerrit.server.index.account.AccountSchemaDefinitions;
@@ -207,7 +207,7 @@
     factory(PluginUser.Factory.class);
     install(new PluginApiModule());
     install(new DefaultPermissionBackendModule());
-    install(new SearchingChangeCacheImplModule());
+    install(new ChangesByProjectCache.Module(ChangesByProjectCache.UseIndex.TRUE, cfg));
     factory(GarbageCollection.Factory.class);
     install(new AuditModule());
     install(new SubscriptionGraphModule());
diff --git a/polygerrit-ui/app/yarn.lock b/polygerrit-ui/app/yarn.lock
index a27631c..d1bcdda 100644
--- a/polygerrit-ui/app/yarn.lock
+++ b/polygerrit-ui/app/yarn.lock
@@ -46,7 +46,7 @@
   resolved "https://registry.yarnpkg.com/@polymer/font-roboto/-/font-roboto-3.0.2.tgz#80cdaa7225db2359130dfb2c6d9a3be1820020c3"
   integrity sha512-tx5TauYSmzsIvmSqepUPDYbs4/Ejz2XbZ1IkD7JEGqkdNUJlh+9KU85G56Tfdk/xjEZ8zorFfN09OSwiMrIQWA==
 
-"@polymer/iron-a11y-announcer@^3.0.0-pre.26", "@polymer/iron-a11y-announcer@^3.1.0":
+"@polymer/iron-a11y-announcer@^3.0.0-pre.26", "@polymer/iron-a11y-announcer@^3.2.0":
   version "3.2.0"
   resolved "https://registry.yarnpkg.com/@polymer/iron-a11y-announcer/-/iron-a11y-announcer-3.2.0.tgz#d04b1301413d473336cc5797dfd97b3b36dd0cd7"
   integrity sha512-We+hyaFHcg7Ke8ovsoxUpYEXFIJLHxMCDaLehTB4dELS+C+K0zMnGSiqQvb/YzGS+nSYpAfkQIyg1msOCdHMtA==
@@ -423,7 +423,7 @@
     "@polymer/paper-styles" "^3.0.0-pre.26"
     "@polymer/polymer" "^3.0.0"
 
-"@polymer/polymer@^3.0.0", "@polymer/polymer@^3.0.2", "@polymer/polymer@^3.0.5", "@polymer/polymer@^3.3.1", "@polymer/polymer@^3.4.1":
+"@polymer/polymer@^3.0.0", "@polymer/polymer@^3.0.2", "@polymer/polymer@^3.0.5", "@polymer/polymer@^3.3.1", "@polymer/polymer@^3.5.1":
   version "3.5.1"
   resolved "https://registry.yarnpkg.com/@polymer/polymer/-/polymer-3.5.1.tgz#4b5234e43b8876441022bcb91313ab3c4a29f0c8"
   integrity sha512-JlAHuy+1qIC6hL1ojEUfIVD58fzTpJAoCxFwV5yr0mYTXV1H8bz5zy0+rC963Cgr9iNXQ4T9ncSjC2fkF9BQfw==
@@ -435,17 +435,17 @@
   resolved "https://registry.yarnpkg.com/@types/resemblejs/-/resemblejs-4.1.0.tgz#1c150e0de4117b29f9d5d5231489edc7cef8263e"
   integrity sha512-+MIkKy/UngDfhTnvn2yK/KSzlbtLeB5BU73qqZrzIF24+e2r8enJ4cW3UbtkstByYSDV8pbheGAqg7zT8ZZ2pA==
 
-"@types/resize-observer-browser@^0.1.5":
-  version "0.1.7"
-  resolved "https://registry.yarnpkg.com/@types/resize-observer-browser/-/resize-observer-browser-0.1.7.tgz#294aaadf24ac6580b8fbd1fe3ab7b59fe85f9ef3"
-  integrity sha512-G9eN0Sn0ii9PWQ3Vl72jDPgeJwRWhv2Qk/nQkJuWmRmOB4HX3/BhD5SE1dZs/hzPZL/WKnvF0RHdTSG54QJFyg==
+"@types/resize-observer-browser@^0.1.7":
+  version "0.1.8"
+  resolved "https://registry.yarnpkg.com/@types/resize-observer-browser/-/resize-observer-browser-0.1.8.tgz#db41a93f9f37dad8e6f0c7bd2f5bbc042bf714d1"
+  integrity sha512-OpjAd26fD1G2OWlYzkrapJ12n+kyi0znYgE2AHfNccHY/am3kG+lfJ5brfcZ7+1CIybkPWGKrW+Wm97kbcOQaQ==
 
 "@types/trusted-types@^2.0.2":
   version "2.0.4"
   resolved "https://registry.yarnpkg.com/@types/trusted-types/-/trusted-types-2.0.4.tgz#2b38784cd16957d3782e8e2b31c03bc1d13b4d65"
   integrity sha512-IDaobHimLQhjwsQ/NMwRVfa/yL7L/wriQPMhw1ZJall0KX6E1oxk29XMDeilW5qTIg5aoiqf5Udy8U/51aNoQQ==
 
-"@webcomponents/shadycss@^1.10.2", "@webcomponents/shadycss@^1.9.1":
+"@webcomponents/shadycss@^1.11.2", "@webcomponents/shadycss@^1.9.1":
   version "1.11.2"
   resolved "https://registry.yarnpkg.com/@webcomponents/shadycss/-/shadycss-1.11.2.tgz#7539b0ad29598aa2eafee8b341059e20ac9e1006"
   integrity sha512-vRq+GniJAYSBmTRnhCYPAPq6THYqovJ/gzGThWbgEZUQaBccndGTi1hdiUP15HzEco0I6t4RCtXyX0rsSmwgPw==