Merge "Add missing permission test for Revert"
diff --git a/java/com/google/gerrit/common/data/PatchScript.java b/java/com/google/gerrit/common/data/PatchScript.java
index 3428580..28fda17 100644
--- a/java/com/google/gerrit/common/data/PatchScript.java
+++ b/java/com/google/gerrit/common/data/PatchScript.java
@@ -56,7 +56,6 @@
   private CommentDetail comments;
   private List<Patch> history;
   private boolean hugeFile;
-  private boolean intralineDifference;
   private boolean intralineFailure;
   private boolean intralineTimeout;
   private boolean binary;
@@ -83,7 +82,6 @@
       CommentDetail cd,
       List<Patch> hist,
       boolean hf,
-      boolean id,
       boolean idf,
       boolean idt,
       boolean bin,
@@ -108,7 +106,6 @@
     comments = cd;
     history = hist;
     hugeFile = hf;
-    intralineDifference = id;
     intralineFailure = idf;
     intralineTimeout = idt;
     binary = bin;
@@ -178,10 +175,6 @@
     return diffPrefs.ignoreWhitespace != Whitespace.IGNORE_NONE;
   }
 
-  public boolean hasIntralineDifference() {
-    return intralineDifference;
-  }
-
   public boolean hasIntralineFailure() {
     return intralineFailure;
   }
diff --git a/java/com/google/gerrit/metrics/proc/OperatingSystemMXBeanFactory.java b/java/com/google/gerrit/metrics/proc/OperatingSystemMXBeanFactory.java
new file mode 100644
index 0000000..9befe16a
--- /dev/null
+++ b/java/com/google/gerrit/metrics/proc/OperatingSystemMXBeanFactory.java
@@ -0,0 +1,49 @@
+// Copyright (C) 2017 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.metrics.proc;
+
+import com.google.common.flogger.FluentLogger;
+import com.sun.management.UnixOperatingSystemMXBean;
+import java.lang.management.ManagementFactory;
+import java.lang.management.OperatingSystemMXBean;
+import java.util.Arrays;
+
+@SuppressWarnings("restriction")
+class OperatingSystemMXBeanFactory {
+  private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+
+  static OperatingSystemMXBeanInterface create() {
+    OperatingSystemMXBean sys = ManagementFactory.getOperatingSystemMXBean();
+    if (sys instanceof UnixOperatingSystemMXBean) {
+      return new OperatingSystemMXBeanUnixNative((UnixOperatingSystemMXBean) sys);
+    }
+
+    for (String name :
+        Arrays.asList(
+            "com.sun.management.UnixOperatingSystemMXBean",
+            "com.ibm.lang.management.UnixOperatingSystemMXBean")) {
+      try {
+        Class<?> impl = Class.forName(name);
+        if (impl.isInstance(sys)) {
+          return new OperatingSystemMXBeanReflectionBased(sys);
+        }
+      } catch (ReflectiveOperationException e) {
+        logger.atFine().withCause(e).log("No implementation for %s", name);
+      }
+    }
+    logger.atWarning().log("No implementation of UnixOperatingSystemMXBean found");
+    return null;
+  }
+}
diff --git a/java/com/google/gerrit/metrics/proc/OperatingSystemMXBeanInterface.java b/java/com/google/gerrit/metrics/proc/OperatingSystemMXBeanInterface.java
new file mode 100644
index 0000000..b7d6ebf
--- /dev/null
+++ b/java/com/google/gerrit/metrics/proc/OperatingSystemMXBeanInterface.java
@@ -0,0 +1,21 @@
+// Copyright (C) 2019 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.metrics.proc;
+
+interface OperatingSystemMXBeanInterface {
+  long getProcessCpuTime();
+
+  long getOpenFileDescriptorCount();
+}
diff --git a/java/com/google/gerrit/metrics/proc/OperatingSystemMXBeanProvider.java b/java/com/google/gerrit/metrics/proc/OperatingSystemMXBeanProvider.java
deleted file mode 100644
index 35c147e..0000000
--- a/java/com/google/gerrit/metrics/proc/OperatingSystemMXBeanProvider.java
+++ /dev/null
@@ -1,75 +0,0 @@
-// Copyright (C) 2017 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.metrics.proc;
-
-import com.google.common.flogger.FluentLogger;
-import java.lang.management.ManagementFactory;
-import java.lang.management.OperatingSystemMXBean;
-import java.lang.reflect.Method;
-import java.util.Arrays;
-
-class OperatingSystemMXBeanProvider {
-  private static final FluentLogger logger = FluentLogger.forEnclosingClass();
-
-  private final OperatingSystemMXBean sys;
-  private final Method getProcessCpuTime;
-  private final Method getOpenFileDescriptorCount;
-
-  static class Factory {
-    static OperatingSystemMXBeanProvider create() {
-      OperatingSystemMXBean sys = ManagementFactory.getOperatingSystemMXBean();
-      for (String name :
-          Arrays.asList(
-              "com.sun.management.UnixOperatingSystemMXBean",
-              "com.ibm.lang.management.UnixOperatingSystemMXBean")) {
-        try {
-          Class<?> impl = Class.forName(name);
-          if (impl.isInstance(sys)) {
-            return new OperatingSystemMXBeanProvider(sys);
-          }
-        } catch (ReflectiveOperationException e) {
-          logger.atFine().withCause(e).log("No implementation for %s", name);
-        }
-      }
-      logger.atWarning().log("No implementation of UnixOperatingSystemMXBean found");
-      return null;
-    }
-  }
-
-  private OperatingSystemMXBeanProvider(OperatingSystemMXBean sys)
-      throws ReflectiveOperationException {
-    this.sys = sys;
-    getProcessCpuTime = sys.getClass().getMethod("getProcessCpuTime");
-    getProcessCpuTime.setAccessible(true);
-    getOpenFileDescriptorCount = sys.getClass().getMethod("getOpenFileDescriptorCount");
-    getOpenFileDescriptorCount.setAccessible(true);
-  }
-
-  public long getProcessCpuTime() {
-    try {
-      return (long) getProcessCpuTime.invoke(sys, new Object[] {});
-    } catch (ReflectiveOperationException e) {
-      return -1;
-    }
-  }
-
-  public long getOpenFileDescriptorCount() {
-    try {
-      return (long) getOpenFileDescriptorCount.invoke(sys, new Object[] {});
-    } catch (ReflectiveOperationException e) {
-      return -1;
-    }
-  }
-}
diff --git a/java/com/google/gerrit/metrics/proc/OperatingSystemMXBeanReflectionBased.java b/java/com/google/gerrit/metrics/proc/OperatingSystemMXBeanReflectionBased.java
new file mode 100644
index 0000000..8dc54ab
--- /dev/null
+++ b/java/com/google/gerrit/metrics/proc/OperatingSystemMXBeanReflectionBased.java
@@ -0,0 +1,51 @@
+// Copyright (C) 2019 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.metrics.proc;
+
+import java.lang.management.OperatingSystemMXBean;
+import java.lang.reflect.Method;
+
+class OperatingSystemMXBeanReflectionBased implements OperatingSystemMXBeanInterface {
+  private final OperatingSystemMXBean sys;
+  private final Method getProcessCpuTime;
+  private final Method getOpenFileDescriptorCount;
+
+  OperatingSystemMXBeanReflectionBased(OperatingSystemMXBean sys)
+      throws ReflectiveOperationException {
+    this.sys = sys;
+    getProcessCpuTime = sys.getClass().getMethod("getProcessCpuTime");
+    getProcessCpuTime.setAccessible(true);
+    getOpenFileDescriptorCount = sys.getClass().getMethod("getOpenFileDescriptorCount");
+    getOpenFileDescriptorCount.setAccessible(true);
+  }
+
+  @Override
+  public long getProcessCpuTime() {
+    try {
+      return (long) getProcessCpuTime.invoke(sys, new Object[] {});
+    } catch (ReflectiveOperationException e) {
+      return -1;
+    }
+  }
+
+  @Override
+  public long getOpenFileDescriptorCount() {
+    try {
+      return (long) getOpenFileDescriptorCount.invoke(sys, new Object[] {});
+    } catch (ReflectiveOperationException e) {
+      return -1;
+    }
+  }
+}
diff --git a/java/com/google/gerrit/metrics/proc/OperatingSystemMXBeanUnixNative.java b/java/com/google/gerrit/metrics/proc/OperatingSystemMXBeanUnixNative.java
new file mode 100644
index 0000000..a7f5bba
--- /dev/null
+++ b/java/com/google/gerrit/metrics/proc/OperatingSystemMXBeanUnixNative.java
@@ -0,0 +1,36 @@
+// Copyright (C) 2019 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.metrics.proc;
+
+import com.sun.management.UnixOperatingSystemMXBean;
+
+@SuppressWarnings("restriction")
+class OperatingSystemMXBeanUnixNative implements OperatingSystemMXBeanInterface {
+  private final UnixOperatingSystemMXBean sys;
+
+  OperatingSystemMXBeanUnixNative(UnixOperatingSystemMXBean sys) {
+    this.sys = sys;
+  }
+
+  @Override
+  public long getProcessCpuTime() {
+    return sys.getProcessCpuTime();
+  }
+
+  @Override
+  public long getOpenFileDescriptorCount() {
+    return sys.getOpenFileDescriptorCount();
+  }
+}
diff --git a/java/com/google/gerrit/metrics/proc/ProcMetricModule.java b/java/com/google/gerrit/metrics/proc/ProcMetricModule.java
index d9781b5..b97cc54 100644
--- a/java/com/google/gerrit/metrics/proc/ProcMetricModule.java
+++ b/java/com/google/gerrit/metrics/proc/ProcMetricModule.java
@@ -63,7 +63,7 @@
   }
 
   private void procCpuUsage(MetricMaker metrics) {
-    final OperatingSystemMXBeanProvider provider = OperatingSystemMXBeanProvider.Factory.create();
+    OperatingSystemMXBeanInterface provider = OperatingSystemMXBeanFactory.create();
 
     if (provider == null) {
       return;
diff --git a/java/com/google/gerrit/reviewdb/client/Comment.java b/java/com/google/gerrit/reviewdb/client/Comment.java
index 94e7583..eb2aa52 100644
--- a/java/com/google/gerrit/reviewdb/client/Comment.java
+++ b/java/com/google/gerrit/reviewdb/client/Comment.java
@@ -124,6 +124,24 @@
     }
   }
 
+  /**
+   * The Range class defines continuous range of character.
+   *
+   * <p>The pair (startLine, startChar) defines the first character in the range. The pair (endLine,
+   * endChar) defines the first character AFTER the range (i.e. it doesn't belong the range).
+   * (endLine, endChar) must be a valid character inside text, except EOF case.</p>
+   * <p>Special cases:</p>
+   * <ul>
+   *   <li>Zero length range: (startLine, startChar) = (endLine, endChar). Range defines insert
+   *       position right before the (startLine, startChar) character (for {@link FixReplacement)
+   *   <li>EOF case - range includes the last character in the file:
+   *       <ul>
+   *         <li>if a file ends with EOL mark, then (endLine, endChar) = (num_of_lines + 1, 0)
+   *         <li>if a file doesn't end with EOL mark, then
+   *             (endLine, endChar) = (num_of_lines, num_of_chars_in_last_line)
+   *       </ul>
+   * </ul>
+   */
   public static class Range implements Comparable<Range> {
     private static final Comparator<Range> RANGE_COMPARATOR =
         Comparator.<Range>comparingInt(range -> range.startLine)
@@ -131,10 +149,10 @@
             .thenComparingInt(range -> range.endLine)
             .thenComparingInt(range -> range.endChar);
 
-    public int startLine; // 1-based, inclusive
-    public int startChar; // 0-based, inclusive
-    public int endLine; // 1-based, exclusive
-    public int endChar; // 0-based, exclusive
+    public int startLine; // 1-based
+    public int startChar; // 0-based
+    public int endLine; // 1-based
+    public int endChar; // 0-based
 
     public Range(Range r) {
       this(r.startLine, r.startChar, r.endLine, r.endChar);
diff --git a/java/com/google/gerrit/server/account/VersionedAccountQueries.java b/java/com/google/gerrit/server/account/VersionedAccountQueries.java
index daf7100..7b5e5ce 100644
--- a/java/com/google/gerrit/server/account/VersionedAccountQueries.java
+++ b/java/com/google/gerrit/server/account/VersionedAccountQueries.java
@@ -14,11 +14,17 @@
 
 package com.google.gerrit.server.account;
 
+import static java.util.stream.Collectors.joining;
+
+import com.google.common.base.Strings;
 import com.google.common.flogger.FluentLogger;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.RefNames;
+import com.google.gerrit.server.git.ValidationError;
 import com.google.gerrit.server.git.meta.VersionedMetaData;
 import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
 import org.eclipse.jgit.errors.ConfigInvalidException;
 import org.eclipse.jgit.lib.CommitBuilder;
 
@@ -46,6 +52,16 @@
     return queryList;
   }
 
+  public void setQueryList(String text) throws IOException, ConfigInvalidException {
+    List<ValidationError> errors = new ArrayList<>();
+    QueryList newQueryList = QueryList.parse(text, error -> errors.add(error));
+    if (!errors.isEmpty()) {
+      String messages = errors.stream().map(ValidationError::getMessage).collect(joining(", "));
+      throw new ConfigInvalidException("Invalid named queries: " + messages);
+    }
+    queryList = newQueryList;
+  }
+
   @Override
   protected void onLoad() throws IOException, ConfigInvalidException {
     queryList =
@@ -58,6 +74,10 @@
 
   @Override
   protected boolean onSave(CommitBuilder commit) throws IOException, ConfigInvalidException {
-    throw new UnsupportedOperationException("Cannot yet save named queries");
+    if (Strings.isNullOrEmpty(commit.getMessage())) {
+      commit.setMessage("Updated named queries\n");
+    }
+    saveUTF8(QueryList.FILE_NAME, queryList.asText());
+    return true;
   }
 }
diff --git a/java/com/google/gerrit/server/patch/PatchScriptBuilder.java b/java/com/google/gerrit/server/patch/PatchScriptBuilder.java
index acf88e1..b9c13b1 100644
--- a/java/com/google/gerrit/server/patch/PatchScriptBuilder.java
+++ b/java/com/google/gerrit/server/patch/PatchScriptBuilder.java
@@ -121,7 +121,6 @@
 
   private PatchScript build(PatchListEntry content, CommentDetail comments, List<Patch> history)
       throws IOException {
-    boolean intralineDifferenceIsPossible = true;
     boolean intralineFailure = false;
     boolean intralineTimeout = false;
 
@@ -134,9 +133,7 @@
     edits = new ArrayList<>(content.getEdits());
     ImmutableSet<Edit> editsDueToRebase = content.getEditsDueToRebase();
 
-    if (!isModify(content)) {
-      intralineDifferenceIsPossible = false;
-    } else if (diffPrefs.intralineDifference) {
+    if (isModify(content) && diffPrefs.intralineDifference) {
       IntraLineDiff d =
           patchListCache.getIntraLineDiff(
               IntraLineDiffKey.create(a.id, b.id, diffPrefs.ignoreWhitespace),
@@ -149,21 +146,17 @@
             break;
 
           case DISABLED:
-            intralineDifferenceIsPossible = false;
             break;
 
           case ERROR:
-            intralineDifferenceIsPossible = false;
             intralineFailure = true;
             break;
 
           case TIMEOUT:
-            intralineDifferenceIsPossible = false;
             intralineTimeout = true;
             break;
         }
       } else {
-        intralineDifferenceIsPossible = false;
         intralineFailure = true;
       }
     }
@@ -223,7 +216,6 @@
         comments,
         history,
         hugeFile,
-        intralineDifferenceIsPossible,
         intralineFailure,
         intralineTimeout,
         content.getPatchType() == Patch.PatchType.BINARY,
diff --git a/javatests/com/google/gerrit/server/fixes/FixReplacementInterpreterTest.java b/javatests/com/google/gerrit/server/fixes/FixReplacementInterpreterTest.java
index 2174927..fb49657 100644
--- a/javatests/com/google/gerrit/server/fixes/FixReplacementInterpreterTest.java
+++ b/javatests/com/google/gerrit/server/fixes/FixReplacementInterpreterTest.java
@@ -148,6 +148,22 @@
         .isEqualTo("First line\nA modification\nThird line\n");
   }
 
+  @Test()
+  public void startAfterEndOfLineMarkThrowsAnException() throws Exception {
+    FixReplacement fixReplacement =
+        new FixReplacement(filePath1, new Range(1, 11, 2, 6), "A modification");
+    mockFileContent(filePath1, "First line\nSecond line\nThird line\n");
+    assertThrows(ResourceConflictException.class, () -> toTreeModifications(fixReplacement));
+  }
+
+  @Test()
+  public void endAfterEndOfLineMarkThrowsAnException() throws Exception {
+    FixReplacement fixReplacement =
+        new FixReplacement(filePath1, new Range(2, 0, 2, 12), "A modification");
+    mockFileContent(filePath1, "First line\nSecond line\nThird line\n");
+    assertThrows(ResourceConflictException.class, () -> toTreeModifications(fixReplacement));
+  }
+
   @Test
   public void replacementsMayTouch() throws Exception {
     FixReplacement fixReplacement1 =
@@ -180,6 +196,34 @@
   }
 
   @Test
+  public void replacementsCanChangeLastLine() throws Exception {
+    FixReplacement fixReplacement =
+        new FixReplacement(filePath1, new Range(3, 0, 4, 0), "New content\n");
+    mockFileContent(filePath1, "First line\nSecond line\nThird line\n");
+
+    List<TreeModification> treeModifications = toTreeModifications(fixReplacement);
+    assertThatList(treeModifications)
+        .onlyElement()
+        .asChangeFileContentModification()
+        .newContent()
+        .isEqualTo("First line\nSecond line\nNew content\n");
+  }
+
+  @Test
+  public void replacementsCanChangeLastLineWithoutEOLMark() throws Exception {
+    FixReplacement fixReplacement =
+        new FixReplacement(filePath1, new Range(3, 0, 3, 10), "New content\n");
+    mockFileContent(filePath1, "First line\nSecond line\nThird line");
+
+    List<TreeModification> treeModifications = toTreeModifications(fixReplacement);
+    assertThatList(treeModifications)
+        .onlyElement()
+        .asChangeFileContentModification()
+        .newContent()
+        .isEqualTo("First line\nSecond line\nNew content\n");
+  }
+
+  @Test
   public void replacementsCanModifySeveralFilesInAnyOrder() throws Exception {
     FixReplacement fixReplacement1 =
         new FixReplacement(filePath1, new Range(1, 1, 3, 2), "Modified content");
diff --git a/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java b/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
index 350c6d2..fb5d93d 100644
--- a/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
+++ b/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
@@ -91,6 +91,7 @@
 import com.google.gerrit.server.account.Accounts;
 import com.google.gerrit.server.account.AccountsUpdate;
 import com.google.gerrit.server.account.AuthRequest;
+import com.google.gerrit.server.account.VersionedAccountQueries;
 import com.google.gerrit.server.account.externalids.ExternalId;
 import com.google.gerrit.server.change.ChangeInserter;
 import com.google.gerrit.server.change.ChangeTriplet;
@@ -2953,19 +2954,19 @@
     Change change1 = insert(repo, newChange(repo));
     Change change2 = insert(repo, newChangeForBranch(repo, "stable"));
 
-    String queries =
+    String queryListText =
         "query1\tproject:repo\n"
             + "query2\tproject:repo status:open\n"
             + "query3\tproject:repo branch:stable\n"
             + "query4\tproject:repo branch:other";
 
     try (TestRepository<Repo> allUsers =
-        new TestRepository<>(repoManager.openRepository(allUsersName))) {
-      String refsUsers = RefNames.refsUsers(userId);
-      allUsers.branch(refsUsers).commit().add("queries", queries).create();
-
-      Ref userRef = allUsers.getRepository().exactRef(refsUsers);
-      assertThat(userRef).isNotNull();
+            new TestRepository<>(repoManager.openRepository(allUsersName));
+        MetaDataUpdate md = metaDataUpdateFactory.create(allUsersName)) {
+      VersionedAccountQueries queries = VersionedAccountQueries.forUser(userId);
+      queries.load(md);
+      queries.setQueryList(queryListText);
+      queries.commit(md);
     }
 
     assertThatQueryException("query:foo").hasMessageThat().isEqualTo("Unknown named query: foo");
diff --git a/plugins/delete-project b/plugins/delete-project
index 4223c71..d520c74 160000
--- a/plugins/delete-project
+++ b/plugins/delete-project
@@ -1 +1 @@
-Subproject commit 4223c71d319b04c6b42566d2d12adca20734526d
+Subproject commit d520c74076714ed6f607693b6a737615dfd829b2
diff --git a/plugins/gitiles b/plugins/gitiles
index bdbed9a..9f7b84e 160000
--- a/plugins/gitiles
+++ b/plugins/gitiles
@@ -1 +1 @@
-Subproject commit bdbed9af9bb2b77cd7fc8681da2dcee7e8f30264
+Subproject commit 9f7b84e3ad1192b10a549d7e2ea2b920f84492de
diff --git a/plugins/webhooks b/plugins/webhooks
index e267456..b8fe69d 160000
--- a/plugins/webhooks
+++ b/plugins/webhooks
@@ -1 +1 @@
-Subproject commit e26745604c1f4301cf5d130d4bcc9da8463e0b1a
+Subproject commit b8fe69d328f77af32da6b05af28c8ed10beb8bd9
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-api-utils.js b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-api-utils.js
deleted file mode 100644
index cf2376e..0000000
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-api-utils.js
+++ /dev/null
@@ -1,95 +0,0 @@
-/**
- * @license
- * Copyright (C) 2019 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.
- */
-
-(function(window) {
-  'use strict';
-
-  const PRELOADED_PROTOCOL = 'preloaded:';
-
-  let _restAPI;
-
-  const getRestAPI = () => {
-    if (!_restAPI) {
-      _restAPI = document.createElement('gr-rest-api-interface');
-    }
-    return _restAPI;
-  };
-
-  function getPluginNameFromUrl(url) {
-    if (!(url instanceof URL)) {
-      try {
-        url = new URL(url);
-      } catch (e) {
-        console.warn(e);
-        return null;
-      }
-    }
-    if (url.protocol === PRELOADED_PROTOCOL) {
-      return url.pathname;
-    }
-    const base = Gerrit.BaseUrlBehavior.getBaseUrl();
-    const pathname = url.pathname.replace(base, '');
-    // Site theme is server from predefined path.
-    if (pathname === '/static/gerrit-theme.html') {
-      return 'gerrit-theme';
-    } else if (!pathname.startsWith('/plugins')) {
-      console.warn('Plugin not being loaded from /plugins base path:',
-          url.href, '— Unable to determine name.');
-      return null;
-    }
-    // Pathname should normally look like this:
-    // /plugins/PLUGINNAME/static/SCRIPTNAME.html
-    // Or, for app/samples:
-    // /plugins/PLUGINNAME.html
-    return pathname.split('/')[2].split('.')[0];
-  }
-
-  // TODO (viktard): deprecate in favor of GrPluginRestApi.
-  function send(method, url, opt_callback, opt_payload) {
-    return getRestAPI().send(method, url, opt_payload).then(response => {
-      if (response.status < 200 || response.status >= 300) {
-        return response.text().then(text => {
-          if (text) {
-            return Promise.reject(text);
-          } else {
-            return Promise.reject(response.status);
-          }
-        });
-      } else {
-        return getRestAPI().getResponseObject(response);
-      }
-    }).then(response => {
-      if (opt_callback) {
-        opt_callback(response);
-      }
-      return response;
-    });
-  }
-
-  function resetInternalState() {
-    _restAPI = null;
-  }
-
-  window._apiUtils = {
-    getPluginNameFromUrl,
-    send,
-    getRestAPI,
-
-    // TEST only methods
-    resetInternalState,
-  };
-})(window);
\ No newline at end of file
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-api-utils_test.html b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-api-utils_test.html
deleted file mode 100644
index c407aa8..0000000
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-api-utils_test.html
+++ /dev/null
@@ -1,78 +0,0 @@
-<!DOCTYPE html>
-<!--
-@license
-Copyright (C) 2019 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.
--->
-
-<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
-<title>gr-api-interface</title>
-<script src="/test/common-test-setup.js"></script>
-<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-
-<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
-<script src="/bower_components/web-component-tester/browser.js"></script>
-<link rel="import" href="../../../test/common-test-setup.html"/>
-<link rel="import" href="gr-js-api-interface.html">
-
-<script>void(0);</script>
-
-<script>
-
-  const PRELOADED_PROTOCOL = 'preloaded:';
-
-  suite('gr-api-utils tests', () => {
-    suite('test getPluginNameFromUrl', () => {
-      const {getPluginNameFromUrl} = window._apiUtils;
-
-      test('with empty string', () => {
-        assert.equal(getPluginNameFromUrl(''), null);
-      });
-
-      test('with invalid url', () => {
-        assert.equal(getPluginNameFromUrl('test'), null);
-      });
-
-      test('with random invalid url', () => {
-        assert.equal(getPluginNameFromUrl('http://example.com'), null);
-        assert.equal(
-            getPluginNameFromUrl('http://example.com/static/a.html'),
-            null
-        );
-      });
-
-      test('with valid urls', () => {
-        assert.equal(
-            getPluginNameFromUrl('http://example.com/plugins/a.html'),
-            'a'
-        );
-        assert.equal(
-            getPluginNameFromUrl('http://example.com/plugins/a/static/t.html'),
-            'a'
-        );
-      });
-
-      test('with preloaded urls', () => {
-        assert.equal(getPluginNameFromUrl(`${PRELOADED_PROTOCOL}a`), 'a');
-      });
-
-      test('with gerrit-theme override', () => {
-        assert.equal(
-            getPluginNameFromUrl('http://example.com/static/gerrit-theme.html'),
-            'gerrit-theme'
-        );
-      });
-    });
-  });
-</script>
\ No newline at end of file
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-gerrit.js b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-gerrit.js
deleted file mode 100644
index 8cdbcb7..0000000
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-gerrit.js
+++ /dev/null
@@ -1,280 +0,0 @@
-/**
- * @license
- * Copyright (C) 2019 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.
- */
-
-(function(window) {
-  'use strict';
-
-  /**
-   * Hash of loaded and installed plugins, name to Plugin object.
-   */
-  const _plugins = {};
-
-  /**
-   * Array of plugin URLs to be loaded, name to url.
-   */
-  let _pluginsPending = {};
-
-  let _pluginsInstalled = [];
-
-  let _pluginsPendingCount = -1;
-
-  const UNKNOWN_PLUGIN = 'unknown';
-  const PRELOADED_PROTOCOL = 'preloaded:';
-
-  const PLUGIN_LOADING_TIMEOUT_MS = 10000;
-
-  let _reporting;
-  const getReporting = () => {
-    if (!_reporting) {
-      _reporting = document.createElement('gr-reporting');
-    }
-    return _reporting;
-  };
-
-  // Import utils methods
-  const {
-      getPluginNameFromUrl,
-      send,
-      getRestAPI,
-      resetInternalState,
-  } = window._apiUtils;
-
-  const API_VERSION = '0.1';
-
-  window.Gerrit = window.Gerrit || {};
-  const Gerrit = window.Gerrit;
-
-  let _resolveAllPluginsLoaded = null;
-  let _allPluginsPromise = null;
-
-  Gerrit._endpoints = new GrPluginEndpoints();
-
-  // Provide reset plugins function to clear installed plugins between tests.
-  const app = document.querySelector('#app');
-  if (!app) {
-    // No gr-app found (running tests)
-    Gerrit._installPreloadedPlugins = installPreloadedPlugins;
-    Gerrit._flushPreinstalls = flushPreinstalls;
-    Gerrit._resetPlugins = () => {
-      _allPluginsPromise = null;
-      _pluginsInstalled = [];
-      _pluginsPending = {};
-      _pluginsPendingCount = -1;
-      _reporting = null;
-      _resolveAllPluginsLoaded = null;
-      resetInternalState();
-      Gerrit._endpoints = new GrPluginEndpoints();
-      for (const k of Object.keys(_plugins)) {
-        delete _plugins[k];
-      }
-    };
-  }
-
-  /**
-   * @deprecated Use plugin.styles().css(rulesStr) instead. Please, consult
-   * the documentation how to replace it accordingly.
-   */
-  Gerrit.css = function(rulesStr) {
-    console.warn('Gerrit.css(rulesStr) is deprecated!',
-        'Use plugin.styles().css(rulesStr)');
-    if (!Gerrit._customStyleSheet) {
-      const styleEl = document.createElement('style');
-      document.head.appendChild(styleEl);
-      Gerrit._customStyleSheet = styleEl.sheet;
-    }
-
-    const name = '__pg_js_api_class_' +
-        Gerrit._customStyleSheet.cssRules.length;
-    Gerrit._customStyleSheet.insertRule('.' + name + '{' + rulesStr + '}', 0);
-    return name;
-  };
-
-  Gerrit.install = function(callback, opt_version, opt_src) {
-    // HTML import polyfill adds __importElement pointing to the import tag.
-    const script = document.currentScript &&
-        (document.currentScript.__importElement || document.currentScript);
-
-    let src = opt_src || (script && script.src);
-    if (!src || src.startsWith('data:')) {
-      src = script && script.baseURI;
-    }
-    const name = getPluginNameFromUrl(src);
-
-    if (opt_version && opt_version !== API_VERSION) {
-      Gerrit._pluginInstallError(`Plugin ${name} install error: only version ` +
-          API_VERSION + ' is supported in PolyGerrit. ' + opt_version +
-          ' was given.');
-      return;
-    }
-
-    const existingPlugin = _plugins[name];
-    const plugin = existingPlugin || new Plugin(src);
-    try {
-      callback(plugin);
-      if (name) {
-        _plugins[name] = plugin;
-      }
-      if (!existingPlugin) {
-        Gerrit._pluginInstalled(src);
-      }
-    } catch (e) {
-      Gerrit._pluginInstallError(`${e.name}: ${e.message}`);
-    }
-  };
-
-  Gerrit.getLoggedIn = function() {
-    console.warn('Gerrit.getLoggedIn() is deprecated! ' +
-        'Use plugin.restApi().getLoggedIn()');
-    return document.createElement('gr-rest-api-interface').getLoggedIn();
-  };
-
-  Gerrit.get = function(url, callback) {
-    console.warn('.get() is deprecated! Use plugin.restApi().get()');
-    send('GET', url, callback);
-  };
-
-  Gerrit.post = function(url, payload, callback) {
-    console.warn('.post() is deprecated! Use plugin.restApi().post()');
-    send('POST', url, callback, payload);
-  };
-
-  Gerrit.put = function(url, payload, callback) {
-    console.warn('.put() is deprecated! Use plugin.restApi().put()');
-    send('PUT', url, callback, payload);
-  };
-
-  Gerrit.delete = function(url, opt_callback) {
-    console.warn('.delete() is deprecated! Use plugin.restApi().delete()');
-    return getRestAPI().send('DELETE', url).then(response => {
-      if (response.status !== 204) {
-        return response.text().then(text => {
-          if (text) {
-            return Promise.reject(text);
-          } else {
-            return Promise.reject(response.status);
-          }
-        });
-      }
-      if (opt_callback) {
-        opt_callback(response);
-      }
-      return response;
-    });
-  };
-
-  Gerrit.awaitPluginsLoaded = function() {
-    if (!_allPluginsPromise) {
-      if (Gerrit._arePluginsLoaded()) {
-        _allPluginsPromise = Promise.resolve();
-      } else {
-        let timeoutId;
-        _allPluginsPromise =
-          Promise.race([
-            new Promise(resolve => _resolveAllPluginsLoaded = resolve),
-            new Promise(resolve => timeoutId = setTimeout(
-                Gerrit._pluginLoadingTimeout, PLUGIN_LOADING_TIMEOUT_MS)),
-          ]).then(() => clearTimeout(timeoutId));
-      }
-    }
-    return _allPluginsPromise;
-  };
-
-  Gerrit._pluginLoadingTimeout = function() {
-    console.error(`Failed to load plugins: ${Object.keys(_pluginsPending)}`);
-    Gerrit._setPluginsPending([]);
-  };
-
-  Gerrit._setPluginsPending = function(plugins) {
-    _pluginsPending = plugins.reduce((o, url) => {
-      // TODO(viktard): Remove guard (@see Issue 8962)
-      o[getPluginNameFromUrl(url) || UNKNOWN_PLUGIN] = url;
-      return o;
-    }, {});
-    Gerrit._setPluginsCount(Object.keys(_pluginsPending).length);
-  };
-
-  Gerrit._setPluginsCount = function(count) {
-    _pluginsPendingCount = count;
-    if (Gerrit._arePluginsLoaded()) {
-      getReporting().pluginsLoaded(_pluginsInstalled);
-      if (_resolveAllPluginsLoaded) {
-        _resolveAllPluginsLoaded();
-      }
-    }
-  };
-
-  Gerrit._pluginInstallError = function(message) {
-    document.dispatchEvent(new CustomEvent('show-alert', {
-      detail: {
-        message: `Plugin install error: ${message}`,
-      },
-    }));
-    console.info(`Plugin install error: ${message}`);
-    Gerrit._setPluginsCount(_pluginsPendingCount - 1);
-  };
-
-  Gerrit._pluginInstalled = function(url) {
-    const name = getPluginNameFromUrl(url) || UNKNOWN_PLUGIN;
-    if (!_pluginsPending[name]) {
-      console.warn(`Unexpected plugin ${name} installed from ${url}.`);
-    } else {
-      delete _pluginsPending[name];
-      _pluginsInstalled.push(name);
-      Gerrit._setPluginsCount(_pluginsPendingCount - 1);
-      getReporting().pluginLoaded(name);
-      console.log(`Plugin ${name} installed.`);
-    }
-  };
-
-  Gerrit._arePluginsLoaded = function() {
-    return _pluginsPendingCount === 0;
-  };
-
-  Gerrit._getPluginScreenName = function(pluginName, screenName) {
-    return `${pluginName}-screen-${screenName}`;
-  };
-
-  Gerrit._isPluginPreloaded = function(url) {
-    const name = getPluginNameFromUrl(url);
-    if (name && Gerrit._preloadedPlugins) {
-      return name in Gerrit._preloadedPlugins;
-    } else {
-      return false;
-    }
-  };
-
-  function flushPreinstalls() {
-    if (window.Gerrit.flushPreinstalls) {
-      window.Gerrit.flushPreinstalls();
-    }
-  }
-
-  function installPreloadedPlugins() {
-    if (!Gerrit._preloadedPlugins) { return; }
-    for (const name in Gerrit._preloadedPlugins) {
-      if (!Gerrit._preloadedPlugins.hasOwnProperty(name)) { continue; }
-      const callback = Gerrit._preloadedPlugins[name];
-      Gerrit.install(callback, API_VERSION, PRELOADED_PROTOCOL + name);
-    }
-  }
-
-  flushPreinstalls();
-
-  // Preloaded plugins should be installed after Gerrit.install() is set,
-  // since plugin preloader substitutes Gerrit.install() temporarily.
-  installPreloadedPlugins();
-})(window);
\ No newline at end of file
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-gerrit_test.html b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-gerrit_test.html
deleted file mode 100644
index b3ec30f..0000000
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-gerrit_test.html
+++ /dev/null
@@ -1,188 +0,0 @@
-<!DOCTYPE html>
-<!--
-@license
-Copyright (C) 2019 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.
--->
-
-<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
-<title>gr-api-interface</title>
-<script src="/test/common-test-setup.js"></script>
-<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-
-<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
-<script src="/bower_components/web-component-tester/browser.js"></script>
-<link rel="import" href="../../../test/common-test-setup.html"/>
-<link rel="import" href="gr-js-api-interface.html">
-
-<script>void(0);</script>
-
-<test-fixture id="basic">
-  <template>
-    <gr-js-api-interface></gr-js-api-interface>
-  </template>
-</test-fixture>
-
-<script>
-  suite('gr-gerrit tests', () => {
-    let element;
-    let plugin;
-    let sandbox;
-    let sendStub;
-
-    setup(() => {
-      sandbox = sinon.sandbox.create();
-      sendStub = sandbox.stub().returns(Promise.resolve({status: 200}));
-      stub('gr-rest-api-interface', {
-        getAccount() {
-          return Promise.resolve({name: 'Judy Hopps'});
-        },
-        send(...args) {
-          return sendStub(...args);
-        },
-      });
-      element = fixture('basic');
-      Gerrit.install(p => { plugin = p; }, '0.1',
-          'http://test.com/plugins/testplugin/static/test.js');
-      Gerrit._setPluginsPending([]);
-    });
-
-    teardown(() => {
-      sandbox.restore();
-      element._removeEventCallbacks();
-      plugin = null;
-    });
-
-    test('reuse plugin for install calls', () => {
-      let otherPlugin;
-      Gerrit.install(p => { otherPlugin = p; }, '0.1',
-          'http://test.com/plugins/testplugin/static/test.js');
-      assert.strictEqual(plugin, otherPlugin);
-    });
-
-    test('flushes preinstalls if provided', () => {
-      assert.doesNotThrow(() => {
-        Gerrit._flushPreinstalls();
-      });
-      window.Gerrit.flushPreinstalls = sandbox.stub();
-      Gerrit._flushPreinstalls();
-      assert.isTrue(window.Gerrit.flushPreinstalls.calledOnce);
-      delete window.Gerrit.flushPreinstalls;
-    });
-
-    test('versioning', () => {
-      const callback = sandbox.spy();
-      Gerrit.install(callback, '0.0pre-alpha');
-      assert(callback.notCalled);
-    });
-
-    test('_setPluginsCount', done => {
-      stub('gr-reporting', {
-        pluginsLoaded() {
-          done();
-        },
-      });
-      Gerrit._setPluginsCount(0);
-    });
-
-    test('_arePluginsLoaded', () => {
-      assert.isTrue(Gerrit._arePluginsLoaded());
-      Gerrit._setPluginsCount(1);
-      assert.isFalse(Gerrit._arePluginsLoaded());
-      Gerrit._setPluginsCount(0);
-      assert.isTrue(Gerrit._arePluginsLoaded());
-    });
-
-    test('_pluginInstalled', () => {
-      const pluginsLoadedStub = sandbox.stub();
-      stub('gr-reporting', {
-        pluginsLoaded: (...args) => pluginsLoadedStub(...args),
-      });
-      const plugins = [
-        'http://test.com/plugins/foo/static/test.js',
-        'http://test.com/plugins/bar/static/test.js',
-      ];
-      Gerrit._setPluginsPending(plugins);
-      Gerrit._pluginInstalled(plugins[0]);
-      Gerrit._pluginInstalled(plugins[1]);
-      assert.isTrue(pluginsLoadedStub.calledWithExactly(['foo', 'bar']));
-    });
-
-    test('install calls _pluginInstalled', () => {
-      sandbox.stub(Gerrit, '_pluginInstalled');
-      Gerrit.install(p => { plugin = p; }, '0.1',
-          'http://test.com/plugins/testplugin/static/test.js');
-
-      // testplugin has already been installed once (in setup).
-      assert.isFalse(Gerrit._pluginInstalled.called);
-
-      // testplugin2 plugin has not yet been installed.
-      Gerrit.install(p => { plugin = p; }, '0.1',
-          'http://test.com/plugins/testplugin2/static/test.js');
-      assert.isTrue(Gerrit._pluginInstalled.calledOnce);
-    });
-
-    test('plugin install errors mark plugins as loaded', () => {
-      Gerrit._setPluginsCount(1);
-      Gerrit.install(() => {}, '0.0pre-alpha');
-      return Gerrit.awaitPluginsLoaded();
-    });
-
-    test('multiple ui plugins per java plugin', () => {
-      const file1 = 'http://test.com/plugins/qaz/static/foo.nocache.js';
-      const file2 = 'http://test.com/plugins/qaz/static/bar.js';
-      Gerrit._setPluginsPending([file1, file2]);
-      Gerrit.install(() => {}, '0.1', file1);
-      Gerrit.install(() => {}, '0.1', file2);
-      return Gerrit.awaitPluginsLoaded();
-    });
-
-    test('plugin install errors shows toasts', () => {
-      const alertStub = sandbox.stub();
-      document.addEventListener('show-alert', alertStub);
-      Gerrit._setPluginsCount(1);
-      Gerrit.install(() => {}, '0.0pre-alpha');
-      return Gerrit.awaitPluginsLoaded().then(() => {
-        assert.isTrue(alertStub.calledOnce);
-      });
-    });
-
-    test('Gerrit._isPluginPreloaded', () => {
-      Gerrit._preloadedPlugins = {baz: ()=>{}};
-      assert.isFalse(Gerrit._isPluginPreloaded('plugins/foo/bar'));
-      assert.isFalse(Gerrit._isPluginPreloaded('http://a.com/42'));
-      assert.isTrue(Gerrit._isPluginPreloaded('preloaded:baz'));
-      Gerrit._preloadedPlugins = null;
-    });
-
-    test('preloaded plugins are installed', () => {
-      const installStub = sandbox.stub();
-      Gerrit._preloadedPlugins = {foo: installStub};
-      Gerrit._installPreloadedPlugins();
-      assert.isTrue(installStub.called);
-      const pluginApi = installStub.lastCall.args[0];
-      assert.strictEqual(pluginApi.getPluginName(), 'foo');
-    });
-
-    test('installing preloaded plugin', () => {
-      let plugin;
-      window.ASSETS_PATH = 'http://blips.com/chitz';
-      Gerrit.install(p => { plugin = p; }, '0.1', 'preloaded:foo');
-      assert.strictEqual(plugin.getPluginName(), 'foo');
-      assert.strictEqual(plugin.url('/some/thing.html'),
-          'http://blips.com/chitz/plugins/foo/some/thing.html');
-      delete window.ASSETS_PATH;
-    });
-  });
-</script>
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface.html b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface.html
index 7fa2250..e460660 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface.html
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface.html
@@ -31,7 +31,6 @@
 <link rel="import" href="../gr-rest-api-interface/gr-rest-api-interface.html">
 
 <dom-module id="gr-js-api-interface">
-  <script src="gr-api-utils.js"></script>
   <script src="gr-annotation-actions-context.js"></script>
   <script src="gr-annotation-actions-js-api.js"></script>
   <script src="gr-change-actions-js-api.js"></script>
@@ -41,5 +40,4 @@
   <script src="gr-plugin-action-context.js"></script>
   <script src="gr-plugin-rest-api.js"></script>
   <script src="gr-public-js-api.js"></script>
-  <script src="gr-gerrit.js"></script>
 </dom-module>
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface_test.html b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface_test.html
index 330310f..c7e4f09 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface_test.html
@@ -73,6 +73,23 @@
       plugin = null;
     });
 
+    test('reuse plugin for install calls', () => {
+      let otherPlugin;
+      Gerrit.install(p => { otherPlugin = p; }, '0.1',
+          'http://test.com/plugins/testplugin/static/test.js');
+      assert.strictEqual(plugin, otherPlugin);
+    });
+
+    test('flushes preinstalls if provided', () => {
+      assert.doesNotThrow(() => {
+        Gerrit._flushPreinstalls();
+      });
+      window.Gerrit.flushPreinstalls = sandbox.stub();
+      Gerrit._flushPreinstalls();
+      assert.isTrue(window.Gerrit.flushPreinstalls.calledOnce);
+      delete window.Gerrit.flushPreinstalls;
+    });
+
     test('url', () => {
       assert.equal(plugin.url(), 'http://test.com/plugins/testplugin/');
       assert.equal(plugin.url('/static/test.js'),
@@ -296,6 +313,12 @@
       element.handleEvent(element.EventType.HIGHLIGHTJS_LOADED, {hljs: testHljs});
     });
 
+    test('versioning', () => {
+      const callback = sandbox.spy();
+      Gerrit.install(callback, '0.0pre-alpha');
+      assert(callback.notCalled);
+    });
+
     test('getAccount', done => {
       plugin.restApi().getLoggedIn().then(loggedIn => {
         assert.isTrue(loggedIn);
@@ -303,6 +326,77 @@
       });
     });
 
+    test('_setPluginsCount', done => {
+      stub('gr-reporting', {
+        pluginsLoaded() {
+          done();
+        },
+      });
+      Gerrit._setPluginsCount(0);
+    });
+
+    test('_arePluginsLoaded', () => {
+      assert.isTrue(Gerrit._arePluginsLoaded());
+      Gerrit._setPluginsCount(1);
+      assert.isFalse(Gerrit._arePluginsLoaded());
+      Gerrit._setPluginsCount(0);
+      assert.isTrue(Gerrit._arePluginsLoaded());
+    });
+
+    test('_pluginInstalled', () => {
+      const pluginsLoadedStub = sandbox.stub();
+      stub('gr-reporting', {
+        pluginsLoaded: (...args) => pluginsLoadedStub(...args),
+      });
+      const plugins = [
+        'http://test.com/plugins/foo/static/test.js',
+        'http://test.com/plugins/bar/static/test.js',
+      ];
+      Gerrit._setPluginsPending(plugins);
+      Gerrit._pluginInstalled(plugins[0]);
+      Gerrit._pluginInstalled(plugins[1]);
+      assert.isTrue(pluginsLoadedStub.calledWithExactly(['foo', 'bar']));
+    });
+
+    test('install calls _pluginInstalled', () => {
+      sandbox.stub(Gerrit, '_pluginInstalled');
+      Gerrit.install(p => { plugin = p; }, '0.1',
+          'http://test.com/plugins/testplugin/static/test.js');
+
+      // testplugin has already been installed once (in setup).
+      assert.isFalse(Gerrit._pluginInstalled.called);
+
+      // testplugin2 plugin has not yet been installed.
+      Gerrit.install(p => { plugin = p; }, '0.1',
+          'http://test.com/plugins/testplugin2/static/test.js');
+      assert.isTrue(Gerrit._pluginInstalled.calledOnce);
+    });
+
+    test('plugin install errors mark plugins as loaded', () => {
+      Gerrit._setPluginsCount(1);
+      Gerrit.install(() => {}, '0.0pre-alpha');
+      return Gerrit.awaitPluginsLoaded();
+    });
+
+    test('multiple ui plugins per java plugin', () => {
+      const file1 = 'http://test.com/plugins/qaz/static/foo.nocache.js';
+      const file2 = 'http://test.com/plugins/qaz/static/bar.js';
+      Gerrit._setPluginsPending([file1, file2]);
+      Gerrit.install(() => {}, '0.1', file1);
+      Gerrit.install(() => {}, '0.1', file2);
+      return Gerrit.awaitPluginsLoaded();
+    });
+
+    test('plugin install errors shows toasts', () => {
+      const alertStub = sandbox.stub();
+      document.addEventListener('show-alert', alertStub);
+      Gerrit._setPluginsCount(1);
+      Gerrit.install(() => {}, '0.0pre-alpha');
+      return Gerrit.awaitPluginsLoaded().then(() => {
+        assert.isTrue(alertStub.calledOnce);
+      });
+    });
+
     test('attributeHelper', () => {
       assert.isOk(plugin.attributeHelper());
     });
@@ -328,6 +422,33 @@
           element.EventType.ADMIN_MENU_LINKS);
     });
 
+    test('Gerrit._isPluginPreloaded', () => {
+      Gerrit._preloadedPlugins = {baz: ()=>{}};
+      assert.isFalse(Gerrit._isPluginPreloaded('plugins/foo/bar'));
+      assert.isFalse(Gerrit._isPluginPreloaded('http://a.com/42'));
+      assert.isTrue(Gerrit._isPluginPreloaded('preloaded:baz'));
+      Gerrit._preloadedPlugins = null;
+    });
+
+    test('preloaded plugins are installed', () => {
+      const installStub = sandbox.stub();
+      Gerrit._preloadedPlugins = {foo: installStub};
+      Gerrit._installPreloadedPlugins();
+      assert.isTrue(installStub.called);
+      const pluginApi = installStub.lastCall.args[0];
+      assert.strictEqual(pluginApi.getPluginName(), 'foo');
+    });
+
+    test('installing preloaded plugin', () => {
+      let plugin;
+      window.ASSETS_PATH = 'http://blips.com/chitz';
+      Gerrit.install(p => { plugin = p; }, '0.1', 'preloaded:foo');
+      assert.strictEqual(plugin.getPluginName(), 'foo');
+      assert.strictEqual(plugin.url('/some/thing.html'),
+          'http://blips.com/chitz/plugins/foo/some/thing.html');
+      delete window.ASSETS_PATH;
+    });
+
     suite('test plugin with base url', () => {
       let baseUrlPlugin;
 
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-public-js-api.js b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-public-js-api.js
index d44acea..3b7b80d 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-public-js-api.js
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-public-js-api.js
@@ -17,18 +17,71 @@
 (function(window) {
   'use strict';
 
+  /**
+   * Hash of loaded and installed plugins, name to Plugin object.
+   */
+  const _plugins = {};
+
+  /**
+   * Array of plugin URLs to be loaded, name to url.
+   */
+  let _pluginsPending = {};
+
+  let _pluginsInstalled = [];
+
+  let _pluginsPendingCount = -1;
+
   const PRELOADED_PROTOCOL = 'preloaded:';
 
+  const UNKNOWN_PLUGIN = 'unknown';
+
   const PANEL_ENDPOINTS_MAPPING = {
     CHANGE_SCREEN_BELOW_COMMIT_INFO_BLOCK: 'change-view-integration',
     CHANGE_SCREEN_BELOW_CHANGE_INFO_BLOCK: 'change-metadata-item',
   };
 
-  // Import utils methods
-  const {
-      getPluginNameFromUrl,
-      send,
-  } = window._apiUtils;
+  const PLUGIN_LOADING_TIMEOUT_MS = 10000;
+
+  let _restAPI;
+
+  const getRestAPI = () => {
+    if (!_restAPI) {
+      _restAPI = document.createElement('gr-rest-api-interface');
+    }
+    return _restAPI;
+  };
+
+  let _reporting;
+  const getReporting = () => {
+    if (!_reporting) {
+      _reporting = document.createElement('gr-reporting');
+    }
+    return _reporting;
+  };
+
+  // TODO (viktard): deprecate in favor of GrPluginRestApi.
+  function send(method, url, opt_callback, opt_payload) {
+    return getRestAPI().send(method, url, opt_payload).then(response => {
+      if (response.status < 200 || response.status >= 300) {
+        return response.text().then(text => {
+          if (text) {
+            return Promise.reject(text);
+          } else {
+            return Promise.reject(response.status);
+          }
+        });
+      } else {
+        return getRestAPI().getResponseObject(response);
+      }
+    }).then(response => {
+      if (opt_callback) {
+        opt_callback(response);
+      }
+      return response;
+    });
+  }
+
+  const API_VERSION = '0.1';
 
   /**
    * Plugin-provided custom components can affect content in extension
@@ -46,6 +99,50 @@
     STYLE: 'style',
   };
 
+  function flushPreinstalls() {
+    if (window.Gerrit.flushPreinstalls) {
+      window.Gerrit.flushPreinstalls();
+    }
+  }
+
+  function installPreloadedPlugins() {
+    if (!Gerrit._preloadedPlugins) { return; }
+    for (const name in Gerrit._preloadedPlugins) {
+      if (!Gerrit._preloadedPlugins.hasOwnProperty(name)) { continue; }
+      const callback = Gerrit._preloadedPlugins[name];
+      Gerrit.install(callback, API_VERSION, PRELOADED_PROTOCOL + name);
+    }
+  }
+
+  function getPluginNameFromUrl(url) {
+    if (!(url instanceof URL)) {
+      try {
+        url = new URL(url);
+      } catch (e) {
+        console.warn(e);
+        return null;
+      }
+    }
+    if (url.protocol === PRELOADED_PROTOCOL) {
+      return url.pathname;
+    }
+    const base = Gerrit.BaseUrlBehavior.getBaseUrl();
+    const pathname = url.pathname.replace(base, '');
+    // Site theme is server from predefined path.
+    if (pathname === '/static/gerrit-theme.html') {
+      return 'gerrit-theme';
+    } else if (!pathname.startsWith('/plugins')) {
+      console.warn('Plugin not being loaded from /plugins base path:',
+          url.href, '— Unable to determine name.');
+      return;
+    }
+    // Pathname should normally look like this:
+    // /plugins/PLUGINNAME/static/SCRIPTNAME.html
+    // Or, for app/samples:
+    // /plugins/PLUGINNAME.html
+    return pathname.split('/')[2].split('.')[0];
+  }
+
   function Plugin(opt_url) {
     this._domHooks = new GrDomHooksManager(this);
 
@@ -378,5 +475,211 @@
     },
   };
 
-  window.Plugin = Plugin;
+  flushPreinstalls();
+
+  window.Gerrit = window.Gerrit || {};
+  const Gerrit = window.Gerrit;
+
+  let _resolveAllPluginsLoaded = null;
+  let _allPluginsPromise = null;
+
+  Gerrit._endpoints = new GrPluginEndpoints();
+
+  // Provide reset plugins function to clear installed plugins between tests.
+  const app = document.querySelector('#app');
+  if (!app) {
+    // No gr-app found (running tests)
+    Gerrit._installPreloadedPlugins = installPreloadedPlugins;
+    Gerrit._flushPreinstalls = flushPreinstalls;
+    Gerrit._resetPlugins = () => {
+      _allPluginsPromise = null;
+      _pluginsInstalled = [];
+      _pluginsPending = {};
+      _pluginsPendingCount = -1;
+      _reporting = null;
+      _resolveAllPluginsLoaded = null;
+      _restAPI = null;
+      Gerrit._endpoints = new GrPluginEndpoints();
+      for (const k of Object.keys(_plugins)) {
+        delete _plugins[k];
+      }
+    };
+  }
+
+  /**
+   * @deprecated Use plugin.styles().css(rulesStr) instead. Please, consult
+   * the documentation how to replace it accordingly.
+   */
+  Gerrit.css = function(rulesStr) {
+    console.warn('Gerrit.css(rulesStr) is deprecated!',
+        'Use plugin.styles().css(rulesStr)');
+    if (!Gerrit._customStyleSheet) {
+      const styleEl = document.createElement('style');
+      document.head.appendChild(styleEl);
+      Gerrit._customStyleSheet = styleEl.sheet;
+    }
+
+    const name = '__pg_js_api_class_' +
+        Gerrit._customStyleSheet.cssRules.length;
+    Gerrit._customStyleSheet.insertRule('.' + name + '{' + rulesStr + '}', 0);
+    return name;
+  };
+
+  Gerrit.install = function(callback, opt_version, opt_src) {
+    // HTML import polyfill adds __importElement pointing to the import tag.
+    const script = document.currentScript &&
+        (document.currentScript.__importElement || document.currentScript);
+
+    let src = opt_src || (script && script.src);
+    if (!src || src.startsWith('data:')) {
+      src = script && script.baseURI;
+    }
+    const name = getPluginNameFromUrl(src);
+
+    if (opt_version && opt_version !== API_VERSION) {
+      Gerrit._pluginInstallError(`Plugin ${name} install error: only version ` +
+          API_VERSION + ' is supported in PolyGerrit. ' + opt_version +
+          ' was given.');
+      return;
+    }
+
+    const existingPlugin = _plugins[name];
+    const plugin = existingPlugin || new Plugin(src);
+    try {
+      callback(plugin);
+      if (name) {
+        _plugins[name] = plugin;
+      }
+      if (!existingPlugin) {
+        Gerrit._pluginInstalled(src);
+      }
+    } catch (e) {
+      Gerrit._pluginInstallError(`${e.name}: ${e.message}`);
+    }
+  };
+
+  Gerrit.getLoggedIn = function() {
+    console.warn('Gerrit.getLoggedIn() is deprecated! ' +
+        'Use plugin.restApi().getLoggedIn()');
+    return document.createElement('gr-rest-api-interface').getLoggedIn();
+  };
+
+  Gerrit.get = function(url, callback) {
+    console.warn('.get() is deprecated! Use plugin.restApi().get()');
+    send('GET', url, callback);
+  };
+
+  Gerrit.post = function(url, payload, callback) {
+    console.warn('.post() is deprecated! Use plugin.restApi().post()');
+    send('POST', url, callback, payload);
+  };
+
+  Gerrit.put = function(url, payload, callback) {
+    console.warn('.put() is deprecated! Use plugin.restApi().put()');
+    send('PUT', url, callback, payload);
+  };
+
+  Gerrit.delete = function(url, opt_callback) {
+    console.warn('.delete() is deprecated! Use plugin.restApi().delete()');
+    return getRestAPI().send('DELETE', url).then(response => {
+      if (response.status !== 204) {
+        return response.text().then(text => {
+          if (text) {
+            return Promise.reject(text);
+          } else {
+            return Promise.reject(response.status);
+          }
+        });
+      }
+      if (opt_callback) {
+        opt_callback(response);
+      }
+      return response;
+    });
+  };
+
+  Gerrit.awaitPluginsLoaded = function() {
+    if (!_allPluginsPromise) {
+      if (Gerrit._arePluginsLoaded()) {
+        _allPluginsPromise = Promise.resolve();
+      } else {
+        let timeoutId;
+        _allPluginsPromise =
+          Promise.race([
+            new Promise(resolve => _resolveAllPluginsLoaded = resolve),
+            new Promise(resolve => timeoutId = setTimeout(
+                Gerrit._pluginLoadingTimeout, PLUGIN_LOADING_TIMEOUT_MS)),
+          ]).then(() => clearTimeout(timeoutId));
+      }
+    }
+    return _allPluginsPromise;
+  };
+
+  Gerrit._pluginLoadingTimeout = function() {
+    console.error(`Failed to load plugins: ${Object.keys(_pluginsPending)}`);
+    Gerrit._setPluginsPending([]);
+  };
+
+  Gerrit._setPluginsPending = function(plugins) {
+    _pluginsPending = plugins.reduce((o, url) => {
+      // TODO(viktard): Remove guard (@see Issue 8962)
+      o[getPluginNameFromUrl(url) || UNKNOWN_PLUGIN] = url;
+      return o;
+    }, {});
+    Gerrit._setPluginsCount(Object.keys(_pluginsPending).length);
+  };
+
+  Gerrit._setPluginsCount = function(count) {
+    _pluginsPendingCount = count;
+    if (Gerrit._arePluginsLoaded()) {
+      getReporting().pluginsLoaded(_pluginsInstalled);
+      if (_resolveAllPluginsLoaded) {
+        _resolveAllPluginsLoaded();
+      }
+    }
+  };
+
+  Gerrit._pluginInstallError = function(message) {
+    document.dispatchEvent(new CustomEvent('show-alert', {
+      detail: {
+        message: `Plugin install error: ${message}`,
+      },
+    }));
+    console.info(`Plugin install error: ${message}`);
+    Gerrit._setPluginsCount(_pluginsPendingCount - 1);
+  };
+
+  Gerrit._pluginInstalled = function(url) {
+    const name = getPluginNameFromUrl(url) || UNKNOWN_PLUGIN;
+    if (!_pluginsPending[name]) {
+      console.warn(`Unexpected plugin ${name} installed from ${url}.`);
+    } else {
+      delete _pluginsPending[name];
+      _pluginsInstalled.push(name);
+      Gerrit._setPluginsCount(_pluginsPendingCount - 1);
+      getReporting().pluginLoaded(name);
+      console.log(`Plugin ${name} installed.`);
+    }
+  };
+
+  Gerrit._arePluginsLoaded = function() {
+    return _pluginsPendingCount === 0;
+  };
+
+  Gerrit._getPluginScreenName = function(pluginName, screenName) {
+    return `${pluginName}-screen-${screenName}`;
+  };
+
+  Gerrit._isPluginPreloaded = function(url) {
+    const name = getPluginNameFromUrl(url);
+    if (name && Gerrit._preloadedPlugins) {
+      return name in Gerrit._preloadedPlugins;
+    } else {
+      return false;
+    }
+  };
+
+  // Preloaded plugins should be installed after Gerrit.install() is set,
+  // since plugin preloader substitutes Gerrit.install() temporarily.
+  installPreloadedPlugins();
 })(window);
diff --git a/polygerrit-ui/app/test/index.html b/polygerrit-ui/app/test/index.html
index a0e85ef..10d5aff 100644
--- a/polygerrit-ui/app/test/index.html
+++ b/polygerrit-ui/app/test/index.html
@@ -188,9 +188,7 @@
     'shared/gr-js-api-interface/gr-annotation-actions-js-api_test.html',
     'shared/gr-js-api-interface/gr-change-actions-js-api_test.html',
     'shared/gr-js-api-interface/gr-change-reply-js-api_test.html',
-    'shared/gr-js-api-interface/gr-api-utils_test.html',
     'shared/gr-js-api-interface/gr-js-api-interface_test.html',
-    'shared/gr-js-api-interface/gr-gerrit_test.html',
     // TODO: uncomment file & fix tests. The file was missed in this list for a long time.
     // 'shared/gr-js-api-interface/gr-plugin-action-context_test.html',
     'shared/gr-js-api-interface/gr-plugin-endpoints_test.html',